feat(478-2 문서 사이트): 묶음 규칙 · 원고 생성 · mkdocs 설정 · 손글 옮김 · 검사 시험(가짜 추출)
- docs/tools/groups.yml · group.py — 함수를 로직 묶음에 자동 배정(계산 등록부 id · 파일 이름 무늬 · 안 맞음 = 묶음 없음 · 함수 목록 지문) - docs/tools/build.py — 추출 JSON + 손글로 페이지 · 묶음 화면 · 함수 칸 · mermaid 흐름 · 세션 칸 · 서버 주소 · 설명 확인 필요 표시 · 함수 화면은 TypeDoc(markdown) · mkdocstrings(griffe) - docs/mkdocs.yml · nav_hook.py — Material · 한국어 · 검색 · mermaid · 왼쪽 메뉴 페이지 → 묶음 → 함수 - docs/guide/B05_B06 — 8 파일을 page.md(개요 · 규칙 · 사용자 결정) + groups/*.md 17 개로 줄여 옮김 - resources/tester/test_478_docs.py · fixtures/478_docs — 가짜 추출로 배정 · 사이트 strict · 도구 대 추출 · 진짜 추출은 있을 때만 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/route/node-edits",
|
||||
"handler": "py:B05_Profile/B05_Profile_Route_NodeEdits.py#node_edits",
|
||||
"callers": [
|
||||
"ts:B05_Profile/logic/B05_Profile_Grade_Seat.ts#seatBornNodes"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,407 @@
|
||||
[
|
||||
{
|
||||
"id": "ts:common_util/common_util_station_key.ts#chainageKey",
|
||||
"lang": "ts",
|
||||
"file": "common_util/common_util_station_key.ts",
|
||||
"line": 24,
|
||||
"name": "chainageKey",
|
||||
"exported": true,
|
||||
"doc": "누가거리 → 측점 열쇠 글.",
|
||||
"params": [
|
||||
{
|
||||
"name": "chainageM",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "string",
|
||||
"calls": [],
|
||||
"called_by": [
|
||||
"ts:common_util/common_util_station_key.ts#stationKey"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ts:common_util/common_util_station_key.ts#stationKey",
|
||||
"lang": "ts",
|
||||
"file": "common_util/common_util_station_key.ts",
|
||||
"line": 32,
|
||||
"name": "stationKey",
|
||||
"exported": true,
|
||||
"doc": "측점 열쇠(grid:k · pipe: · structure:).",
|
||||
"params": [
|
||||
{
|
||||
"name": "kind",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"name": "chainageM",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "string",
|
||||
"calls": [
|
||||
"ts:common_util/common_util_station_key.ts#chainageKey"
|
||||
],
|
||||
"called_by": [
|
||||
"ts:common_util/common_util_station_key.ts#dedupeStationKeys"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ts:common_util/common_util_station_key.ts#dedupeStationKeys",
|
||||
"lang": "ts",
|
||||
"file": "common_util/common_util_station_key.ts",
|
||||
"line": 54,
|
||||
"name": "dedupeStationKeys",
|
||||
"exported": true,
|
||||
"doc": "",
|
||||
"params": [
|
||||
{
|
||||
"name": "keys",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"name": "chainages",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "string[]",
|
||||
"calls": [
|
||||
"ts:common_util/common_util_station_key.ts#stationKey"
|
||||
],
|
||||
"called_by": []
|
||||
},
|
||||
{
|
||||
"id": "ts:common_util/common_util_station_key.ts#looseChainage",
|
||||
"lang": "ts",
|
||||
"file": "common_util/common_util_station_key.ts",
|
||||
"line": 67,
|
||||
"name": "looseChainage",
|
||||
"exported": true,
|
||||
"doc": "",
|
||||
"params": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "number | null",
|
||||
"calls": [],
|
||||
"called_by": []
|
||||
},
|
||||
{
|
||||
"id": "ts:common_util/common_util_station_key.ts#curveMoves",
|
||||
"lang": "ts",
|
||||
"file": "common_util/common_util_station_key.ts",
|
||||
"line": 80,
|
||||
"name": "curveMoves",
|
||||
"exported": true,
|
||||
"doc": "",
|
||||
"params": [
|
||||
{
|
||||
"name": "before",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"name": "after",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "Map",
|
||||
"calls": [],
|
||||
"called_by": []
|
||||
},
|
||||
{
|
||||
"id": "ts:common_util/common_util_station_key.ts#sameSeat",
|
||||
"lang": "ts",
|
||||
"file": "common_util/common_util_station_key.ts",
|
||||
"line": 116,
|
||||
"name": "sameSeat",
|
||||
"exported": true,
|
||||
"doc": "같은 자리 판정.",
|
||||
"params": [
|
||||
{
|
||||
"name": "a",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"name": "b",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "boolean",
|
||||
"calls": [],
|
||||
"called_by": [
|
||||
"ts:B05_Profile/logic/B05_Profile_Grade_Seat.ts#seatBornNodes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ts:B05_Profile/logic/B05_Profile_Grade_Seat.ts#median",
|
||||
"lang": "ts",
|
||||
"file": "B05_Profile/logic/B05_Profile_Grade_Seat.ts",
|
||||
"line": 24,
|
||||
"name": "median",
|
||||
"exported": false,
|
||||
"doc": "",
|
||||
"params": [
|
||||
{
|
||||
"name": "values",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "number",
|
||||
"calls": [],
|
||||
"called_by": [
|
||||
"ts:B05_Profile/logic/B05_Profile_Grade_Seat.ts#seatBornNodes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ts:B05_Profile/logic/B05_Profile_Grade_Seat.ts#seatBornNodes",
|
||||
"lang": "ts",
|
||||
"file": "B05_Profile/logic/B05_Profile_Grade_Seat.ts",
|
||||
"line": 32,
|
||||
"name": "seatBornNodes",
|
||||
"exported": true,
|
||||
"doc": "새 측점에 원지반을 지나는 꼭짓점.",
|
||||
"params": [
|
||||
{
|
||||
"name": "base",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"name": "born",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"name": "held",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "AlignmentNode[]",
|
||||
"calls": [
|
||||
"ts:B05_Profile/logic/B05_Profile_Grade_Seat.ts#median",
|
||||
"ts:common_util/common_util_station_key.ts#sameSeat"
|
||||
],
|
||||
"called_by": []
|
||||
},
|
||||
{
|
||||
"id": "py:common_util/common_util_station_key.py#_half_up",
|
||||
"lang": "py",
|
||||
"file": "common_util/common_util_station_key.py",
|
||||
"line": 22,
|
||||
"name": "_half_up",
|
||||
"exported": false,
|
||||
"doc": "",
|
||||
"params": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "int",
|
||||
"calls": [],
|
||||
"called_by": []
|
||||
},
|
||||
{
|
||||
"id": "py:common_util/common_util_station_key.py#chainage_key",
|
||||
"lang": "py",
|
||||
"file": "common_util/common_util_station_key.py",
|
||||
"line": 27,
|
||||
"name": "chainage_key",
|
||||
"exported": true,
|
||||
"doc": "누가거리 → 측점 열쇠 글.",
|
||||
"params": [
|
||||
{
|
||||
"name": "chainage_m",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "str",
|
||||
"calls": [],
|
||||
"called_by": []
|
||||
},
|
||||
{
|
||||
"id": "py:common_util/common_util_station_key.py#station_key",
|
||||
"lang": "py",
|
||||
"file": "common_util/common_util_station_key.py",
|
||||
"line": 32,
|
||||
"name": "station_key",
|
||||
"exported": true,
|
||||
"doc": "",
|
||||
"params": [
|
||||
{
|
||||
"name": "kind",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"name": "chainage_m",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "str",
|
||||
"calls": [],
|
||||
"called_by": []
|
||||
},
|
||||
{
|
||||
"id": "py:common_util/common_util_station_key.py#dedupe_station_keys",
|
||||
"lang": "py",
|
||||
"file": "common_util/common_util_station_key.py",
|
||||
"line": 68,
|
||||
"name": "dedupe_station_keys",
|
||||
"exported": true,
|
||||
"doc": "",
|
||||
"params": [
|
||||
{
|
||||
"name": "keys",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"name": "chainages",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "list[str]",
|
||||
"calls": [],
|
||||
"called_by": []
|
||||
},
|
||||
{
|
||||
"id": "py:common_util/common_util_station_key.py#loose_chainage",
|
||||
"lang": "py",
|
||||
"file": "common_util/common_util_station_key.py",
|
||||
"line": 79,
|
||||
"name": "loose_chainage",
|
||||
"exported": true,
|
||||
"doc": "",
|
||||
"params": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "float | None",
|
||||
"calls": [],
|
||||
"called_by": []
|
||||
},
|
||||
{
|
||||
"id": "py:common_util/common_util_station_key.py#curve_moves",
|
||||
"lang": "py",
|
||||
"file": "common_util/common_util_station_key.py",
|
||||
"line": 89,
|
||||
"name": "curve_moves",
|
||||
"exported": true,
|
||||
"doc": "",
|
||||
"params": [
|
||||
{
|
||||
"name": "before",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"name": "after",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "dict[str, str]",
|
||||
"calls": [],
|
||||
"called_by": []
|
||||
},
|
||||
{
|
||||
"id": "py:common_util/common_util_station_key.py#station_seat",
|
||||
"lang": "py",
|
||||
"file": "common_util/common_util_station_key.py",
|
||||
"line": 117,
|
||||
"name": "station_seat",
|
||||
"exported": true,
|
||||
"doc": "",
|
||||
"params": [
|
||||
{
|
||||
"name": "station",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "tuple | None",
|
||||
"calls": [],
|
||||
"called_by": []
|
||||
},
|
||||
{
|
||||
"id": "py:common_util/common_util_station_key.py#same_seat",
|
||||
"lang": "py",
|
||||
"file": "common_util/common_util_station_key.py",
|
||||
"line": 130,
|
||||
"name": "same_seat",
|
||||
"exported": true,
|
||||
"doc": "",
|
||||
"params": [
|
||||
{
|
||||
"name": "a",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"name": "b",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "bool",
|
||||
"calls": [],
|
||||
"called_by": []
|
||||
},
|
||||
{
|
||||
"id": "py:B05_Profile/B05_Profile_Route_NodeEdits.py#node_edits",
|
||||
"lang": "py",
|
||||
"file": "B05_Profile/B05_Profile_Route_NodeEdits.py",
|
||||
"line": 21,
|
||||
"name": "node_edits",
|
||||
"exported": true,
|
||||
"doc": "노드 편집 규칙.",
|
||||
"params": [
|
||||
{
|
||||
"name": "nodes",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "dict",
|
||||
"calls": [],
|
||||
"called_by": []
|
||||
},
|
||||
{
|
||||
"id": "py:common_util/common_util_atomic.py#atomic_write_bytes",
|
||||
"lang": "py",
|
||||
"file": "common_util/common_util_atomic.py",
|
||||
"line": 10,
|
||||
"name": "atomic_write_bytes",
|
||||
"exported": true,
|
||||
"doc": "",
|
||||
"params": [
|
||||
{
|
||||
"name": "path",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"name": "payload",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "None",
|
||||
"calls": [],
|
||||
"called_by": []
|
||||
},
|
||||
{
|
||||
"id": "py:common_util/common_util_atomic.py#atomic_write_npz",
|
||||
"lang": "py",
|
||||
"file": "common_util/common_util_atomic.py",
|
||||
"line": 34,
|
||||
"name": "atomic_write_npz",
|
||||
"exported": true,
|
||||
"doc": "",
|
||||
"params": [
|
||||
{
|
||||
"name": "path",
|
||||
"type": ""
|
||||
},
|
||||
{
|
||||
"name": "arrays",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"returns": "None",
|
||||
"calls": [],
|
||||
"called_by": []
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"id": "stations.key",
|
||||
"no": "R7",
|
||||
"label": "측점 열쇠",
|
||||
"site": "browser_now",
|
||||
"ts": "common_util/common_util_station_key.ts#stationKey",
|
||||
"pair": "common_util/common_util_station_key.py#station_key"
|
||||
},
|
||||
{
|
||||
"id": "profile.seat",
|
||||
"no": "P6",
|
||||
"label": "옮긴 시설 꼭짓점 세움",
|
||||
"site": "browser_now",
|
||||
"ts": "B05_Profile/logic/B05_Profile_Grade_Seat.ts#seatBornNodes",
|
||||
"pair": null
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"key": "aislo:draft:route-draft",
|
||||
"reads": [
|
||||
"ts:common_util/common_util_station_key.ts#stationKey"
|
||||
],
|
||||
"writes": [
|
||||
"B05_Profile/logic/B05_Profile_Grade_Seat.ts:32"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,129 @@
|
||||
"""478-2 문서 사이트 검사 — 묶음 배정 · 손글 함수 · 함수 화면 도구 대 추출 · mkdocs --strict.
|
||||
|
||||
가짜 추출(fixtures/478_docs)은 늘 돎 · 진짜 추출(docs/_data)은 있을 때만.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT / "docs" / "tools"))
|
||||
|
||||
import build # noqa: E402
|
||||
import group # noqa: E402
|
||||
|
||||
FIXTURE = Path(__file__).parent / "fixtures" / "478_docs"
|
||||
REAL = ROOT / "docs" / "_data"
|
||||
|
||||
|
||||
def make_site(data_dir: Path, out: Path) -> tuple[build.Site, dict]:
|
||||
site = build.Site(build.load_data(data_dir), group.load_rules(), out)
|
||||
return site, site.build()
|
||||
|
||||
|
||||
def shown_anchors(site: build.Site) -> set[str]:
|
||||
text = "".join(p.read_text(encoding="utf-8") for p in (site.src).glob("*/*.md"))
|
||||
return set(re.findall(r"\{ #(f-[0-9a-f]{10}) \}", text))
|
||||
|
||||
|
||||
def typedoc_functions(out: Path) -> set[tuple[str, str]]:
|
||||
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]
|
||||
return {
|
||||
(c["sources"][0]["fileName"], c["name"])
|
||||
for m in modules
|
||||
for c in m.get("children", [])
|
||||
if c["kind"] == 64
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def fake(tmp_path_factory):
|
||||
return make_site(FIXTURE, tmp_path_factory.mktemp("fake"))
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def real(tmp_path_factory):
|
||||
if not (REAL / "functions.json").exists():
|
||||
pytest.skip("추출 없음 — docs/_data")
|
||||
return make_site(REAL, tmp_path_factory.mktemp("real"))
|
||||
|
||||
|
||||
def test_assign_rules():
|
||||
data = build.load_data(FIXTURE)
|
||||
page = group.load_rules()["pages"][0]
|
||||
gid = {
|
||||
f["name"]: g
|
||||
for g, fns in group.assign(data["functions"], data["registry"], page["groups"]).items()
|
||||
for f in fns
|
||||
}
|
||||
assert gid["stationKey"] == "stations" # 등록부 stations.key
|
||||
assert gid["station_key"] == "stations" # 등록부 pair
|
||||
assert gid["seatBornNodes"] == "profile" # 등록부 profile.seat
|
||||
assert gid["median"] == "profile" # 파일 B05_Profile_Grade_*
|
||||
assert gid["node_edits"] == "route_edit" # 파일 B05_Profile_Route_NodeEdits
|
||||
assert gid["atomic_write_bytes"] == group.NONE
|
||||
|
||||
|
||||
def test_fake_site(fake):
|
||||
site, rep = fake
|
||||
assert shown_anchors(site) == {build.anchor(f["id"]) for f in site.fns}
|
||||
assert rep["ungrouped"] == 2
|
||||
assert rep["session_keys"] == 1 and rep["api"] == 1
|
||||
assert "stations" in rep["stale_groups"] # 손글 지문 ≠ 가짜 목록
|
||||
build.build_site(site.out, strict=True)
|
||||
|
||||
|
||||
def test_fake_tools_match_extract(fake):
|
||||
site, _ = fake
|
||||
ts = {(f["file"], f["name"]) for f in site.fns if f["lang"] == "ts" and f["exported"]}
|
||||
assert typedoc_functions(site.out) == ts
|
||||
py = {(f["file"], f["name"]) for f in site.fns if f["lang"] == "py"}
|
||||
assert {(file, n) for file, _ in py for n in build.griffe_names(file)} == py
|
||||
|
||||
|
||||
def test_real_hand_refs(real):
|
||||
_, rep = real
|
||||
assert rep["missing_refs"] == [], "손글이 가리키는 함수가 추출에 없음"
|
||||
|
||||
|
||||
def test_real_every_function_shown(real):
|
||||
site, _ = real
|
||||
assert shown_anchors(site) == {build.anchor(f["id"]) for f in site.fns}, "조용히 빠진 함수"
|
||||
|
||||
|
||||
def test_real_typedoc_matches_extract(real):
|
||||
site, _ = real
|
||||
ts = {(f["file"], f["name"]) for f in site.fns if f["lang"] == "ts" and f["exported"]}
|
||||
got = typedoc_functions(site.out)
|
||||
assert not (ts - got) and not (got - ts), (
|
||||
f"추출만 {sorted(ts - got)[:10]} · TypeDoc 만 {sorted(got - ts)[:10]}"
|
||||
)
|
||||
|
||||
|
||||
def test_real_griffe_matches_extract(real):
|
||||
site, _ = real
|
||||
py = {(f["file"], f["name"]) for f in site.fns if f["lang"] == "py"}
|
||||
got = {(file, n) for file in {f for f, _ in py} for n in build.griffe_names(file)}
|
||||
assert not (py - got) and not (got - py), (
|
||||
f"추출만 {sorted(py - got)[:10]} · griffe 만 {sorted(got - py)[:10]}"
|
||||
)
|
||||
|
||||
|
||||
def test_real_counts(real):
|
||||
_, rep = real
|
||||
if rep["undocumented_exports"]:
|
||||
warnings.warn(f"설명 없는 export 함수 {len(rep['undocumented_exports'])}")
|
||||
if rep["ungrouped"]:
|
||||
warnings.warn(f"묶음 없는 함수 {rep['ungrouped']}")
|
||||
assert rep["ungrouped"] < rep["functions"]
|
||||
|
||||
|
||||
def test_real_strict_build(real):
|
||||
site, _ = real
|
||||
build.build_site(site.out, strict=True)
|
||||
Reference in New Issue
Block a user