feat(478-2 문서 사이트): 진짜 추출로 맞춤 — TypeDoc kind-dir(대소문자 겹침 막음) · 도구 대 추출 비교 규칙 · 공용 도구 묶음 · 손글 지문 · TS 메뉴 쪽

- TypeDoc router kind-dir — structure 는 같은 이름 인터페이스 · 함수가 Windows 에서 한 파일로 덮임 · 겹침 링크 안 함
- 비교: TS = export 함수 · 메서드 · 생성자 · 접근자(상속 · 숨은 생성자 뺌 · export * 는 원본으로) · 파이썬 = 안쪽 함수 뺀 전부 ↔ griffe(속성 · 자동 생성 __init__ 반영)
- groups.yml 공용 도구(common_util_* 끝 규칙) · 묶음 없음 0 · 손글 18 묶음 지문
- 결과: 함수 5,746 · 묶음 18 · 세션 칸 45 · 서버 주소 41 · strict 경고 0 · test_478_docs 9 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 19:22:10 +09:00
co-authored by Claude Opus 5.5
parent 67e58c45a4
commit 47a63d3fa4
22 changed files with 223 additions and 60 deletions
+55 -17
View File
@@ -79,7 +79,7 @@ def run_typedoc(ts_files: list[str], out: Path) -> dict[tuple[str, str], str]:
"entryPoints": [(ROOT / f).as_posix() for f in ts_files],
"tsconfig": (ROOT / "tsconfig.json").as_posix(),
"skipErrorChecking": True,
"router": "structure",
"router": "kind-dir",
"out": (out / "src" / "ts").as_posix(),
"json": (out / "typedoc.json").as_posix(),
"readme": "none",
@@ -95,20 +95,55 @@ def run_typedoc(ts_files: list[str], out: Path) -> dict[tuple[str, str], str]:
return typedoc_pages(out)
def typedoc_pages(out: Path) -> dict[tuple[str, str], str]:
FN_KINDS = {64, 512, 2048, 262144} # TypeDoc 함수 · 생성자 · 메서드 · 접근자
KIND_DIR = {64: "functions", 32: "variables", 128: "classes"} # router kind-dir 폴더
def typedoc_entries(out: Path) -> list[tuple[str, str, int, str]]:
"""TypeDoc JSON → (파일, 이름, 종류, 화면 경로).
클래스 칸 = `Class.name` · 다시 내보내기 = 가리키는 것의 종류.
"""
project = json.loads((out / "typedoc.json").read_text(encoding="utf-8"))
modules = [m for m in project.get("children", []) if m["kind"] == 2] or [
{**project, "name": ""}
]
pages = {}
modules = [m for m in project.get("children", []) if m["kind"] == 2]
by_id: dict[int, dict] = {}
def index(r: dict) -> None:
by_id[r["id"]] = r
for c in r.get("children", []):
index(c)
index(project)
rows = []
for mod in modules:
base = f"ts/{mod['name']}/" if mod["name"] else "ts/"
file = mod["sources"][0]["fileName"] if mod.get("sources") else f"{mod['name']}.ts"
slug = mod["name"].replace("/", "_")
for c in mod.get("children", []):
if c["kind"] in (64, 32) and c.get("sources"):
rel = f"{base}{c['name']}.html"
if (out / "src" / rel).exists():
pages[(c["sources"][0]["fileName"], c["name"])] = rel
return pages
folder = KIND_DIR.get(c["kind"])
page = f"ts/{folder}/{slug}.{c['name']}/index.html" if folder else ""
kind = c["kind"]
if kind == 4194304:
kind = by_id.get(c.get("target"), {}).get("kind", kind)
rows.append((file, c["name"], kind, page))
if c["kind"] != 128:
continue
for m in c.get("children", []):
if m.get("sources") and not m.get("flags", {}).get("isInherited"):
rows.append(
(file, f"{c['name']}.{m['name']}", m["kind"], f"{page}#{m['name'].lower()}")
)
return rows
def typedoc_pages(out: Path) -> dict[tuple[str, str], str]:
"""(파일, 이름) → 화면 경로 · 대소문자만 다른 겹침(Windows 한 파일)은 링크 안 함."""
rows = [r for r in typedoc_entries(out) if r[3]]
seen = Counter(rel.split("#")[0].lower() for *_, rel in rows if "#" not in rel)
return {
(file, name): rel
for file, name, _, rel in rows
if seen[rel.split("#")[0].lower()] == 1 and (out / "src" / rel.split("#")[0]).exists()
}
def py_ident(file: str) -> str:
@@ -116,7 +151,7 @@ def py_ident(file: str) -> str:
def griffe_names(file: str) -> set[str]:
"""griffe 가 보는 함수 이름(모듈 함수 · 클래스 메서드 `Class.name`)."""
"""griffe 가 보는 함수 이름(모듈 함수 · 클래스 메서드 · 속성 `Class.name` · 자동 생성 뺌)."""
mod = griffe.load(py_ident(file), search_paths=[str(ROOT)], allow_inspection=False)
names: set[str] = set()
@@ -124,7 +159,7 @@ def griffe_names(file: str) -> set[str]:
for name, m in obj.members.items():
if m.is_alias:
continue
if m.is_function:
if (m.is_function or "property" in m.labels) and m.lineno:
names.add(prefix + name)
elif m.is_class:
walk(m, f"{prefix}{name}.")
@@ -386,9 +421,12 @@ class Site:
self.gid.update({f["id"]: gid for gid, fns in groups.items() for f in fns})
nav.append({page["title"]: self.write_page(page, groups)})
home.append(f"- [{page['title']}]({page['id']}/index.md)")
ts_nav = (
[{"TypeScript": "ts/index.html"}] if (self.src / "ts" / "index.html").exists() else []
)
ts_nav = []
if (self.src / "ts" / "index.html").exists():
(self.src / "ts.md").write_text(
"# TypeScript 함수 화면\n\n[TypeDoc 열기](ts/index.html)\n", encoding="utf-8"
)
ts_nav = [{"TypeScript": "ts.md"}]
nav.append({"함수 화면": [*ts_nav, {"파이썬": "py/index.md"}]})
(self.src / "index.md").write_text("\n".join(home) + "\n", encoding="utf-8")
(self.src / ".nav.json").write_text(json.dumps(nav, ensure_ascii=False), encoding="utf-8")
+9
View File
@@ -69,6 +69,7 @@ pages:
- design_pipeline_stations
- design_pipeline_probe_station
- common_util_station_key
- common_util_station
- id: ground
title: 지반 표본
registry: ["ground.*"]
@@ -80,6 +81,7 @@ pages:
- B06_Section_Engine_SampleExtend
- B06_Section_Router_Widen
- common_util_surface_sampler
- common_util_surface_confirmation
- id: earthwork
title: 토량 · 유토 배분
registry: ["earth.*", "haul.*"]
@@ -102,6 +104,7 @@ pages:
- B05_Profile_PageSettings
- B05_Profile_Api_PageSettings
- design_profile*
- common_util_route_profile
- id: cross
title: 횡단 설계 계산
registry: ["cross.*"]
@@ -179,6 +182,8 @@ pages:
- B06_Section_Section_*
- B06_Section_UI_Page_Persist
- common_util_calc_runner
- common_util_derived_store
- common_util_node_bundle
- id: save
title: 저장 · 확정 · 초기화 · 떠남
files:
@@ -216,3 +221,7 @@ pages:
- B05_Profile
- B06_Section
- B05_Profile_Debug
- id: common
title: 공용 도구
files:
- common_util_*