inauguration 0.1.1

Swift developer toolchain CLI (hybrid compiler pipeline, staging, plugins)
Documentation
//! Regenerate `PatchType` Rust + Swift enums from `shared/protocol/events.schema.json`.

use serde_json::Value;
use std::fs;
use std::path::{Path, PathBuf};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = workspace_root()?;
    let schema_path = root.join("shared/protocol/events.schema.json");
    let schema_raw = fs::read_to_string(&schema_path)?;
    let schema: Value = serde_json::from_str(&schema_raw)?;
    let values = parse_patch_types(&schema)?;

    let rust_out = root.join("in-cli/src/hotreload/generated_protocol.rs");
    let swift_out =
        root.join("runtime/swift-preview-host/Sources/SwiftPreviewHost/GeneratedProtocol.swift");

    write_rust(&rust_out, &values)?;
    write_swift(&swift_out, &values)?;

    println!("generated: {}", rust_out.display());
    println!("generated: {}", swift_out.display());
    Ok(())
}

fn workspace_root() -> Result<PathBuf, Box<dyn std::error::Error>> {
    if let Some(arg) = std::env::args().nth(1) {
        return Ok(PathBuf::from(arg));
    }
    let mut dir = std::env::current_dir()?;
    loop {
        if dir.join("shared/protocol/events.schema.json").exists() {
            return Ok(dir);
        }
        if !dir.pop() {
            break;
        }
    }
    Err(
        "could not find repository root containing shared/protocol/events.schema.json (pass root as argv[1])"
            .into(),
    )
}

fn parse_patch_types(schema: &Value) -> Result<Vec<String>, Box<dyn std::error::Error>> {
    let arr = schema
        .pointer("/$defs/ReloadPatch/properties/patch_type/enum")
        .and_then(|v| v.as_array())
        .ok_or("schema missing $defs.ReloadPatch.properties.patch_type.enum")?;
    let mut out = Vec::new();
    for item in arr {
        let s = item
            .as_str()
            .ok_or("patch_type enum values must be strings")?;
        out.push(s.to_string());
    }
    if out.is_empty() {
        return Err("patch_type enum is empty".into());
    }
    Ok(out)
}

fn snake_to_pascal(value: &str) -> String {
    value
        .split('_')
        .filter(|p| !p.is_empty())
        .map(|part| {
            let mut c = part.chars();
            match c.next() {
                None => String::new(),
                Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
            }
        })
        .collect()
}

fn snake_to_camel(value: &str) -> String {
    let pascal = snake_to_pascal(value);
    let mut ch = pascal.chars();
    match ch.next() {
        None => String::new(),
        Some(f) => f.to_lowercase().collect::<String>() + ch.as_str(),
    }
}

fn write_rust(out_path: &Path, values: &[String]) -> Result<(), Box<dyn std::error::Error>> {
    let mut lines: Vec<String> = Vec::new();
    lines.push("// AUTO-GENERATED by inauguration protocol-gen (Rust)".to_string());
    lines.push("// Source: shared/protocol/events.schema.json".to_string());
    lines.push(String::new());
    lines.push(
        "#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]".to_string(),
    );
    lines.push("#[serde(rename_all = \"snake_case\")]".to_string());
    lines.push("pub enum PatchType {".to_string());
    for v in values {
        lines.push(format!("    {},", snake_to_pascal(v)));
    }
    lines.push("}".to_string());
    lines.push(String::new());
    fs::write(out_path, lines.join("\n"))?;
    Ok(())
}

fn write_swift(out_path: &Path, values: &[String]) -> Result<(), Box<dyn std::error::Error>> {
    let mut lines: Vec<String> = Vec::new();
    lines.push("// AUTO-GENERATED by inauguration protocol-gen (Rust)".to_string());
    lines.push("// Source: shared/protocol/events.schema.json".to_string());
    lines.push(String::new());
    lines.push("public enum GeneratedWirePatchType: String, Codable, Sendable {".to_string());
    for v in values {
        lines.push(format!("    case {} = \"{}\"", snake_to_camel(v), v));
    }
    lines.push("}".to_string());
    lines.push(String::new());
    fs::write(out_path, lines.join("\n"))?;
    Ok(())
}