This commit is contained in:
root committed 2026-06-06 12:36:25 +00:00
1 parent 206997510c
commit 251bd3e65f
14 files changed
+836

No files matched your search

+1
View File
@@ -0,0 +1 @@
{"docs/system_design/20260606123307.json": ["docs/prd/20260606123307.json"], "docs/task/20260606123307.json": ["docs/system_design/20260606123307.json"], "todo_cli/storage.py": ["docs/system_design/20260606123307.json", "docs/task/20260606123307.json"], "todo_cli/todo.py": ["docs/system_design/20260606123307.json", "docs/task/20260606123307.json"], "todo_cli/main.py": ["docs/system_design/20260606123307.json", "docs/task/20260606123307.json"]}
+1
View File
@@ -0,0 +1 @@
{"Language":"zh_cn","Programming Language":"Python","Original Requirements":"创建一个Python命令行TODO应用,支持增删改查,数据存JSON文件","Project Name":"todo_cli","Product Goals":["提供简洁高效的命令行交互体验","确保数据持久化与可靠性","支持快速增删改查操作"],"User Stories":["作为用户,我希望能够通过命令行快速添加新的待办事项","作为用户,我希望能够查看所有待办事项及其完成状态","作为用户,我希望能够标记待办事项为已完成","作为用户,我希望能够删除不再需要的待办事项","作为用户,我希望能够修改已有待办事项的内容"],"Competitive Analysis":["Todo.txt CLI: 纯文本文件存储,支持标签和优先级,但无JSON格式","Taskwarrior: 功能强大的命令行任务管理,但学习曲线较陡","t: 轻量级TODO工具,使用JSON存储,但功能单一","GitHub Issues CLI: 与GitHub集成,不适合本地简单使用","Microsoft To Do CLI: 非官方,依赖云服务,离线不可用"],"Competitive Quadrant Chart":"quadrantChart\n title \"命令行TODO工具竞争力分析\"\n x-axis \"功能简单\" --> \"功能丰富\"\n y-axis \"易用性低\" --> \"易用性高\"\n quadrant-1 \"功能丰富且易用\"\n quadrant-2 \"功能简单但易用\"\n quadrant-3 \"功能简单且难用\"\n quadrant-4 \"功能丰富但难用\"\n \"Todo.txt CLI\": [0.3, 0.7]\n \"Taskwarrior\": [0.8, 0.3]\n \"t\": [0.2, 0.6]\n \"GitHub Issues CLI\": [0.7, 0.4]\n \"Microsoft To Do CLI\": [0.5, 0.5]\n \"Our Target Product\": [0.4, 0.8]","Requirement Analysis":"本项目要求开发一个Python命令行TODO应用,核心功能包括增删改查(CRUD),数据以JSON文件形式持久化存储。用户通过命令行参数或交互式菜单操作。需要支持:添加任务(标题、描述、截止日期等可选字段)、列出所有任务(可筛选状态)、标记完成/未完成、删除任务、编辑任务内容。数据存储于本地JSON文件,需处理文件读写异常。命令行界面应清晰易用,提供帮助信息。","Requirement Pool":[["P0","实现添加待办事项功能,支持输入标题,可选描述和截止日期"],["P0","实现查看所有待办事项列表,显示ID、标题、状态、创建时间等"],["P0","实现删除待办事项功能,通过ID或索引删除"],["P0","实现修改待办事项功能,支持更新标题、描述、截止日期、状态"],["P1","实现数据持久化,使用JSON文件存储,支持自动加载和保存"]],"UI Design draft":"命令行界面,无图形UI。用户通过命令参数操作,例如:todo add \"任务标题\" -d \"描述\" -due 2025-01-01;todo list [--all|--done|--pending];todo done <id>;todo delete <id>;todo edit <id> --title \"新标题\"。支持--help查看帮助。输出格式为表格或列表,清晰显示任务信息。","Anything UNCLEAR":"无"}
+1
View File
@@ -0,0 +1 @@
创建一个Python命令行TODO应用,支持增删改查,数据存JSON文件
+1
View File
@@ -0,0 +1 @@
{"Implementation approach":"本项目使用Python标准库实现,无需第三方依赖。命令行参数解析采用argparse模块,数据持久化使用json模块,文件存储于用户主目录下的.todo_cli/todos.json。核心逻辑封装在TodoManager类中,提供增删改查方法。CLI接口通过TodoCLI类处理用户输入并调用TodoManager。数据模型TodoItem使用dataclass简化定义。整体设计遵循单一职责原则,确保代码简洁可维护。","File list":["main.py","todo.py","storage.py"],"Data structures and interfaces":"classDiagram\n class TodoItem {\n -int id\n -str title\n -str description\n -str due_date\n -bool completed\n -str created_at\n +__init__(id: int, title: str, description: str = \"\", due_date: str = \"\", completed: bool = False, created_at: str = \"\")\n +to_dict() dict\n +from_dict(data: dict) TodoItem\n }\n class Storage {\n -str file_path\n +__init__(file_path: str)\n +load() list[dict]\n +save(data: list[dict]) None\n }\n class TodoManager {\n -list[TodoItem] items\n -Storage storage\n -int next_id\n +__init__(storage: Storage)\n +add(title: str, description: str = \"\", due_date: str = \"\") TodoItem\n +list_all() list[TodoItem]\n +list_by_status(completed: bool) list[TodoItem]\n +mark_done(item_id: int) bool\n +mark_undone(item_id: int) bool\n +delete(item_id: int) bool\n +edit(item_id: int, title: str = None, description: str = None, due_date: str = None, completed: bool = None) bool\n +_load() None\n +_save() None\n +_find_by_id(item_id: int) TodoItem | None\n }\n class TodoCLI {\n -TodoManager manager\n -argparse.ArgumentParser parser\n +__init__(manager: TodoManager)\n +run(args: list[str] = None) None\n +_setup_parser() None\n +_handle_add(args) None\n +_handle_list(args) None\n +_handle_done(args) None\n +_handle_delete(args) None\n +_handle_edit(args) None\n }\n TodoCLI --> TodoManager\n TodoManager --> Storage\n TodoManager --> TodoItem\n Storage ..> TodoItem : uses to_dict/from_dict\n","Program call flow":"sequenceDiagram\n participant User as 用户\n participant CLI as TodoCLI\n participant Manager as TodoManager\n participant Storage as Storage\n participant File as JSON文件\n User->>CLI: python main.py add \"任务标题\" -d \"描述\" -due 2025-01-01\n CLI->>CLI: _setup_parser() 解析参数\n CLI->>CLI: _handle_add(args)\n CLI->>Manager: add(title, description, due_date)\n Manager->>Manager: 生成新TodoItem,设置id=next_id\n Manager->>Manager: 将item加入items列表\n Manager->>Manager: _save()\n Manager->>Storage: save([item.to_dict() for item in items])\n Storage->>File: 写入JSON文件\n File-->>Storage: 成功\n Storage-->>Manager: None\n Manager-->>CLI: 返回新TodoItem\n CLI-->>User: 打印添加成功信息\n\n User->>CLI: python main.py list\n CLI->>CLI: _handle_list(args)\n CLI->>Manager: list_all()\n Manager->>Manager: 返回items列表\n Manager-->>CLI: list[TodoItem]\n CLI->>CLI: 格式化输出表格\n CLI-->>User: 显示所有任务\n\n User->>CLI: python main.py done 1\n CLI->>CLI: _handle_done(args)\n CLI->>Manager: mark_done(1)\n Manager->>Manager: _find_by_id(1) -> item\n Manager->>Manager: item.completed = True\n Manager->>Manager: _save()\n Manager-->>CLI: True\n CLI-->>User: 打印标记成功\n\n User->>CLI: python main.py delete 1\n CLI->>CLI: _handle_delete(args)\n CLI->>Manager: delete(1)\n Manager->>Manager: _find_by_id(1) -> item\n Manager->>Manager: 从items中移除\n Manager->>Manager: _save()\n Manager-->>CLI: True\n CLI-->>User: 打印删除成功\n\n User->>CLI: python main.py edit 1 --title \"新标题\" --description \"新描述\"\n CLI->>CLI: _handle_edit(args)\n CLI->>Manager: edit(1, title=\"新标题\", description=\"新描述\")\n Manager->>Manager: _find_by_id(1) -> item\n Manager->>Manager: 更新item属性\n Manager->>Manager: _save()\n Manager-->>CLI: True\n CLI-->>User: 打印编辑成功\n","Anything UNCLEAR":"无"}
+1
View File
@@ -0,0 +1 @@
{"Required packages":[],"Required Other language third-party packages":[],"Logic Analysis":[["storage.py","Contains Storage class with load() and save() methods for JSON file persistence. No dependencies on other project files."],["todo.py","Contains TodoItem dataclass and TodoManager class. TodoManager depends on Storage for data persistence. Imports from storage.py."],["main.py","Contains TodoCLI class and main entry point. TodoCLI depends on TodoManager. Imports from todo.py."]],"Task list":["storage.py","todo.py","main.py"],"Full API spec":"","Shared Knowledge":"Data model: TodoItem with fields id, title, description, due_date, completed, created_at. Storage path: ~/.todo_cli/todos.json. All modules use Python standard library only.","Anything UNCLEAR":"无"}
View File
Whitespace-only changes.
@@ -0,0 +1,14 @@
quadrantChart
title "命令行TODO工具竞争力分析"
x-axis "功能简单" --> "功能丰富"
y-axis "易用性低" --> "易用性高"
quadrant-1 "功能丰富且易用"
quadrant-2 "功能简单但易用"
quadrant-3 "功能简单且难用"
quadrant-4 "功能丰富但难用"
"Todo.txt CLI": [0.3, 0.7]
"Taskwarrior": [0.8, 0.3]
"t": [0.2, 0.6]
"GitHub Issues CLI": [0.7, 0.4]
"Microsoft To Do CLI": [0.5, 0.5]
"Our Target Product": [0.4, 0.8]
@@ -0,0 +1,50 @@
classDiagram
class TodoItem {
-int id
-str title
-str description
-str due_date
-bool completed
-str created_at
+__init__(id: int, title: str, description: str = "", due_date: str = "", completed: bool = False, created_at: str = "")
+to_dict() dict
+from_dict(data: dict) TodoItem
}
class Storage {
-str file_path
+__init__(file_path: str)
+load() list[dict]
+save(data: list[dict]) None
}
class TodoManager {
-list[TodoItem] items
-Storage storage
-int next_id
+__init__(storage: Storage)
+add(title: str, description: str = "", due_date: str = "") TodoItem
+list_all() list[TodoItem]
+list_by_status(completed: bool) list[TodoItem]
+mark_done(item_id: int) bool
+mark_undone(item_id: int) bool
+delete(item_id: int) bool
+edit(item_id: int, title: str = None, description: str = None, due_date: str = None, completed: bool = None) bool
+_load() None
+_save() None
+_find_by_id(item_id: int) TodoItem | None
}
class TodoCLI {
-TodoManager manager
-argparse.ArgumentParser parser
+__init__(manager: TodoManager)
+run(args: list[str] = None) None
+_setup_parser() None
+_handle_add(args) None
+_handle_list(args) None
+_handle_done(args) None
+_handle_delete(args) None
+_handle_edit(args) None
}
TodoCLI --> TodoManager
TodoManager --> Storage
TodoManager --> TodoItem
Storage ..> TodoItem : uses to_dict/from_dict
+75
View File
@@ -0,0 +1,75 @@
## Language
zh_cn
## Programming Language
Python
## Original Requirements
创建一个Python命令行TODO应用,支持增删改查,数据存JSON文件
## Project Name
todo_cli
## Product Goals
- 提供简洁高效的命令行交互体验
- 确保数据持久化与可靠性
- 支持快速增删改查操作
## User Stories
- 作为用户,我希望能够通过命令行快速添加新的待办事项
- 作为用户,我希望能够查看所有待办事项及其完成状态
- 作为用户,我希望能够标记待办事项为已完成
- 作为用户,我希望能够删除不再需要的待办事项
- 作为用户,我希望能够修改已有待办事项的内容
## Competitive Analysis
- Todo.txt CLI: 纯文本文件存储,支持标签和优先级,但无JSON格式
- Taskwarrior: 功能强大的命令行任务管理,但学习曲线较陡
- t: 轻量级TODO工具,使用JSON存储,但功能单一
- GitHub Issues CLI: 与GitHub集成,不适合本地简单使用
- Microsoft To Do CLI: 非官方,依赖云服务,离线不可用
## Competitive Quadrant Chart
quadrantChart
title "命令行TODO工具竞争力分析"
x-axis "功能简单" --> "功能丰富"
y-axis "易用性低" --> "易用性高"
quadrant-1 "功能丰富且易用"
quadrant-2 "功能简单但易用"
quadrant-3 "功能简单且难用"
quadrant-4 "功能丰富但难用"
"Todo.txt CLI": [0.3, 0.7]
"Taskwarrior": [0.8, 0.3]
"t": [0.2, 0.6]
"GitHub Issues CLI": [0.7, 0.4]
"Microsoft To Do CLI": [0.5, 0.5]
"Our Target Product": [0.4, 0.8]
## Requirement Analysis
本项目要求开发一个Python命令行TODO应用,核心功能包括增删改查(CRUD),数据以JSON文件形式持久化存储。用户通过命令行参数或交互式菜单操作。需要支持:添加任务(标题、描述、截止日期等可选字段)、列出所有任务(可筛选状态)、标记完成/未完成、删除任务、编辑任务内容。数据存储于本地JSON文件,需处理文件读写异常。命令行界面应清晰易用,提供帮助信息。
## Requirement Pool
- ['P0', '实现添加待办事项功能,支持输入标题,可选描述和截止日期']
- ['P0', '实现查看所有待办事项列表,显示ID、标题、状态、创建时间等']
- ['P0', '实现删除待办事项功能,通过ID或索引删除']
- ['P0', '实现修改待办事项功能,支持更新标题、描述、截止日期、状态']
- ['P1', '实现数据持久化,使用JSON文件存储,支持自动加载和保存']
## UI Design draft
命令行界面,无图形UI。用户通过命令参数操作,例如:todo add "任务标题" -d "描述" -due 2025-01-01;todo list [--all|--done|--pending];todo done <id>;todo delete <id>;todo edit <id> --title "新标题"。支持--help查看帮助。输出格式为表格或列表,清晰显示任务信息。
## Anything UNCLEAR
无
+54
View File
@@ -0,0 +1,54 @@
sequenceDiagram
participant User as 用户
participant CLI as TodoCLI
participant Manager as TodoManager
participant Storage as Storage
participant File as JSON文件
User->>CLI: python main.py add "任务标题" -d "描述" -due 2025-01-01
CLI->>CLI: _setup_parser() 解析参数
CLI->>CLI: _handle_add(args)
CLI->>Manager: add(title, description, due_date)
Manager->>Manager: 生成新TodoItem,设置id=next_id
Manager->>Manager: 将item加入items列表
Manager->>Manager: _save()
Manager->>Storage: save([item.to_dict() for item in items])
Storage->>File: 写入JSON文件
File-->>Storage: 成功
Storage-->>Manager: None
Manager-->>CLI: 返回新TodoItem
CLI-->>User: 打印添加成功信息
User->>CLI: python main.py list
CLI->>CLI: _handle_list(args)
CLI->>Manager: list_all()
Manager->>Manager: 返回items列表
Manager-->>CLI: list[TodoItem]
CLI->>CLI: 格式化输出表格
CLI-->>User: 显示所有任务
User->>CLI: python main.py done 1
CLI->>CLI: _handle_done(args)
CLI->>Manager: mark_done(1)
Manager->>Manager: _find_by_id(1) -> item
Manager->>Manager: item.completed = True
Manager->>Manager: _save()
Manager-->>CLI: True
CLI-->>User: 打印标记成功
User->>CLI: python main.py delete 1
CLI->>CLI: _handle_delete(args)
CLI->>Manager: delete(1)
Manager->>Manager: _find_by_id(1) -> item
Manager->>Manager: 从items中移除
Manager->>Manager: _save()
Manager-->>CLI: True
CLI-->>User: 打印删除成功
User->>CLI: python main.py edit 1 --title "新标题" --description "新描述"
CLI->>CLI: _handle_edit(args)
CLI->>Manager: edit(1, title="新标题", description="新描述")
Manager->>Manager: _find_by_id(1) -> item
Manager->>Manager: 更新item属性
Manager->>Manager: _save()
Manager-->>CLI: True
CLI-->>User: 打印编辑成功
+126
View File
@@ -0,0 +1,126 @@
## Implementation approach
本项目使用Python标准库实现,无需第三方依赖。命令行参数解析采用argparse模块,数据持久化使用json模块,文件存储于用户主目录下的.todo_cli/todos.json。核心逻辑封装在TodoManager类中,提供增删改查方法。CLI接口通过TodoCLI类处理用户输入并调用TodoManager。数据模型TodoItem使用dataclass简化定义。整体设计遵循单一职责原则,确保代码简洁可维护。
## File list
- main.py
- todo.py
- storage.py
## Data structures and interfaces
classDiagram
class TodoItem {
-int id
-str title
-str description
-str due_date
-bool completed
-str created_at
+__init__(id: int, title: str, description: str = "", due_date: str = "", completed: bool = False, created_at: str = "")
+to_dict() dict
+from_dict(data: dict) TodoItem
}
class Storage {
-str file_path
+__init__(file_path: str)
+load() list[dict]
+save(data: list[dict]) None
}
class TodoManager {
-list[TodoItem] items
-Storage storage
-int next_id
+__init__(storage: Storage)
+add(title: str, description: str = "", due_date: str = "") TodoItem
+list_all() list[TodoItem]
+list_by_status(completed: bool) list[TodoItem]
+mark_done(item_id: int) bool
+mark_undone(item_id: int) bool
+delete(item_id: int) bool
+edit(item_id: int, title: str = None, description: str = None, due_date: str = None, completed: bool = None) bool
+_load() None
+_save() None
+_find_by_id(item_id: int) TodoItem | None
}
class TodoCLI {
-TodoManager manager
-argparse.ArgumentParser parser
+__init__(manager: TodoManager)
+run(args: list[str] = None) None
+_setup_parser() None
+_handle_add(args) None
+_handle_list(args) None
+_handle_done(args) None
+_handle_delete(args) None
+_handle_edit(args) None
}
TodoCLI --> TodoManager
TodoManager --> Storage
TodoManager --> TodoItem
Storage ..> TodoItem : uses to_dict/from_dict
## Program call flow
sequenceDiagram
participant User as 用户
participant CLI as TodoCLI
participant Manager as TodoManager
participant Storage as Storage
participant File as JSON文件
User->>CLI: python main.py add "任务标题" -d "描述" -due 2025-01-01
CLI->>CLI: _setup_parser() 解析参数
CLI->>CLI: _handle_add(args)
CLI->>Manager: add(title, description, due_date)
Manager->>Manager: 生成新TodoItem,设置id=next_id
Manager->>Manager: 将item加入items列表
Manager->>Manager: _save()
Manager->>Storage: save([item.to_dict() for item in items])
Storage->>File: 写入JSON文件
File-->>Storage: 成功
Storage-->>Manager: None
Manager-->>CLI: 返回新TodoItem
CLI-->>User: 打印添加成功信息
User->>CLI: python main.py list
CLI->>CLI: _handle_list(args)
CLI->>Manager: list_all()
Manager->>Manager: 返回items列表
Manager-->>CLI: list[TodoItem]
CLI->>CLI: 格式化输出表格
CLI-->>User: 显示所有任务
User->>CLI: python main.py done 1
CLI->>CLI: _handle_done(args)
CLI->>Manager: mark_done(1)
Manager->>Manager: _find_by_id(1) -> item
Manager->>Manager: item.completed = True
Manager->>Manager: _save()
Manager-->>CLI: True
CLI-->>User: 打印标记成功
User->>CLI: python main.py delete 1
CLI->>CLI: _handle_delete(args)
CLI->>Manager: delete(1)
Manager->>Manager: _find_by_id(1) -> item
Manager->>Manager: 从items中移除
Manager->>Manager: _save()
Manager-->>CLI: True
CLI-->>User: 打印删除成功
User->>CLI: python main.py edit 1 --title "新标题" --description "新描述"
CLI->>CLI: _handle_edit(args)
CLI->>Manager: edit(1, title="新标题", description="新描述")
Manager->>Manager: _find_by_id(1) -> item
Manager->>Manager: 更新item属性
Manager->>Manager: _save()
Manager-->>CLI: True
CLI-->>User: 打印编辑成功
## Anything UNCLEAR
无
+213
View File
@@ -0,0 +1,213 @@
"""Main entry point and CLI interface for the Todo CLI application.
This module defines the TodoCLI class that handles command-line argument
parsing and user interaction, as well as the main() function to start
the application.
"""
import argparse
import sys
from typing import List, Optional
from todo import TodoManager
from storage import Storage
class TodoCLI:
"""Command-line interface for managing todo items.
Attributes:
manager: The TodoManager instance handling business logic.
parser: The ArgumentParser for parsing command-line arguments.
"""
def __init__(self, manager: TodoManager) -> None:
"""Initialize the CLI with a TodoManager.
Args:
manager: A TodoManager instance for data operations.
"""
self.manager = manager
self.parser = argparse.ArgumentParser(
description="A simple CLI todo list manager."
)
self._setup_parser()
def _setup_parser(self) -> None:
"""Configure subparsers for each command."""
subparsers = self.parser.add_subparsers(dest="command", help="Available commands")
# add command
add_parser = subparsers.add_parser("add", help="Add a new todo item")
add_parser.add_argument("title", type=str, help="Title of the task")
add_parser.add_argument("-d", "--description", type=str, default="",
help="Description of the task")
add_parser.add_argument("-due", "--due_date", type=str, default="",
help="Due date (e.g., 2025-01-01)")
# list command
list_parser = subparsers.add_parser("list", help="List all todo items")
list_parser.add_argument("-s", "--status", type=str, choices=["done", "pending"],
help="Filter by completion status")
# done command
done_parser = subparsers.add_parser("done", help="Mark a todo item as completed")
done_parser.add_argument("item_id", type=int, help="ID of the item to mark done")
# undone command
undone_parser = subparsers.add_parser("undone", help="Mark a todo item as not completed")
undone_parser.add_argument("item_id", type=int, help="ID of the item to mark undone")
# delete command
delete_parser = subparsers.add_parser("delete", help="Delete a todo item")
delete_parser.add_argument("item_id", type=int, help="ID of the item to delete")
# edit command
edit_parser = subparsers.add_parser("edit", help="Edit a todo item")
edit_parser.add_argument("item_id", type=int, help="ID of the item to edit")
edit_parser.add_argument("--title", type=str, default=None,
help="New title (leave unchanged if omitted)")
edit_parser.add_argument("--description", type=str, default=None,
help="New description (leave unchanged if omitted)")
edit_parser.add_argument("--due_date", type=str, default=None,
help="New due date (leave unchanged if omitted)")
edit_parser.add_argument("--completed", type=str, choices=["true", "false"],
default=None,
help="Set completion status (true/false)")
def run(self, args: Optional[List[str]] = None) -> None:
"""Parse arguments and execute the corresponding command.
Args:
args: Command-line arguments (defaults to sys.argv[1:]).
"""
parsed_args = self.parser.parse_args(args)
if parsed_args.command is None:
self.parser.print_help()
return
command_handlers = {
"add": self._handle_add,
"list": self._handle_list,
"done": self._handle_done,
"undone": self._handle_undone,
"delete": self._handle_delete,
"edit": self._handle_edit,
}
handler = command_handlers.get(parsed_args.command)
if handler:
handler(parsed_args)
else:
self.parser.print_help()
def _handle_add(self, args: argparse.Namespace) -> None:
"""Handle the 'add' command.
Args:
args: Parsed arguments containing title, description, due_date.
"""
item = self.manager.add(args.title, args.description, args.due_date)
print(f"Added todo item #{item.id}: {item.title}")
def _handle_list(self, args: argparse.Namespace) -> None:
"""Handle the 'list' command.
Args:
args: Parsed arguments (may contain status filter).
"""
if args.status == "done":
items = self.manager.list_by_status(True)
elif args.status == "pending":
items = self.manager.list_by_status(False)
else:
items = self.manager.list_all()
if not items:
print("No todo items found.")
return
# Print header
print(f"{'ID':<4} {'Title':<30} {'Completed':<10} {'Due Date':<12} {'Description'}")
print("-" * 80)
for item in items:
completed_str = "✓" if item.completed else "✗"
due = item.due_date if item.due_date else "-"
desc = item.description if item.description else ""
print(f"{item.id:<4} {item.title:<30} {completed_str:<10} {due:<12} {desc}")
def _handle_done(self, args: argparse.Namespace) -> None:
"""Handle the 'done' command.
Args:
args: Parsed arguments containing item_id.
"""
success = self.manager.mark_done(args.item_id)
if success:
print(f"Marked item #{args.item_id} as completed.")
else:
print(f"Error: Item #{args.item_id} not found.")
def _handle_undone(self, args: argparse.Namespace) -> None:
"""Handle the 'undone' command.
Args:
args: Parsed arguments containing item_id.
"""
success = self.manager.mark_undone(args.item_id)
if success:
print(f"Marked item #{args.item_id} as not completed.")
else:
print(f"Error: Item #{args.item_id} not found.")
def _handle_delete(self, args: argparse.Namespace) -> None:
"""Handle the 'delete' command.
Args:
args: Parsed arguments containing item_id.
"""
success = self.manager.delete(args.item_id)
if success:
print(f"Deleted item #{args.item_id}.")
else:
print(f"Error: Item #{args.item_id} not found.")
def _handle_edit(self, args: argparse.Namespace) -> None:
"""Handle the 'edit' command.
Args:
args: Parsed arguments containing item_id and optional fields.
"""
# Convert --completed string to bool if provided
completed = None
if args.completed is not None:
completed = args.completed.lower() == "true"
success = self.manager.edit(
args.item_id,
title=args.title,
description=args.description,
due_date=args.due_date,
completed=completed,
)
if success:
print(f"Updated item #{args.item_id}.")
else:
print(f"Error: Item #{args.item_id} not found.")
def main() -> None:
"""Application entry point.
Creates a Storage instance with default path, initializes TodoManager,
and runs the CLI.
"""
storage = Storage()
manager = TodoManager(storage)
cli = TodoCLI(manager)
cli.run()
if __name__ == "__main__":
main()
+66
View File
@@ -0,0 +1,66 @@
"""Storage module for JSON file persistence.
This module provides the Storage class that handles reading and writing
todo items to a JSON file located at ~/.todo_cli/todos.json.
"""
import json
import os
from typing import List, Dict, Any
class Storage:
"""Handles persistent storage of todo items in a JSON file.
Attributes:
file_path: The absolute path to the JSON storage file.
"""
def __init__(self, file_path: str = None) -> None:
"""Initialize Storage with a file path.
If no file_path is provided, defaults to ~/.todo_cli/todos.json.
Args:
file_path: Optional custom path for the JSON file.
"""
if file_path is None:
home_dir = os.path.expanduser("~")
self.file_path = os.path.join(home_dir, ".todo_cli", "todos.json")
else:
self.file_path = file_path
def load(self) -> List[Dict[str, Any]]:
"""Load todo items from the JSON file.
Returns:
A list of dictionaries representing todo items.
Returns an empty list if the file does not exist or is corrupted.
"""
if not os.path.exists(self.file_path):
return []
try:
with open(self.file_path, "r", encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, list):
return data
else:
return []
except (json.JSONDecodeError, IOError):
return []
def save(self, data: List[Dict[str, Any]]) -> None:
"""Save a list of todo item dictionaries to the JSON file.
Creates the directory structure if it does not exist.
Args:
data: A list of dictionaries representing todo items to persist.
"""
directory = os.path.dirname(self.file_path)
if directory:
os.makedirs(directory, exist_ok=True)
with open(self.file_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
+233
View File
@@ -0,0 +1,233 @@
"""Todo item data model and manager module.
This module defines the TodoItem dataclass and the TodoManager class
that provides CRUD operations for todo items, backed by a Storage instance.
"""
import datetime
from dataclasses import dataclass, field, asdict
from typing import List, Optional, Dict, Any
from storage import Storage
@dataclass
class TodoItem:
"""Represents a single todo item.
Attributes:
id: Unique identifier for the item.
title: Short title of the task.
description: Optional detailed description.
due_date: Optional due date string (e.g., '2025-01-01').
completed: Whether the task is completed.
created_at: ISO-formatted creation timestamp.
"""
id: int
title: str
description: str = ""
due_date: str = ""
completed: bool = False
created_at: str = field(default_factory=lambda: datetime.datetime.now().isoformat())
def to_dict(self) -> Dict[str, Any]:
"""Convert the TodoItem to a dictionary for JSON serialization.
Returns:
A dictionary with all fields.
"""
return asdict(self)
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "TodoItem":
"""Create a TodoItem from a dictionary (e.g., loaded from JSON).
Args:
data: Dictionary containing todo item fields.
Returns:
A new TodoItem instance.
"""
return cls(
id=data["id"],
title=data["title"],
description=data.get("description", ""),
due_date=data.get("due_date", ""),
completed=data.get("completed", False),
created_at=data.get("created_at", ""),
)
class TodoManager:
"""Manages a collection of TodoItems with persistent storage.
Attributes:
items: List of current TodoItem objects.
storage: Storage instance for data persistence.
next_id: The next available ID for new items.
"""
def __init__(self, storage: Storage) -> None:
"""Initialize the manager with a Storage instance and load existing data.
Args:
storage: A Storage object for reading/writing JSON data.
"""
self.storage = storage
self.items: List[TodoItem] = []
self.next_id: int = 1
self._load()
def add(self, title: str, description: str = "", due_date: str = "") -> TodoItem:
"""Add a new todo item.
Args:
title: The title of the task.
description: Optional description.
due_date: Optional due date string.
Returns:
The newly created TodoItem.
"""
item = TodoItem(
id=self.next_id,
title=title,
description=description,
due_date=due_date,
)
self.items.append(item)
self.next_id += 1
self._save()
return item
def list_all(self) -> List[TodoItem]:
"""Return all todo items.
Returns:
A list of all TodoItem objects.
"""
return self.items
def list_by_status(self, completed: bool) -> List[TodoItem]:
"""Return todo items filtered by completion status.
Args:
completed: True for completed items, False for pending.
Returns:
A list of matching TodoItem objects.
"""
return [item for item in self.items if item.completed == completed]
def mark_done(self, item_id: int) -> bool:
"""Mark a todo item as completed.
Args:
item_id: The ID of the item to mark.
Returns:
True if the item was found and updated, False otherwise.
"""
item = self._find_by_id(item_id)
if item is None:
return False
item.completed = True
self._save()
return True
def mark_undone(self, item_id: int) -> bool:
"""Mark a todo item as not completed.
Args:
item_id: The ID of the item to mark.
Returns:
True if the item was found and updated, False otherwise.
"""
item = self._find_by_id(item_id)
if item is None:
return False
item.completed = False
self._save()
return True
def delete(self, item_id: int) -> bool:
"""Delete a todo item by its ID.
Args:
item_id: The ID of the item to delete.
Returns:
True if the item was found and removed, False otherwise.
"""
item = self._find_by_id(item_id)
if item is None:
return False
self.items.remove(item)
self._save()
return True
def edit(
self,
item_id: int,
title: Optional[str] = None,
description: Optional[str] = None,
due_date: Optional[str] = None,
completed: Optional[bool] = None,
) -> bool:
"""Edit fields of an existing todo item.
Only fields that are not None will be updated.
Args:
item_id: The ID of the item to edit.
title: New title (or None to keep unchanged).
description: New description (or None to keep unchanged).
due_date: New due date (or None to keep unchanged).
completed: New completion status (or None to keep unchanged).
Returns:
True if the item was found and updated, False otherwise.
"""
item = self._find_by_id(item_id)
if item is None:
return False
if title is not None:
item.title = title
if description is not None:
item.description = description
if due_date is not None:
item.due_date = due_date
if completed is not None:
item.completed = completed
self._save()
return True
def _load(self) -> None:
"""Load todo items from storage and initialize next_id."""
raw_data = self.storage.load()
self.items = [TodoItem.from_dict(d) for d in raw_data]
if self.items:
max_id = max(item.id for item in self.items)
self.next_id = max_id + 1
else:
self.next_id = 1
def _save(self) -> None:
"""Save current todo items to storage."""
data = [item.to_dict() for item in self.items]
self.storage.save(data)
def _find_by_id(self, item_id: int) -> Optional[TodoItem]:
"""Find a todo item by its ID.
Args:
item_id: The ID to search for.
Returns:
The TodoItem if found, None otherwise.
"""
for item in self.items:
if item.id == item_id:
return item
return None