主要变更: - 将 templates_dir 参数改为 template_file,支持单个模板库 YAML 文件 - 添加模板库 YAML 验证功能 - 为模板添加 base_dir 支持,正确解析相对路径资源 - 内联模板与外部模板同名时改为警告(内联优先) - 移除模板缓存机制,直接使用模板库字典 - 更新所有相关测试以适配新的模板加载方式 此重构简化了模板管理,使模板资源的路径解析更加清晰明确。
239 lines
7.1 KiB
Markdown
239 lines
7.1 KiB
Markdown
## 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)
|