diff --git a/README.md b/README.md index 07f7739..facfdd0 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ ### 基本用法 ```bash -# 使用 convert 子命令(推荐) +# 转换 YAML 为 PPTX uv run yaml2pptx.py convert presentation.yaml output.pptx # 自动生成输出文件名 @@ -29,22 +29,22 @@ uv run yaml2pptx.py convert presentation.yaml # 使用模板 uv run yaml2pptx.py convert presentation.yaml output.pptx --template-dir ./templates - -# 传统用法(向后兼容) -uv run yaml2pptx.py presentation.yaml output.pptx ``` ### 实时预览 ```bash # 启动预览服务器(自动打开浏览器) -uv run yaml2pptx.py convert presentation.yaml --preview +uv run yaml2pptx.py preview presentation.yaml # 指定端口 -uv run yaml2pptx.py convert presentation.yaml --preview --port 8080 +uv run yaml2pptx.py preview presentation.yaml --port 8080 -# 传统用法(向后兼容) -uv run yaml2pptx.py presentation.yaml --preview +# 允许局域网访问 +uv run yaml2pptx.py preview presentation.yaml --host 0.0.0.0 + +# 不自动打开浏览器 +uv run yaml2pptx.py preview presentation.yaml --no-browser ``` 预览模式会自动监听文件变化,修改 YAML 文件后浏览器会自动刷新。 @@ -69,14 +69,11 @@ uv run yaml2pptx.py check presentation.yaml --template-dir ./templates - ✅ 字体大小是否合理 - ✅ 表格数据是否一致 -**自动验证**:转换时默认会自动验证,如果发现错误会终止转换。可以使用 `--no-check` 跳过验证: +**自动验证**:转换时默认会自动验证,如果发现错误会终止转换。可以使用 `--skip-validation` 跳过验证: ```bash -# 跳过自动验证(convert 子命令) -uv run yaml2pptx.py convert presentation.yaml --no-check - -# 跳过自动验证(传统用法) -uv run yaml2pptx.py presentation.yaml --no-check +# 跳过自动验证 +uv run yaml2pptx.py convert presentation.yaml --skip-validation ``` **验证结果示例**: @@ -260,13 +257,38 @@ slides: ## 🎯 命令行选项 +### check 命令 + +验证 YAML 文件的正确性。 + +| 选项 | 说明 | +|------|------| +| `input` | 输入的 YAML 文件路径(必需) | +| `--template-dir` | 模板文件目录 | + +### convert 命令 + +将 YAML 文件转换为 PPTX 文件。 + | 选项 | 说明 | |------|------| | `input` | 输入的 YAML 文件路径(必需) | | `output` | 输出的 PPTX 文件路径(可选) | | `--template-dir` | 模板文件目录 | -| `--preview` | 启用浏览器预览模式 | -| `--port` | 预览服务器端口(默认:随机) | +| `--skip-validation` | 跳过自动验证 | +| `--force` / `-f` | 强制覆盖已存在文件 | + +### preview 命令 + +启动预览服务器,实时查看演示文稿效果。 + +| 选项 | 说明 | +|------|------| +| `input` | 输入的 YAML 文件路径(必需) | +| `--template-dir` | 模板文件目录 | +| `--port` | 服务器端口(默认:随机端口 30000-40000) | +| `--host` | 主机地址(默认:127.0.0.1) | +| `--no-browser` | 不自动打开浏览器 | ## 📐 坐标系统 diff --git a/README_DEV.md b/README_DEV.md index 65b158a..f98c5c0 100644 --- a/README_DEV.md +++ b/README_DEV.md @@ -189,18 +189,40 @@ python yaml2pptx.py input.yaml output.pptx - 所有依赖在 `yaml2pptx.py` 的 `/// script` 头部声明 - uv 会自动安装依赖,无需手动 `pip install` -### 2. 文件组织 +### 2. 命令行接口 + +**子命令架构**: +```bash +# check - 验证 YAML 文件 +uv run yaml2pptx.py check [--template-dir ] + +# convert - 转换为 PPTX +uv run yaml2pptx.py convert [output] [--template-dir ] [--skip-validation] [--force] + +# preview - 启动预览服务器 +uv run yaml2pptx.py preview [--template-dir ] [--port ] [--host ] [--no-browser] +``` + +**参数说明**: +- `--template-dir`:所有命令通用,指定模板目录 +- `--skip-validation`:convert 专用,跳过自动验证 +- `--force/-f`:convert 专用,强制覆盖已存在文件 +- `--port`:preview 专用,指定端口(默认随机 30000-40000) +- `--host`:preview 专用,指定主机地址(默认 127.0.0.1) +- `--no-browser`:preview 专用,不自动打开浏览器 + +### 3. 文件组织 **代码文件**: - 每个模块文件控制在 150-300 行 -- 入口脚本约 100 行 +- 入口脚本约 200 行 - 使用有意义的文件名和目录结构 **测试文件**: - 所有测试文件、临时文件必须放在 `temp/` 目录下 - 不污染项目根目录 -### 3. 代码风格 +### 4. 代码风格 **导入顺序**: ```python @@ -458,23 +480,32 @@ uv run yaml2pptx.py check temp/test.yaml # 使用模板时验证 uv run yaml2pptx.py check temp/demo.yaml --template-dir temp/templates -# 使用 convert 子命令转换(推荐) +# 转换 YAML 为 PPTX uv run yaml2pptx.py convert temp/test.yaml temp/output.pptx -# 传统模式转换(向后兼容) -uv run yaml2pptx.py temp/test.yaml temp/output.pptx +# 自动生成输出文件名 +uv run yaml2pptx.py convert temp/test.yaml # 跳过自动验证 -uv run yaml2pptx.py convert temp/test.yaml temp/output.pptx --no-check +uv run yaml2pptx.py convert temp/test.yaml temp/output.pptx --skip-validation + +# 强制覆盖已存在文件 +uv run yaml2pptx.py convert temp/test.yaml temp/output.pptx --force # 使用模板 uv run yaml2pptx.py convert temp/demo.yaml temp/output.pptx --template-dir temp/templates -# 预览模式 -uv run yaml2pptx.py convert temp/test.yaml --preview +# 启动预览服务器 +uv run yaml2pptx.py preview temp/test.yaml # 指定端口 -uv run yaml2pptx.py convert temp/test.yaml --preview --port 8080 +uv run yaml2pptx.py preview temp/test.yaml --port 8080 + +# 允许局域网访问 +uv run yaml2pptx.py preview temp/test.yaml --host 0.0.0.0 + +# 不自动打开浏览器 +uv run yaml2pptx.py preview temp/test.yaml --no-browser ``` ### 测试文件位置 @@ -518,7 +549,7 @@ A: dataclass 提供: A: 使用预览模式: ```bash -uv run yaml2pptx.py temp/test.yaml --preview +uv run yaml2pptx.py preview temp/test.yaml ``` 在浏览器中查看渲染结果,支持热重载。 diff --git a/openspec/changes/archive/2026-03-02-refactor-cli-args/.openspec.yaml b/openspec/changes/archive/2026-03-02-refactor-cli-args/.openspec.yaml new file mode 100644 index 0000000..fd79bfc --- /dev/null +++ b/openspec/changes/archive/2026-03-02-refactor-cli-args/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-03-02 diff --git a/openspec/changes/archive/2026-03-02-refactor-cli-args/design.md b/openspec/changes/archive/2026-03-02-refactor-cli-args/design.md new file mode 100644 index 0000000..3ef4bea --- /dev/null +++ b/openspec/changes/archive/2026-03-02-refactor-cli-args/design.md @@ -0,0 +1,187 @@ +## Context + +当前 `yaml2pptx.py` 的 CLI 实现存在以下问题: + +1. **双模式解析**:同时支持子命令模式(`check`/`convert`)和传统模式(直接参数),导致参数解析逻辑复杂,代码中需要手动检查 `sys.argv[1]` 来决定使用哪种解析器 +2. **职责混乱**:`convert` 命令通过 `--preview` 标志切换到预览模式,但 convert 和 preview 是完全不同的操作(一个生成文件,一个启动服务器) +3. **参数重复定义**:`--template-dir`、`--preview`、`--port` 等参数在两个解析器中重复定义 +4. **命名不清晰**:`--no-check` 是双重否定,不够直观 + +本次重构的目标是建立清晰的子命令架构,提升代码可维护性和用户体验。 + +## Goals / Non-Goals + +**Goals:** +- 统一使用子命令架构,移除传统模式 +- 将 preview 独立为子命令,职责分离 +- 简化参数解析逻辑,消除代码重复 +- 优化参数命名和默认值 +- 新增实用功能(文件覆盖控制、局域网预览、浏览器控制) + +**Non-Goals:** +- 不改变核心转换逻辑(YAML 解析、PPTX 生成) +- 不改变验证逻辑(ERROR/WARNING/INFO 分级) +- 不改变预览服务器的核心功能(文件监听、SSE 推送) +- 不考虑向后兼容传统模式(项目未上线) + +## Decisions + +### 决策 1:使用纯子命令架构 + +**选择:** 移除传统模式,统一使用 ` ` 结构 + +**理由:** +- 子命令架构更清晰,每个子命令职责单一 +- 避免双模式解析的复杂性 +- 符合现代 CLI 工具的设计习惯(如 git、docker) + +**替代方案:** +- 保留传统模式作为快捷方式:增加维护成本,用户容易混淆 +- 使用选项组而非子命令:无法清晰表达互斥的操作模式 + +### 决策 2:preview 独立为子命令 + +**选择:** 将 preview 从 `convert --preview` 改为独立的 `preview` 子命令 + +**理由:** +- convert 和 preview 是完全不同的操作: + - convert:一次性操作,生成文件后退出 + - preview:长期运行,启动服务器,监听文件变化 +- 独立子命令使参数更清晰(preview 特有的 `--port`、`--host`、`--no-browser` 不会出现在 convert 中) +- 符合单一职责原则 + +**替代方案:** +- 保持 `convert --preview`:职责混乱,参数难以组织 +- 使用 `serve` 或 `watch` 命名:preview 更直观,表达预览意图 + +### 决策 3:参数解析使用 argparse 的 subparsers + +**选择:** 使用 argparse 的 `add_subparsers()` 和 `required=True` + +**理由:** +- argparse 是 Python 标准库,无需额外依赖 +- subparsers 自动处理子命令路由,无需手动检查 `sys.argv` +- 自动生成帮助信息 + +**实现结构:** +```python +parser = argparse.ArgumentParser() +subparsers = parser.add_subparsers(dest='command', required=True) + +# 每个子命令独立配置 +check_parser = subparsers.add_parser('check') +convert_parser = subparsers.add_parser('convert') +preview_parser = subparsers.add_parser('preview') +``` + +### 决策 4:preview 默认使用随机端口(30000-40000) + +**选择:** `--port` 默认值为 None,运行时随机选择 30000-40000 范围内的端口 + +**理由:** +- 避免端口冲突,支持同时运行多个预览实例 +- 30000+ 端口通常不被系统服务占用 +- 用户可以通过 `--port` 指定固定端口 + +**替代方案:** +- 固定端口 5000:容易冲突,不支持多实例 +- 完全随机端口(1024-65535):可能与其他服务冲突 + +### 决策 5:convert 默认不覆盖已存在文件 + +**选择:** 输出文件已存在时报错,需要 `--force/-f` 强制覆盖 + +**理由:** +- 避免意外覆盖用户文件 +- 符合安全优先原则 +- 提供明确的覆盖控制 + +**替代方案:** +- 默认覆盖:不安全,可能导致数据丢失 +- 自动重命名(如 output-1.pptx):用户可能不知道生成了多个文件 + +### 决策 6:简化参数,移除不必要的选项 + +**选择:** 移除 `--verbose`、`--quiet`、`--strict`、`--format`、`--no-watch` + +**理由:** +- `--verbose`/`--quiet`:当前输出已经足够简洁,不需要额外控制 +- `--strict`:保持原有的 ERROR/WARNING/INFO 分级即可 +- `--format`:命令行工具主要面向人类用户,text 格式足够 +- `--no-watch`:preview 的核心价值就是实时监听,不需要关闭 + +**保留的参数:** +- `--template-dir`:所有命令通用 +- `--skip-validation`:convert 特有,用于快速转换 +- `--force/-f`:convert 特有,文件覆盖控制 +- `--port`、`--host`、`--no-browser`:preview 特有 + +## Risks / Trade-offs + +### 风险 1:破坏性变更影响现有用户 + +**风险:** 移除传统模式和 `convert --preview` 会导致现有脚本失效 + +**缓解措施:** +- 项目未上线,无现有用户 +- 更新所有文档和示例 +- 在错误信息中提示新的使用方式(如检测到 `--preview` 时提示使用 `preview` 子命令) + +### 风险 2:随机端口可能导致防火墙问题 + +**风险:** 随机端口可能被防火墙阻止 + +**缓解措施:** +- 用户可以通过 `--port` 指定固定端口 +- 在文档中说明端口范围(30000-40000) +- 默认 host 为 127.0.0.1,仅本地访问 + +### 风险 3:参数验证逻辑需要重新实现 + +**风险:** 移除双模式后,参数验证逻辑需要调整 + +**缓解措施:** +- 在 `main()` 函数中统一验证输入文件存在性 +- 在 `handle_convert()` 中验证输出文件覆盖逻辑 +- 在 `handle_preview()` 中验证端口合法性 + +## Migration Plan + +### 实施步骤 + +1. **重构 `parse_args()` 函数** + - 移除双模式检查逻辑 + - 使用 `subparsers.add_parser()` 定义三个子命令 + - 为每个子命令配置独立的参数 + +2. **更新 `handle_convert()` 函数** + - 移除 `--preview` 相关逻辑 + - 添加输出文件存在性检查(`--force` 控制) + - 保持自动验证逻辑(`--skip-validation` 控制) + +3. **创建 `handle_preview()` 函数** + - 从 `handle_convert()` 中提取 preview 逻辑 + - 添加随机端口生成逻辑 + - 调用 `start_preview_server()` 时传递新参数 + +4. **更新 `preview/server.py`** + - 修改 `start_preview_server()` 函数签名 + - 添加 `host` 参数(默认 127.0.0.1) + - 添加 `open_browser` 参数(默认 True) + - 更新端口随机生成逻辑(30000-40000) + +5. **更新文档** + - 更新 `README.md` 中的所有使用示例 + - 更新命令行选项说明表格 + - 添加迁移指南(如果需要) + +### 回滚策略 + +如果发现重大问题,可以: +1. 回退到上一个 commit +2. 保留新的子命令架构,但临时添加传统模式兼容层 +3. 通过环境变量或配置文件控制行为 + +## Open Questions + +无待解决的问题。设计已经明确,可以开始实施。 diff --git a/openspec/changes/archive/2026-03-02-refactor-cli-args/proposal.md b/openspec/changes/archive/2026-03-02-refactor-cli-args/proposal.md new file mode 100644 index 0000000..576e4c2 --- /dev/null +++ b/openspec/changes/archive/2026-03-02-refactor-cli-args/proposal.md @@ -0,0 +1,37 @@ +## Why + +当前 CLI 结构存在混乱:双模式解析(子命令模式和传统模式)增加了代码复杂度,preview 作为 convert 的一个标志导致职责不清晰。需要建立清晰的子命令架构,提升用户体验和代码可维护性。 + +## What Changes + +- **BREAKING**: 移除传统模式(直接 `yaml2pptx.py input.yaml output.pptx`),统一使用子命令模式 +- **BREAKING**: 将 preview 从 convert 的 `--preview` 标志独立为 `preview` 子命令 +- 重命名参数:`--no-check` → `--skip-validation`(更清晰的语义) +- 新增 `--force/-f` 参数:convert 命令支持强制覆盖已存在文件 +- 新增 `--host` 参数:preview 命令支持配置主机地址(支持局域网预览) +- 新增 `--no-browser` 参数:preview 命令支持不自动打开浏览器 +- 优化 `--port` 默认值:从固定 5000 改为随机端口(30000-40000) +- 简化参数:移除不必要的 `--verbose`、`--quiet`、`--strict`、`--format`、`--no-watch` 等参数 + +## Capabilities + +### New Capabilities + +- `cli-interface`: 命令行接口规范,定义 check、convert、preview 三个子命令的参数、行为和交互方式 + +### Modified Capabilities + +- `browser-preview-server`: 预览服务器的启动方式和参数配置发生变化,从 convert 的标志变为独立子命令,新增 host、no-browser 等配置选项 + +## Impact + +**受影响的代码:** +- `yaml2pptx.py`:完全重构参数解析逻辑,移除双模式支持 +- `preview/server.py`:修改 `start_preview_server` 函数签名,支持 host 和 open_browser 参数 + +**破坏性变更:** +- 用户需要从传统模式迁移到子命令模式 +- 使用 `convert --preview` 的用户需要改用 `preview` 子命令 + +**文档更新:** +- `README.md`:更新所有使用示例和命令行选项说明 diff --git a/openspec/changes/archive/2026-03-02-refactor-cli-args/specs/browser-preview-server/spec.md b/openspec/changes/archive/2026-03-02-refactor-cli-args/specs/browser-preview-server/spec.md new file mode 100644 index 0000000..0a7409e --- /dev/null +++ b/openspec/changes/archive/2026-03-02-refactor-cli-args/specs/browser-preview-server/spec.md @@ -0,0 +1,74 @@ +# Browser Preview Server (Delta Spec) + +## MODIFIED Requirements + +### Requirement: 系统必须支持通过独立子命令启用预览模式 + +系统 SHALL 通过独立的 `preview` 子命令启用浏览器预览模式,而不是作为 convert 命令的参数。 + +#### Scenario: 使用 preview 子命令启动预览服务器 + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml` +- **THEN** 系统启动预览服务器 + +#### Scenario: convert 命令不再支持 --preview 参数 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml --preview` +- **THEN** 系统报错,提示使用 `preview` 子命令 + +#### Scenario: preview 子命令与其他参数兼容 + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml --template-dir templates` +- **THEN** 系统启动预览服务器,并使用指定的模板目录 + +### Requirement: 系统必须提供 HTTP 服务 + +系统 SHALL 使用 Flask 启动 HTTP 服务器,监听指定端口,提供预览页面。 + +#### Scenario: 启动 HTTP 服务器(随机端口) + +- **WHEN** 预览模式启动且未指定端口 +- **THEN** 系统在 30000-40000 范围内随机选择端口启动 Flask HTTP 服务器 + +#### Scenario: 自定义端口 + +- **WHEN** 用户使用 `--port 8080` 参数 +- **THEN** 系统在端口 8080 启动 HTTP 服务器 + +#### Scenario: 配置主机地址 + +- **WHEN** 用户使用 `--host 0.0.0.0` 参数 +- **THEN** 系统在 0.0.0.0 启动 HTTP 服务器,允许局域网访问 + +#### Scenario: 端口被占用时报错 + +- **WHEN** 指定的端口已被其他服务占用 +- **THEN** 系统抛出错误,提示端口被占用,建议使用 `--port` 参数指定其他端口 + +#### Scenario: 提供主页面路由 + +- **WHEN** 浏览器访问服务器根路径 +- **THEN** 系统返回包含所有幻灯片预览的 HTML 页面 + +### Requirement: 系统必须支持控制浏览器自动打开 + +系统 SHALL 支持通过参数控制是否自动打开浏览器。 + +#### Scenario: 默认自动打开浏览器 + +- **WHEN** 预览服务器启动成功且未使用 --no-browser 参数 +- **THEN** 系统自动在默认浏览器中打开预览页面 + +#### Scenario: 不自动打开浏览器 + +- **WHEN** 用户使用 `--no-browser` 参数 +- **THEN** 系统启动服务器但不自动打开浏览器,仅在终端输出预览 URL + +#### Scenario: 浏览器打开失败时提示 URL + +- **WHEN** 系统无法自动打开浏览器(如无图形界面环境) +- **THEN** 系统在终端输出预览 URL,提示用户手动打开 + +## REMOVED Requirements + +无移除的需求。其他需求(文件监听、SSE 推送、错误处理等)保持不变。 diff --git a/openspec/changes/archive/2026-03-02-refactor-cli-args/specs/cli-interface/spec.md b/openspec/changes/archive/2026-03-02-refactor-cli-args/specs/cli-interface/spec.md new file mode 100644 index 0000000..77ac8a5 --- /dev/null +++ b/openspec/changes/archive/2026-03-02-refactor-cli-args/specs/cli-interface/spec.md @@ -0,0 +1,184 @@ +# CLI Interface + +## Purpose + +CLI Interface 定义 yaml2pptx 命令行工具的用户交互界面,包括子命令结构、参数规范、行为约定和错误处理。它确保用户能够通过清晰、一致的命令行接口使用工具的所有功能。 + +## ADDED Requirements + +### Requirement: 系统必须使用子命令架构 + +系统 SHALL 采用子命令架构,提供 check、convert、preview 三个独立子命令,每个子命令有明确的职责。 + +#### Scenario: 使用 check 子命令验证文件 + +- **WHEN** 用户运行 `uv run yaml2pptx.py check input.yaml` +- **THEN** 系统验证 YAML 文件并输出验证结果 + +#### Scenario: 使用 convert 子命令转换文件 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml` +- **THEN** 系统将 YAML 文件转换为 PPTX 文件 + +#### Scenario: 使用 preview 子命令启动预览 + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml` +- **THEN** 系统启动预览服务器 + +#### Scenario: 不提供子命令时显示帮助 + +- **WHEN** 用户运行 `uv run yaml2pptx.py` +- **THEN** 系统显示帮助信息,列出所有可用子命令 + +### Requirement: check 子命令必须验证 YAML 文件 + +系统 SHALL 提供 check 子命令,用于验证 YAML 文件的正确性,输出 ERROR/WARNING/INFO 三级分类的验证结果。 + +#### Scenario: 基本验证 + +- **WHEN** 用户运行 `uv run yaml2pptx.py check input.yaml` +- **THEN** 系统验证文件并输出文本格式的验证结果 + +#### Scenario: 使用模板目录验证 + +- **WHEN** 用户运行 `uv run yaml2pptx.py check input.yaml --template-dir ./templates` +- **THEN** 系统使用指定的模板目录进行验证 + +#### Scenario: 验证通过时返回退出码 0 + +- **WHEN** 验证完成且无错误 +- **THEN** 系统返回退出码 0 + +#### Scenario: 验证失败时返回退出码 1 + +- **WHEN** 验证发现错误 +- **THEN** 系统返回退出码 1 + +### Requirement: convert 子命令必须转换 YAML 为 PPTX + +系统 SHALL 提供 convert 子命令,将 YAML 文件转换为 PPTX 文件,支持自动验证和文件覆盖控制。 + +#### Scenario: 基本转换(自动生成输出文件名) + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml` +- **THEN** 系统生成 `input.pptx` 文件 + +#### Scenario: 指定输出文件名 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml output.pptx` +- **THEN** 系统生成 `output.pptx` 文件 + +#### Scenario: 使用模板目录转换 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml --template-dir ./templates` +- **THEN** 系统使用指定的模板目录进行转换 + +#### Scenario: 默认自动验证 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml` +- **THEN** 系统先验证 YAML 文件,发现错误时终止转换 + +#### Scenario: 跳过自动验证 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml --skip-validation` +- **THEN** 系统跳过验证,直接转换 + +#### Scenario: 输出文件已存在时报错 + +- **WHEN** 输出文件已存在且未使用 --force 参数 +- **THEN** 系统报错并提示使用 --force 强制覆盖 + +#### Scenario: 强制覆盖已存在文件 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml output.pptx --force` +- **THEN** 系统覆盖已存在的 output.pptx 文件 + +### Requirement: preview 子命令必须启动预览服务器 + +系统 SHALL 提供 preview 子命令,启动预览服务器,支持端口配置、主机配置和浏览器控制。 + +#### Scenario: 基本预览(随机端口) + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml` +- **THEN** 系统在随机端口(30000-40000)启动预览服务器并自动打开浏览器 + +#### Scenario: 指定端口 + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml --port 8080` +- **THEN** 系统在端口 8080 启动预览服务器 + +#### Scenario: 配置主机地址(局域网预览) + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml --host 0.0.0.0` +- **THEN** 系统在 0.0.0.0 启动服务器,允许局域网访问 + +#### Scenario: 不自动打开浏览器 + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml --no-browser` +- **THEN** 系统启动服务器但不自动打开浏览器 + +#### Scenario: 使用模板目录预览 + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml --template-dir ./templates` +- **THEN** 系统使用指定的模板目录进行预览 + +### Requirement: 系统必须提供 --template-dir 通用参数 + +系统 SHALL 在所有子命令中提供 --template-dir 参数,用于指定模板文件目录。 + +#### Scenario: check 命令使用模板目录 + +- **WHEN** 用户运行 `uv run yaml2pptx.py check input.yaml --template-dir ./templates` +- **THEN** 系统使用指定的模板目录进行验证 + +#### Scenario: convert 命令使用模板目录 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml --template-dir ./templates` +- **THEN** 系统使用指定的模板目录进行转换 + +#### Scenario: preview 命令使用模板目录 + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml --template-dir ./templates` +- **THEN** 系统使用指定的模板目录进行预览 + +### Requirement: 系统必须验证输入文件存在性 + +系统 SHALL 在执行任何操作前验证输入文件是否存在,不存在时报错并退出。 + +#### Scenario: 输入文件不存在时报错 + +- **WHEN** 用户指定的输入文件不存在 +- **THEN** 系统输出错误信息 "输入文件不存在: <文件路径>" 并返回退出码 1 + +#### Scenario: 输入文件存在时继续执行 + +- **WHEN** 用户指定的输入文件存在 +- **THEN** 系统继续执行相应的子命令操作 + +### Requirement: 系统必须提供清晰的帮助信息 + +系统 SHALL 为每个子命令提供清晰的帮助信息,包括参数说明和使用示例。 + +#### Scenario: 显示全局帮助 + +- **WHEN** 用户运行 `uv run yaml2pptx.py --help` +- **THEN** 系统显示工具描述和所有可用子命令列表 + +#### Scenario: 显示子命令帮助 + +- **WHEN** 用户运行 `uv run yaml2pptx.py check --help` +- **THEN** 系统显示 check 子命令的参数说明和使用示例 + +### Requirement: 系统必须提供短选项支持 + +系统 SHALL 为常用参数提供短选项,提升用户体验。 + +#### Scenario: 使用 -f 作为 --force 的短选项 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml -f` +- **THEN** 系统强制覆盖已存在文件 + +#### Scenario: 短选项和长选项等效 + +- **WHEN** 用户使用 -f 或 --force +- **THEN** 系统行为完全一致 diff --git a/openspec/changes/archive/2026-03-02-refactor-cli-args/tasks.md b/openspec/changes/archive/2026-03-02-refactor-cli-args/tasks.md new file mode 100644 index 0000000..ab7cf90 --- /dev/null +++ b/openspec/changes/archive/2026-03-02-refactor-cli-args/tasks.md @@ -0,0 +1,65 @@ +## 1. 重构参数解析逻辑 + +- [x] 1.1 移除 `parse_args()` 中的双模式检查逻辑(删除 `sys.argv[1]` 检查) +- [x] 1.2 使用 `subparsers.add_parser()` 创建 check 子命令解析器 +- [x] 1.3 使用 `subparsers.add_parser()` 创建 convert 子命令解析器 +- [x] 1.4 使用 `subparsers.add_parser()` 创建 preview 子命令解析器 +- [x] 1.5 为 convert 子命令添加 `--force/-f` 参数 +- [x] 1.6 将 convert 子命令的 `--no-check` 重命名为 `--skip-validation` +- [x] 1.7 移除 convert 子命令的 `--preview` 和 `--port` 参数 +- [x] 1.8 为 preview 子命令添加 `--port`、`--host`、`--no-browser` 参数 + +## 2. 更新 convert 命令处理逻辑 + +- [x] 2.1 从 `handle_convert()` 中移除 preview 相关逻辑 +- [x] 2.2 在 `handle_convert()` 中添加输出文件存在性检查 +- [x] 2.3 实现 `--force` 参数逻辑(已存在文件时检查是否有 --force) +- [x] 2.4 更新 `--skip-validation` 参数的处理(原 --no-check) + +## 3. 创建 preview 命令处理逻辑 + +- [x] 3.1 创建 `handle_preview()` 函数 +- [x] 3.2 实现随机端口生成逻辑(30000-40000 范围) +- [x] 3.3 处理 `--port` 参数(用户指定时使用指定端口) +- [x] 3.4 处理 `--host` 参数(默认 127.0.0.1) +- [x] 3.5 处理 `--no-browser` 参数(控制是否自动打开浏览器) +- [x] 3.6 调用 `start_preview_server()` 并传递新参数 + +## 4. 更新预览服务器模块 + +- [x] 4.1 修改 `start_preview_server()` 函数签名,添加 `host` 参数 +- [x] 4.2 修改 `start_preview_server()` 函数签名,添加 `open_browser` 参数 +- [x] 4.3 更新 Flask 服务器启动逻辑,使用传入的 `host` 参数 +- [x] 4.4 更新浏览器自动打开逻辑,根据 `open_browser` 参数决定是否打开 +- [x] 4.5 移除或更新端口随机生成逻辑(现在由调用方处理) + +## 5. 更新主函数路由逻辑 + +- [x] 5.1 更新 `main()` 函数,根据 `args.command` 路由到对应的处理函数 +- [x] 5.2 确保 check 命令路由到 `handle_check()` +- [x] 5.3 确保 convert 命令路由到 `handle_convert()` +- [x] 5.4 确保 preview 命令路由到 `handle_preview()` +- [x] 5.5 移除传统模式的兼容逻辑(`args.command is None` 的处理) + +## 6. 更新文档 + +- [x] 6.1 更新 README.md 中的"快速开始"部分,移除传统模式示例 +- [x] 6.2 更新 README.md 中的"实时预览"部分,使用 `preview` 子命令 +- [x] 6.3 更新 README.md 中的"命令行选项"表格,反映新的参数结构 +- [x] 6.4 更新 README.md 中的所有使用示例 +- [x] 6.5 更新 `yaml2pptx.py` 文件顶部的 docstring 使用说明 + +## 7. 测试验证 + +- [x] 7.1 测试 check 子命令基本功能 +- [x] 7.2 测试 check 子命令的 --template-dir 参数 +- [x] 7.3 测试 convert 子命令基本功能(自动生成输出文件名) +- [x] 7.4 测试 convert 子命令指定输出文件名 +- [x] 7.5 测试 convert 子命令的 --force 参数(覆盖已存在文件) +- [x] 7.6 测试 convert 子命令的 --skip-validation 参数 +- [x] 7.7 测试 preview 子命令基本功能(随机端口) +- [x] 7.8 测试 preview 子命令的 --port 参数 +- [x] 7.9 测试 preview 子命令的 --host 参数 +- [x] 7.10 测试 preview 子命令的 --no-browser 参数 +- [x] 7.11 验证输入文件不存在时的错误提示 +- [x] 7.12 验证输出文件已存在且未使用 --force 时的错误提示 diff --git a/openspec/config.yaml b/openspec/config.yaml index 64f7757..4aa2388 100644 --- a/openspec/config.yaml +++ b/openspec/config.yaml @@ -6,5 +6,5 @@ context: | 严禁直接使用主机环境的python直接执行脚本或命令,严禁在主机环境直接安装python依赖包; 本项目编写的测试文件、临时文件必须放在temp目录下; 严禁污染主机环境的任何配置,如有需要,必须请求用户审核操作; - 当前项目的面向用户的使用文档在README.md; - 当前项目的面向AI和开发者的开发规范文档在README_DEV.md; + 当前项目的面向用户的使用文档在README.md;当前项目的面向AI和开发者的开发规范文档在README_DEV.md;每次功能迭代都需要同步更新这两份说明文档; + 所有的文档、日志、说明严禁使用emoji或其他特殊字符,保证字符显示的兼容性; diff --git a/openspec/specs/browser-preview-server/spec.md b/openspec/specs/browser-preview-server/spec.md index f210e2a..eeae795 100644 --- a/openspec/specs/browser-preview-server/spec.md +++ b/openspec/specs/browser-preview-server/spec.md @@ -6,39 +6,44 @@ Browser Preview Server 负责提供浏览器实时预览功能,包括启动 We ## Requirements -### Requirement: 系统必须支持通过命令行参数启用预览模式 +### Requirement: 系统必须支持通过独立子命令启用预览模式 -系统 SHALL 在 `yaml2pptx.py` 中提供 `--preview` 参数,启用浏览器预览模式。 +系统 SHALL 通过独立的 `preview` 子命令启用浏览器预览模式,而不是作为 convert 命令的参数。 -#### Scenario: 使用 --preview 参数启动预览服务器 +#### Scenario: 使用 preview 子命令启动预览服务器 -- **WHEN** 用户运行 `uv run yaml2pptx.py input.yaml --preview` -- **THEN** 系统启动预览服务器,而不是生成 PPTX 文件 +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml` +- **THEN** 系统启动预览服务器 -#### Scenario: 不使用 --preview 参数时保持原有行为 +#### Scenario: convert 命令不再支持 --preview 参数 -- **WHEN** 用户运行 `uv run yaml2pptx.py input.yaml` -- **THEN** 系统生成 PPTX 文件,不启动预览服务器 +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml --preview` +- **THEN** 系统报错,提示使用 `preview` 子命令 -#### Scenario: --preview 参数与其他参数兼容 +#### Scenario: preview 子命令与其他参数兼容 -- **WHEN** 用户运行 `uv run yaml2pptx.py input.yaml --preview --template-dir templates` +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml --template-dir templates` - **THEN** 系统启动预览服务器,并使用指定的模板目录 ### Requirement: 系统必须提供 HTTP 服务 系统 SHALL 使用 Flask 启动 HTTP 服务器,监听指定端口,提供预览页面。 -#### Scenario: 启动 HTTP 服务器 +#### Scenario: 启动 HTTP 服务器(随机端口) -- **WHEN** 预览模式启动 -- **THEN** 系统在默认端口 5000 启动 Flask HTTP 服务器 +- **WHEN** 预览模式启动且未指定端口 +- **THEN** 系统在 30000-40000 范围内随机选择端口启动 Flask HTTP 服务器 #### Scenario: 自定义端口 - **WHEN** 用户使用 `--port 8080` 参数 - **THEN** 系统在端口 8080 启动 HTTP 服务器 +#### Scenario: 配置主机地址 + +- **WHEN** 用户使用 `--host 0.0.0.0` 参数 +- **THEN** 系统在 0.0.0.0 启动 HTTP 服务器,允许局域网访问 + #### Scenario: 端口被占用时报错 - **WHEN** 指定的端口已被其他服务占用 @@ -46,7 +51,7 @@ Browser Preview Server 负责提供浏览器实时预览功能,包括启动 We #### Scenario: 提供主页面路由 -- **WHEN** 浏览器访问 `http://localhost:5000/` +- **WHEN** 浏览器访问服务器根路径 - **THEN** 系统返回包含所有幻灯片预览的 HTML 页面 ### Requirement: 系统必须监听 YAML 文件变化 @@ -97,14 +102,19 @@ Browser Preview Server 负责提供浏览器实时预览功能,包括启动 We - **WHEN** SSE 连接因网络问题断开 - **THEN** 浏览器自动尝试重新连接到 SSE 端点 -### Requirement: 系统必须自动打开浏览器 +### Requirement: 系统必须支持控制浏览器自动打开 -系统 SHALL 在预览服务器启动后,自动在默认浏览器中打开预览页面。 +系统 SHALL 支持通过参数控制是否自动打开浏览器。 -#### Scenario: 启动后自动打开浏览器 +#### Scenario: 默认自动打开浏览器 -- **WHEN** 预览服务器启动成功 -- **THEN** 系统自动在默认浏览器中打开 `http://localhost:5000/` +- **WHEN** 预览服务器启动成功且未使用 --no-browser 参数 +- **THEN** 系统自动在默认浏览器中打开预览页面 + +#### Scenario: 不自动打开浏览器 + +- **WHEN** 用户使用 `--no-browser` 参数 +- **THEN** 系统启动服务器但不自动打开浏览器,仅在终端输出预览 URL #### Scenario: 浏览器打开失败时提示 URL diff --git a/openspec/specs/cli-interface/spec.md b/openspec/specs/cli-interface/spec.md new file mode 100644 index 0000000..3745a73 --- /dev/null +++ b/openspec/specs/cli-interface/spec.md @@ -0,0 +1,184 @@ +# CLI Interface + +## Purpose + +CLI Interface 定义 yaml2pptx 命令行工具的用户交互界面,包括子命令结构、参数规范、行为约定和错误处理。它确保用户能够通过清晰、一致的命令行接口使用工具的所有功能。 + +## Requirements + +### Requirement: 系统必须使用子命令架构 + +系统 SHALL 采用子命令架构,提供 check、convert、preview 三个独立子命令,每个子命令有明确的职责。 + +#### Scenario: 使用 check 子命令验证文件 + +- **WHEN** 用户运行 `uv run yaml2pptx.py check input.yaml` +- **THEN** 系统验证 YAML 文件并输出验证结果 + +#### Scenario: 使用 convert 子命令转换文件 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml` +- **THEN** 系统将 YAML 文件转换为 PPTX 文件 + +#### Scenario: 使用 preview 子命令启动预览 + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml` +- **THEN** 系统启动预览服务器 + +#### Scenario: 不提供子命令时显示帮助 + +- **WHEN** 用户运行 `uv run yaml2pptx.py` +- **THEN** 系统显示帮助信息,列出所有可用子命令 + +### Requirement: check 子命令必须验证 YAML 文件 + +系统 SHALL 提供 check 子命令,用于验证 YAML 文件的正确性,输出 ERROR/WARNING/INFO 三级分类的验证结果。 + +#### Scenario: 基本验证 + +- **WHEN** 用户运行 `uv run yaml2pptx.py check input.yaml` +- **THEN** 系统验证文件并输出文本格式的验证结果 + +#### Scenario: 使用模板目录验证 + +- **WHEN** 用户运行 `uv run yaml2pptx.py check input.yaml --template-dir ./templates` +- **THEN** 系统使用指定的模板目录进行验证 + +#### Scenario: 验证通过时返回退出码 0 + +- **WHEN** 验证完成且无错误 +- **THEN** 系统返回退出码 0 + +#### Scenario: 验证失败时返回退出码 1 + +- **WHEN** 验证发现错误 +- **THEN** 系统返回退出码 1 + +### Requirement: convert 子命令必须转换 YAML 为 PPTX + +系统 SHALL 提供 convert 子命令,将 YAML 文件转换为 PPTX 文件,支持自动验证和文件覆盖控制。 + +#### Scenario: 基本转换(自动生成输出文件名) + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml` +- **THEN** 系统生成 `input.pptx` 文件 + +#### Scenario: 指定输出文件名 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml output.pptx` +- **THEN** 系统生成 `output.pptx` 文件 + +#### Scenario: 使用模板目录转换 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml --template-dir ./templates` +- **THEN** 系统使用指定的模板目录进行转换 + +#### Scenario: 默认自动验证 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml` +- **THEN** 系统先验证 YAML 文件,发现错误时终止转换 + +#### Scenario: 跳过自动验证 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml --skip-validation` +- **THEN** 系统跳过验证,直接转换 + +#### Scenario: 输出文件已存在时报错 + +- **WHEN** 输出文件已存在且未使用 --force 参数 +- **THEN** 系统报错并提示使用 --force 强制覆盖 + +#### Scenario: 强制覆盖已存在文件 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml output.pptx --force` +- **THEN** 系统覆盖已存在的 output.pptx 文件 + +### Requirement: preview 子命令必须启动预览服务器 + +系统 SHALL 提供 preview 子命令,启动预览服务器,支持端口配置、主机配置和浏览器控制。 + +#### Scenario: 基本预览(随机端口) + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml` +- **THEN** 系统在随机端口(30000-40000)启动预览服务器并自动打开浏览器 + +#### Scenario: 指定端口 + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml --port 8080` +- **THEN** 系统在端口 8080 启动预览服务器 + +#### Scenario: 配置主机地址(局域网预览) + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml --host 0.0.0.0` +- **THEN** 系统在 0.0.0.0 启动服务器,允许局域网访问 + +#### Scenario: 不自动打开浏览器 + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml --no-browser` +- **THEN** 系统启动服务器但不自动打开浏览器 + +#### Scenario: 使用模板目录预览 + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml --template-dir ./templates` +- **THEN** 系统使用指定的模板目录进行预览 + +### Requirement: 系统必须提供 --template-dir 通用参数 + +系统 SHALL 在所有子命令中提供 --template-dir 参数,用于指定模板文件目录。 + +#### Scenario: check 命令使用模板目录 + +- **WHEN** 用户运行 `uv run yaml2pptx.py check input.yaml --template-dir ./templates` +- **THEN** 系统使用指定的模板目录进行验证 + +#### Scenario: convert 命令使用模板目录 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml --template-dir ./templates` +- **THEN** 系统使用指定的模板目录进行转换 + +#### Scenario: preview 命令使用模板目录 + +- **WHEN** 用户运行 `uv run yaml2pptx.py preview input.yaml --template-dir ./templates` +- **THEN** 系统使用指定的模板目录进行预览 + +### Requirement: 系统必须验证输入文件存在性 + +系统 SHALL 在执行任何操作前验证输入文件是否存在,不存在时报错并退出。 + +#### Scenario: 输入文件不存在时报错 + +- **WHEN** 用户指定的输入文件不存在 +- **THEN** 系统输出错误信息 "输入文件不存在: <文件路径>" 并返回退出码 1 + +#### Scenario: 输入文件存在时继续执行 + +- **WHEN** 用户指定的输入文件存在 +- **THEN** 系统继续执行相应的子命令操作 + +### Requirement: 系统必须提供清晰的帮助信息 + +系统 SHALL 为每个子命令提供清晰的帮助信息,包括参数说明和使用示例。 + +#### Scenario: 显示全局帮助 + +- **WHEN** 用户运行 `uv run yaml2pptx.py --help` +- **THEN** 系统显示工具描述和所有可用子命令列表 + +#### Scenario: 显示子命令帮助 + +- **WHEN** 用户运行 `uv run yaml2pptx.py check --help` +- **THEN** 系统显示 check 子命令的参数说明和使用示例 + +### Requirement: 系统必须提供短选项支持 + +系统 SHALL 为常用参数提供短选项,提升用户体验。 + +#### Scenario: 使用 -f 作为 --force 的短选项 + +- **WHEN** 用户运行 `uv run yaml2pptx.py convert input.yaml -f` +- **THEN** 系统强制覆盖已存在文件 + +#### Scenario: 短选项和长选项等效 + +- **WHEN** 用户使用 -f 或 --force +- **THEN** 系统行为完全一致 diff --git a/preview/server.py b/preview/server.py index a317ef5..c90477d 100644 --- a/preview/server.py +++ b/preview/server.py @@ -186,8 +186,16 @@ def create_flask_app(): return flask_app -def start_preview_server(yaml_file, template_dir, port): - """启动预览服务器""" +def start_preview_server(yaml_file, template_dir, port, host='127.0.0.1', open_browser=True): + """启动预览服务器 + + Args: + yaml_file: YAML 文件路径 + template_dir: 模板目录路径 + port: 服务器端口 + host: 主机地址(默认:127.0.0.1) + open_browser: 是否自动打开浏览器(默认:True) + """ global app, change_queue, current_yaml_file, current_template_dir if Flask is None: @@ -195,10 +203,6 @@ def start_preview_server(yaml_file, template_dir, port): log_error("请确保使用 uv 运行脚本,依赖会自动安装") sys.exit(1) - # 如果没有指定端口,随机选择 20000-30000 之间的端口 - if port is None: - port = random.randint(20000, 30000) - current_yaml_file = yaml_file current_template_dir = template_dir change_queue = queue.Queue() @@ -221,15 +225,16 @@ def start_preview_server(yaml_file, template_dir, port): # 输出日志 log_info(f"正在监听: {yaml_file}") - log_info(f"预览地址: http://localhost:{port}") + log_info(f"预览地址: http://{host}:{port}") log_info("按 Ctrl+C 停止") - # 自动打开浏览器 - Thread(target=lambda: webbrowser.open(f'http://localhost:{port}')).start() + # 自动打开浏览器(如果启用) + if open_browser: + Thread(target=lambda: webbrowser.open(f'http://localhost:{port}')).start() # 启动 Flask try: - app.run(host='0.0.0.0', port=port, debug=False, threaded=True) + app.run(host=host, port=port, debug=False, threaded=True) except OSError as e: if 'Address already in use' in str(e): log_error(f"端口 {port} 已被占用") diff --git a/yaml2pptx.py b/yaml2pptx.py index 27ca474..4b0dc69 100644 --- a/yaml2pptx.py +++ b/yaml2pptx.py @@ -14,17 +14,20 @@ YAML to PPTX Converter 将 YAML 格式的演示文稿源文件转换为 PPTX 文件 使用方法: - # 子命令模式(推荐) - uv run yaml2pptx.py convert input.yaml output.pptx + # 验证 YAML 文件 uv run yaml2pptx.py check input.yaml - # 传统模式(向后兼容) - uv run yaml2pptx.py input.yaml output.pptx - uv run yaml2pptx.py input.yaml # 自动生成 input.pptx + # 转换为 PPTX + uv run yaml2pptx.py convert input.yaml output.pptx + uv run yaml2pptx.py convert input.yaml # 自动生成 input.pptx + + # 启动预览服务器 + uv run yaml2pptx.py preview input.yaml """ import sys import argparse +import random from pathlib import Path # 导入模块 @@ -38,43 +41,33 @@ from validators import Validator def parse_args(): """解析命令行参数""" - # 检查是否使用了子命令 - if len(sys.argv) > 1 and sys.argv[1] in ['check', 'convert']: - # 使用子命令解析 - parser = argparse.ArgumentParser( - description="将 YAML 格式的演示文稿源文件转换为 PPTX 文件" - ) - subparsers = parser.add_subparsers(dest='command') + parser = argparse.ArgumentParser( + description="将 YAML 格式的演示文稿源文件转换为 PPTX 文件" + ) + subparsers = parser.add_subparsers(dest='command', required=True) - # check 子命令 - check_parser = subparsers.add_parser('check', help='验证 YAML 文件') - check_parser.add_argument("input", type=str, help="输入的 YAML 文件路径") - check_parser.add_argument("--template-dir", type=str, default=None, help="模板文件目录路径") + # check 子命令 + check_parser = subparsers.add_parser('check', help='验证 YAML 文件') + check_parser.add_argument("input", type=str, help="输入的 YAML 文件路径") + check_parser.add_argument("--template-dir", type=str, default=None, help="模板文件目录路径") - # convert 子命令 - convert_parser = subparsers.add_parser('convert', help='转换 YAML 为 PPTX') - convert_parser.add_argument("input", type=str, help="输入的 YAML 文件路径") - convert_parser.add_argument("output", type=str, nargs="?", help="输出的 PPTX 文件路径(可选,默认为输入文件名.pptx)") - convert_parser.add_argument("--template-dir", type=str, default=None, help="模板文件目录路径") - convert_parser.add_argument("--preview", action="store_true", help="启动浏览器预览模式") - convert_parser.add_argument("--port", type=int, default=None, help="预览服务器端口") - convert_parser.add_argument("--no-check", action="store_true", help="跳过自动验证") + # convert 子命令 + convert_parser = subparsers.add_parser('convert', help='转换 YAML 为 PPTX') + convert_parser.add_argument("input", type=str, help="输入的 YAML 文件路径") + convert_parser.add_argument("output", type=str, nargs="?", help="输出的 PPTX 文件路径(可选,默认为输入文件名.pptx)") + convert_parser.add_argument("--template-dir", type=str, default=None, help="模板文件目录路径") + convert_parser.add_argument("--skip-validation", action="store_true", help="跳过自动验证") + convert_parser.add_argument("-f", "--force", action="store_true", help="强制覆盖已存在文件") - return parser.parse_args() - else: - # 使用传统解析(转换命令)- 向后兼容 - parser = argparse.ArgumentParser( - description="将 YAML 格式的演示文稿源文件转换为 PPTX 文件" - ) - parser.add_argument("input", type=str, help="输入的 YAML 文件路径") - parser.add_argument("output", type=str, nargs="?", help="输出的 PPTX 文件路径(可选,默认为输入文件名.pptx)") - parser.add_argument("--template-dir", type=str, default=None, help="模板文件目录路径") - parser.add_argument("--preview", action="store_true", help="启动浏览器预览模式") - parser.add_argument("--port", type=int, default=None, help="预览服务器端口") - parser.add_argument("--no-check", action="store_true", help="跳过自动验证") - args = parser.parse_args() - args.command = None # 标记为非子命令(向后兼容模式) - return args + # preview 子命令 + preview_parser = subparsers.add_parser('preview', help='启动预览服务器') + preview_parser.add_argument("input", type=str, help="输入的 YAML 文件路径") + preview_parser.add_argument("--template-dir", type=str, default=None, help="模板文件目录路径") + preview_parser.add_argument("--port", type=int, default=None, help="服务器端口(默认:随机端口 30000-40000)") + preview_parser.add_argument("--host", type=str, default="127.0.0.1", help="主机地址(默认:127.0.0.1)") + preview_parser.add_argument("--no-browser", action="store_true", help="不自动打开浏览器") + + return parser.parse_args() def handle_check(args): @@ -107,21 +100,44 @@ def handle_check(args): sys.exit(1) +def handle_preview(args): + """处理 preview 子命令""" + # 检查输入文件是否存在 + input_path = Path(args.input) + if not input_path.exists(): + log_error(f"输入文件不存在: {input_path}") + sys.exit(1) + + # 处理端口:如果未指定,随机选择 30000-40000 范围内的端口 + port = args.port + if port is None: + port = random.randint(30000, 40000) + + # 处理模板目录 + template_dir = Path(args.template_dir) if args.template_dir else None + + # 启动预览服务器 + start_preview_server( + yaml_file=input_path, + template_dir=template_dir, + port=port, + host=args.host, + open_browser=not args.no_browser + ) + + def main(): """主函数:加载 YAML → 渲染幻灯片 → 生成 PPTX""" try: args = parse_args() - # 处理 check 子命令 + # 路由到对应的处理函数 if args.command == 'check': handle_check(args) - return - - # 处理 convert 子命令或传统模式(向后兼容) - # convert 子命令和无子命令的行为相同 - if args.command == 'convert' or args.command is None: + elif args.command == 'convert': handle_convert(args) - return + elif args.command == 'preview': + handle_preview(args) except YAMLError as e: log_error(str(e)) @@ -134,7 +150,7 @@ def main(): def handle_convert(args): - """处理 convert 子命令或传统转换模式""" + """处理 convert 子命令""" # 检查是否提供了输入文件 if not args.input: log_error("错误: 缺少输入文件参数") @@ -143,12 +159,11 @@ def handle_convert(args): # 处理输入文件路径 input_path = Path(args.input) - # 预览模式 - if args.preview: - start_preview_server(input_path, args.template_dir, args.port) - return + # 检查输入文件是否存在 + if not input_path.exists(): + log_error(f"输入文件不存在: {input_path}") + sys.exit(1) - # PPTX 生成模式 # 处理输出文件路径 if args.output: output_path = Path(args.output) @@ -156,11 +171,17 @@ def handle_convert(args): # 自动生成输出文件名 output_path = input_path.with_suffix(".pptx") + # 检查输出文件是否已存在(除非使用 --force) + if output_path.exists() and not args.force: + log_error(f"输出文件已存在: {output_path}") + log_error("使用 --force 或 -f 强制覆盖") + sys.exit(1) + log_info(f"开始转换: {input_path}") log_info(f"输出文件: {output_path}") - # 自动验证(除非使用 --no-check) - if not args.no_check: + # 自动验证(除非使用 --skip-validation) + if not args.skip_validation: log_info("验证 YAML 文件...") template_dir = Path(args.template_dir) if args.template_dir else None validator = Validator()