1
0

refactor: 重构外部模板系统,改为单文件模板库模式

主要变更:
- 将 templates_dir 参数改为 template_file,支持单个模板库 YAML 文件
- 添加模板库 YAML 验证功能
- 为模板添加 base_dir 支持,正确解析相对路径资源
- 内联模板与外部模板同名时改为警告(内联优先)
- 移除模板缓存机制,直接使用模板库字典
- 更新所有相关测试以适配新的模板加载方式

此重构简化了模板管理,使模板资源的路径解析更加清晰明确。
This commit is contained in:
2026-03-05 13:26:29 +08:00
parent bd12fce14b
commit f1aae96a04
27 changed files with 2141 additions and 1988 deletions

View File

@@ -0,0 +1,238 @@
## 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