#!/usr/bin/env python3 """기록의 본문을 Studio 가 받는 형태로 바꿔 낸다. 저장소의 `.md` 는 사람이 읽는 파일이라 그림을 마크다운 이미지로 적는다. 그래야 편집기에서 그대로 보인다. Studio 는 그 자리에 `:::evidence key="…"` 를 요구하고 상대 경로 이미지는 `unsafe image URL` 로 거절한다. 두 형태를 한 파일에 담을 수 없으므로 **저장소는 읽는 형태로 두고 Studio 로 보낼 때 이 스크립트가 바꾼다.** python3 scripts/studio-body.py <기록.md> # 바꾼 전문을 표준출력으로 python3 scripts/studio-body.py <기록.md> -o /tmp/x.md # 파일로 (check_body 용) python3 scripts/studio-body.py <기록.md> --body-only # 본문 구간만 (Studio 붙여넣기용) python3 scripts/studio-body.py <기록.md> --key a=a-1234 # 서버가 준 키로 바꿔서 `--key` 를 주지 않으면 frontmatter 의 `assets` key 를 그대로 쓴다. Studio 에 올린 뒤에는 서버가 `<이름>-<해시8>` 을 주므로 그때 `--key` 로 짝지어 준다. """ from __future__ import annotations import argparse import os import re import sys BODY = re.compile(r"()(.*?)()", re.S) IMAGE = re.compile(r"^!\[([^\]]*)\]\(([^)]+)\)$", re.M) def asset_keys(text: str) -> dict[str, str]: """frontmatter 의 assets 를 상대경로 → key 로 읽는다.""" head = text.split("\n---\n", 1)[0] keys = re.findall(r"^ - key: (\S+)$", head, re.M) files = re.findall(r"^ file: (\S+)$", head, re.M) return {f: k for k, f in zip(keys, files)} # 본문이 있는 종류는 둘뿐이다. 나머지 셋은 본문 구간이 없는 것이 정상이다 BODY_KINDS = {"CASE", "CONCEPT"} def record_kind(text: str) -> str: """frontmatter 의 kind. 없으면 빈 문자열.""" head = text.split("\n---\n", 1)[0] m = re.search(r"^kind:\s*(\S+)", head, re.M) return m.group(1).upper() if m else "" def convert(text: str, overrides: dict[str, str]) -> tuple[str, list[str]]: by_path = asset_keys(text) missing: list[str] = [] def one(m: re.Match) -> str: alt, path = m.group(1), m.group(2) key = by_path.get(path) if key is None: missing.append(path) return m.group(0) key = overrides.get(key, key) return f':::evidence key="{key}" alt="{alt}" caption=" " zoom="true"\n:::' m = BODY.search(text) if not m: if record_kind(text) in BODY_KINDS: return text, ["본문 구간(body:start ~ body:end)이 없다"] # Reference·Question·Decision 은 본문을 둘 자리가 없다. 결함이 아니다 return text, [] body = IMAGE.sub(one, m.group(2)) return text[:m.start(2)] + body + text[m.end(2):], missing def main() -> int: ap = argparse.ArgumentParser(description="기록 본문을 Studio 형태로 바꾼다.") ap.add_argument("record") ap.add_argument("-o", "--out") ap.add_argument("--body-only", action="store_true") ap.add_argument("--key", action="append", default=[], help="frontmatter key=서버가 준 key. 여러 번 줄 수 있다") args = ap.parse_args() overrides = {} for pair in args.key: if "=" not in pair: sys.exit(f"--key 는 a=b 형태다: {pair}") a, b = pair.split("=", 1) overrides[a] = b text = open(args.record, encoding="utf-8").read() out, missing = convert(text, overrides) for path in missing: print(f"경고: frontmatter 의 assets 에 없다 — {path}", file=sys.stderr) if args.body_only: m = BODY.search(out) out = m.group(2).strip("\n") if m else out if args.out: open(args.out, "w", encoding="utf-8").write(out) print(args.out) else: sys.stdout.write(out) return 1 if missing else 0 if __name__ == "__main__": raise SystemExit(main())