czwjss-todo-cli 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- czwjss_todo_cli-0.1.1/LICENSE +21 -0
- czwjss_todo_cli-0.1.1/PKG-INFO +38 -0
- czwjss_todo_cli-0.1.1/README.md +25 -0
- czwjss_todo_cli-0.1.1/czwjss_todo_cli.egg-info/PKG-INFO +38 -0
- czwjss_todo_cli-0.1.1/czwjss_todo_cli.egg-info/SOURCES.txt +9 -0
- czwjss_todo_cli-0.1.1/czwjss_todo_cli.egg-info/dependency_links.txt +1 -0
- czwjss_todo_cli-0.1.1/czwjss_todo_cli.egg-info/entry_points.txt +2 -0
- czwjss_todo_cli-0.1.1/czwjss_todo_cli.egg-info/top_level.txt +1 -0
- czwjss_todo_cli-0.1.1/pyproject.toml +22 -0
- czwjss_todo_cli-0.1.1/setup.cfg +4 -0
- czwjss_todo_cli-0.1.1/todo.py +282 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 czwjss
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: czwjss-todo-cli
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: 练手用命令行任务清单工具(Python 版本)
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: todo,cli,task,todo-list
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# czwjss-todo-cli(Python 版)
|
|
15
|
+
|
|
16
|
+
练手用命令行任务清单工具。纯标准库实现,无第三方依赖。
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install czwjss-todo-cli
|
|
22
|
+
# 或从源码目录安装
|
|
23
|
+
pip install ./python
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
安装后命令为 `todo`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
todo add "写周报" --priority high
|
|
30
|
+
todo list
|
|
31
|
+
todo done 1
|
|
32
|
+
todo delete 2
|
|
33
|
+
todo stats
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 存储
|
|
37
|
+
|
|
38
|
+
默认 `~/.todo/tasks.json`,可用环境变量 `TODO_DIR` / `TODO_FILE` 覆盖。
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# czwjss-todo-cli(Python 版)
|
|
2
|
+
|
|
3
|
+
练手用命令行任务清单工具。纯标准库实现,无第三方依赖。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install czwjss-todo-cli
|
|
9
|
+
# 或从源码目录安装
|
|
10
|
+
pip install ./python
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
安装后命令为 `todo`:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
todo add "写周报" --priority high
|
|
17
|
+
todo list
|
|
18
|
+
todo done 1
|
|
19
|
+
todo delete 2
|
|
20
|
+
todo stats
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 存储
|
|
24
|
+
|
|
25
|
+
默认 `~/.todo/tasks.json`,可用环境变量 `TODO_DIR` / `TODO_FILE` 覆盖。
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: czwjss-todo-cli
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: 练手用命令行任务清单工具(Python 版本)
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: todo,cli,task,todo-list
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# czwjss-todo-cli(Python 版)
|
|
15
|
+
|
|
16
|
+
练手用命令行任务清单工具。纯标准库实现,无第三方依赖。
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install czwjss-todo-cli
|
|
22
|
+
# 或从源码目录安装
|
|
23
|
+
pip install ./python
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
安装后命令为 `todo`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
todo add "写周报" --priority high
|
|
30
|
+
todo list
|
|
31
|
+
todo done 1
|
|
32
|
+
todo delete 2
|
|
33
|
+
todo stats
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 存储
|
|
37
|
+
|
|
38
|
+
默认 `~/.todo/tasks.json`,可用环境变量 `TODO_DIR` / `TODO_FILE` 覆盖。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
todo
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "czwjss-todo-cli"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "练手用命令行任务清单工具(Python 版本)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
keywords = ["todo", "cli", "task", "todo-list"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
todo = "todo:main"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools]
|
|
22
|
+
py-modules = ["todo"]
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
todo —— 练手用命令行任务清单工具(Python 版本)
|
|
4
|
+
|
|
5
|
+
功能:
|
|
6
|
+
todo add "任务内容" [--priority high|medium|low] 添加任务
|
|
7
|
+
todo list [--status pending|done] [--json] 列出任务
|
|
8
|
+
todo done <id> 标记任务完成
|
|
9
|
+
todo delete <id> 删除任务
|
|
10
|
+
todo stats 统计信息
|
|
11
|
+
|
|
12
|
+
设计要点(对应 clig.dev 指南):
|
|
13
|
+
- 使用标准库 argparse 解析参数,自动获得 -h/--help 和用法错误
|
|
14
|
+
- 成功退出码为 0,业务错误退出码为 1,参数错误由 argparse 返回 2
|
|
15
|
+
- 正常输出走 stdout,错误信息走 stderr
|
|
16
|
+
- 提供 --json 机器可读输出(管道/grep 友好)
|
|
17
|
+
|
|
18
|
+
存储:
|
|
19
|
+
默认保存到 ~/.todo/tasks.json;可用环境变量 TODO_DIR 或 TODO_FILE 覆盖
|
|
20
|
+
(覆盖方便测试,避免污染真实数据)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import sys
|
|
27
|
+
from datetime import date
|
|
28
|
+
from typing import Optional
|
|
29
|
+
import unicodedata
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# 常量与数据存储
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
# 优先级:中文标签 + 排序权重 + 终端显示颜色(ANSI 转义码)
|
|
37
|
+
PRIORITIES = {
|
|
38
|
+
"high": {"label": "高", "weight": 0, "color": "\033[31m"}, # 红色
|
|
39
|
+
"medium": {"label": "中", "weight": 1, "color": "\033[33m"}, # 黄色
|
|
40
|
+
"low": {"label": "低", "weight": 2, "color": "\033[32m"}, # 绿色
|
|
41
|
+
}
|
|
42
|
+
RESET = "\033[0m" # 重置颜色
|
|
43
|
+
|
|
44
|
+
# 数据文件位置:优先取环境变量(便于测试),否则 ~/.todo/tasks.json
|
|
45
|
+
DATA_DIR = os.environ.get("TODO_DIR", os.path.join(os.path.expanduser("~"), ".todo"))
|
|
46
|
+
DATA_FILE = os.environ.get("TODO_FILE", os.path.join(DATA_DIR, "tasks.json"))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def display_width(text: str) -> int:
|
|
50
|
+
"""计算字符串在终端中的显示宽度。
|
|
51
|
+
|
|
52
|
+
中文字符等全角/宽字符在终端占 2 列,ASCII 等占 1 列。
|
|
53
|
+
len() 按字符数统计,直接用它对齐会出现错位。
|
|
54
|
+
"""
|
|
55
|
+
width = 0
|
|
56
|
+
for ch in text:
|
|
57
|
+
# East Asian Wide(W)与 Fullwidth(F)字符按 2 列计
|
|
58
|
+
if unicodedata.east_asian_width(ch) in ("W", "F"):
|
|
59
|
+
width += 2
|
|
60
|
+
else:
|
|
61
|
+
width += 1
|
|
62
|
+
return width
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def pad(text: str, width: int) -> str:
|
|
66
|
+
"""按显示宽度把文本补齐到指定宽度(不足则补空格)。"""
|
|
67
|
+
return text + " " * max(0, width - display_width(text))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def load_data() -> dict:
|
|
71
|
+
"""从 JSON 文件加载全部任务数据。
|
|
72
|
+
|
|
73
|
+
文件不存在时返回空结构(首次运行);文件损坏时给出可理解错误并退出。
|
|
74
|
+
返回结构:
|
|
75
|
+
{"next_id": int, "tasks": [{"id", "text", "priority", "status", "created_at"}]}
|
|
76
|
+
"""
|
|
77
|
+
if not os.path.exists(DATA_FILE):
|
|
78
|
+
# 首次运行:返回空数据骨架
|
|
79
|
+
return {"next_id": 1, "tasks": []}
|
|
80
|
+
try:
|
|
81
|
+
with open(DATA_FILE, encoding="utf-8") as f:
|
|
82
|
+
return json.load(f)
|
|
83
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
84
|
+
# 把底层异常重写为人类可读的错误,并给出排查方向
|
|
85
|
+
sys.stderr.write(f"错误:无法读取数据文件 {DATA_FILE}({e})\n")
|
|
86
|
+
sys.stderr.write("提示:文件损坏时可删除该文件后重试。\n")
|
|
87
|
+
sys.exit(1)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def save_data(data: dict) -> None:
|
|
91
|
+
"""把任务数据写回 JSON 文件。
|
|
92
|
+
|
|
93
|
+
自动创建目录;写入失败(如权限不足)时输出可理解错误。
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
os.makedirs(DATA_DIR, exist_ok=True) # 目录不存在则创建
|
|
97
|
+
with open(DATA_FILE, "w", encoding="utf-8") as f:
|
|
98
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
99
|
+
except OSError as e:
|
|
100
|
+
sys.stderr.write(f"错误:无法写入数据文件 {DATA_FILE}({e})\n")
|
|
101
|
+
sys.exit(1)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
# 业务逻辑(每个命令一个函数,返回值用于退出码)
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
def cmd_add(text: str, priority: str) -> int:
|
|
109
|
+
"""添加一条任务并返回退出码。
|
|
110
|
+
|
|
111
|
+
text: 任务内容(已由调用方校验非空)
|
|
112
|
+
priority: 优先级,取值来自 PRIORITIES 的键
|
|
113
|
+
"""
|
|
114
|
+
data = load_data()
|
|
115
|
+
task = {
|
|
116
|
+
"id": data["next_id"], # 自增 ID,永不复用(删除后不回收)
|
|
117
|
+
"text": text,
|
|
118
|
+
"priority": priority,
|
|
119
|
+
"status": "pending", # 新任务默认未完成
|
|
120
|
+
"created_at": date.today().isoformat(), # 创建日期,如 2026-09-08
|
|
121
|
+
}
|
|
122
|
+
data["tasks"].append(task)
|
|
123
|
+
data["next_id"] += 1
|
|
124
|
+
save_data(data)
|
|
125
|
+
print(f"已添加任务 #{task['id']}: {text}")
|
|
126
|
+
return 0
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def cmd_list(status: Optional[str], as_json: bool) -> int:
|
|
130
|
+
"""列出任务。
|
|
131
|
+
|
|
132
|
+
status: 过滤条件,None 表示全部,'pending' 或 'done' 表示只看某种状态
|
|
133
|
+
as_json: True 时输出 JSON(机器可读),否则输出人可读表格
|
|
134
|
+
"""
|
|
135
|
+
data = load_data()
|
|
136
|
+
# 按状态过滤;未指定则不过滤
|
|
137
|
+
tasks = [t for t in data["tasks"] if status is None or t["status"] == status]
|
|
138
|
+
# 排序:未完成的在前,同状态下高优先级在前(weight 越小优先级越高)
|
|
139
|
+
tasks.sort(key=lambda t: (t["status"] != "pending", PRIORITIES[t["priority"]]["weight"]))
|
|
140
|
+
|
|
141
|
+
if as_json:
|
|
142
|
+
# 机器可读输出:整个任务列表输出为 JSON,供脚本/jq 消费
|
|
143
|
+
print(json.dumps(tasks, ensure_ascii=False, indent=2))
|
|
144
|
+
return 0
|
|
145
|
+
|
|
146
|
+
if not tasks:
|
|
147
|
+
# 空列表时给出友好提示(而不是输出空表格)
|
|
148
|
+
print("(没有任务" + (f",状态:{status}" if status else "") + ")")
|
|
149
|
+
return 0
|
|
150
|
+
|
|
151
|
+
# 人可读表格:ID、状态、优先级、内容、创建日期
|
|
152
|
+
# 用显示宽度(中文按 2 列)计算各列宽度,保证对齐
|
|
153
|
+
id_w = max(display_width("ID"), max(display_width(str(t["id"])) for t in tasks))
|
|
154
|
+
text_w = max(display_width("任务"), max(display_width(t["text"]) for t in tasks))
|
|
155
|
+
header = f"{pad('ID', id_w)} {pad('状态', 4)} {pad('优先级', 4)} {pad('任务', text_w)} 创建日期"
|
|
156
|
+
print(header)
|
|
157
|
+
print("-" * display_width(header))
|
|
158
|
+
for t in tasks:
|
|
159
|
+
p = PRIORITIES[t["priority"]]
|
|
160
|
+
# 终端是 TTY 时用颜色区分优先级,管道/重定向时保持纯文本
|
|
161
|
+
prio = f"{p['color']}{p['label']}{RESET}" if sys.stdout.isatty() else p["label"]
|
|
162
|
+
status = "完成" if t["status"] == "done" else "待办"
|
|
163
|
+
print(f"{pad(str(t['id']), id_w)} {pad(status, 4)} {pad(prio, 4)} {pad(t['text'], text_w)} {t['created_at']}")
|
|
164
|
+
return 0
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _find_task(data: dict, task_id: int) -> dict:
|
|
168
|
+
"""按 ID 查找任务;找不到则输出错误并退出(返回类型实际为 dict 或 None)。"""
|
|
169
|
+
for t in data["tasks"]:
|
|
170
|
+
if t["id"] == task_id:
|
|
171
|
+
return t
|
|
172
|
+
sys.stderr.write(f"错误:不存在 ID 为 {task_id} 的任务。可用 todo list 查看。\n")
|
|
173
|
+
sys.exit(1)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def cmd_done(task_id: int) -> int:
|
|
177
|
+
"""把任务标记为已完成(幂等:已完成再标记不报错)。"""
|
|
178
|
+
data = load_data()
|
|
179
|
+
task = _find_task(data, task_id)
|
|
180
|
+
if task["status"] == "done":
|
|
181
|
+
print(f"任务 #{task_id} 已是完成状态。")
|
|
182
|
+
else:
|
|
183
|
+
task["status"] = "done"
|
|
184
|
+
save_data(data)
|
|
185
|
+
print(f"已完成任务 #{task_id}: {task['text']}")
|
|
186
|
+
return 0
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def cmd_delete(task_id: int) -> int:
|
|
190
|
+
"""删除一条任务。"""
|
|
191
|
+
data = load_data()
|
|
192
|
+
task = _find_task(data, task_id) # 找不到会退出
|
|
193
|
+
data["tasks"].remove(task) # 按对象移除
|
|
194
|
+
save_data(data)
|
|
195
|
+
print(f"已删除任务 #{task_id}: {task['text']}")
|
|
196
|
+
return 0
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def cmd_stats() -> int:
|
|
200
|
+
"""输出统计信息:总数、完成数、待办数、按优先级分布。"""
|
|
201
|
+
data = load_data()
|
|
202
|
+
tasks = data["tasks"]
|
|
203
|
+
total = len(tasks)
|
|
204
|
+
done = sum(1 for t in tasks if t["status"] == "done")
|
|
205
|
+
pending = total - done
|
|
206
|
+
print(f"任务总数:{total}")
|
|
207
|
+
print(f"已完成:{done}")
|
|
208
|
+
print(f"待办:{pending}")
|
|
209
|
+
if total:
|
|
210
|
+
# 按优先级统计未完成任务的分布,输出进度条效果
|
|
211
|
+
print("待办优先级分布:")
|
|
212
|
+
for key in ("high", "medium", "low"):
|
|
213
|
+
p = PRIORITIES[key]
|
|
214
|
+
count = sum(1 for t in tasks if t["status"] == "pending" and t["priority"] == key)
|
|
215
|
+
# 简易进度条:每项任务一个 # 符号
|
|
216
|
+
bar = "#" * count if count else "-"
|
|
217
|
+
print(f" {p['label']}优先级:{count} {bar}")
|
|
218
|
+
return 0
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
# 参数解析与入口
|
|
223
|
+
# ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
226
|
+
"""构造命令行参数解析器(含子命令与各自的 flags)。"""
|
|
227
|
+
parser = argparse.ArgumentParser(
|
|
228
|
+
prog="todo",
|
|
229
|
+
description="练手用命令行任务清单工具",
|
|
230
|
+
)
|
|
231
|
+
# addsubparsers 创建子命令;required=True 保证不带子命令时输出用法错误
|
|
232
|
+
sub = parser.add_subparsers(dest="command", required=True, help="子命令")
|
|
233
|
+
|
|
234
|
+
# todo add <text> [--priority high|medium|low]
|
|
235
|
+
p_add = sub.add_parser("add", help="添加任务")
|
|
236
|
+
p_add.add_argument("text", help="任务内容") # 位置参数
|
|
237
|
+
p_add.add_argument("-p", "--priority", choices=PRIORITIES.keys(), # 短/长 flag
|
|
238
|
+
default="medium", help="优先级:high/medium/low(默认 medium)")
|
|
239
|
+
|
|
240
|
+
# todo list [--status pending|done] [--json]
|
|
241
|
+
p_list = sub.add_parser("list", help="列出任务")
|
|
242
|
+
p_list.add_argument("--status", choices=["pending", "done"], help="按状态过滤")
|
|
243
|
+
p_list.add_argument("--json", action="store_true", help="以 JSON 输出(机器可读)")
|
|
244
|
+
|
|
245
|
+
# todo done <id>
|
|
246
|
+
p_done = sub.add_parser("done", help="标记任务完成")
|
|
247
|
+
p_done.add_argument("id", type=int, help="任务 ID")
|
|
248
|
+
|
|
249
|
+
# todo delete <id>
|
|
250
|
+
p_delete = sub.add_parser("delete", help="删除任务")
|
|
251
|
+
p_delete.add_argument("id", type=int, help="任务 ID")
|
|
252
|
+
|
|
253
|
+
# todo stats
|
|
254
|
+
sub.add_parser("stats", help="显示统计信息")
|
|
255
|
+
return parser
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def main() -> int:
|
|
259
|
+
"""程序入口:解析参数后分发到对应命令函数,统一返回退出码。"""
|
|
260
|
+
parser = build_parser()
|
|
261
|
+
args = parser.parse_args() # 参数错误时 argparse 自动打印用法并 exit(2)
|
|
262
|
+
|
|
263
|
+
if args.command == "add":
|
|
264
|
+
# 业务校验:空任务内容没有意义,给出明确错误(先于写文件)
|
|
265
|
+
if not args.text.strip():
|
|
266
|
+
sys.stderr.write("错误:任务内容不能为空。\n")
|
|
267
|
+
return 1
|
|
268
|
+
return cmd_add(args.text.strip(), args.priority)
|
|
269
|
+
if args.command == "list":
|
|
270
|
+
return cmd_list(args.status, args.json)
|
|
271
|
+
if args.command == "done":
|
|
272
|
+
return cmd_done(args.id)
|
|
273
|
+
if args.command == "delete":
|
|
274
|
+
return cmd_delete(args.id)
|
|
275
|
+
if args.command == "stats":
|
|
276
|
+
return cmd_stats()
|
|
277
|
+
# 理论上到不了这里(argparse required=True 已拦截)
|
|
278
|
+
return 0
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
if __name__ == "__main__":
|
|
282
|
+
sys.exit(main())
|