1
0

refactor: modularize yaml2pptx into layered architecture

Refactor yaml2pptx.py from a 1,245-line monolithic script into a modular
architecture with clear separation of concerns. The entry point is now
127 lines, with business logic distributed across focused modules.

Architecture:
- core/: Domain models (elements, template, presentation)
- loaders/: YAML loading and validation
- renderers/: PPTX and HTML rendering
- preview/: Flask preview server
- utils.py: Shared utilities

Key improvements:
- Element abstraction layer using dataclass with validation
- Renderer logic built into generator classes
- Single-direction dependencies (no circular imports)
- Each module 150-300 lines for better readability
- Backward compatible CLI interface

Documentation:
- README.md: User-facing usage guide
- README_DEV.md: Developer documentation

OpenSpec:
- Archive refactor-yaml2pptx-modular change (63/70 tasks complete)
- Sync 5 delta specs to main specs (2 new + 3 updated)
This commit is contained in:
2026-03-02 16:43:45 +08:00
parent b2132dc06b
commit ed940f0690
31 changed files with 3142 additions and 1307 deletions

113
loaders/yaml_loader.py Normal file
View File

@@ -0,0 +1,113 @@
"""
YAML 加载和验证模块
负责加载 YAML 文件并验证其结构。
"""
from pathlib import Path
import yaml
# ============= YAML 解析和验证 =============
class YAMLError(Exception):
"""YAML 相关错误"""
pass
def load_yaml_file(file_path):
"""
加载 YAML 文件UTF-8 编码,错误处理)
Args:
file_path: 文件路径(字符串或 Path 对象)
Returns:
解析后的 Python 字典
Raises:
YAMLError: 文件不存在、权限不足、YAML 语法错误等
"""
file_path = Path(file_path)
# 检查文件是否存在
if not file_path.exists():
raise YAMLError(f"文件不存在: {file_path}")
# 检查是否有读取权限
if not file_path.is_file():
raise YAMLError(f"不是有效的文件: {file_path}")
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
return data
except PermissionError:
raise YAMLError(f"权限不足,无法读取文件: {file_path}")
except yaml.YAMLError as e:
# 提取行号信息
if hasattr(e, 'problem_mark'):
mark = e.problem_mark
raise YAMLError(
f"YAML 语法错误: {file_path}, 第 {mark.line + 1} 行: {e.problem}"
)
else:
raise YAMLError(f"YAML 解析错误: {file_path}: {str(e)}")
except Exception as e:
raise YAMLError(f"读取文件失败: {file_path}: {str(e)}")
def validate_presentation_yaml(data, file_path=""):
"""
验证演示文稿 YAML 结构必需字段slides
Args:
data: 解析后的 YAML 数据
file_path: 文件路径(用于错误消息)
Raises:
YAMLError: 结构验证失败
"""
if not isinstance(data, dict):
raise YAMLError(f"{file_path}: 演示文稿必须是一个字典对象")
# 验证 slides 字段
if 'slides' not in data:
raise YAMLError(f"{file_path}: 缺少必需字段 'slides'")
if not isinstance(data['slides'], list):
raise YAMLError(f"{file_path}: 'slides' 必须是一个列表")
def validate_template_yaml(data, file_path=""):
"""
验证模板 YAML 结构vars, elements
Args:
data: 解析后的 YAML 数据
file_path: 文件路径(用于错误消息)
Raises:
YAMLError: 结构验证失败
"""
if not isinstance(data, dict):
raise YAMLError(f"{file_path}: 模板必须是一个字典对象")
# 验证 vars 字段
if 'vars' in data:
if not isinstance(data['vars'], list):
raise YAMLError(f"{file_path}: 'vars' 必须是一个列表")
# 验证每个变量定义
for i, var_def in enumerate(data['vars']):
if not isinstance(var_def, dict):
raise YAMLError(f"{file_path}: vars[{i}] 必须是一个字典对象")
if 'name' not in var_def:
raise YAMLError(f"{file_path}: vars[{i}] 缺少必需字段 'name'")
# 验证 elements 字段
if 'elements' not in data:
raise YAMLError(f"{file_path}: 缺少必需字段 'elements'")
if not isinstance(data['elements'], list):
raise YAMLError(f"{file_path}: 'elements' 必须是一个列表")