# Conflicts: # content/npc-conversations/overheard.yaml # server/content/global/zone-types/administrative_civil.ron # server/content/global/zone-types/administrative_judicial.ron # server/content/global/zone-types/archaeological_site.ron # server/content/global/zone-types/commercial_market.ron # server/content/global/zone-types/commercial_transit.ron # server/content/global/zone-types/detention_facility.ron # server/content/global/zone-types/diplomatic_elite.ron # server/content/global/zone-types/entertainment_hospitality.ron # server/content/global/zone-types/entertainment_venue.ron # server/content/global/zone-types/extraction_platform.ron # server/content/global/zone-types/extraction_space.ron # server/content/global/zone-types/extraction_surface.ron # server/content/global/zone-types/industrial_manufacturing.ron # server/content/global/zone-types/industrial_processing.ron # server/content/global/zone-types/medical_facility.ron # server/content/global/zone-types/military_garrison.ron # server/content/global/zone-types/port_fishing.ron # server/content/global/zone-types/port_maritime.ron # server/content/global/zone-types/port_space.ron # server/content/global/zone-types/port_surface.ron # server/content/global/zone-types/research_station.ron # server/content/global/zone-types/residential_dispersed.ron # server/content/global/zone-types/residential_station.ron # server/content/global/zone-types/residential_surface.ron # server/content/global/zone-types/rural_aquaculture.ron # server/content/global/zone-types/rural_orbital.ron # server/content/global/zone-types/rural_pastoral.ron # server/content/global/zone-types/security_checkpoint.ron # server/content/global/zone-types/wilderness_frontier.ron # server/content/npc-conversations/overheard.ron # server/content/npc-conversations/overheard.yaml # server/content/npc-conversations/overheard.yaml.deprecated # tooling/validate-ron
138 lines
4.7 KiB
Bash
Executable File
138 lines
4.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# RON content validator — wrapper for the Rust validate_ron binary (#611).
|
|
#
|
|
# Usage:
|
|
# tooling/validate-ron <file.ron> <zone|zone_type|culture>
|
|
# tooling/validate-ron --check-name-collisions <content/dir/>
|
|
#
|
|
# Examples:
|
|
# tooling/validate-ron server/content/global/zone-types/rural_agricultural.ron zone_type
|
|
# tooling/validate-ron server/content/global/culture-van-maanens-star.example.ron culture
|
|
# tooling/validate-ron server/content/global/zone-identity-spec.example.ron zone
|
|
# tooling/validate-ron --check-name-collisions server/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 <directory>"
|
|
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)
|
|
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 <file.ron> <zone|zone_type|culture>"
|
|
echo " tooling/validate-ron --check-name-collisions <directory>"
|
|
echo ""
|
|
echo "Validates a RON file against the Rust struct schema."
|
|
echo "Schema types:"
|
|
echo " zone_type — ZoneTypeTemplate (D-142 zone-type template)"
|
|
echo " zone — ZoneSpec (legacy zone identity spec)"
|
|
echo " culture — CultureProfile (culture profile)"
|
|
echo ""
|
|
echo "Name collision check:"
|
|
echo " --check-name-collisions <dir> Scan culture-*.ron files for shared name pool entries"
|
|
exit 1
|
|
fi
|
|
|
|
# Check file exists before resolving — realpath gives unhelpful errors otherwise
|
|
if [ ! -f "$1" ]; then
|
|
echo "Error: file not found: $1"
|
|
exit 1
|
|
fi
|
|
|
|
FILE="$(realpath "$1")"
|
|
SCHEMA="$2"
|
|
|
|
cd "$(dirname "$0")/../server"
|
|
exec cargo run --quiet --bin validate_ron -- "$FILE" "$SCHEMA"
|