Pre-existing formatting issues in generator_spike.rs and validate_ron.rs caught by the new pre-push lint hook. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
165 lines
5.9 KiB
Rust
165 lines
5.9 KiB
Rust
//! RON content validator CLI (#611).
|
|
//!
|
|
//! Deserializes a RON file into the actual Rust structs and prints errors.
|
|
//! This is the copy team's lint tool — run it to check RON files without
|
|
//! needing to compile the full server.
|
|
//!
|
|
//! # Usage
|
|
//!
|
|
//! ```sh
|
|
//! # Via wrapper script (recommended):
|
|
//! tooling/validate-ron server/content/global/zone-identity-spec.example.ron zone
|
|
//! tooling/validate-ron server/content/global/culture-van-maanens-star.example.ron culture
|
|
//!
|
|
//! # Direct:
|
|
//! cargo run --bin validate_ron -- <file.ron> <zone|culture>
|
|
//! ```
|
|
|
|
use std::process;
|
|
|
|
use clap::Parser;
|
|
|
|
use settled_reach_server::npc::blueprint::{CultureProfile, ZoneSpec, ZoneTypeTemplate};
|
|
|
|
#[derive(Parser)]
|
|
#[command(
|
|
name = "validate_ron",
|
|
about = "Validate RON content files against Rust struct schemas"
|
|
)]
|
|
struct Args {
|
|
/// Path to the RON file to validate.
|
|
file: String,
|
|
/// Schema type: "zone" (ZoneSpec), "zone_type" (ZoneTypeTemplate), or "culture" (CultureProfile).
|
|
schema: String,
|
|
}
|
|
|
|
fn main() {
|
|
let args = Args::parse();
|
|
|
|
let content = match std::fs::read_to_string(&args.file) {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
eprintln!("Error reading {}: {}", args.file, e);
|
|
process::exit(1);
|
|
}
|
|
};
|
|
|
|
match args.schema.as_str() {
|
|
"zone" => match ron::from_str::<ZoneSpec>(&content) {
|
|
Ok(spec) => {
|
|
println!("Valid ZoneSpec: {} ({})", spec.label, spec.zone_type);
|
|
println!(
|
|
" {} roles, {} social sites",
|
|
spec.roles.len(),
|
|
spec.social_sites.len()
|
|
);
|
|
let mut warnings = 0;
|
|
if spec.roles.is_empty() {
|
|
eprintln!(" WARNING: no roles defined — generator will reject this");
|
|
warnings += 1;
|
|
}
|
|
if spec.population_density < 1 {
|
|
eprintln!(" WARNING: population_density is 0 — generator requires >= 1");
|
|
warnings += 1;
|
|
}
|
|
for site in &spec.social_sites {
|
|
if site.min_npcs > site.max_npcs {
|
|
eprintln!(
|
|
" WARNING: social site '{}' has min_npcs ({}) > max_npcs ({})",
|
|
site.site_type, site.min_npcs, site.max_npcs
|
|
);
|
|
warnings += 1;
|
|
}
|
|
}
|
|
if warnings > 0 {
|
|
process::exit(1);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Invalid ZoneSpec in {}:", args.file);
|
|
eprintln!(" {}", e);
|
|
process::exit(1);
|
|
}
|
|
},
|
|
"culture" => match ron::from_str::<CultureProfile>(&content) {
|
|
Ok(profile) => {
|
|
println!("Valid CultureProfile: {} ({})", profile.name, profile.id);
|
|
println!(
|
|
" {} given names, {} family names",
|
|
profile.naming.given_names.len(),
|
|
profile.naming.family_names.len()
|
|
);
|
|
println!(
|
|
" {} filler words, {} favored traits",
|
|
profile.speech.filler_words.len(),
|
|
profile.values.favored_traits.len()
|
|
);
|
|
let mut warnings = 0;
|
|
if profile.naming.given_names.is_empty() {
|
|
eprintln!(" WARNING: no given_names — generator will reject this");
|
|
warnings += 1;
|
|
}
|
|
if warnings > 0 {
|
|
process::exit(1);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Invalid CultureProfile in {}:", args.file);
|
|
eprintln!(" {}", e);
|
|
process::exit(1);
|
|
}
|
|
},
|
|
"zone_type" => match ron::from_str::<ZoneTypeTemplate>(&content) {
|
|
Ok(template) => {
|
|
println!(
|
|
"Valid ZoneTypeTemplate: {} ({})",
|
|
template.label, template.id
|
|
);
|
|
println!(
|
|
" {} roles, {} social site types",
|
|
template.roles.len(),
|
|
template.social_site_types.len()
|
|
);
|
|
let mut warnings = 0;
|
|
if template.roles.is_empty() {
|
|
eprintln!(" WARNING: no roles defined — generator will reject this");
|
|
warnings += 1;
|
|
}
|
|
let role_ids: Vec<&str> = template.roles.iter().map(|r| r.id.as_str()).collect();
|
|
for role in &template.roles {
|
|
if role.behavior_primitives.is_empty() {
|
|
eprintln!(" WARNING: role '{}' has no behavior_primitives", role.id);
|
|
warnings += 1;
|
|
}
|
|
}
|
|
for site in &template.social_site_types {
|
|
for eligible in &site.eligible_roles {
|
|
if !role_ids.contains(&eligible.as_str()) {
|
|
eprintln!(
|
|
" WARNING: social site '{}' references eligible_role '{}' which is not a defined role",
|
|
site.site_type, eligible
|
|
);
|
|
warnings += 1;
|
|
}
|
|
}
|
|
}
|
|
if warnings > 0 {
|
|
process::exit(1);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Invalid ZoneTypeTemplate in {}:", args.file);
|
|
eprintln!(" {}", e);
|
|
process::exit(1);
|
|
}
|
|
},
|
|
other => {
|
|
eprintln!(
|
|
"Unknown schema type: '{}'. Use 'zone', 'zone_type', or 'culture'.",
|
|
other
|
|
);
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|