## Context ### 当前状态 当前项目有两种模板系统: 1. **内联模板**:在文档 YAML 的 `templates` 字段定义,使用字典结构 2. **外部模板**:通过 `--template-dir` 指定文件夹,每个模板是单独的 `.yaml` 文件 两种系统的查询逻辑不一致: - 内联模板:字典键查找 `data['templates'][name]` - 外部模板:文件系统查找 `(templates_dir / name).yaml.exists()` ### 约束条件 - 必须保持中文注释和文档 - 使用 uv 运行 Python 脚本 - 不能修改主机环境配置 - 需要更新 README.md 和 README_DEV.md ### 相关利益方 - 使用外部模板的现有用户(需要迁移) - 模板开发者(需要了解新的模板库格式) ## Goals / Non-Goals **Goals:** 1. 统一内联模板和外部模板的数据结构和查询逻辑 2. 支持模板库元数据(description、version、author) 3. 统一资源路径解析,避免相对路径错误 4. 简化模板的分发和管理(单个文件 vs 多个文件) 5. 优化错误提示,区分不同类型的错误 **Non-Goals:** 1. 不支持向后兼容 `--template-dir` 参数 2. 不支持模板库文件的热重载(文件监听仅在文档级别) 3. 不改变模板的 vars 和 elements 结构 ## Decisions ### 1. 命令行参数命名:使用 `--template` **决策**: 命令行参数从 `--template-dir` 改为 `--template` **理由**: - `--template` 更简洁,直接表示指定模板文件 - 与内联模板的概念更一致(都是指定模板来源) - 避免与 slide 中的 `template` 字段混淆(上下文清晰区分) **考虑的替代方案**: - `--template-file`: 更明确但冗长 - `--template-lib`: 表示模板库,但不如 `--template` 直观 ### 2. 模板库文件格式:包含 `templates` 字典 **决策**: 模板库文件使用以下格式: ```yaml # 元数据字段(可选) description: "模板库描述" version: "1.0.0" author: "作者" # 必需字段 templates: template-name-1: vars: ... elements: ... template-name-2: vars: ... elements: ... ``` **理由**: - `templates` 作为必需字段,验证时检查其存在性 - 元数据字段放在顶层,便于人类阅读和工具解析 - 与内联模板的结构保持一致 **考虑的替代方案**: - 将所有内容放在 `templates` 下:会增加嵌套层级 - 使用不同的必需字段名:`templates` 语义最清晰 ### 3. 资源路径解析:提前解析为绝对路径 **决策**: 在模板渲染阶段就将图片相对路径解析为绝对路径 **实现位置**: - 外部模板:在 `Template.render()` 方法中解析 - 自定义元素:在 `Presentation.render_slide()` 中解析 **路径规则**: - 外部模板元素:`base_dir = 模板库文件所在目录` - 内联模板元素:`base_dir = 文档 YAML 所在目录` - 自定义元素:`base_dir = 文档 YAML 所在目录` **理由**: - 避免在渲染器中传递多个 `base_path` - 元素数据在渲染时就是绝对路径,避免后续错误 - 符合"尽早解析"的设计原则 **考虑的替代方案**: - 在渲染时根据元素来源动态选择 base_path:需要在元素对象上标记来源,增加复杂度 - 在渲染器中检查元素路径是否已解析:增加运行时开销 ### 4. 移除外部模板缓存机制 **决策**: 移除 `Presentation.template_cache`,每次都从模板库创建新模板 **理由**: - 模板库文件是单个 YAML,加载开销小 - 简化代码逻辑,减少状态管理 - 避免缓存一致性问题(模板库文件修改后) **考虑的替代方案**: - 保留缓存并添加失效机制:增加复杂度,收益不大 - 使用 LRU 缓存:对于少量模板没有必要 ### 5. 同名冲突处理:WARNING + 优先内联 **决策**: - 检测到同名冲突时,返回 `ValidationIssue` 级别 `WARNING` - 优先使用内联模板 - 记录警告信息到验证结果 **理由**: - 内联模板是文档的一部分,优先级更高 - WARNING 不会阻止验证,但提醒用户注意 - 保持向后兼容(之前的行为是 ERROR) **考虑的替代方案**: - 抛出 ERROR:过于严格,与之前行为不一致 - 优先外部模板:内联模板更贴近文档,应该优先 ### 6. 错误类型区分 **决策**: 区分三种错误类型 | 错误类型 | 错误代码 | 级别 | 条件 | |---------|---------|------|------| | 模板库文件不存在 | `TEMPLATE_LIBRARY_FILE_NOT_FOUND` | ERROR | `--template` 指定的文件不存在 | | 缺少 templates 字段 | `TEMPLATE_LIBRARY_MISSING_TEMPLATES_FIELD` | ERROR | 模板库文件没有 `templates` 字段 | | 模板名称不存在 | `TEMPLATE_NOT_FOUND_IN_LIBRARY` | ERROR | 模板库中找不到指定的模板名称 | | 同名冲突 | `TEMPLATE_NAME_CONFLICT` | WARNING | 内联和外部模板同名 | **理由**: - 帮助用户快速定位问题 - 错误消息更精确,减少调试时间 ## Risks / Trade-offs ### Risk 1: 现有用户迁移成本 **风险**: 使用 `--template-dir` 的用户需要手动迁移模板 **缓解措施**: - 错误消息中提示使用新的参数格式 ### Risk 2: 模板库文件格式错误 **风险**: 用户创建的模板库文件格式不正确 **缓解措施**: - 递归验证每个模板的结构 - 提供详细的错误消息,指出具体位置 - 在文档中提供完整示例 ### Risk 3: 资源路径解析错误 **风险**: 相对路径解析后,模板库文件移动导致路径失效 **缓解措施**: - 错误消息中显示解析后的绝对路径 - 建议用户使用绝对路径或将资源放在相对稳定的位置 ### Trade-off 1: 不支持向后兼容 **权衡**: 简化代码 vs 用户迁移成本 **决策**: 选择简化代码,不向后兼容 **理由**: - 项目处于活跃开发阶段,破坏性变更可接受 - 统一的架构带来长期收益 - 迁移成本可控(单个模板库文件) ### Trade-off 2: 移除缓存 vs 性能 **权衡**: 简化代码 vs 略微的性能损失 **决策**: 选择简化代码 **理由**: - 模板数量通常不多(< 100) - YAML 加载开销很小 - 代码简洁性更重要 ## Migration Plan ### 迁移步骤 1. **代码变更** - 按照任务列表依次修改各模块 - 每个模块修改后运行对应测试 2. **测试更新** - 移除旧模板系统的相关测试 - 设计使用新模板系统的测试用例 - 添加模板库文件的测试用例 - 添加资源路径解析的测试用例 3. **文档更新** - 更新 README.md:新的命令行参数格式 - 更新 README_DEV.md:模板库文件格式说明 - 添加迁移指南 ### 回滚策略 如果发现严重问题: 1. 回滚代码到变更前的 commit 2. 重新发布旧版本 3. 修复问题后再次发布 ## Open Questions 1. **是否需要提供模板库文件生成工具?** - 当前未计划,手动创建 YAML 文件即可 - 如果有大量需求,未来可考虑 2. **是否需要支持模板库文件的远程 URL?** - 当前未计划,仅支持本地文件 - 未来可通过 HTTP 下载支持 3. **模板库元数据字段是否需要验证?** - 当前不验证,用户可自定义 - 未来可考虑定义标准字段(如 license、homepage)