#!/usr/bin/env python3 """Generate docs/endpoint-index.md from the OpenAPI spec. Do not edit docs/endpoint-index.md by hand -- it is generated by this script from ErsatzTV/wwwroot/openapi/v1.json. Regenerated automatically as part of scripts/update-openapi.sh. """ import json from pathlib import Path HTTP_METHODS = ("get", "put", "post", "delete", "options", "head", "patch", "trace") REPO_ROOT = Path(__file__).resolve().parent.parent SPEC_PATH = REPO_ROOT / "ErsatzTV" / "wwwroot" / "openapi" / "v1.json" OUTPUT_PATH = REPO_ROOT / "docs" / "endpoint-index.md" UNTAGGED = "(untagged)" def load_operations(spec: dict) -> list[dict]: """Flatten the OpenAPI paths object into a list of per-operation records.""" operations = [] for path, path_item in spec.get("paths", {}).items(): for method, operation in path_item.items(): if method.lower() not in HTTP_METHODS: continue operations.append( { "path": path, "method": method.upper(), "operationId": operation.get("operationId", ""), "summary": operation.get("summary", "") or "", "tags": operation.get("tags") or [UNTAGGED], } ) return operations def group_by_tag(operations: list[dict]) -> dict[str, list[dict]]: grouped: dict[str, list[dict]] = {} for op in operations: for tag in op["tags"]: grouped.setdefault(tag, []).append(op) return grouped def render(spec: dict) -> str: operations = load_operations(spec) grouped = group_by_tag(operations) tags = sorted(t for t in grouped if t != UNTAGGED) if UNTAGGED in grouped: tags.append(UNTAGGED) lines = [] lines.append("# API endpoint index") lines.append("") lines.append( "*Generated by `scripts/generate-endpoint-index.py` from " "`ErsatzTV/wwwroot/openapi/v1.json`. Do not edit by hand -- regenerated by " "`scripts/update-openapi.sh`.*" ) lines.append("") lines.append( f"{len(spec.get('paths', {}))} endpoints, {len(operations)} operations." ) lines.append("") for tag in tags: lines.append(f"## {tag}") lines.append("") lines.append("| Method | Path | Operation | Summary |") lines.append("|---|---|---|---|") for op in sorted(grouped[tag], key=lambda o: (o["path"], o["method"])): lines.append( f"| {op['method']} | `{op['path']}` | {op['operationId']} | {op['summary']} |" ) lines.append("") return "\n".join(lines).rstrip("\n") + "\n" def main() -> None: spec = json.loads(SPEC_PATH.read_text()) OUTPUT_PATH.write_text(render(spec)) operations = load_operations(spec) print( f"Wrote {OUTPUT_PATH.relative_to(REPO_ROOT)}: " f"{len(spec.get('paths', {}))} endpoints, {len(operations)} operations." ) if __name__ == "__main__": main()