muse2/cli/
example.rs

1//! Code related to the example models and the CLI commands for interacting with them.
2use super::handle_run_command;
3use crate::settings::Settings;
4use anyhow::{Context, Result, ensure};
5use clap::Subcommand;
6use include_dir::{Dir, DirEntry, include_dir};
7use std::fs;
8use std::path::{Path, PathBuf};
9use tempfile::TempDir;
10
11/// The directory containing the example models.
12const EXAMPLES_DIR: Dir = include_dir!("examples");
13
14/// The available subcommands for managing example models.
15#[derive(Subcommand)]
16pub enum ExampleSubcommands {
17    /// List available examples.
18    List,
19    /// Provide information about the specified example.
20    Info {
21        /// The name of the example.
22        name: String,
23    },
24    /// Extract an example model configuration to a new directory.
25    Extract {
26        /// The name of the example to extract.
27        name: String,
28        /// The destination folder for the example.
29        new_path: Option<PathBuf>,
30    },
31    /// Run an example.
32    Run {
33        /// The name of the example to run.
34        name: String,
35        /// Directory for output files
36        #[arg(short, long)]
37        output_dir: Option<PathBuf>,
38        /// Whether to write additional information to CSV files
39        #[arg(long)]
40        debug_model: bool,
41    },
42}
43
44impl ExampleSubcommands {
45    /// Execute the supplied example subcommand
46    pub fn execute(self) -> Result<()> {
47        match self {
48            Self::List => handle_example_list_command(),
49            Self::Info { name } => handle_example_info_command(&name)?,
50            Self::Extract {
51                name,
52                new_path: dest,
53            } => handle_example_extract_command(&name, dest.as_deref())?,
54            Self::Run {
55                name,
56                output_dir,
57                debug_model,
58            } => handle_example_run_command(&name, output_dir.as_deref(), debug_model, None)?,
59        }
60
61        Ok(())
62    }
63}
64
65/// Handle the `example list` command.
66fn handle_example_list_command() {
67    for entry in EXAMPLES_DIR.dirs() {
68        println!("{}", entry.path().display());
69    }
70}
71
72/// Handle the `example info` command.
73fn handle_example_info_command(name: &str) -> Result<()> {
74    let path: PathBuf = [name, "README.txt"].iter().collect();
75    let readme = EXAMPLES_DIR
76        .get_file(path)
77        .context("Example not found.")?
78        .contents_utf8()
79        .expect("README.txt is not UTF-8 encoded");
80
81    println!("{}", readme);
82
83    Ok(())
84}
85
86/// Handle the `example extract` command
87fn handle_example_extract_command(name: &str, dest: Option<&Path>) -> Result<()> {
88    let dest = dest.unwrap_or(Path::new(name));
89    extract_example(name, dest)
90}
91
92/// Extract the specified example to a new directory
93fn extract_example(name: &str, new_path: &Path) -> Result<()> {
94    // Find the subdirectory in EXAMPLES_DIR whose name matches `name`.
95    let sub_dir = EXAMPLES_DIR.get_dir(name).context("Example not found.")?;
96
97    ensure!(
98        !new_path.exists(),
99        "Destination directory {} already exists",
100        new_path.display()
101    );
102
103    // Copy the contents of the subdirectory to the destination
104    fs::create_dir(new_path)?;
105    for entry in sub_dir.entries() {
106        match entry {
107            DirEntry::Dir(_) => panic!("Subdirectories in examples not supported"),
108            DirEntry::File(f) => {
109                let file_name = f.path().file_name().unwrap();
110                let file_path = new_path.join(file_name);
111                fs::write(&file_path, f.contents())?;
112            }
113        }
114    }
115
116    Ok(())
117}
118
119/// Handle the `example run` command.
120pub fn handle_example_run_command(
121    name: &str,
122    output_path: Option<&Path>,
123    debug_model: bool,
124    settings: Option<Settings>,
125) -> Result<()> {
126    let temp_dir = TempDir::new().context("Failed to create temporary directory.")?;
127    let model_path = temp_dir.path().join(name);
128    extract_example(name, &model_path)?;
129    handle_run_command(&model_path, output_path, debug_model, settings)
130}