feat: 가상화 문서들 추가

This commit is contained in:
DongHyeonka
2026-09-10 08:54:05 +09:00
parent e9f6a93327
commit 43e1aadef0
695 changed files with 153404 additions and 12754 deletions
+104
View File
@@ -0,0 +1,104 @@
#!/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"(<!-- body:start -->)(.*?)(<!-- body:end -->)", 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())