muse2/
commands.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::{ensure, Context, Result};
8use clap::{Parser, Subcommand};
9use include_dir::{include_dir, Dir, DirEntry};
10use std::fs;
11use std::path::{Path, PathBuf};
12use tempfile::TempDir;
13
14/// The directory containing the example models.
15pub const EXAMPLES_DIR: Dir = include_dir!("examples");
16
17/// The command line interface for the simulation.
18#[derive(Parser)]
19#[command(version, about)]
20pub struct Cli {
21    /// The available commands.
22    #[command(subcommand)]
23    pub command: Option<Commands>,
24    /// Flag to provide the CLI docs as markdown
25    #[arg(long, hide = true)]
26    pub markdown_help: bool,
27}
28
29/// The available commands.
30#[derive(Subcommand)]
31pub enum Commands {
32    /// Run a simulation model.
33    Run {
34        /// Path to the model directory.
35        model_dir: PathBuf,
36        /// Directory for output files
37        #[arg(short, long)]
38        output_dir: Option<PathBuf>,
39        /// Whether to write additional information to CSV files
40        #[arg(long)]
41        debug_model: bool,
42    },
43    /// Manage example models.
44    Example {
45        /// The available subcommands for managing example models.
46        #[command(subcommand)]
47        subcommand: ExampleSubcommands,
48    },
49}
50
51/// The available subcommands for managing example models.
52#[derive(Subcommand)]
53pub enum ExampleSubcommands {
54    /// List available examples.
55    List,
56    /// Extract an example model configuration to a new directory.
57    Extract {
58        /// The name of the example to extract.
59        name: String,
60        /// The destination folder for the example.
61        new_path: Option<PathBuf>,
62    },
63    /// Run an example.
64    Run {
65        /// The name of the example to run.
66        name: String,
67        /// Directory for output files
68        #[arg(short, long)]
69        output_dir: Option<PathBuf>,
70        /// Whether to write additional information to CSV files
71        #[arg(long)]
72        debug_model: bool,
73    },
74}
75
76/// Handle the `run` command.
77pub fn handle_run_command(
78    model_path: &Path,
79    output_path: Option<&Path>,
80    debug_model: bool,
81) -> Result<()> {
82    // Load program settings
83    let mut settings = Settings::load().context("Failed to load settings.")?;
84
85    // This setting can be overridden by command-line argument
86    if debug_model {
87        settings.debug_model = true;
88    }
89
90    // Create output folder
91    let output_path = match output_path {
92        Some(p) => p.to_owned(),
93        None => get_output_dir(model_path)?,
94    };
95    create_output_directory(&output_path).context("Failed to create output directory.")?;
96
97    // Initialise program logger
98    log::init(settings.log_level.as_deref(), &output_path)
99        .context("Failed to initialise logging.")?;
100
101    // Load the model to run
102    let (model, assets) = load_model(model_path).context("Failed to load model.")?;
103    info!("Loaded model from {}", model_path.display());
104    info!("Output data will be written to {}", output_path.display());
105
106    // Run the simulation
107    crate::simulation::run(model, assets, &output_path, settings.debug_model)?;
108
109    Ok(())
110}
111
112/// Handle the `example list` command.
113pub fn handle_example_list_command() {
114    for entry in EXAMPLES_DIR.dirs() {
115        println!("{}", entry.path().display());
116    }
117}
118
119/// Handle the `example extract` command
120pub fn handle_example_extract_command(name: &str, dest: Option<&Path>) -> Result<()> {
121    let dest = dest.unwrap_or(Path::new(name));
122    extract_example(name, dest)
123}
124
125/// Extract the specified example to a new directory
126fn extract_example(name: &str, new_path: &Path) -> Result<()> {
127    // Find the subdirectory in EXAMPLES_DIR whose name matches `name`.
128    let sub_dir = EXAMPLES_DIR.get_dir(name).context("Example not found.")?;
129
130    ensure!(
131        !new_path.exists(),
132        "Destination directory {} already exists",
133        new_path.display()
134    );
135
136    // Copy the contents of the subdirectory to the destination
137    fs::create_dir(new_path)?;
138    for entry in sub_dir.entries() {
139        match entry {
140            DirEntry::Dir(_) => panic!("Subdirectories in examples not supported"),
141            DirEntry::File(f) => {
142                let file_name = f.path().file_name().unwrap();
143                let file_path = new_path.join(file_name);
144                fs::write(&file_path, f.contents())?;
145            }
146        }
147    }
148
149    Ok(())
150}
151
152/// Handle the `example run` command.
153pub fn handle_example_run_command(
154    name: &str,
155    output_path: Option<&Path>,
156    debug_model: bool,
157) -> Result<()> {
158    let temp_dir = TempDir::new().context("Failed to create temporary directory.")?;
159    let model_path = temp_dir.path().join(name);
160    extract_example(name, &model_path)?;
161    handle_run_command(&model_path, output_path, debug_model)
162}