FletAppDemo/tools/generate_source_manifest.py

52 lines
2 KiB
Python

"""Generate exact repository source anchors without importing application code."""
from __future__ import annotations
import ast
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
EXAMPLES_DIR = ROOT / "src" / "gallery" / "examples"
OUTPUT = ROOT / "src" / "gallery" / "source_manifest.json"
def collect() -> dict[str, dict[str, str | int]]:
manifest: dict[str, dict[str, str | int]] = {}
for path in sorted(EXAMPLES_DIR.glob("*.py")):
if path.name == "__init__.py":
continue
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
functions = {
node.name: node.lineno
for node in tree.body
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
}
for node in ast.walk(tree):
if not isinstance(node, ast.Call) or not isinstance(node.func, ast.Name):
continue
if node.func.id != "Example" or len(node.args) < 5:
continue
example_id, builder = node.args[0], node.args[4]
if not isinstance(example_id, ast.Constant) or not isinstance(example_id.value, str):
raise TypeError(f"Example id must be a string in {path}")
if not isinstance(builder, ast.Name) or builder.id not in functions:
raise ValueError(f"Example {example_id.value} must reference a local function")
if example_id.value in manifest:
raise ValueError(f"Duplicate example id: {example_id.value}")
manifest[example_id.value] = {
"path": path.relative_to(ROOT).as_posix(),
"line": functions[builder.id],
"function": builder.id,
}
return manifest
def main() -> None:
content = json.dumps(collect(), ensure_ascii=False, indent=2, sort_keys=True) + "\n"
OUTPUT.write_text(content, encoding="utf-8")
print(f"Generated {OUTPUT.relative_to(ROOT)}")
if __name__ == "__main__":
main()