From ea704870372d65ca0a29176445e72c939a38cdba Mon Sep 17 00:00:00 2001 From: Rahulthespy Date: Fri, 15 May 2026 05:50:59 +0400 Subject: [PATCH] Implement complete task tracker solution with tests --- .gitignore | 4 +++ README.md | 35 +++++++++++++++++++++++- pyproject.toml | 18 +++++++++++++ src/main.py | 60 +++++++++++++++++++++++++++++++++++++++++ src/tracker.py | 63 +++++++++++++++++++++++++++++++++++++++++++ tests/test_tracker.py | 37 +++++++++++++++++++++++++ 6 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 pyproject.toml create mode 100644 src/main.py create mode 100644 src/tracker.py create mode 100644 tests/test_tracker.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e3d76b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__/ +*.pyc +.venv/ +.pytest_cache/ diff --git a/README.md b/README.md index 77624bc..deb368e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,35 @@ # Rahul-s-LAB -My LAB respository.. + +A complete, working lab solution implemented as a **Task Tracker CLI**. + +## Features +- Add tasks +- List tasks +- Mark tasks as complete +- Delete tasks +- JSON file persistence + +## Quickstart +```bash +python -m venv .venv +source .venv/bin/activate +pip install -e . pytest +``` + +Run commands: +```bash +labtracker add "Write the complete code" +labtracker list +labtracker done 1 +labtracker delete 1 +``` + +Use a custom database file: +```bash +labtracker --db ./my_tasks.json add "Sample" +``` + +## Test +```bash +pytest +``` diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7ea82b6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["setuptools>=68", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "rahul-lab" +version = "0.1.0" +description = "A complete command-line lab task tracker solution" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [] + +[project.scripts] +labtracker = "src.main:main" + +[tool.pytest.ini_options] +pythonpath = ["."] +testpaths = ["tests"] diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..4b149d6 --- /dev/null +++ b/src/main.py @@ -0,0 +1,60 @@ +from __future__ import annotations + +import argparse +from pathlib import Path + +from src.tracker import TaskTracker + + +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Lab Task Tracker") + parser.add_argument( + "--db", + default=".lab/tasks.json", + help="Path to the tracker JSON file (default: .lab/tasks.json)", + ) + + subparsers = parser.add_subparsers(dest="command", required=True) + + add_parser = subparsers.add_parser("add", help="Add a new task") + add_parser.add_argument("title", help="Task title") + + subparsers.add_parser("list", help="List tasks") + + done_parser = subparsers.add_parser("done", help="Mark task as completed") + done_parser.add_argument("id", type=int, help="Task id") + + delete_parser = subparsers.add_parser("delete", help="Delete a task") + delete_parser.add_argument("id", type=int, help="Task id") + + return parser + + +def main() -> None: + parser = _build_parser() + args = parser.parse_args() + + tracker = TaskTracker(Path(args.db)) + + if args.command == "add": + task = tracker.add_task(args.title) + print(f"Added task #{task.id}: {task.title}") + elif args.command == "list": + tasks = tracker.list_tasks() + if not tasks: + print("No tasks yet.") + return + + for task in tasks: + status = "✅" if task.done else "⬜" + print(f"{status} [{task.id}] {task.title}") + elif args.command == "done": + task = tracker.complete_task(args.id) + print(f"Completed task #{task.id}: {task.title}") + elif args.command == "delete": + tracker.delete_task(args.id) + print(f"Deleted task #{args.id}") + + +if __name__ == "__main__": + main() diff --git a/src/tracker.py b/src/tracker.py new file mode 100644 index 0000000..18e58c7 --- /dev/null +++ b/src/tracker.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from dataclasses import asdict, dataclass +from pathlib import Path +import json + + +@dataclass +class Task: + id: int + title: str + done: bool = False + + +class TaskTracker: + def __init__(self, storage_path: Path) -> None: + self.storage_path = storage_path + self.tasks: list[Task] = [] + self._load() + + def _load(self) -> None: + if not self.storage_path.exists(): + self.tasks = [] + return + + with self.storage_path.open("r", encoding="utf-8") as f: + payload = json.load(f) + + self.tasks = [Task(**item) for item in payload] + + def _save(self) -> None: + self.storage_path.parent.mkdir(parents=True, exist_ok=True) + with self.storage_path.open("w", encoding="utf-8") as f: + json.dump([asdict(task) for task in self.tasks], f, indent=2) + + def add_task(self, title: str) -> Task: + next_id = 1 if not self.tasks else max(task.id for task in self.tasks) + 1 + task = Task(id=next_id, title=title.strip(), done=False) + if not task.title: + raise ValueError("Task title cannot be empty.") + self.tasks.append(task) + self._save() + return task + + def list_tasks(self) -> list[Task]: + return self.tasks + + def complete_task(self, task_id: int) -> Task: + task = self._find(task_id) + task.done = True + self._save() + return task + + def delete_task(self, task_id: int) -> None: + task = self._find(task_id) + self.tasks.remove(task) + self._save() + + def _find(self, task_id: int) -> Task: + for task in self.tasks: + if task.id == task_id: + return task + raise ValueError(f"Task with id={task_id} was not found.") diff --git a/tests/test_tracker.py b/tests/test_tracker.py new file mode 100644 index 0000000..547c001 --- /dev/null +++ b/tests/test_tracker.py @@ -0,0 +1,37 @@ +from pathlib import Path + +import pytest + +from src.tracker import TaskTracker + + +def test_add_and_list_tasks(tmp_path: Path) -> None: + tracker = TaskTracker(tmp_path / "tasks.json") + tracker.add_task("Write code") + tracker.add_task("Build solution") + + tasks = tracker.list_tasks() + assert [t.title for t in tasks] == ["Write code", "Build solution"] + assert [t.id for t in tasks] == [1, 2] + + +def test_complete_and_delete_task(tmp_path: Path) -> None: + tracker = TaskTracker(tmp_path / "tasks.json") + tracker.add_task("Task A") + tracker.complete_task(1) + assert tracker.list_tasks()[0].done is True + + tracker.delete_task(1) + assert tracker.list_tasks() == [] + + +def test_raises_for_invalid_task(tmp_path: Path) -> None: + tracker = TaskTracker(tmp_path / "tasks.json") + with pytest.raises(ValueError): + tracker.complete_task(9) + + +def test_rejects_blank_title(tmp_path: Path) -> None: + tracker = TaskTracker(tmp_path / "tasks.json") + with pytest.raises(ValueError): + tracker.add_task(" ")