Files
GameOfMoon/tools/check_invalid_nodes.py

82 lines
2.5 KiB
Python

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