-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtodo.py
More file actions
138 lines (117 loc) · 4.97 KB
/
Copy pathtodo.py
File metadata and controls
138 lines (117 loc) · 4.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
from __future__ import annotations
import csv
from datetime import datetime
from pathlib import Path
from typing import List, Optional
from models import Task
from storage import Storage
from utils import validate_date
class TodoManager:
"""Coordinates task operations for the CLI application."""
def __init__(self, storage_path: str | Path | None = None) -> None:
self.storage = Storage(storage_path)
self.tasks = self.storage.read_tasks()
self.next_id = self._load_next_id()
def _load_next_id(self) -> int:
data = self.storage.load()
return int(data.get("next_id", 1))
def _persist(self) -> None:
self.storage.write_tasks(self.tasks, self.next_id)
def add_task(
self,
title: str,
priority: str = "Medium",
category: str = "General",
due_date: Optional[str] = None,
) -> Task:
title = title.strip()
if not title:
raise ValueError("Task title cannot be empty.")
priority = priority.title()
if priority not in {"Low", "Medium", "High"}:
raise ValueError("Priority must be Low, Medium, or High.")
if due_date and not validate_date(due_date):
raise ValueError("Due date must be in YYYY-MM-DD format.")
task = Task(
id=self.next_id,
title=title,
priority=priority,
category=category.strip() or "General",
due_date=due_date,
)
self.tasks.append(task)
self.next_id += 1
self._persist()
return task
def list_tasks(self, completed: Optional[bool] = None) -> List[Task]:
if completed is None:
return self.tasks
return [task for task in self.tasks if task.completed is completed]
def mark_done(self, task_id: int) -> Task:
task = self._get_task(task_id)
task.completed = True
self._persist()
return task
def delete_task(self, task_id: int) -> Task:
task = self._get_task(task_id)
self.tasks = [item for item in self.tasks if item.id != task_id]
self._persist()
return task
def search_tasks(self, keyword: str) -> List[Task]:
keyword = keyword.lower()
return [task for task in self.tasks if keyword in task.title.lower()]
def clear_completed(self) -> int:
remaining = [task for task in self.tasks if not task.completed]
removed = len(self.tasks) - len(remaining)
self.tasks = remaining
self._persist()
return removed
def _get_task(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} does not exist.")
def stats(self) -> dict:
total = len(self.tasks)
completed = sum(1 for task in self.tasks if task.completed)
pending = total - completed
return {"total": total, "completed": completed, "pending": pending}
def sort_tasks(self, by: str = "priority") -> List[Task]:
if by == "due":
return sorted(self.tasks, key=lambda task: (task.due_date or "", task.priority))
if by == "priority":
order = {"High": 0, "Medium": 1, "Low": 2}
return sorted(self.tasks, key=lambda task: (order.get(task.priority, 99), task.title))
return sorted(self.tasks, key=lambda task: task.title.lower())
def export_tasks(self, output_path: str | Path) -> None:
output = Path(output_path)
output.parent.mkdir(parents=True, exist_ok=True)
with output.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(handle, fieldnames=["id", "title", "priority", "category", "completed", "created_at", "due_date"])
writer.writeheader()
for task in self.tasks:
writer.writerow(task.to_dict())
def import_tasks(self, input_path: str | Path) -> int:
input_file = Path(input_path)
if not input_file.exists():
raise FileNotFoundError(f"CSV file not found: {input_file}")
with input_file.open("r", newline="", encoding="utf-8") as handle:
reader = csv.DictReader(handle)
imported = 0
for row in reader:
task = Task(
id=self.next_id,
title=row.get("title", "").strip(),
priority=row.get("priority", "Medium").title() or "Medium",
category=row.get("category", "General").strip() or "General",
completed=row.get("completed", "False").lower() == "true",
created_at=row.get("created_at") or str(datetime.now().date()),
due_date=row.get("due_date") or None,
)
if task.title:
self.tasks.append(task)
self.next_id += 1
imported += 1
if imported:
self._persist()
return imported