diff --git a/commands/add.py b/commands/add.py index 1b1a943..d29b6b3 100644 --- a/commands/add.py +++ b/commands/add.py @@ -1,22 +1,9 @@ """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.paths import get_tasks_file +from utils.validation import validate_description def add_task(description): diff --git a/commands/done.py b/commands/done.py index c9dfd42..ec3c0c4 100644 --- a/commands/done.py +++ b/commands/done.py @@ -1,20 +1,9 @@ """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.paths import get_tasks_file +from utils.validation import validate_task_id def mark_done(task_id): diff --git a/commands/list.py b/commands/list.py index 714315d..d9fb9f0 100644 --- a/commands/list.py +++ b/commands/list.py @@ -1,26 +1,12 @@ """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.validation import validate_task_file def list_tasks(): """List all tasks.""" - # NOTE: No --json flag support yet (feature bounty) tasks_file = validate_task_file() if not tasks_file: print("No tasks yet!") diff --git a/task.py b/task.py index 53cc8ed..9638a05 100644 --- a/task.py +++ b/task.py @@ -13,7 +13,10 @@ def load_config(): """Load configuration from file.""" config_path = Path.home() / ".config" / "task-cli" / "config.yaml" - # NOTE: This will crash if config doesn't exist - known bug for bounty testing + if not config_path.exists(): + raise FileNotFoundError( + f"Config not found at {config_path}. Copy config.yaml.example to this location to get started." + ) with open(config_path) as f: return f.read() @@ -35,6 +38,12 @@ def main(): args = parser.parse_args() + try: + load_config() + except FileNotFoundError as e: + print(str(e), file=sys.stderr) + sys.exit(1) + if args.command == "add": add_task(args.description) elif args.command == "list": diff --git a/test_task.py b/test_task.py index ba98e43..34b30ba 100644 --- a/test_task.py +++ b/test_task.py @@ -1,10 +1,10 @@ """Basic tests for task CLI.""" -import json import pytest from pathlib import Path from commands.add import add_task, validate_description from commands.done import validate_task_id +from task import load_config def test_validate_description(): @@ -28,3 +28,20 @@ def test_validate_task_id(): with pytest.raises(ValueError): validate_task_id(tasks, 99) + + +def test_load_config_missing_file(monkeypatch, tmp_path): + """Missing config should raise a friendly FileNotFoundError.""" + monkeypatch.setattr(Path, "home", lambda: tmp_path) + with pytest.raises(FileNotFoundError, match="config.yaml.example"): + load_config() + + +def test_load_config_success(monkeypatch, tmp_path): + """Existing config should be loaded successfully.""" + monkeypatch.setattr(Path, "home", lambda: tmp_path) + config_dir = tmp_path / ".config" / "task-cli" + config_dir.mkdir(parents=True) + config_file = config_dir / "config.yaml" + config_file.write_text("theme: default\n") + assert load_config() == "theme: default\n" diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..91a7322 --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1 @@ +"""Utility helpers for task CLI.""" diff --git a/utils/paths.py b/utils/paths.py new file mode 100644 index 0000000..4ce0eef --- /dev/null +++ b/utils/paths.py @@ -0,0 +1,8 @@ +"""Shared path helpers.""" + +from pathlib import Path + + +def get_tasks_file(): + """Get path to tasks file.""" + return Path.home() / ".local" / "share" / "task-cli" / "tasks.json" diff --git a/utils/validation.py b/utils/validation.py new file mode 100644 index 0000000..571f56e --- /dev/null +++ b/utils/validation.py @@ -0,0 +1,27 @@ +"""Shared validation helpers.""" + +from utils.paths import get_tasks_file + + +def validate_description(description): + """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_file(): + """Validate tasks file exists.""" + tasks_file = get_tasks_file() + if not tasks_file.exists(): + return [] + return tasks_file + + +def validate_task_id(tasks, task_id): + """Validate task ID exists.""" + if task_id < 1 or task_id > len(tasks): + raise ValueError(f"Invalid task ID: {task_id}") + return task_id