#!/usr/bin/env python3 """procedure skill 渲染入口 — 零校验, 直接出 HTML. 质量门禁全部在 validate.py; 本脚本只负责把 workflow.json 拼成 case_data 并渲染, 数据再不完美也尽量出图 (复用 spec/tools 的 renderer.build_html + merge_raw_source, 跳过 render-case.py 的 schema / 占位 / 结构三层门禁)。 用法: python procedure/tools/render.py --workflow outputs/case-N/workflow.json \ --source-input input/case-N.json --page-title "Case N · 主题" \ --case-id N --out outputs/case-N/case-N.html 退出码: 0 成功 / 1 渲染异常 / 2 CLI、文件问题 """ import argparse import importlib.util import json import sys from pathlib import Path if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8", errors="replace") sys.stderr.reconfigure(encoding="utf-8", errors="replace") SPEC_TOOLS = Path(__file__).resolve().parent.parent.parent / "spec" / "tools" def _load_module(name, path): spec = importlib.util.spec_from_file_location(name, path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod def main(): ap = argparse.ArgumentParser() ap.add_argument("--workflow", type=Path, required=True) ap.add_argument("--source-input", type=Path, dest="source_input", help="原帖 raw json — 正文/封面 merge 进 HTML 折叠原文区") ap.add_argument("--page-title", default=None) ap.add_argument("--case-id", default=None) ap.add_argument("--out", type=Path, required=True) args = ap.parse_args() try: case_data = json.loads(args.workflow.read_text(encoding="utf-8")) except FileNotFoundError: print(f"✗ 文件不存在: {args.workflow}") return 2 except json.JSONDecodeError as e: print(f"✗ JSON 解析失败: {e}") return 2 title = (case_data.get("source") or {}).get("title") or args.workflow.stem case_data.setdefault("page_title", args.page_title or title) if args.case_id is not None: case_data["case_id"] = args.case_id try: if args.source_input: _load_module("_render_case_mod", SPEC_TOOLS / "render-case.py") \ .merge_raw_source(case_data, args.source_input) html = _load_module("_renderer_mod", SPEC_TOOLS / "renderer.py").build_html(case_data) except Exception as e: print(f"✗ 渲染异常 ({type(e).__name__}: {e}) — workflow.json 可能有渲染器吃不下的字段值") return 1 args.out.parent.mkdir(parents=True, exist_ok=True) args.out.write_text(html, encoding="utf-8", newline="") n_procs = len(case_data.get("procedures") or []) n_steps = sum(len(p.get("steps") or []) for p in case_data.get("procedures") or []) print(f"render: ✓ wrote {args.out} ({len(html):,} chars, {n_procs} procedures, {n_steps} steps)") return 0 if __name__ == "__main__": sys.exit(main())