use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq)]
pub struct Args {
pub command: Command,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Command {
Run {
experiment_path: PathBuf,
seed_override: Option<u64>,
verbose: bool,
},
Render {
domain: String,
format: RenderFormat,
output: PathBuf,
fps: u32,
duration: f64,
seed: u64,
},
Validate {
experiment_path: PathBuf,
},
Verify {
experiment_path: PathBuf,
runs: usize,
},
EmcCheck {
experiment_path: PathBuf,
},
EmcValidate {
emc_path: PathBuf,
},
ListEmc,
Help,
Version,
Error(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RenderFormat {
SvgFrames,
SvgKeyframes,
}
impl Args {
#[must_use]
pub fn parse_from<I, S>(args: I) -> Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let args: Vec<String> = args.into_iter().map(|s| s.as_ref().to_string()).collect();
Self::parse_from_vec(&args)
}
#[must_use]
pub fn parse() -> Self {
Self::parse_from(std::env::args())
}
fn parse_from_vec(args: &[String]) -> Self {
if args.len() < 2 {
return Self {
command: Command::Help,
};
}
let command = match args[1].as_str() {
"run" => Self::parse_run_command(args),
"render" => Self::parse_render_command(args),
"validate" => Self::parse_validate_command(args),
"verify" => Self::parse_verify_command(args),
"emc-check" => Self::parse_emc_check_command(args),
"emc-validate" => Self::parse_emc_validate_command(args),
"list-emc" => Command::ListEmc,
"-h" | "--help" | "help" => Command::Help,
"-V" | "--version" | "version" => Command::Version,
unknown => Command::Error(format!("Unknown command: {unknown}")),
};
Self { command }
}
fn is_help_flag(arg: &str) -> bool {
arg == "--help" || arg == "-h"
}
fn parse_run_command(args: &[String]) -> Command {
if args.len() < 3 || Self::is_help_flag(&args[2]) {
return Command::Help;
}
let mut seed_override = None;
let mut verbose = false;
let mut i = 3;
while i < args.len() {
match args[i].as_str() {
"--seed" => {
if i + 1 < args.len() {
if let Ok(seed) = args[i + 1].parse() {
seed_override = Some(seed);
}
i += 2;
} else {
i += 1;
}
}
"-v" | "--verbose" => {
verbose = true;
i += 1;
}
_ => i += 1,
}
}
Command::Run {
experiment_path: PathBuf::from(&args[2]),
seed_override,
verbose,
}
}
fn parse_validate_command(args: &[String]) -> Command {
if args.len() < 3 || Self::is_help_flag(&args[2]) {
return Command::Help;
}
Command::Validate {
experiment_path: PathBuf::from(&args[2]),
}
}
fn parse_verify_command(args: &[String]) -> Command {
if args.len() < 3 || Self::is_help_flag(&args[2]) {
return Command::Help;
}
let mut runs = 3;
if args.len() > 3 && args[3] == "--runs" && args.len() > 4 {
if let Ok(n) = args[4].parse() {
runs = n;
}
}
Command::Verify {
experiment_path: PathBuf::from(&args[2]),
runs,
}
}
fn parse_emc_check_command(args: &[String]) -> Command {
if args.len() < 3 || Self::is_help_flag(&args[2]) {
return Command::Help;
}
Command::EmcCheck {
experiment_path: PathBuf::from(&args[2]),
}
}
fn parse_emc_validate_command(args: &[String]) -> Command {
if args.len() < 3 || Self::is_help_flag(&args[2]) {
return Command::Help;
}
Command::EmcValidate {
emc_path: PathBuf::from(&args[2]),
}
}
fn collect_flags(args: &[String], start: usize) -> std::collections::HashMap<String, String> {
let mut flags = std::collections::HashMap::new();
let mut i = start;
while i < args.len() {
if args[i].starts_with("--") && i + 1 < args.len() {
flags.insert(args[i].clone(), args[i + 1].clone());
i += 2;
} else {
i += 1;
}
}
flags
}
fn parse_render_command(args: &[String]) -> Command {
if args.len() >= 3 && Self::is_help_flag(&args[2]) {
return Command::Help;
}
let flags = Self::collect_flags(args, 2);
let domain = flags
.get("--domain")
.cloned()
.unwrap_or_else(|| "orbit".to_string());
let format = match flags.get("--format").map(String::as_str) {
Some("svg-frames") => RenderFormat::SvgFrames,
_ => RenderFormat::SvgKeyframes,
};
let output = flags
.get("--output")
.map_or_else(|| PathBuf::from("."), PathBuf::from);
let fps = flags
.get("--fps")
.and_then(|v| v.parse().ok())
.unwrap_or(60);
let duration = flags
.get("--duration")
.and_then(|v| v.parse().ok())
.unwrap_or(10.0);
let seed = flags
.get("--seed")
.and_then(|v| v.parse().ok())
.unwrap_or(42);
Command::Render {
domain,
format,
output,
fps,
duration,
seed,
}
}
}