120 lines
3.3 KiB
Python
120 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
validate_rooms.py — Fleet palace taxonomy validator.
|
|
|
|
Checks a wizard's mempalace.yaml against the fleet standard in rooms.yaml.
|
|
Exits 0 if valid, 1 if core rooms are missing or the config is malformed.
|
|
|
|
Usage:
|
|
python mempalace/validate_rooms.py <wizard_mempalace.yaml>
|
|
python mempalace/validate_rooms.py /root/wizards/bezalel/mempalace.yaml
|
|
|
|
Refs: #1082, #1075
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
try:
|
|
import yaml
|
|
except ImportError:
|
|
print("ERROR: PyYAML is required. Install with: pip install pyyaml", file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
FLEET_STANDARD = Path(__file__).parent / "rooms.yaml"
|
|
|
|
|
|
def load_yaml(path: Path) -> dict[str, Any]:
|
|
with path.open() as fh:
|
|
return yaml.safe_load(fh) or {}
|
|
|
|
|
|
def get_core_room_keys(standard: dict[str, Any]) -> list[str]:
|
|
return [r["key"] for r in standard.get("core_rooms", [])]
|
|
|
|
|
|
def get_wizard_room_keys(config: dict[str, Any]) -> list[str]:
|
|
"""Extract room keys from a wizard's mempalace.yaml.
|
|
|
|
Supports two common shapes:
|
|
rooms:
|
|
- key: forge
|
|
- key: hermes
|
|
or:
|
|
rooms:
|
|
forge: ...
|
|
hermes: ...
|
|
"""
|
|
rooms_field = config.get("rooms", {})
|
|
if isinstance(rooms_field, list):
|
|
return [r["key"] for r in rooms_field if isinstance(r, dict) and "key" in r]
|
|
if isinstance(rooms_field, dict):
|
|
return list(rooms_field.keys())
|
|
return []
|
|
|
|
|
|
def validate(wizard_config_path: Path, standard_path: Path = FLEET_STANDARD) -> list[str]:
|
|
"""Return a list of validation errors. Empty list means valid."""
|
|
errors: list[str] = []
|
|
|
|
if not standard_path.exists():
|
|
errors.append(f"Fleet standard not found: {standard_path}")
|
|
return errors
|
|
|
|
if not wizard_config_path.exists():
|
|
errors.append(f"Wizard config not found: {wizard_config_path}")
|
|
return errors
|
|
|
|
standard = load_yaml(standard_path)
|
|
config = load_yaml(wizard_config_path)
|
|
|
|
core_keys = get_core_room_keys(standard)
|
|
wizard_keys = get_wizard_room_keys(config)
|
|
|
|
missing = [k for k in core_keys if k not in wizard_keys]
|
|
for key in missing:
|
|
errors.append(f"Missing required core room: '{key}'")
|
|
|
|
return errors
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(
|
|
description="Validate a wizard's mempalace.yaml against the fleet room standard."
|
|
)
|
|
parser.add_argument(
|
|
"config",
|
|
metavar="mempalace.yaml",
|
|
help="Path to the wizard's mempalace.yaml",
|
|
)
|
|
parser.add_argument(
|
|
"--standard",
|
|
default=str(FLEET_STANDARD),
|
|
metavar="rooms.yaml",
|
|
help="Path to the fleet rooms.yaml standard (default: mempalace/rooms.yaml)",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
|
|
wizard_path = Path(args.config)
|
|
standard_path = Path(args.standard)
|
|
|
|
errors = validate(wizard_path, standard_path)
|
|
|
|
if errors:
|
|
print(f"[validate_rooms] FAIL: {wizard_path}", file=sys.stderr)
|
|
for err in errors:
|
|
print(f" ✗ {err}", file=sys.stderr)
|
|
return 1
|
|
|
|
core_count = len(get_core_room_keys(load_yaml(standard_path)))
|
|
print(f"[validate_rooms] OK: {wizard_path} — all {core_count} core rooms present.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|