diff --git a/tooling/validate-ron b/tooling/validate-ron index 74bbb2d95..30f2e94d8 100755 --- a/tooling/validate-ron +++ b/tooling/validate-ron @@ -3,20 +3,123 @@ # # Usage: # tooling/validate-ron +# tooling/validate-ron --check-name-collisions # # Examples: # tooling/validate-ron content/global/zone-identity-spec.example.ron zone # tooling/validate-ron content/global/culture-van-maanens-star.example.ron culture +# tooling/validate-ron --check-name-collisions content/global/ +# +# --check-name-collisions: +# Scans all culture-*.ron files in the given directory. Extracts naming.given_names +# and naming.family_names pools from each file. Reports any name that appears in more +# than one culture's pool. Exits 1 on collision; exits 0 if no collisions found. +# Output is machine-readable (one collision per line). set -euo pipefail +# --- Name collision mode -------------------------------------------------- +if [ "${1:-}" = "--check-name-collisions" ]; then + if [ $# -lt 2 ]; then + echo "Usage: tooling/validate-ron --check-name-collisions " + exit 1 + fi + SCAN_DIR="$2" + if [ ! -d "$SCAN_DIR" ]; then + echo "Error: directory not found: $SCAN_DIR" + exit 1 + fi + python3 - "$SCAN_DIR" <<'PYEOF' +import sys +import re +import os +import glob + +scan_dir = sys.argv[1] +pattern = os.path.join(scan_dir, "culture-*.ron") +files = sorted(glob.glob(pattern)) + +if not files: + print(f"No culture-*.ron files found in: {scan_dir}") + sys.exit(0) + +def extract_names_from_block(text, field): + """Extract quoted string list from a named RON array field.""" + # Match: field: [\n "name", "name", ...\n] + # Handles multi-line arrays with comments inside. + pat = re.compile( + r'\b' + re.escape(field) + r'\s*:\s*\[([^\]]*)\]', + re.DOTALL + ) + m = pat.search(text) + if not m: + return [] + block = m.group(1) + # Strip line comments before extracting quoted strings + block = re.sub(r'//[^\n]*', '', block) + block = re.sub(r'/[^\n]*', '', block) # single-slash RON comments + return re.findall(r'"([^"]+)"', block) + +# culture_id -> set of names (given + family, tracked separately for reporting) +given_by_culture = {} +family_by_culture = {} + +for fpath in files: + with open(fpath, 'r', encoding='utf-8') as f: + text = f.read() + # Extract culture id from the id: "..." field + id_m = re.search(r'\bid\s*:\s*"([^"]+)"', text) + culture_id = id_m.group(1) if id_m else os.path.basename(fpath) + + given_by_culture[culture_id] = set(extract_names_from_block(text, 'given_names')) + family_by_culture[culture_id] = set(extract_names_from_block(text, 'family_names')) + +cultures = list(given_by_culture.keys()) +collisions_found = False + +# Check given_names collisions +all_given_names = {} # name -> list of cultures +for culture, names in given_by_culture.items(): + for name in names: + all_given_names.setdefault(name, []).append(culture) + +for name, cultures_with_name in sorted(all_given_names.items()): + if len(cultures_with_name) > 1: + collisions_found = True + print(f"COLLISION given_names: \"{name}\" in {', '.join(sorted(cultures_with_name))}") + +# Check family_names collisions +all_family_names = {} # name -> list of cultures +for culture, names in family_by_culture.items(): + for name in names: + all_family_names.setdefault(name, []).append(culture) + +for name, cultures_with_name in sorted(all_family_names.items()): + if len(cultures_with_name) > 1: + collisions_found = True + print(f"COLLISION family_names: \"{name}\" in {', '.join(sorted(cultures_with_name))}") + +if not collisions_found: + print(f"OK: no name collisions across {len(cultures)} culture(s): {', '.join(sorted(cultures))}") + sys.exit(0) +else: + sys.exit(1) +PYEOF + exit $? +fi +# --- End name collision mode ---------------------------------------------- + if [ $# -lt 2 ]; then echo "Usage: tooling/validate-ron " + echo " tooling/validate-ron --check-name-collisions " echo "" echo "Validates a RON file against the Rust struct schema." echo "Schema types:" echo " zone — ZoneSpec (zone identity spec)" echo " culture — CultureProfile (culture profile)" + echo "" + echo "Name collision check:" + echo " --check-name-collisions Scan culture-*.ron files for shared name pool entries" exit 1 fi