|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +Simple CLI Todo app for Hacktoberfest contribution. |
| 4 | +Stores tasks in a JSON file with features: add, list, done, remove, clear. |
| 5 | +""" |
| 6 | +import json |
| 7 | +import argparse |
| 8 | +from pathlib import Path |
| 9 | +from datetime import datetime |
| 10 | + |
| 11 | +DATA_FILE = Path("tasks.json") |
| 12 | + |
| 13 | + |
| 14 | +def load_tasks() -> list: |
| 15 | + """ |
| 16 | + Load and return the list of tasks from the JSON file. |
| 17 | + Returns an empty list if the file does not exist or is invalid. |
| 18 | + """ |
| 19 | + if not DATA_FILE.exists(): |
| 20 | + return [] |
| 21 | + try: |
| 22 | + return json.loads(DATA_FILE.read_text()) |
| 23 | + except json.JSONDecodeError: |
| 24 | + # Warn the user and reset tasks if JSON is invalid. |
| 25 | + print("Warning: tasks.json is corrupted or not valid JSON. Resetting tasks list.") |
| 26 | + return [] |
| 27 | + |
| 28 | + |
| 29 | +def save_tasks(tasks: list) -> None: |
| 30 | + """ |
| 31 | + Save the list of tasks to the JSON file with pretty formatting. |
| 32 | + """ |
| 33 | + DATA_FILE.write_text(json.dumps(tasks, indent=2, sort_keys=False)) |
| 34 | + |
| 35 | + |
| 36 | +def add_task(text: str) -> None: |
| 37 | + """ |
| 38 | + Add a new task with the given text to the list. |
| 39 | + """ |
| 40 | + tasks = load_tasks() |
| 41 | + tasks.append({ |
| 42 | + "id": int(datetime.now().timestamp()), |
| 43 | + "text": text, |
| 44 | + "done": False, |
| 45 | + "created": datetime.now().isoformat() |
| 46 | + }) |
| 47 | + save_tasks(tasks) |
| 48 | + print("Added:", text) |
| 49 | + |
| 50 | + |
| 51 | +def list_tasks() -> None: |
| 52 | + """ |
| 53 | + List all tasks with their status. Uncompleted tasks show [ ] and done tasks show [✓]. |
| 54 | + """ |
| 55 | + tasks = load_tasks() |
| 56 | + if not tasks: |
| 57 | + print('No tasks. Add one using: todo.py add "Buy milk"') |
| 58 | + return |
| 59 | + for i, t in enumerate(tasks, 1): |
| 60 | + status = "✓" if t.get("done") else " " |
| 61 | + print(f"{i}. [{status}] {t.get('text')} (id:{t.get('id')})") |
| 62 | + |
| 63 | + |
| 64 | +def mark_done(index: int) -> None: |
| 65 | + """ |
| 66 | + Mark the task at the given 1-based index as done. |
| 67 | + """ |
| 68 | + tasks = load_tasks() |
| 69 | + if index < 1 or index > len(tasks): |
| 70 | + print("Invalid task number.") |
| 71 | + return |
| 72 | + tasks[index - 1]["done"] = True |
| 73 | + save_tasks(tasks) |
| 74 | + print("Marked done:", tasks[index - 1]["text"]) |
| 75 | + |
| 76 | + |
| 77 | +def remove_task(index: int) -> None: |
| 78 | + """ |
| 79 | + Remove the task at the given 1-based index. |
| 80 | + """ |
| 81 | + tasks = load_tasks() |
| 82 | + if index < 1 or index > len(tasks): |
| 83 | + print("Invalid task number.") |
| 84 | + return |
| 85 | + removed = tasks.pop(index - 1) |
| 86 | + save_tasks(tasks) |
| 87 | + print("Removed:", removed["text"]) |
| 88 | + |
| 89 | + |
| 90 | +def clear_tasks() -> None: |
| 91 | + """ |
| 92 | + Clear all tasks. |
| 93 | + """ |
| 94 | + save_tasks([]) |
| 95 | + print("All tasks cleared.") |
| 96 | + |
| 97 | + |
| 98 | +def parse_args() -> argparse.Namespace: |
| 99 | + """ |
| 100 | + Parse command-line arguments and return the parsed namespace. |
| 101 | + """ |
| 102 | + parser = argparse.ArgumentParser(prog="todo.py", description="Simple CLI todo app") |
| 103 | + subparsers = parser.add_subparsers(dest="cmd") |
| 104 | + |
| 105 | + # Add command |
| 106 | + sp_add = subparsers.add_parser("add", help="Add a new task") |
| 107 | + sp_add.add_argument("text", nargs="+", help="Task text") |
| 108 | + # List command |
| 109 | + sp_list = subparsers.add_parser("list", help="List all tasks") |
| 110 | + # Done command |
| 111 | + sp_done = subparsers.add_parser("done", help="Mark a task as done") |
| 112 | + sp_done.add_argument("number", type=int, help="Task index to mark as done (from list)") |
| 113 | + # Remove command |
| 114 | + sp_rm = subparsers.add_parser("remove", help="Remove a task") |
| 115 | + sp_rm.add_argument("number", type=int, help="Task index to remove (from list)") |
| 116 | + # Clear command |
| 117 | + sp_clear = subparsers.add_parser("clear", help="Remove all tasks") |
| 118 | + |
| 119 | + return parser.parse_args() |
| 120 | + |
| 121 | + |
| 122 | +def main() -> None: |
| 123 | + args = parse_args() |
| 124 | + if args.cmd == "add": |
| 125 | + add_task(" ".join(args.text)) |
| 126 | + elif args.cmd == "list" or args.cmd is None: |
| 127 | + list_tasks() |
| 128 | + elif args.cmd == "done": |
| 129 | + mark_done(args.number) |
| 130 | + elif args.cmd == "remove": |
| 131 | + remove_task(args.number) |
| 132 | + elif args.cmd == "clear": |
| 133 | + clear_tasks() |
| 134 | + else: |
| 135 | + print("Unknown command. Use add/list/done/remove/clear") |
| 136 | + |
| 137 | + |
| 138 | +if __name__ == "__main__": |
| 139 | + main() |
0 commit comments