From 5611c54242cbd503ed504729d0e4db61a859da5b Mon Sep 17 00:00:00 2001 From: nicepopo86-lang Date: Wed, 11 Feb 2026 22:01:39 +0000 Subject: [PATCH 1/2] Add --json output for add/list/done commands --- commands/add.py | 11 ++++++++--- commands/done.py | 17 +++++++++++++---- commands/list.py | 21 +++++++++++++++------ task.py | 9 ++++++--- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/commands/add.py b/commands/add.py index 1b1a943..a9cffb3 100644 --- a/commands/add.py +++ b/commands/add.py @@ -19,7 +19,7 @@ def validate_description(description): return description.strip() -def add_task(description): +def add_task(description, json_output=False): """Add a new task.""" description = validate_description(description) @@ -31,7 +31,12 @@ def add_task(description): tasks = json.loads(tasks_file.read_text()) task_id = len(tasks) + 1 - tasks.append({"id": task_id, "description": description, "done": False}) + task = {"id": task_id, "description": description, "done": False} + tasks.append(task) tasks_file.write_text(json.dumps(tasks, indent=2)) - print(f"Added task {task_id}: {description}") + + if json_output: + print(json.dumps(task)) + else: + print(f"Added task {task_id}: {description}") diff --git a/commands/done.py b/commands/done.py index c9dfd42..fef5672 100644 --- a/commands/done.py +++ b/commands/done.py @@ -17,11 +17,14 @@ def validate_task_id(tasks, task_id): return task_id -def mark_done(task_id): +def mark_done(task_id, json_output=False): """Mark a task as complete.""" tasks_file = get_tasks_file() if not tasks_file.exists(): - print("No tasks found!") + if json_output: + print(json.dumps({})) + else: + print("No tasks found!") return tasks = json.loads(tasks_file.read_text()) @@ -31,7 +34,13 @@ def mark_done(task_id): if task["id"] == task_id: task["done"] = True tasks_file.write_text(json.dumps(tasks, indent=2)) - print(f"Marked task {task_id} as done: {task['description']}") + if json_output: + print(json.dumps(task)) + else: + print(f"Marked task {task_id} as done: {task['description']}") return - print(f"Task {task_id} not found") + if json_output: + print(json.dumps({})) + else: + print(f"Task {task_id} not found") diff --git a/commands/list.py b/commands/list.py index 714315d..86f904e 100644 --- a/commands/list.py +++ b/commands/list.py @@ -18,20 +18,29 @@ def validate_task_file(): return tasks_file -def list_tasks(): +def list_tasks(json_output=False): """List all tasks.""" # NOTE: No --json flag support yet (feature bounty) tasks_file = validate_task_file() if not tasks_file: - print("No tasks yet!") + if json_output: + print(json.dumps([])) + else: + print("No tasks yet!") return tasks = json.loads(tasks_file.read_text()) if not tasks: - print("No tasks yet!") + if json_output: + print(json.dumps([])) + else: + print("No tasks yet!") return - for task in tasks: - status = "✓" if task["done"] else " " - print(f"[{status}] {task['id']}. {task['description']}") + if json_output: + print(json.dumps(tasks)) + else: + for task in tasks: + status = "✓" if task["done"] else " " + print(f"[{status}] {task['id']}. {task['description']}") diff --git a/task.py b/task.py index 53cc8ed..745e401 100644 --- a/task.py +++ b/task.py @@ -25,22 +25,25 @@ def main(): # Add command add_parser = subparsers.add_parser("add", help="Add a new task") add_parser.add_argument("description", help="Task description") + add_parser.add_argument("--json", action="store_true", help="Output in JSON format") # List command list_parser = subparsers.add_parser("list", help="List all tasks") + list_parser.add_argument("--json", action="store_true", help="Output in JSON format") # Done command done_parser = subparsers.add_parser("done", help="Mark task as complete") done_parser.add_argument("task_id", type=int, help="Task ID to mark done") + done_parser.add_argument("--json", action="store_true", help="Output in JSON format") args = parser.parse_args() if args.command == "add": - add_task(args.description) + add_task(args.description, json_output=args.json) elif args.command == "list": - list_tasks() + list_tasks(json_output=args.json) elif args.command == "done": - mark_done(args.task_id) + mark_done(args.task_id, json_output=args.json) else: parser.print_help() From 59a37cf390d8a726aede34ac322742a874d9952f Mon Sep 17 00:00:00 2001 From: nicepopo86-lang Date: Wed, 11 Feb 2026 22:06:21 +0000 Subject: [PATCH 2/2] Refactor validation and file logic into commands.utils --- commands/add.py | 25 +++--------------------- commands/done.py | 21 ++++---------------- commands/list.py | 26 ++----------------------- commands/utils.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 63 deletions(-) create mode 100644 commands/utils.py diff --git a/commands/add.py b/commands/add.py index a9cffb3..99f842e 100644 --- a/commands/add.py +++ b/commands/add.py @@ -1,40 +1,21 @@ """Add task command.""" import json -from pathlib import Path - -def get_tasks_file(): - """Get path to tasks file.""" - return Path.home() / ".local" / "share" / "task-cli" / "tasks.json" - - -def validate_description(description): - """Validate task description.""" - # NOTE: Validation logic scattered here - should be in utils (refactor bounty) - if not description: - raise ValueError("Description cannot be empty") - if len(description) > 200: - raise ValueError("Description too long (max 200 chars)") - return description.strip() +from .utils import load_tasks, save_tasks, validate_description def add_task(description, json_output=False): """Add a new task.""" description = validate_description(description) - tasks_file = get_tasks_file() - tasks_file.parent.mkdir(parents=True, exist_ok=True) - - tasks = [] - if tasks_file.exists(): - tasks = json.loads(tasks_file.read_text()) + tasks = load_tasks() task_id = len(tasks) + 1 task = {"id": task_id, "description": description, "done": False} tasks.append(task) - tasks_file.write_text(json.dumps(tasks, indent=2)) + save_tasks(tasks) if json_output: print(json.dumps(task)) diff --git a/commands/done.py b/commands/done.py index fef5672..eeda87a 100644 --- a/commands/done.py +++ b/commands/done.py @@ -1,39 +1,26 @@ """Mark task done command.""" import json -from pathlib import Path - -def get_tasks_file(): - """Get path to tasks file.""" - return Path.home() / ".local" / "share" / "task-cli" / "tasks.json" - - -def validate_task_id(tasks, task_id): - """Validate task ID exists.""" - # NOTE: Validation logic scattered here - should be in utils (refactor bounty) - if task_id < 1 or task_id > len(tasks): - raise ValueError(f"Invalid task ID: {task_id}") - return task_id +from .utils import load_tasks, save_tasks, validate_task_id def mark_done(task_id, json_output=False): """Mark a task as complete.""" - tasks_file = get_tasks_file() - if not tasks_file.exists(): + tasks = load_tasks() + if not tasks: if json_output: print(json.dumps({})) else: print("No tasks found!") return - tasks = json.loads(tasks_file.read_text()) task_id = validate_task_id(tasks, task_id) for task in tasks: if task["id"] == task_id: task["done"] = True - tasks_file.write_text(json.dumps(tasks, indent=2)) + save_tasks(tasks) if json_output: print(json.dumps(task)) else: diff --git a/commands/list.py b/commands/list.py index 86f904e..28fe039 100644 --- a/commands/list.py +++ b/commands/list.py @@ -1,35 +1,13 @@ """List tasks command.""" import json -from pathlib import Path - -def get_tasks_file(): - """Get path to tasks file.""" - return Path.home() / ".local" / "share" / "task-cli" / "tasks.json" - - -def validate_task_file(): - """Validate tasks file exists.""" - # NOTE: Validation logic scattered here - should be in utils (refactor bounty) - tasks_file = get_tasks_file() - if not tasks_file.exists(): - return [] - return tasks_file +from .utils import load_tasks def list_tasks(json_output=False): """List all tasks.""" - # NOTE: No --json flag support yet (feature bounty) - tasks_file = validate_task_file() - if not tasks_file: - if json_output: - print(json.dumps([])) - else: - print("No tasks yet!") - return - - tasks = json.loads(tasks_file.read_text()) + tasks = load_tasks() if not tasks: if json_output: diff --git a/commands/utils.py b/commands/utils.py new file mode 100644 index 0000000..1c7eec2 --- /dev/null +++ b/commands/utils.py @@ -0,0 +1,49 @@ +"""Shared utilities for task-cli commands. + +This module centralizes common file path and validation logic. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any, List, Dict + + +def get_tasks_file() -> Path: + """Get path to tasks file.""" + return Path.home() / ".local" / "share" / "task-cli" / "tasks.json" + + +def load_tasks() -> List[Dict[str, Any]]: + """Load tasks from disk. + + Returns an empty list when the tasks file does not exist yet. + """ + tasks_file = get_tasks_file() + if not tasks_file.exists(): + return [] + return json.loads(tasks_file.read_text()) + + +def save_tasks(tasks: List[Dict[str, Any]]) -> None: + """Persist tasks to disk.""" + tasks_file = get_tasks_file() + tasks_file.parent.mkdir(parents=True, exist_ok=True) + tasks_file.write_text(json.dumps(tasks, indent=2)) + + +def validate_description(description: str) -> str: + """Validate task description.""" + if not description: + raise ValueError("Description cannot be empty") + if len(description) > 200: + raise ValueError("Description too long (max 200 chars)") + return description.strip() + + +def validate_task_id(tasks: List[Dict[str, Any]], task_id: int) -> int: + """Validate task ID exists.""" + if task_id < 1 or task_id > len(tasks): + raise ValueError(f"Invalid task ID: {task_id}") + return task_id