use anyhow::Result;
use clap::{Parser, Subcommand};
use std::path::PathBuf;
mod schema;
mod selector;
mod util;
use schema::Blueprint;
use selector::StackSelector;
#[derive(Parser)]
#[command(name = "runeforge")]
#[command(about = "Blueprint to optimal stack JSON converter")]
#[command(version = "3.0.0")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Plan {
#[arg(short = 'f', long = "file")]
file: PathBuf,
#[arg(long = "seed")]
seed: Option<u64>,
#[arg(short = 'o', long = "out")]
out: Option<PathBuf>,
#[arg(long = "strict")]
strict: bool,
},
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Plan { file, seed, out, strict } => {
handle_plan_command(file, seed, out, strict)
}
}
}
fn handle_plan_command(
file: PathBuf,
seed: Option<u64>,
out: Option<PathBuf>,
strict: bool,
) -> Result<()> {
let blueprint = load_blueprint(&file, strict)?;
let mut selector = StackSelector::new(seed.unwrap_or(42));
let stack = selector.select_stack(&blueprint)?;
if strict {
schema::validate_stack_schema(&stack)?;
}
let json_output = serde_json::to_string_pretty(&stack)?;
match out {
Some(path) => {
std::fs::write(path, json_output)?;
}
None => {
println!("{}", json_output);
}
}
Ok(())
}
fn load_blueprint(file: &PathBuf, strict: bool) -> Result<Blueprint> {
let content = std::fs::read_to_string(file)?;
let blueprint: Blueprint = if file.extension().and_then(|s| s.to_str()) == Some("yaml")
|| file.extension().and_then(|s| s.to_str()) == Some("yml") {
serde_yaml::from_str(&content)?
} else {
serde_json::from_str(&content)?
};
if strict {
schema::validate_blueprint_schema(&blueprint)?;
}
Ok(blueprint)
}