refactor(todo): 去除 MySQL 依赖,改为 JSON 本地存储
- 数据存储: ~/.local/share/todo/tasks.json 纯字符串数组 - 操作简化: 仅保留 add/list/done,无状态/优先级/归档 - 新增 scripts/validate.py 校验脚本 (格式/结构/去重) - 新增 schema/tasks.schema.json - 删除 references/setup.md (MySQL 配置指南) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env python3
|
||||
"""校验 ~/.local/share/todo/tasks.json 的格式、结构和完整性。
|
||||
|
||||
退出码:
|
||||
0 - 校验通过
|
||||
1 - 校验失败(错误信息输出到 stderr)
|
||||
|
||||
用法:
|
||||
python3 validate.py [文件路径]
|
||||
默认路径: ~/.local/share/todo/tasks.json
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
DEFAULT_PATH = Path.home() / ".local" / "share" / "todo" / "tasks.json"
|
||||
SCHEMA_PATH = Path(__file__).parent.parent / "schema" / "tasks.schema.json"
|
||||
|
||||
|
||||
def load_json(path: Path) -> tuple[dict | None, str | None]:
|
||||
"""加载并解析 JSON 文件。"""
|
||||
if not path.exists():
|
||||
return None, f"文件不存在: {path}"
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return data, None
|
||||
except json.JSONDecodeError as e:
|
||||
return None, f"JSON 格式错误: {e}"
|
||||
|
||||
|
||||
def load_schema() -> tuple[dict | None, str | None]:
|
||||
"""加载 JSON Schema。"""
|
||||
if not SCHEMA_PATH.exists():
|
||||
return None, f"Schema 文件不存在: {SCHEMA_PATH}"
|
||||
try:
|
||||
with open(SCHEMA_PATH, "r", encoding="utf-8") as f:
|
||||
return json.load(f), None
|
||||
except json.JSONDecodeError as e:
|
||||
return None, f"Schema 格式错误: {e}"
|
||||
|
||||
|
||||
def validate_structure(data: dict) -> list[str]:
|
||||
"""校验数据结构。"""
|
||||
errors = []
|
||||
|
||||
if not isinstance(data, dict):
|
||||
errors.append("顶层必须是对象")
|
||||
return errors
|
||||
|
||||
if "tasks" not in data:
|
||||
errors.append("缺少必需字段 'tasks'")
|
||||
return errors
|
||||
|
||||
tasks = data["tasks"]
|
||||
if not isinstance(tasks, list):
|
||||
errors.append("'tasks' 必须是数组")
|
||||
return errors
|
||||
|
||||
for i, item in enumerate(tasks):
|
||||
if not isinstance(item, str):
|
||||
errors.append(f"tasks[{i}] 必须是字符串,实际类型: {type(item).__name__}")
|
||||
elif len(item.strip()) == 0:
|
||||
errors.append(f"tasks[{i}] 不能为空字符串")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def validate_duplicates(tasks: list[str]) -> list[str]:
|
||||
"""校验任务是否有重复(大小写不敏感)。"""
|
||||
errors = []
|
||||
seen = {}
|
||||
for i, task in enumerate(tasks):
|
||||
key = task.strip().lower()
|
||||
if key in seen:
|
||||
errors.append(f"tasks[{i}] '{task}' 与 tasks[{seen[key]}] 重复")
|
||||
else:
|
||||
seen[key] = i
|
||||
return errors
|
||||
|
||||
|
||||
def validate_schema(data: dict) -> list[str]:
|
||||
"""使用 JSON Schema 校验(如果 jsonschema 可用)。"""
|
||||
try:
|
||||
import jsonschema
|
||||
except ImportError:
|
||||
# jsonschema 未安装,跳过 schema 校验
|
||||
return []
|
||||
|
||||
schema, err = load_schema()
|
||||
if err:
|
||||
return [f"Schema 校验跳过: {err}"]
|
||||
|
||||
errors = []
|
||||
try:
|
||||
jsonschema.validate(instance=data, schema=schema)
|
||||
except jsonschema.ValidationError as e:
|
||||
errors.append(f"Schema 校验失败: {e.message}")
|
||||
except jsonschema.SchemaError as e:
|
||||
errors.append(f"Schema 定义错误: {e.message}")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def main() -> int:
|
||||
path = Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_PATH
|
||||
|
||||
# 1. JSON 格式校验
|
||||
data, err = load_json(path)
|
||||
if err:
|
||||
print(err, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# 2. 结构校验
|
||||
errors = validate_structure(data)
|
||||
if errors:
|
||||
for e in errors:
|
||||
print(f"结构错误: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# 3. JSON Schema 校验
|
||||
schema_errors = validate_schema(data)
|
||||
if schema_errors:
|
||||
for e in schema_errors:
|
||||
print(f"Schema 错误: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# 4. 去重校验
|
||||
dup_errors = validate_duplicates(data["tasks"])
|
||||
if dup_errors:
|
||||
for e in dup_errors:
|
||||
print(f"重复错误: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"校验通过: {path} ({len(data['tasks'])} 个任务)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user