render.py 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. #!/usr/bin/env python3
  2. """procedure skill 渲染入口 — 零校验, 直接出 HTML.
  3. 质量门禁全部在 validate.py; 本脚本只负责把 workflow.json 拼成 case_data 并渲染,
  4. 数据再不完美也尽量出图 (复用 spec/tools 的 renderer.build_html + merge_raw_source,
  5. 跳过 render-case.py 的 schema / 占位 / 结构三层门禁)。
  6. 用法:
  7. python procedure/tools/render.py --workflow outputs/case-N/workflow.json \
  8. --source-input input/case-N.json --page-title "Case N · 主题" \
  9. --case-id N --out outputs/case-N/case-N.html
  10. 退出码: 0 成功 / 1 渲染异常 / 2 CLI、文件问题
  11. """
  12. import argparse
  13. import importlib.util
  14. import json
  15. import sys
  16. from pathlib import Path
  17. if hasattr(sys.stdout, "reconfigure"):
  18. sys.stdout.reconfigure(encoding="utf-8", errors="replace")
  19. sys.stderr.reconfigure(encoding="utf-8", errors="replace")
  20. SPEC_TOOLS = Path(__file__).resolve().parent.parent.parent / "spec" / "tools"
  21. def _load_module(name, path):
  22. spec = importlib.util.spec_from_file_location(name, path)
  23. mod = importlib.util.module_from_spec(spec)
  24. spec.loader.exec_module(mod)
  25. return mod
  26. def main():
  27. ap = argparse.ArgumentParser()
  28. ap.add_argument("--workflow", type=Path, required=True)
  29. ap.add_argument("--source-input", type=Path, dest="source_input",
  30. help="原帖 raw json — 正文/封面 merge 进 HTML 折叠原文区")
  31. ap.add_argument("--page-title", default=None)
  32. ap.add_argument("--case-id", default=None)
  33. ap.add_argument("--out", type=Path, required=True)
  34. args = ap.parse_args()
  35. try:
  36. case_data = json.loads(args.workflow.read_text(encoding="utf-8"))
  37. except FileNotFoundError:
  38. print(f"✗ 文件不存在: {args.workflow}")
  39. return 2
  40. except json.JSONDecodeError as e:
  41. print(f"✗ JSON 解析失败: {e}")
  42. return 2
  43. title = (case_data.get("source") or {}).get("title") or args.workflow.stem
  44. case_data.setdefault("page_title", args.page_title or title)
  45. if args.case_id is not None:
  46. case_data["case_id"] = args.case_id
  47. try:
  48. if args.source_input:
  49. _load_module("_render_case_mod", SPEC_TOOLS / "render-case.py") \
  50. .merge_raw_source(case_data, args.source_input)
  51. html = _load_module("_renderer_mod", SPEC_TOOLS / "renderer.py").build_html(case_data)
  52. except Exception as e:
  53. print(f"✗ 渲染异常 ({type(e).__name__}: {e}) — workflow.json 可能有渲染器吃不下的字段值")
  54. return 1
  55. args.out.parent.mkdir(parents=True, exist_ok=True)
  56. args.out.write_text(html, encoding="utf-8", newline="")
  57. n_procs = len(case_data.get("procedures") or [])
  58. n_steps = sum(len(p.get("steps") or []) for p in case_data.get("procedures") or [])
  59. print(f"render: ✓ wrote {args.out} ({len(html):,} chars, {n_procs} procedures, {n_steps} steps)")
  60. return 0
  61. if __name__ == "__main__":
  62. sys.exit(main())