#!/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()