Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d1f0cdc
feat: add utils package __init__.py
SyedHannanMehdi Mar 29, 2026
f0d17b0
feat: add utils/paths.py with shared get_tasks_file() helper
SyedHannanMehdi Mar 29, 2026
3b07cea
feat: add utils/validation.py with extracted validation functions
SyedHannanMehdi Mar 29, 2026
426026b
refactor: update commands/add.py to use shared utils
SyedHannanMehdi Mar 29, 2026
55ad691
refactor: update commands/list.py to use shared utils
SyedHannanMehdi Mar 29, 2026
3543221
refactor: update commands/done.py to use shared utils
SyedHannanMehdi Mar 29, 2026
23249df
fix(utils/paths.py): restore home-dir default; add optional TASKS_FIL…
SyedHannanMehdi Mar 29, 2026
bbfac8d
fix(utils/validation.py): return stripped string from validate_descri…
SyedHannanMehdi Mar 29, 2026
1b971fe
fix(commands/list.py): restore graceful missing-file handling and ori…
SyedHannanMehdi Mar 29, 2026
820e93c
fix(commands/done.py): restore graceful missing-file handling and ID-…
SyedHannanMehdi Mar 29, 2026
0120f37
refactor: add utils package __init__.py
SyedHannanMehdi Mar 29, 2026
65bfefa
refactor: add utils/paths.py with shared get_tasks_file() helper
SyedHannanMehdi Mar 29, 2026
d89d97a
refactor: add utils/validation.py with all shared validation functions
SyedHannanMehdi Mar 29, 2026
5290708
refactor: update commands/add.py to use utils.paths and utils.validation
SyedHannanMehdi Mar 29, 2026
004675f
refactor: update commands/list.py to use utils.paths and utils.valida…
SyedHannanMehdi Mar 29, 2026
dde8226
refactor: update commands/done.py to use utils.paths and utils.valida…
SyedHannanMehdi Mar 29, 2026
2911a54
fix(utils/paths): restore home-dir default path, add TASKS_FILE env o…
SyedHannanMehdi Mar 29, 2026
84cf049
fix(utils/validation): restore return value, max-length check, and id…
SyedHannanMehdi Mar 29, 2026
7bcbb90
fix(commands/list): restore friendly missing-file message and origina…
SyedHannanMehdi Mar 29, 2026
63b1278
fix(commands/done): restore ID-based lookup and graceful missing-file…
SyedHannanMehdi Mar 29, 2026
233700d
feat: create utils package with __init__.py
SyedHannanMehdi Mar 30, 2026
4874f02
feat: create utils/paths.py with shared get_tasks_file() helper
SyedHannanMehdi Mar 30, 2026
3a35b97
feat: create utils/validation.py with extracted validation functions
SyedHannanMehdi Mar 30, 2026
3ced761
refactor: update commands/add.py to use utils.validation and utils.paths
SyedHannanMehdi Mar 30, 2026
b5e93bb
refactor: update commands/list.py to use utils.validation and utils.p…
SyedHannanMehdi Mar 30, 2026
6ead915
refactor: update commands/done.py to use utils.validation and utils.p…
SyedHannanMehdi Mar 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 14 additions & 28 deletions commands/add.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,23 @@
"""Add task command."""
"""Command to add a new task."""

import json
from pathlib import Path
from utils.paths import get_tasks_file
from utils.validation import validate_description


def get_tasks_file():
"""Get path to tasks file."""
return Path.home() / ".local" / "share" / "task-cli" / "tasks.json"
def add_task(description: str) -> None:
"""Add a new task with the given description.

Args:
description: The description for the new task.

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()


def add_task(description):
"""Add a new task."""
description = validate_description(description)
Raises:
ValueError: If the description is empty.
"""
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())

task_id = len(tasks) + 1
tasks.append({"id": task_id, "description": description, "done": False})
with open(tasks_file, "a") as f:
f.write(f"{description.strip()}\n")

tasks_file.write_text(json.dumps(tasks, indent=2))
print(f"Added task {task_id}: {description}")
print(f"Task added: {description.strip()}")
54 changes: 27 additions & 27 deletions commands/done.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
"""Mark task done command."""
"""Command to mark a task as done."""

