## Context 当前 `yaml2pptx.py` 的 CLI 实现存在以下问题: 1. **双模式解析**:同时支持子命令模式(`check`/`convert`)和传统模式(直接参数),导致参数解析逻辑复杂,代码中需要手动检查 `sys.argv[1]` 来决定使用哪种解析器 2. **职责混乱**:`convert` 命令通过 `--preview` 标志切换到预览模式,但 convert 和 preview 是完全不同的操作(一个生成文件,一个启动服务器) 3. **参数重复定义**:`--template-dir`、`--preview`、`--port` 等参数在两个解析器中重复定义 4. **命名不清晰**:`--no-check` 是双重否定,不够直观 本次重构的目标是建立清晰的子命令架构,提升代码可维护性和用户体验。 ## Goals / Non-Goals **Goals:** - 统一使用子命令架构,移除传统模式 - 将 preview 独立为子命令,职责分离 - 简化参数解析逻辑,消除代码重复 - 优化参数命名和默认值 - 新增实用功能(文件覆盖控制、局域网预览、浏览器控制) **Non-Goals:** - 不改变核心转换逻辑(YAML 解析、PPTX 生成) - 不改变验证逻辑(ERROR/WARNING/INFO 分级) - 不改变预览服务器的核心功能(文件监听、SSE 推送) - 不考虑向后兼容传统模式(项目未上线) ## Decisions ### 决策 1:使用纯子命令架构 **选择:** 移除传统模式,统一使用 ` ` 结构 **理由:** - 子命令架构更清晰,每个子命令职责单一 - 避免双模式解析的复杂性 - 符合现代 CLI 工具的设计习惯(如 git、docker) **替代方案:** - 保留传统模式作为快捷方式:增加维护成本,用户容易混淆 - 使用选项组而非子命令:无法清晰表达互斥的操作模式 ### 决策 2:preview 独立为子命令 **选择:** 将 preview 从 `convert --preview` 改为独立的 `preview` 子命令 **理由:** - convert 和 preview 是完全不同的操作: - convert:一次性操作,生成文件后退出 - preview:长期运行,启动服务器,监听文件变化 - 独立子命令使参数更清晰(preview 特有的 `--port`、`--host`、`--no-browser` 不会出现在 convert 中) - 符合单一职责原则 **替代方案:** - 保持 `convert --preview`:职责混乱,参数难以组织 - 使用 `serve` 或 `watch` 命名:preview 更直观,表达预览意图 ### 决策 3:参数解析使用 argparse 的 subparsers **选择:** 使用 argparse 的 `add_subparsers()` 和 `required=True` **理由:** - argparse 是 Python 标准库,无需额外依赖 - subparsers 自动处理子命令路由,无需手动检查 `sys.argv` - 自动生成帮助信息 **实现结构:** ```python parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(dest='command', required=True) # 每个子命令独立配置 check_parser = subparsers.add_parser('check') convert_parser = subparsers.add_parser('convert') preview_parser = subparsers.add_parser('preview') ``` ### 决策 4:preview 默认使用随机端口(30000-40000) **选择:** `--port` 默认值为 None,运行时随机选择 30000-40000 范围内的端口 **理由:** - 避免端口冲突,支持同时运行多个预览实例 - 30000+ 端口通常不被系统服务占用 - 用户可以通过 `--port` 指定固定端口 **替代方案:** - 固定端口 5000:容易冲突,不支持多实例 - 完全随机端口(1024-65535):可能与其他服务冲突 ### 决策 5:convert 默认不覆盖已存在文件 **选择:** 输出文件已存在时报错,需要 `--force/-f` 强制覆盖 **理由:** - 避免意外覆盖用户文件 - 符合安全优先原则 - 提供明确的覆盖控制 **替代方案:** - 默认覆盖:不安全,可能导致数据丢失 - 自动重命名(如 output-1.pptx):用户可能不知道生成了多个文件 ### 决策 6:简化参数,移除不必要的选项 **选择:** 移除 `--verbose`、`--quiet`、`--strict`、`--format`、`--no-watch` **理由:** - `--verbose`/`--quiet`:当前输出已经足够简洁,不需要额外控制 - `--strict`:保持原有的 ERROR/WARNING/INFO 分级即可 - `--format`:命令行工具主要面向人类用户,text 格式足够 - `--no-watch`:preview 的核心价值就是实时监听,不需要关闭 **保留的参数:** - `--template-dir`:所有命令通用 - `--skip-validation`:convert 特有,用于快速转换 - `--force/-f`:convert 特有,文件覆盖控制 - `--port`、`--host`、`--no-browser`:preview 特有 ## Risks / Trade-offs ### 风险 1:破坏性变更影响现有用户 **风险:** 移除传统模式和 `convert --preview` 会导致现有脚本失效 **缓解措施:** - 项目未上线,无现有用户 - 更新所有文档和示例 - 在错误信息中提示新的使用方式(如检测到 `--preview` 时提示使用 `preview` 子命令) ### 风险 2:随机端口可能导致防火墙问题 **风险:** 随机端口可能被防火墙阻止 **缓解措施:** - 用户可以通过 `--port` 指定固定端口 - 在文档中说明端口范围(30000-40000) - 默认 host 为 127.0.0.1,仅本地访问 ### 风险 3:参数验证逻辑需要重新实现 **风险:** 移除双模式后,参数验证逻辑需要调整 **缓解措施:** - 在 `main()` 函数中统一验证输入文件存在性 - 在 `handle_convert()` 中验证输出文件覆盖逻辑 - 在 `handle_preview()` 中验证端口合法性 ## Migration Plan ### 实施步骤 1. **重构 `parse_args()` 函数** - 移除双模式检查逻辑 - 使用 `subparsers.add_parser()` 定义三个子命令 - 为每个子命令配置独立的参数 2. **更新 `handle_convert()` 函数** - 移除 `--preview` 相关逻辑 - 添加输出文件存在性检查(`--force` 控制) - 保持自动验证逻辑(`--skip-validation` 控制) 3. **创建 `handle_preview()` 函数** - 从 `handle_convert()` 中提取 preview 逻辑 - 添加随机端口生成逻辑 - 调用 `start_preview_server()` 时传递新参数 4. **更新 `preview/server.py`** - 修改 `start_preview_server()` 函数签名 - 添加 `host` 参数(默认 127.0.0.1) - 添加 `open_browser` 参数(默认 True) - 更新端口随机生成逻辑(30000-40000) 5. **更新文档** - 更新 `README.md` 中的所有使用示例 - 更新命令行选项说明表格 - 添加迁移指南(如果需要) ### 回滚策略 如果发现重大问题,可以: 1. 回退到上一个 commit 2. 保留新的子命令架构,但临时添加传统模式兼容层 3. 通过环境变量或配置文件控制行为 ## Open Questions 无待解决的问题。设计已经明确,可以开始实施。