diff --git a/README.md b/README.md new file mode 100644 index 0000000..86dabed --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Ajal Todo CLI + +A todo utility diff --git a/todo.py b/todo.py new file mode 100755 index 0000000..6594f88 --- /dev/null +++ b/todo.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +import os +import re +import sys + +base_dir = os.path.abspath(os.path.dirname(__file__)) +todo_file = os.path.join(base_dir, "todo.md") + + +def add_task(task: str, parent=None): + print(f"Adding task '{task}' to '{parent if parent else 'root'}'") + todo = parse_todo() + if parent and parent not in todo: + print(f"Could not find parent task '{parent}' in root task list") + exit(1) + if parent: + todo[parent]["sub_tasks"][task] = {"completed": False} + else: + todo[task] = {"completed": False, "sub_tasks": {}} + write_todo(todo) + + +def list_tasks(parent=None): + print(f"Listing tasks in '{parent if parent else 'root'}'") + todo = parse_todo() + if parent and parent not in todo: + print(f"Could not find parent task '{parent}' in root task list") + exit(1) + if parent: + todo = {parent: todo[parent]} + print(build_todo(todo)) + + +def complete_task(task, parent=None): + print(f"Marking task '{task}' in '{parent if parent else 'root'}' as completed") + todo = parse_todo() + if parent: + if parent not in todo: + print(f"Could not find parent task '{parent}' in root task list") + exit(1) + if task not in todo[parent]["sub_tasks"]: + print(f"Could not find task '{task}' in parent task '{parent}'") + exit(1) + if task not in todo: + print(f"Could not find task '{task}' in root task list") + exit(1) + if parent: + task = todo[parent]["sub_tasks"][task] + else: + task = todo[task] + task["completed"] = True + write_todo(todo) + + +def remove_task(task, parent=None): + print(f"Removing task '{task}' from '{parent if parent else 'root'}'") + todo = parse_todo() + if parent: + if parent not in todo: + print(f"Could not find parent task '{parent}' in root task list") + exit(1) + if task not in todo[parent]["sub_tasks"]: + print(f"Could not find task '{task}' in parent task '{parent}'") + exit(1) + if task not in todo: + print(f"Could not find task '{task}' in root task list") + exit(1) + if parent: + del todo[parent]["sub_tasks"][task] + else: + del todo[task] + write_todo(todo) + + +def print_help(): + print("""Usage: todo.py [add|complete|remove] TASK [PARENT] + todo.py list PARENT + + Manages todo.md file as a ToDo file""") + + +def parse_todo() -> dict[str, dict]: + todo = {} + with open(todo_file, "r") as f: + lines = f.readlines() + root_task = None + for line in lines: + if matches := re.match(r"^-\s+(\[(?P[x ])]\s+)?(?P.*)$", line.rstrip()): + root_task_name = matches.group("task") + root_task = { + "completed": matches.group("status") == "x", + "sub_tasks": {}, + } + todo[root_task_name] = root_task + elif matches := re.match(r"^\s+-\s+(\[(?P[x ])]\s+)?(?P.*)$", line.rstrip()): + leaf_task = matches.group("task") + root_task["sub_tasks"][leaf_task] = { + "completed": matches.group("status") == "x", + } + return todo + + +def build_todo(todo: dict[str, dict]) -> str: + lines = ["# A ToDo list", ""] + for task, task_data in todo.items(): # type: str, dict + lines.append(f"- [{'x' if task_data["completed"] else ' '}] {task}") + for leaf_task, leaf_task_data in task_data["sub_tasks"].items(): + lines.append(f" - [{'x' if leaf_task_data["completed"] else ' '}] {leaf_task}") + lines += [""] + return "\n".join(lines) + + +def write_todo(todo: dict[str, dict]): + with open(todo_file, "w") as f: + f.write(build_todo(todo)) + + +def main(): + if len(sys.argv) < 2: + print_help() + exit(1) + + match sys.argv[1]: + case "help" | "--help" | "h" | "-h": + print_help() + case "list": + match sys.argv[2:]: + case [parent]: + list_tasks(parent) + case _: + list_tasks() + case "add" | "complete" | "remove" as task_name: + task_func = f"{task_name}_task" + match sys.argv[2:]: + case [task]: + globals()[task_func](task) + case [task, parent]: + globals()[task_func](task, parent) + case _: + print_help() + exit(1) + case _: + print_help() + exit(1) + + +if __name__ == "__main__": + main()