feat: 添加内联模板支持
支持在 YAML 源文件中直接定义模板,无需单独的模板文件。 简化单文档编写流程,降低模板系统使用门槛。 核心功能: - 在 YAML 顶层新增 templates 字段定义内联模板 - 支持变量替换、条件渲染、默认值等完整模板功能 - 内联模板优先于外部模板查找 - 同名冲突检测:禁止内联和外部模板同名 - 相互引用检测:禁止内联模板之间相互引用 - 完整的错误处理和验证机制 代码变更: - core/template.py: 新增 from_data() 类方法 - core/presentation.py: 支持内联模板查找和冲突检测 - loaders/yaml_loader.py: 新增 validate_templates_yaml() 验证 - validators/: 扩展验证器支持内联模板 测试: - 新增 9 个内联模板专项测试 - 修复 1 个变量验证测试 - 所有 333 个测试通过 文档: - README.md: 添加内联模板使用指南和最佳实践 - README_DEV.md: 说明实现细节和设计决策 完全向后兼容,不使用 templates 字段时行为不变。
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-03-03
|
||||
185
openspec/changes/archive/2026-03-03-inline-templates/design.md
Normal file
185
openspec/changes/archive/2026-03-03-inline-templates/design.md
Normal file
@@ -0,0 +1,185 @@
|
||||
## Context
|
||||
|
||||
**当前状态**:
|
||||
- 项目已有完整的外部模板系统(core/template.py),包括变量替换、条件渲染等功能
|
||||
- 模板系统采用文件隔离方式,模板定义在独立的 YAML 文件中
|
||||
- 通过 `template: <name>` 引用外部模板,使用 `--template-dir` 指定模板目录
|
||||
- 项目中没有任何实际使用模板的案例,说明外部模板的使用门槛较高
|
||||
|
||||
**问题分析**:
|
||||
通过代码分析和 grep 搜索发现:
|
||||
1. 整个项目中没有包含 `template:` 字段的 YAML 文件
|
||||
2. 所有模板测试用例都是动态生成的,没有真实的用户使用场景
|
||||
3. 外部模板需要额外的文件管理工作,增加了使用复杂度
|
||||
|
||||
**项目约束**:
|
||||
- 面向中文开发者,使用中文编写注释和文档
|
||||
- 使用 uv 运行 Python 脚本,禁止直接使用主机环境 Python
|
||||
- 测试文件必须放在 temp/ 目录
|
||||
- 每次功能迭代需要更新 README.md 和 README_DEV.md
|
||||
- 所有需求必须设计全面、合理的测试内容
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 支持在 YAML 源文件中定义和使用内联模板
|
||||
- 保持与外部模板系统的兼容性,允许混合使用
|
||||
- 禁止内联和外部模板同名,要求模板名称必须唯一
|
||||
- 提供完整的错误处理和验证机制
|
||||
- 保持代码向后兼容,不破坏现有功能
|
||||
|
||||
**Non-Goals:**
|
||||
- 不支持内联模板之间的相互引用
|
||||
- 不支持内联模板引用外部模板
|
||||
- 不实现模板继承或组合功能
|
||||
- 不修改外部模板的现有功能
|
||||
|
||||
## Decisions
|
||||
|
||||
### 决策 1: 使用 `templates` 字段定义内联模板
|
||||
|
||||
**选择**: 在 YAML 文件顶层新增 `templates` 字段,字典类型,键为模板名称,值为模板定义
|
||||
|
||||
**理由**:
|
||||
- 保持 YAML 结构清晰,与 `metadata`、`slides` 字段同级
|
||||
- 字典结构便于查找和管理
|
||||
- 不与现有字段冲突,向后兼容
|
||||
|
||||
**替代方案考虑**:
|
||||
- 方案 A: 在 `metadata` 中定义 templates - 会造成 metadata 职责混乱
|
||||
- 方案 B: 使用 YAML 锚点语法 - 无法支持变量替换和条件渲染
|
||||
- 方案 C: 在每个 slide 中定义内联模板 - 无法实现模板复用
|
||||
|
||||
### 决策 2: 禁止内联和外部模板同名
|
||||
|
||||
**选择**: 当同名模板同时存在于内联和外部时,直接抛出 ERROR 级别错误,不允许使用该模板
|
||||
|
||||
**理由**:
|
||||
- 显式报错比隐式选择更安全,避免用户意外使用了错误的模板
|
||||
- 内联和外部模板混合使用的情况较少,同名通常是错误或命名不当
|
||||
- 强制用户明确模板来源,提升代码清晰度
|
||||
- 降低调试难度,错误立即暴露而不是隐式选择
|
||||
|
||||
**实现**:
|
||||
```python
|
||||
def get_template(self, template_name):
|
||||
# 1. 先检查内联模板
|
||||
if hasattr(self, 'inline_templates') and template_name in self.inline_templates:
|
||||
# 2. 检查外部模板是否也存在同名
|
||||
if self._external_template_exists(template_name):
|
||||
raise YAMLError(
|
||||
f"模板名称冲突: '{template_name}' 同时存在于内联模板和外部模板目录\n"
|
||||
f"请使用不同的模板名称以避免冲突"
|
||||
)
|
||||
inline_data = self.inline_templates[template_name]
|
||||
return Template.from_data(inline_data, template_name)
|
||||
|
||||
# 3. 回退到外部模板
|
||||
if template_name not in self.template_cache:
|
||||
self.template_cache[template_name] = Template(
|
||||
template_name, self.templates_dir
|
||||
)
|
||||
return self.template_cache[template_name]
|
||||
|
||||
# 辅助方法:检查外部模板是否存在
|
||||
def _external_template_exists(self, template_name):
|
||||
"""检查外部模板文件是否存在"""
|
||||
if not self.templates_dir:
|
||||
return False
|
||||
template_path = Path(self.templates_dir) / f"{template_name}.yaml"
|
||||
return template_path.exists()
|
||||
```
|
||||
|
||||
### 决策 3: 新增 `Template.from_data()` 类方法
|
||||
|
||||
**选择**: 添加类方法从字典创建模板实例,而不是修改现有的 `__init__`
|
||||
|
||||
**理由**:
|
||||
- 保持现有 `Template.__init__` 的不变性,专注于外部模板加载
|
||||
- 区分内联和外部模板的创建路径,降低耦合度
|
||||
- 类方法语义清晰:`Template.from_data(data, name)` vs `Template(name, dir)`
|
||||
|
||||
**实现**:
|
||||
```python
|
||||
@classmethod
|
||||
def from_data(cls, template_data, template_name):
|
||||
"""从字典创建模板(内联模板)"""
|
||||
obj = cls.__new__(cls)
|
||||
obj.data = template_data
|
||||
obj.vars_def = {}
|
||||
for var in template_data.get('vars', []):
|
||||
obj.vars_def[var['name']] = var
|
||||
obj.elements = template_data.get('elements', [])
|
||||
return obj
|
||||
```
|
||||
|
||||
### 决策 4: 禁止内联模板相互引用
|
||||
|
||||
**选择**: 在设计阶段明确禁止内联模板相互引用,不在实现中处理此场景
|
||||
|
||||
**理由**:
|
||||
- 降低实现复杂度,避免循环引用检测逻辑
|
||||
- 内联模板主要用于单文档简单场景,不需要复杂引用
|
||||
- 外部模板系统已经支持跨文档复用,复杂场景应使用外部模板
|
||||
|
||||
**错误处理**:
|
||||
- 如果检测到内联模板引用其他内联模板,抛出 ERROR 级别错误
|
||||
- 明确在文档中说明此限制
|
||||
|
||||
### 决策 5: 完整的错误处理机制
|
||||
|
||||
**选择**: 实现四层错误处理:模板定义错误、模板引用错误、变量传递错误、循环引用错误
|
||||
|
||||
**理由**:
|
||||
- 确保用户能够快速定位和修复问题
|
||||
- 提前发现潜在的错误,避免在渲染时才暴露
|
||||
- 符合项目现有的错误处理模式(YAMLError 异常)
|
||||
|
||||
**错误场景**:
|
||||
1. 模板定义错误: `templates` 不是字典、缺少 `elements` 字段、变量定义错误
|
||||
2. 模板引用错误: 引用的内联模板不存在、外部模板不存在
|
||||
3. 变量传递错误: 缺少必需变量、传递未定义变量
|
||||
4. 循环引用错误: 内联模板自引用(虽然禁止相互引用,但需要检测自引用)
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
### 风险 1: 内存占用增加
|
||||
|
||||
**风险**: 内联模板数据保存在内存中,可能增加内存占用
|
||||
|
||||
**缓解措施**:
|
||||
- 单个模板数据量通常很小(<1KB),内存增加可忽略
|
||||
- 内联模板不需要缓存(已在内存中),不会重复占用
|
||||
- 未来可以添加内存监控,如果问题严重再优化
|
||||
|
||||
### 风险 2: YAML 文件可读性下降
|
||||
|
||||
**风险**: 大量的内联模板定义可能使 YAML 文件过长,降低可读性
|
||||
|
||||
**缓解措施**:
|
||||
- 内联模板适合简单场景,复杂场景建议使用外部模板
|
||||
- 在文档中提供最佳实践建议
|
||||
- IDE 的折叠功能可以帮助管理长文件
|
||||
|
||||
### 风险 3: 模板命名冲突强制检测
|
||||
|
||||
**风险**: 内联和外部模板同名时,用户可能期望使用外部模板,但系统直接报错
|
||||
|
||||
**缓解措施**:
|
||||
- 明确的错误消息,指出模板冲突的具体位置(内联 templates 字段 vs 外部模板目录)
|
||||
- 在文档中详细说明命名规则,建议使用不同的命名空间或前缀
|
||||
- 在示例代码中展示正确的命名方式
|
||||
- 提供 migration 指南,帮助用户解决命名冲突
|
||||
|
||||
### 风险 4: 循环引用误判
|
||||
|
||||
**风险**: 虽然禁止相互引用,但可能存在复杂的循环引用路径
|
||||
|
||||
**缓解措施**:
|
||||
- 实现简单的循环引用检测(DFS 遍历)
|
||||
- 在设计阶段就明确规则,降低实现复杂度
|
||||
- 添加充分的测试用例覆盖边界情况
|
||||
|
||||
## Open Questions
|
||||
|
||||
无。设计阶段已明确所有关键决策。
|
||||
@@ -0,0 +1,41 @@
|
||||
## Why
|
||||
|
||||
当前外部模板系统虽然功能完整,但需要单独管理模板文件,使用门槛较高。项目中没有任何实际使用的案例,说明用户体验有待改善。通过支持在 YAML 源文件中内联定义模板,可以在不需要跨文档复用的情况下,大幅简化单文档的编写流程,提升开发效率。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 在 YAML 文件中新增 `templates` 字段,允许在源文件中定义模板
|
||||
- 修改 `Presentation.get_template()` 方法,支持内联模板和外部模板的查找,内联模板优先
|
||||
- 在 `Template` 类中新增 `from_data()` 类方法,支持从字典创建模板实例
|
||||
- 在 `yaml_loader` 中新增 `validate_templates_yaml()` 函数,验证 templates 字段结构
|
||||
- 修改现有模板验证逻辑,支持内联模板和外部模板共存
|
||||
- 添加完整的错误处理机制,包括模板定义错误、引用错误、变量传递错误和循环引用检测
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `inline-templates`: 支持 YAML 源文件中定义和使用内联模板,包括模板定义、变量替换、条件渲染等功能
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无。此变更为新增功能,不修改现有功能的需求行为。
|
||||
|
||||
## Impact
|
||||
|
||||
**受影响的代码模块**:
|
||||
- `core/presentation.py`: 修改 `__init__` 保存 templates 字段,修改 `get_template()` 支持内联模板查找
|
||||
- `core/template.py`: 新增 `from_data()` 类方法,支持从字典创建模板
|
||||
- `loaders/yaml_loader.py`: 新增 `validate_templates_yaml()` 验证函数
|
||||
- `validators/`: 可能需要扩展验证器以支持 templates 字段的验证
|
||||
|
||||
**受影响的测试**:
|
||||
- 需要新增内联模板的单元测试和集成测试
|
||||
- 现有模板测试需要覆盖内联模板场景
|
||||
|
||||
**向后兼容性**:
|
||||
- 完全向后兼容。不使用 `templates` 字段时,系统行为与现有版本完全一致
|
||||
- 外部模板功能保持不变,可以与内联模板混合使用
|
||||
|
||||
**文档**:
|
||||
- 需要更新 README.md 和 README_DEV.md,说明内联模板的语法和使用方法
|
||||
@@ -0,0 +1,112 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: YAML 文件支持内联模板定义
|
||||
|
||||
系统 SHALL 允许用户在 YAML 源文件的 `templates` 字段中定义内联模板,模板定义包括变量和元素。
|
||||
|
||||
#### Scenario: 定义简单的内联模板
|
||||
- **WHEN** 用户在 YAML 文件的 `templates` 字段中定义一个名为 `title-slide` 的模板,包含 `vars` 和 `elements` 字段
|
||||
- **THEN** 系统 SHALL 成功解析模板定义,并将模板存储在 `inline_templates` 字典中
|
||||
|
||||
#### Scenario: 定义多个内联模板
|
||||
- **WHEN** 用户在 YAML 文件的 `templates` 字段中定义多个不同的内联模板
|
||||
- **THEN** 系统 SHALL 成功解析所有模板定义,并允许通过模板名称引用任何一个模板
|
||||
|
||||
### Requirement: 内联模板支持变量替换
|
||||
|
||||
系统 SHALL 支持在内联模板的元素中使用 `{variable}` 语法定义变量占位符,并在渲染时替换为实际值。
|
||||
|
||||
#### Scenario: 简单变量替换
|
||||
- **WHEN** 用户定义内联模板,在元素的 `content` 字段中使用 `{title}` 占位符,并在使用模板时提供 `title: "My Title"` 变量值
|
||||
- **THEN** 系统 SHALL 将占位符替换为 `My Title`
|
||||
|
||||
#### Scenario: 嵌套变量替换
|
||||
- **WHEN** 用户的模板默认值引用其他变量,如 `default: "{title} - Extended"`
|
||||
- **THEN** 系统 SHALL 递归解析所有变量引用,生成完整的替换结果
|
||||
|
||||
### Requirement: 内联模板支持条件渲染
|
||||
|
||||
系统 SHALL 支持通过 `visible` 字段控制元素的显示,基于变量值的条件表达式。
|
||||
|
||||
#### Scenario: 条件渲染为真
|
||||
- **WHEN** 元素的 `visible` 字段设置为 `"{subtitle != ''}"`,且用户提供非空的 `subtitle` 值
|
||||
- **THEN** 系统 SHALL 在渲染结果中包含该元素
|
||||
|
||||
#### Scenario: 条件渲染为假
|
||||
- **WHEN** 元素的 `visible` 字段设置为 `"{subtitle != ''}"`,且用户提供空的 `subtitle` 值
|
||||
- **THEN** 系统 SHALL 在渲染结果中排除该元素
|
||||
|
||||
### Requirement: 内联模板支持变量默认值
|
||||
|
||||
系统 SHALL 支持在模板的 `vars` 定义中为可选变量提供 `default` 值。
|
||||
|
||||
#### Scenario: 使用默认值
|
||||
- **WHEN** 用户的模板定义 `subtitle` 变量为可选(`required: false`),并提供 `default: ""`
|
||||
- **THEN** 系统 SHALL 在用户未提供 `subtitle` 变量时使用默认值
|
||||
|
||||
#### Scenario: 提供值覆盖默认值
|
||||
- **WHEN** 用户的模板定义了默认值,且用户在使用模板时提供了该变量的值
|
||||
- **THEN** 系统 SHALL 使用用户提供的值,而不是默认值
|
||||
|
||||
### Requirement: 禁止内联和外部模板同名
|
||||
|
||||
系统 SHALL 检测内联模板和外部模板的名称冲突,当同名模板同时存在时,抛出 ERROR 级别错误。
|
||||
|
||||
#### Scenario: 内联和外部模板同名
|
||||
- **WHEN** 同名模板同时存在于内联 `templates` 字段和外部模板目录中
|
||||
- **THEN** 系统 SHALL 抛出 ERROR 级别错误,明确指出模板名称冲突,并禁止使用该模板
|
||||
|
||||
#### Scene: 内联和外部模板名称唯一
|
||||
- **WHEN** 内联模板和外部模板的名称都不相同
|
||||
- **THEN** 系统 SHALL 正常加载和使用模板,不产生任何错误
|
||||
### Requirement: 禁止内联模板相互引用
|
||||
|
||||
系统 SHALL 检测并禁止内联模板之间的相互引用。
|
||||
|
||||
#### Scenario: 检测到内联模板相互引用
|
||||
- **WHEN** 一个内联模板的 `elements` 中引用了另一个内联模板
|
||||
- **THEN** 系统 SHALL 抛出 ERROR 级别错误,明确指出内联模板不支持相互引用
|
||||
|
||||
#### Scenario: 内联模板正常使用
|
||||
- **WHEN** 用户使用内联模板,且模板的 `elements` 中不包含其他模板引用
|
||||
- **THEN** 系统 SHALL 正常渲染该模板
|
||||
|
||||
### Requirement: 完整的错误处理
|
||||
|
||||
系统 SHALL 对内联模板的错误场景提供清晰的错误消息和适当的错误级别。
|
||||
|
||||
#### Scenario: 模板定义结构错误
|
||||
- **WHEN** `templates` 字段不是字典类型,或某个模板缺少必需的 `elements` 字段
|
||||
- **THEN** 系统 SHALL 在加载时抛出 ERROR 级别错误,包含具体的错误位置和原因
|
||||
|
||||
#### Scenario: 变量定义错误
|
||||
- **WHEN** 模板的 `vars` 定义中某个变量缺少必需的 `name` 字段
|
||||
- **THEN** 系统 SHALL 在加载时抛出 ERROR 级别错误
|
||||
|
||||
#### Scenario: 变量传递错误
|
||||
- **WHEN** 用户使用模板时缺少必需的变量,或传递了未定义的变量
|
||||
- **THEN** 系统 SHALL 在渲染时抛出 ERROR 或 WARNING 级别错误,明确指出问题所在
|
||||
|
||||
### Requirement: 向后兼容
|
||||
|
||||
系统 SHALL 在不使用 `templates` 字段时,保持与现有版本完全一致的行为。
|
||||
|
||||
#### Scenario: 不使用 templates 字段
|
||||
- **WHEN** 用户的 YAML 文件不包含 `templates` 字段,只使用外部模板或直接定义元素
|
||||
- **THEN** 系统 SHALL 表现与现有版本完全一致,不产生任何副作用
|
||||
|
||||
#### Scenario: 混合使用内联和外部模板
|
||||
- **WHEN** 用户的 YAML 文件同时定义了内联模板和使用外部模板
|
||||
- **THEN** 系统 SHALL 支持两种模板类型在同一演示文稿中混合使用
|
||||
|
||||
### Requirement: 内联模板数据验证
|
||||
|
||||
系统 SHALL 在加载 YAML 文件时验证内联模板的结构和内容。
|
||||
|
||||
#### Scenario: 验证模板结构
|
||||
- **WHEN** 用户定义的内联模板包含 `vars` 和 `elements` 字段,且结构正确
|
||||
- **THEN** 系统 SHALL 验证通过,允许后续使用该模板
|
||||
|
||||
#### Scenario: 验证元素结构
|
||||
- **WHEN** 用户的内联模板 `elements` 中定义了有效的元素类型和字段
|
||||
- **THEN** 系统 SHALL 验证通过,元素在渲染时正常工作
|
||||
@@ -0,0 +1,54 @@
|
||||
## 1. 核心功能实现
|
||||
|
||||
- [x] 1.1 在 `core/template.py` 中新增 `from_data()` 类方法,支持从字典创建模板实例
|
||||
- [x] 1.2 修改 `core/presentation.py` 的 `__init__` 方法,解析并保存 `templates` 字段到 `self.inline_templates`
|
||||
- [x] 1.3 修改 `core/presentation.py` 的 `get_template()` 方法,实现同名模板检测并抛出 ERROR 错误
|
||||
- [x] 1.4 在 `loaders/yaml_loader.py` 中新增 `validate_templates_yaml()` 函数,验证 `templates` 字段结构
|
||||
- [x] 1.5 修改 `loaders/yaml_loader.py` 的 `validate_presentation_yaml()` 函数,调用 `validate_templates_yaml()` 进行验证
|
||||
|
||||
## 2. 验证逻辑实现
|
||||
|
||||
- [x] 2.1 实现 `validate_templates_yaml()` 中的模板结构验证:检查 `templates` 是否为字典
|
||||
- [x] 2.2 实现 `validate_templates_yaml()` 中的模板元素验证:检查每个模板是否有必需的 `elements` 字段
|
||||
- [x] 2.3 实现 `validate_templates_yaml()` 中的变量定义验证:检查每个变量是否有必需的 `name` 字段
|
||||
- [x] 2.4 实现 `validate_templates_yaml()` 中的默认值验证:检测默认值中引用不存在的变量
|
||||
|
||||
## 3. 错误处理实现
|
||||
|
||||
- [x] 3.1 在 `core/presentation.py` 的 `get_template()` 中添加同名模板检测逻辑,抛出 ERROR 错误
|
||||
- [x] 3.2 在 `core/template.py` 的 `render()` 方法中添加内联模板相互引用的检测逻辑
|
||||
- [x] 3.3 优化错误消息格式,包含模板名称和具体位置信息(已在上述实现中完成)
|
||||
- [x] 3.4 实现循环引用检测:防止内联模板自引用(已在任务 3.2 中实现)
|
||||
|
||||
## 4. 测试用例实现
|
||||
|
||||
- [x] 4.1 在 `tests/unit/test_template.py` 中新增 `from_data()` 方法的单元测试
|
||||
- [x] 4.2 在 `tests/unit/test_presentation.py` 中新增内联模板查找的单元测试
|
||||
- [x] 4.3 在 `tests/unit/test_loaders/test_yaml_loader.py` 中新增 `validate_templates_yaml()` 的测试用例
|
||||
- [x] 4.4 在 `tests/integration/test_presentation.py` 中新增内联模板集成测试
|
||||
- [x] 4.5 新增内联模板基本使用场景测试:定义、引用、变量替换、条件渲染
|
||||
- [x] 4.6 新增同名模板报错测试:验证内联和外部模板同名时系统抛出 ERROR
|
||||
- [x] 4.7 新增内联模板错误处理测试:验证各种错误场景的错误消息和级别
|
||||
- [x] 4.8 新增向后兼容性测试:验证不使用 `templates` 字段时系统行为不变
|
||||
- [x] 4.2 在 `tests/unit/test_presentation.py` 中新增内联模板查找的单元测试
|
||||
- [x] 4.3 在 `tests/unit/test_loaders/test_yaml_loader.py` 中新增 `validate_templates_yaml()` 的测试用例
|
||||
- [x] 4.4 在 `tests/integration/test_presentation.py` 中新增内联模板集成测试
|
||||
- [x] 4.5 新增内联模板基本使用场景测试:定义、引用、变量替换、条件渲染
|
||||
- [x] 4.6 新增同名模板报错测试:验证内联和外部模板同名时系统抛出 ERROR
|
||||
- [x] 4.7 新增内联模板错误处理测试:验证各种错误场景的错误消息和级别
|
||||
- [x] 4.8 新增向后兼容性测试:验证不使用 `templates` 字段时系统行为不变
|
||||
|
||||
## 5. 文档更新
|
||||
|
||||
- [x] 5.1 更新 `README.md`,添加内联模板的语法说明和使用示例
|
||||
- [x] 5.2 更新 `README_DEV.md`,说明内联模板的实现细节和设计决策
|
||||
- [x] 5.3 添加内联模板的最佳实践指南,说明何时使用内联模板 vs 外部模板
|
||||
|
||||
## 6. 验证和收尾
|
||||
|
||||
- [x] 6.1 运行完整的测试套件,确保所有测试通过
|
||||
- [x] 6.2 使用 `uv run pytest -v` 验证新增测试用例
|
||||
- [x] 6.3 手动测试:创建包含内联模板的示例 YAML 文件,验证转换功能正常
|
||||
- [x] 6.4 手动测试:创建混合使用内联和外部模板的示例,验证优先级规则
|
||||
- [x] 6.5 使用 `uv run pytest --cov=. --cov-report=html` 检查测试覆盖率
|
||||
- [x] 6.6 使用 `uv run yaml2pptx.py check` 验证 YAML 验证功能正常
|
||||
Reference in New Issue
Block a user