muse2/
cli.rs

1//! The command line interface for the simulation.
2use crate::input::load_model;
3use crate::log;
4use crate::output::{create_output_directory, get_output_dir};
5use crate::settings::Settings;
6use ::log::info;
7use anyhow::{Context, Result};
8use clap::{CommandFactory, Parser, Subcommand};
9use std::path::{Path, PathBuf};
10
11pub mod example;
12use example::ExampleSubcommands;
13
14/// The command line interface for the simulation.
15#[derive(Parser)]
16#[command(version, about)]
17struct Cli {
18    /// The available commands.
19    #[command(subcommand)]
20    command: Option<Commands>,
21    /// Flag to provide the CLI docs as markdown
22    #[arg(long, hide = true)]
23    markdown_help: bool,
24}
25
26/// The available commands.
27#[derive(Subcommand)]
28enum Commands {
29    /// Run a simulation model.
30    Run {
31        /// Path to the model directory.
32        model_dir: PathBuf,
33        /// Directory for output files
34        #[arg(short, long)]
35        output_dir: Option<PathBuf>,
36        /// Whether to write additional information to CSV files
37        #[arg(long)]
38        debug_model: bool,
39    },
40    /// Manage example models.
41    Example {
42        /// The available subcommands for managing example models.
43        #[command(subcommand)]
44        subcommand: ExampleSubcommands,
45    },
46}
47
48impl Commands {
49    /// Execute the supplied CLI command
50    fn execute(self) -> Result<()> {
51        match self {
52            Self::Run {
53                model_dir,
54                output_dir,
55                debug_model,
56            } => handle_run_command(&model_dir, output_dir.as_deref(), debug_model, None),
57            Self::Example { subcommand } => subcommand.execute(),
58        }
59    }
60}
61
62/// Parse CLI arguments and start MUSE2
63pub fn run_cli() -> Result<()> {
64    let cli = Cli::parse();
65
66    // Invoked as: `$ muse2 --markdown-help`
67    if cli.markdown_help {
68        clap_markdown::print_help_markdown::<Cli>();
69        return Ok(());
70    }
71
72    let Some(command) = cli.command else {
73        // Output program help in markdown format
74        let help_str = Cli::command().render_long_help().to_string();
75        println!("{help_str}");
76        return Ok(());
77    };
78
79    command.execute()
80}
81
82/// Handle the `run` command.
83pub fn handle_run_command(
84    model_path: &Path,
85    output_path: Option<&Path>,
86    debug_model: bool,
87    settings: Option<Settings>,
88) -> Result<()> {
89    // Load program settings, if not provided
90    let mut settings = if let Some(settings) = settings {
91        settings
92    } else {
93        Settings::load().context("Failed to load settings.")?
94    };
95
96    // This setting can be overridden by command-line argument
97    if debug_model {
98        settings.debug_model = true;
99    }
100
101    // Create output folder
102    let output_path = match output_path {
103        Some(p) => p.to_owned(),
104        None => get_output_dir(model_path)?,
105    };
106    create_output_directory(&output_path).context("Failed to create output directory.")?;
107
108    // Initialise program logger
109    log::init(settings.log_level.as_deref(), &output_path)
110        .context("Failed to initialise logging.")?;
111
112    // Load the model to run
113    let (model, assets) = load_model(model_path).context("Failed to load model.")?;
114    info!("Loaded model from {}", model_path.display());
115    info!("Output data will be written to {}", output_path.display());
116
117    // Run the simulation
118    crate::simulation::run(model, assets, &output_path, settings.debug_model)?;
119
120    Ok(())
121}