Skip to main content

simular/cli/
args.rs

1//! CLI argument parsing.
2//!
3//! The accepted grammar is **declarative**: it is the `Cli` and `Commands`
4//! types below, parsed by clap derive. Nothing in this module inspects `argv`
5//! by hand.
6//!
7//! ## Why declarative
8//!
9//! This module previously hand-rolled a `match args[1]` parser, and every one of
10//! its failure modes was silent — the command exited 0 and did the wrong thing:
11//!
12//! - `--seed notanumber` used `.parse().ok().unwrap_or(default)`, so a typo
13//!   became the DEFAULT seed rather than an error. A simulation is only
14//!   reproducible if the seed it reports is the seed you asked for.
15//! - `--seed` with no value was discarded by the `else { i += 1 }` arm.
16//! - An unknown flag fell through a `_ => i += 1` catch-all and vanished.
17//! - `verify --runs N` was only honoured when `--runs` sat at exactly `argv[3]`.
18//!
19//! clap rejects all four. The grammar is data, not control flow, so it cannot
20//! drift out of sync with itself the way the hand-rolled arms did. Enforced
21//! repo-wide by `scripts/check_no_hand_rolled_parsers.sh`.
22
23use clap::{Parser, Subcommand, ValueEnum};
24use std::path::PathBuf;
25
26/// CLI arguments container.
27#[derive(Debug, Clone, PartialEq, Parser)]
28#[command(
29    name = "simular",
30    version,
31    about = "Unified Simulation Engine for the Sovereign AI Stack",
32    // `help` and `version` are real subcommands below, so that `simular help`
33    // keeps printing simular's own help text (see `output::print_help`) rather
34    // than clap's auto-generated one. The `-h`/`--help` and `-V`/`--version`
35    // FLAGS are still clap's.
36    disable_help_subcommand = true
37)]
38pub struct Cli {
39    /// The command to execute. `None` means no subcommand was given at all.
40    #[command(subcommand)]
41    pub command: Option<Commands>,
42}
43
44impl Cli {
45    /// The selected subcommand, defaulting to [`Commands::Help`].
46    ///
47    /// An empty `argv` showed the help text before the clap conversion; that
48    /// behaviour (help on stdout, exit 0) is preserved here rather than in
49    /// clap's `arg_required_else_help`, which would exit 2 instead.
50    #[must_use]
51    pub fn into_command(self) -> Commands {
52        self.command.unwrap_or(Commands::Help)
53    }
54}
55
56/// Available CLI commands.
57#[derive(Debug, Clone, PartialEq, Subcommand)]
58pub enum Commands {
59    /// Run an experiment
60    Run {
61        /// Path to the experiment YAML file.
62        experiment_path: PathBuf,
63        /// Optional seed override.
64        #[arg(long = "seed", value_name = "N")]
65        seed_override: Option<u64>,
66        /// Enable verbose output.
67        #[arg(short = 'v', long)]
68        verbose: bool,
69    },
70    /// Render simulation to SVG + keyframes
71    Render {
72        /// Simulation domain (orbit, `bouncing_balls`).
73        #[arg(long, default_value = "orbit")]
74        domain: String,
75        /// Output format: svg-frames or svg-keyframes.
76        #[arg(long, value_enum, default_value = "svg-keyframes")]
77        format: RenderFormat,
78        /// Output directory.
79        #[arg(long, default_value = ".")]
80        output: PathBuf,
81        /// Frames per second.
82        #[arg(long, default_value_t = 60)]
83        fps: u32,
84        /// Simulation duration in seconds.
85        #[arg(long, default_value_t = 10.0)]
86        duration: f64,
87        /// Random seed for deterministic output.
88        #[arg(long, default_value_t = 42)]
89        seed: u64,
90    },
91    /// Validate experiment YAML against EDD v2 schema
92    Validate {
93        /// Path to the experiment YAML file.
94        experiment_path: PathBuf,
95    },
96    /// Verify reproducibility of an experiment
97    Verify {
98        /// Path to the experiment YAML file.
99        experiment_path: PathBuf,
100        /// Number of verification runs.
101        #[arg(long, default_value_t = 3, value_name = "N")]
102        runs: usize,
103    },
104    /// Check EMC compliance
105    EmcCheck {
106        /// Path to the experiment YAML file.
107        experiment_path: PathBuf,
108    },
109    /// Validate an EMC YAML file against EDD v2 EMC schema
110    EmcValidate {
111        /// Path to the EMC file.
112        emc_path: PathBuf,
113    },
114    /// List available EMCs in the library
115    ListEmc,
116    /// Show help
117    Help,
118    /// Show version
119    Version,
120}
121
122/// SVG render output format.
123#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)]
124pub enum RenderFormat {
125    /// One SVG file per frame.
126    SvgFrames,
127    /// One template SVG + keyframes JSON.
128    SvgKeyframes,
129}