主要变更: - 将 templates_dir 参数改为 template_file,支持单个模板库 YAML 文件 - 添加模板库 YAML 验证功能 - 为模板添加 base_dir 支持,正确解析相对路径资源 - 内联模板与外部模板同名时改为警告(内联优先) - 移除模板缓存机制,直接使用模板库字典 - 更新所有相关测试以适配新的模板加载方式 此重构简化了模板管理,使模板资源的路径解析更加清晰明确。
7.1 KiB
7.1 KiB
Context
当前状态
当前项目有两种模板系统:
- 内联模板:在文档 YAML 的
templates字段定义,使用字典结构 - 外部模板:通过
--template-dir指定文件夹,每个模板是单独的.yaml文件
两种系统的查询逻辑不一致:
- 内联模板:字典键查找
data['templates'][name] - 外部模板:文件系统查找
(templates_dir / name).yaml.exists()
约束条件
- 必须保持中文注释和文档
- 使用 uv 运行 Python 脚本
- 不能修改主机环境配置
- 需要更新 README.md 和 README_DEV.md
相关利益方
- 使用外部模板的现有用户(需要迁移)
- 模板开发者(需要了解新的模板库格式)
Goals / Non-Goals
Goals:
- 统一内联模板和外部模板的数据结构和查询逻辑
- 支持模板库元数据(description、version、author)
- 统一资源路径解析,避免相对路径错误
- 简化模板的分发和管理(单个文件 vs 多个文件)
- 优化错误提示,区分不同类型的错误
Non-Goals:
- 不支持向后兼容
--template-dir参数 - 不支持模板库文件的热重载(文件监听仅在文档级别)
- 不改变模板的 vars 和 elements 结构
Decisions
1. 命令行参数命名:使用 --template
决策: 命令行参数从 --template-dir 改为 --template
理由:
--template更简洁,直接表示指定模板文件- 与内联模板的概念更一致(都是指定模板来源)
- 避免与 slide 中的
template字段混淆(上下文清晰区分)
考虑的替代方案:
--template-file: 更明确但冗长--template-lib: 表示模板库,但不如--template直观
2. 模板库文件格式:包含 templates 字典
决策: 模板库文件使用以下格式:
# 元数据字段(可选)
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
迁移步骤
-
代码变更
- 按照任务列表依次修改各模块
- 每个模块修改后运行对应测试
-
测试更新
- 移除旧模板系统的相关测试
- 设计使用新模板系统的测试用例
- 添加模板库文件的测试用例
- 添加资源路径解析的测试用例
-
文档更新
- 更新 README.md:新的命令行参数格式
- 更新 README_DEV.md:模板库文件格式说明
- 添加迁移指南
回滚策略
如果发现严重问题:
- 回滚代码到变更前的 commit
- 重新发布旧版本
- 修复问题后再次发布
Open Questions
-
是否需要提供模板库文件生成工具?
- 当前未计划,手动创建 YAML 文件即可
- 如果有大量需求,未来可考虑
-
是否需要支持模板库文件的远程 URL?
- 当前未计划,仅支持本地文件
- 未来可通过 HTTP 下载支持
-
模板库元数据字段是否需要验证?
- 当前不验证,用户可自定义
- 未来可考虑定义标准字段(如 license、homepage)