Files
settled-reach/tooling/validate-ron
T
jpmschweitzer 06d9abbc63 Merge remote-tracking branch 'origin/copy'
# 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
2026-03-17 12:31:38 +01:00

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"