#!/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: /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())