Archive
This commit is contained in:
1 parent
206997510c
commit
251bd3e65f
14 files changed
+836
No files matched your search
@@ -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"]}
|
||||
@@ -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":"无"}
|
||||
@@ -0,0 +1 @@
|
||||
创建一个Python命令行TODO应用,支持增删改查,数据存JSON文件
|
||||
@@ -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":"无"}
|
||||
@@ -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":"无"}
|
||||
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
|
||||
@@ -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
|
||||
|
||||
无
|
||||
|
||||
@@ -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: 打印编辑成功
|
||||
@@ -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
|
||||
|
||||
无
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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
|
||||
Reference in new issue
Block a user