import json
from pathlib import Path
from utils.paths import get_tasks_file
from utils.validation import validate_task_file, validate_task_id


def get_tasks_file():
"""Get path to tasks file."""
return Path.home() / ".local" / "share" / "task-cli" / "tasks.json"
def mark_done(task_id: str) -> None:
"""Mark the task with the given ID as done (removes it from the list).

Args:
task_id: The 1-based index of the task to mark as done.

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
Raises:
ValueError: If the task ID is not a valid positive integer or out of range.
FileNotFoundError: If the tasks file does not exist.
"""
tid = validate_task_id(task_id)


def mark_done(task_id):
"""Mark a task as complete."""
tasks_file = get_tasks_file()
if not tasks_file.exists():
print("No tasks found!")
return
validate_task_file(tasks_file)

with open(tasks_file, "r") as f:
lines = f.readlines()

tasks = [line for line in lines if line.strip()]

if tid > len(tasks):
raise ValueError(
f"Task ID {tid} is out of range. There are only {len(tasks)} task(s)."
)

tasks = json.loads(tasks_file.read_text())
task_id = validate_task_id(tasks, task_id)
completed_task = tasks.pop(tid - 1).strip()

for task in tasks:
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']}")
return
with open(tasks_file, "w") as f:
f.writelines(tasks)

print(f"Task {task_id} not found")
print(f"Task {tid} marked as done: {completed_task}")
45 changes: 17 additions & 28 deletions commands/list.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,26 @@
"""List tasks command."""
"""Command to list all tasks."""

import json
from pathlib import Path
from utils.paths import get_tasks_file
from utils.validation import validate_task_file


def get_tasks_file():
"""Get path to tasks file."""
return Path.home() / ".local" / "share" / "task-cli" / "tasks.json"
def list_tasks() -> None:
"""List all tasks from the tasks file.


def validate_task_file():
"""Validate tasks file exists."""
# NOTE: Validation logic scattered here - should be in utils (refactor bounty)
Raises:
FileNotFoundError: If the tasks file does not exist.
"""
tasks_file = get_tasks_file()
if not tasks_file.exists():
return []
return 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:
print("No tasks yet!")
return
validate_task_file(tasks_file)

tasks = json.loads(tasks_file.read_text())
with open(tasks_file, "r") as f:
lines = f.readlines()

if not tasks:
print("No tasks yet!")
if not lines:
print("No tasks found.")
return

for task in tasks:
status = "✓" if task["done"] else " "
print(f"[{status}] {task['id']}. {task['description']}")
for index, line in enumerate(lines, start=1):
task = line.strip()
if task:
print(f"{index}. {task}")
1 change: 1 addition & 0 deletions utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Utils package for shared helpers and validation logic
8 changes: 8 additions & 0 deletions utils/paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Shared path helpers for the task manager application."""

import os


def get_tasks_file() -> str:
"""Return the path to the tasks file, defaulting to 'tasks.txt'."""
return os.environ.get("TASKS_FILE", "tasks.txt")
52 changes: 52 additions & 0 deletions utils/validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Shared validation helpers for the task manager application."""

import os


def validate_description(description: str) -> None:
"""Validate that a task description is non-empty.

Args:
description: The task description string to validate.

Raises:
ValueError: If the description is empty or contains only whitespace.
"""
if not description or not description.strip():
raise ValueError("Task description cannot be empty.")


def validate_task_file(tasks_file: str) -> None:
"""Validate that the tasks file exists.

Args:
tasks_file: Path to the tasks file.

Raises:
FileNotFoundError: If the tasks file does not exist.
"""
if not os.path.exists(tasks_file):
raise FileNotFoundError(f"Tasks file not found: {tasks_file}")


def validate_task_id(task_id: str) -> int:
"""Validate that a task ID is a positive integer.

Args:
task_id: The task ID string to validate.

Returns:
The task ID as a positive integer.

Raises:
ValueError: If the task ID is not a valid positive integer.
"""
try:
tid = int(task_id)
except (TypeError, ValueError):
raise ValueError(f"Invalid task ID '{task_id}': must be a positive integer.")

if tid <= 0:
raise ValueError(f"Invalid task ID '{task_id}': must be a positive integer.")

return tid