Move generated builtin and dotnet function docs into the upgraded taxonomy, remove legacy route pages, and regenerate function_index.tsv from markdown headings.
137 lines
4.8 KiB
Python
137 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Rebuild docs/tsl/codegen/function_index.tsv from the codegen markdown tree.
|
|
|
|
The markdown tree is the source of truth: each `## `sig`` / `### `sig`` heading
|
|
is one function entry. The tsv is a derived index; regenerate it whenever the
|
|
md changes rather than editing it by hand.
|
|
|
|
Columns (tab-separated, LF line endings, UTF-8):
|
|
name scope module signature page anchor
|
|
- name: signature text up to the first '('
|
|
- scope: first path segment under the codegen root (builtin | dotnet)
|
|
- module: second path segment for nested pages, else the flat file stem
|
|
- signature: verbatim from the heading, backticks stripped
|
|
- page: POSIX path relative to the codegen root
|
|
- anchor: GitHub-style slug of the name (lowercased, chars outside
|
|
[a-z0-9_] removed); per-page duplicate slugs get -1/-2 suffixes
|
|
in document order, matching the rendered heading anchors.
|
|
|
|
Usage:
|
|
python scripts/tsl_codegen_function_index.py # rewrite in place
|
|
python scripts/tsl_codegen_function_index.py --check # verify, no write
|
|
python scripts/tsl_codegen_function_index.py --root PATH --tsv PATH
|
|
"""
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
import re
|
|
|
|
ENTRY_RE = re.compile(r"^#{2,3}(?!#)\s+`(.+?)`\s*$")
|
|
HEADER = ["name", "scope", "module", "signature", "page", "anchor"]
|
|
|
|
|
|
def slug(name):
|
|
"""GitHub-style anchor slug: lowercase, keep [a-z0-9_], drop the rest."""
|
|
return re.sub(r"[^a-z0-9_]", "", name.lower())
|
|
|
|
|
|
def parse_page(codegen_root, md):
|
|
"""Yield [name, scope, module, signature, page, anchor] rows for one md file."""
|
|
page = md.relative_to(codegen_root).as_posix()
|
|
scope, module = scope_module(page)
|
|
seen = {}
|
|
rows = []
|
|
for line in md.read_text(encoding="utf-8").splitlines():
|
|
m = ENTRY_RE.match(line)
|
|
if not m:
|
|
continue
|
|
sig = m.group(1)
|
|
name = sig.split("(", 1)[0]
|
|
base = slug(name)
|
|
n = seen.get(base, 0)
|
|
seen[base] = n + 1
|
|
anchor = base if n == 0 else f"{base}-{n}"
|
|
rows.append([name, scope, module, sig, page, anchor])
|
|
return rows
|
|
|
|
|
|
def scope_module(page):
|
|
parts = page.split("/")
|
|
scope = parts[0]
|
|
module = parts[1] if len(parts) >= 3 else Path(parts[-1]).stem
|
|
return scope, module
|
|
|
|
|
|
def build_rows(codegen_root):
|
|
"""Scan the whole codegen tree, return sorted rows (skips index.md)."""
|
|
rows = []
|
|
for md in sorted(codegen_root.rglob("*.md")):
|
|
if md.name == "index.md":
|
|
continue
|
|
rows.extend(parse_page(codegen_root, md))
|
|
rows.sort(key=lambda r: (r[0].lower(), r[4], r[3]))
|
|
return rows
|
|
|
|
|
|
def render_tsv(rows):
|
|
lines = ["\t".join(HEADER)]
|
|
lines.extend("\t".join(r) for r in rows)
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
def read_tsv(tsv_path):
|
|
text = tsv_path.read_text(encoding="utf-8")
|
|
return [line.split("\t") for line in text.splitlines() if line.strip()]
|
|
|
|
|
|
def main(argv=None):
|
|
if hasattr(sys.stdout, "reconfigure"):
|
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
parser.add_argument("--root", help="codegen root (default: docs/tsl/codegen)")
|
|
parser.add_argument("--tsv", help="output tsv (default: <root>/function_index.tsv)")
|
|
parser.add_argument(
|
|
"--check",
|
|
action="store_true",
|
|
help="verify the tsv matches the md tree; exit 1 if not (no write)",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
|
|
repo_root = Path(__file__).resolve().parents[1]
|
|
codegen_root = Path(args.root) if args.root else repo_root / "docs" / "tsl" / "codegen"
|
|
if not codegen_root.is_dir():
|
|
print(f"ERROR: codegen root not found: {codegen_root}", file=sys.stderr)
|
|
return 1
|
|
tsv_path = Path(args.tsv) if args.tsv else codegen_root / "function_index.tsv"
|
|
|
|
rows = build_rows(codegen_root)
|
|
new_text = render_tsv(rows)
|
|
|
|
if args.check:
|
|
if not tsv_path.is_file():
|
|
print(f"MISMATCH: tsv does not exist: {tsv_path}", file=sys.stderr)
|
|
return 1
|
|
current = tsv_path.read_text(encoding="utf-8")
|
|
if current == new_text:
|
|
print(f"OK: {tsv_path} matches md tree ({len(rows)} rows)")
|
|
return 0
|
|
cur_rows = read_tsv(tsv_path)[1:]
|
|
cur_keys = {tuple(r) for r in cur_rows}
|
|
new_keys = {tuple(r) for r in rows}
|
|
print(
|
|
f"MISMATCH: tsv out of date "
|
|
f"(tsv {len(cur_rows)} rows, md {len(rows)} rows; "
|
|
f"+{len(new_keys - cur_keys)} -{len(cur_keys - new_keys)}). "
|
|
f"Run without --check to rebuild.",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
|
|
tsv_path.write_text(new_text, encoding="utf-8", newline="\n")
|
|
print(f"wrote {tsv_path}: {len(rows)} rows")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|