#!/usr/bin/env python3 import os import re import json from collections import defaultdict ROOT = os.path.join(os.path.dirname(__file__), "..", "app", "src", "main", "assets", "story") ROOT = os.path.abspath(ROOT) NODE_DEF_RE = re.compile(r"^@node\s+([A-Za-z0-9_]+)") TARGET_RE = re.compile(r"->\s*([A-Za-z0-9_]+)\b") def scan(): node_defs = {} refs = [] for dirpath, _, filenames in os.walk(ROOT): for fn in filenames: if not fn.endswith('.story'): continue path = os.path.join(dirpath, fn) try: with open(path, 'r', encoding='utf-8') as f: for ln, raw in enumerate(f, 1): line = raw.strip() m = NODE_DEF_RE.match(line) if m: node_defs[m.group(1)] = path for rm in TARGET_RE.finditer(line): refs.append((rm.group(1), path, ln)) except Exception as e: print(f"! failed to read {path}: {e}") invalid = defaultdict(list) for target, path, ln in refs: if target not in node_defs: invalid[target].append({"file": path, "line": ln}) return node_defs, refs, invalid def module_hint(file_path: str) -> str: base = os.path.basename(file_path) name, _ = os.path.splitext(base) return name def main(): node_defs, refs, invalid = scan() items = [] for t in sorted(invalid.keys()): locs = invalid[t] modules = sorted({module_hint(l['file']) for l in locs}) items.append({ "node": t, "modules": modules, "references": locs, "ref_count": len(locs) }) report = { "story_root": ROOT, "defined_nodes": len(node_defs), "total_refs": len(refs), "invalid_count": len(items), "items": items, } # Print a human-readable summary followed by JSON print("=== Invalid (referenced but undefined) nodes ===") if not items: print("None") else: for it in items: mods = ",".join(it["modules"]) or "unknown" print(f"- {it['node']} [modules: {mods}] refs: {it['ref_count']}") for r in it["references"]: file_rel = os.path.relpath(r["file"], os.path.dirname(ROOT)) print(f" ยท {file_rel}:{r['line']}") print("\n--- JSON ---") print(json.dumps(report, ensure_ascii=False, indent=2)) if __name__ == "__main__": main()