From 605d011c0aa8f9b07070fe86e11650504f620dd2 Mon Sep 17 00:00:00 2001 From: Sofia Willow Date: Wed, 18 Mar 2026 21:45:04 +0100 Subject: [PATCH] refactor: extract validation to utils module (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moved scattered validation and helper functions into a single commands/utils.py module for better code organization. Extracted to commands/utils.py: - get_tasks_file() — was duplicated in add.py, list.py, done.py - validate_description() — was in add.py - validate_task_id() — was in done.py All three command modules now import from commands.utils instead of defining their own copies. Tests updated to import from utils. Closes #3 --- commands/add.py | 15 +-------------- commands/done.py | 14 +------------- commands/list.py | 21 ++++---------------- commands/utils.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++ test_task.py | 13 ++++++++++--- 5 files changed, 65 insertions(+), 47 deletions(-) create mode 100644 commands/utils.py diff --git a/commands/add.py b/commands/add.py index 1b1a943..41a79af 100644 --- a/commands/add.py +++ b/commands/add.py @@ -3,20 +3,7 @@ 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 commands.utils import get_tasks_file, validate_description def add_task(description): diff --git a/commands/done.py b/commands/done.py index c9dfd42..61c7adc 100644 --- a/commands/done.py +++ b/commands/done.py @@ -1,20 +1,8 @@ """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 commands.utils import get_tasks_file, validate_task_id def mark_done(task_id): diff --git a/commands/list.py b/commands/list.py index 714315d..b48b2d7 100644 --- a/commands/list.py +++ b/commands/list.py @@ -1,28 +1,15 @@ """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 commands.utils import get_tasks_file def list_tasks(): """List all tasks.""" - # NOTE: No --json flag support yet (feature bounty) - tasks_file = validate_task_file() - if not tasks_file: + tasks_file = get_tasks_file() + + if not tasks_file.exists(): print("No tasks yet!") return diff --git a/commands/utils.py b/commands/utils.py new file mode 100644 index 0000000..4a3c3be --- /dev/null +++ b/commands/utils.py @@ -0,0 +1,49 @@ +"""Shared validation utilities for task CLI commands.""" + +from pathlib import Path + + +def get_tasks_file(): + """Get path to the tasks storage file. + + Returns: + Path to ~/.local/share/task-cli/tasks.json + """ + return Path.home() / ".local" / "share" / "task-cli" / "tasks.json" + + +def validate_description(description): + """Validate a task description. + + Args: + description: Raw description string. + + Returns: + Stripped description string. + + Raises: + ValueError: If description is empty or exceeds 200 characters. + """ + 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, task_id): + """Validate that a task ID exists in the task list. + + Args: + tasks: List of task dicts. + task_id: Integer task ID to validate. + + Returns: + The validated task_id. + + Raises: + ValueError: If task_id is out of range. + """ + if task_id < 1 or task_id > len(tasks): + raise ValueError(f"Invalid task ID: {task_id}") + return task_id diff --git a/test_task.py b/test_task.py index ba98e43..7ca97ad 100644 --- a/test_task.py +++ b/test_task.py @@ -1,10 +1,10 @@ -"""Basic tests for task CLI.""" +"""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 commands.utils import validate_description, validate_task_id, get_tasks_file def test_validate_description(): @@ -28,3 +28,10 @@ def test_validate_task_id(): with pytest.raises(ValueError): validate_task_id(tasks, 99) + + +def test_get_tasks_file(): + """Test tasks file path.""" + path = get_tasks_file() + assert path.name == "tasks.json" + assert "task-cli" in str(path)