muse2/cli/
example.rs

1//! Code related to the example models and the CLI commands for interacting with them.
2use super::{RunOpts, 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        /// Other run options
36        #[command(flatten)]
37        opts: RunOpts,
38    },
39}
40
41impl ExampleSubcommands {
42    /// Execute the supplied example subcommand
43    pub fn execute(self) -> Result<()> {
44        match self {
45            Self::List => handle_example_list_command(),
46            Self::Info { name } => handle_example_info_command(&name)?,
47            Self::Extract {
48                name,
49                new_path: dest,
50            } => handle_example_extract_command(&name, dest.as_deref())?,
51            Self::Run { name, opts } => handle_example_run_command(&name, &opts, None)?,
52        }
53
54        Ok(())
55    }
56}
57
58/// Handle the `example list` command.
59fn handle_example_list_command() {
60    for entry in EXAMPLES_DIR.dirs() {
61        println!("{}", entry.path().display());
62    }
63}
64
65/// Handle the `example info` command.
66fn handle_example_info_command(name: &str) -> Result<()> {
67    let path: PathBuf = [name, "README.txt"].iter().collect();
68    let readme = EXAMPLES_DIR
69        .get_file(path)
70        .context("Example not found.")?
71        .contents_utf8()
72        .expect("README.txt is not UTF-8 encoded");
73
74    print!("{readme}");
75
76    Ok(())
77}
78
79/// Handle the `example extract` command
80fn handle_example_extract_command(name: &str, dest: Option<&Path>) -> Result<()> {
81    let dest = dest.unwrap_or(Path::new(name));
82    extract_example(name, dest)
83}
84
85/// Extract the specified example to a new directory
86fn extract_example(name: &str, new_path: &Path) -> Result<()> {
87    // Find the subdirectory in EXAMPLES_DIR whose name matches `name`.
88    let sub_dir = EXAMPLES_DIR.get_dir(name).context("Example not found.")?;
89
90    ensure!(
91        !new_path.exists(),
92        "Destination directory {} already exists",
93        new_path.display()
94    );
95
96    // Copy the contents of the subdirectory to the destination
97    fs::create_dir(new_path)?;
98    for entry in sub_dir.entries() {
99        match entry {
100            DirEntry::Dir(_) => panic!("Subdirectories in examples not supported"),
101            DirEntry::File(f) => {
102                let file_name = f.path().file_name().unwrap();
103                let file_path = new_path.join(file_name);
104                fs::write(&file_path, f.contents())?;
105            }
106        }
107    }
108
109    Ok(())
110}
111
112/// Handle the `example run` command.
113pub fn handle_example_run_command(
114    name: &str,
115    opts: &RunOpts,
116    settings: Option<Settings>,
117) -> Result<()> {
118    let temp_dir = TempDir::new().context("Failed to create temporary directory.")?;
119    let model_path = temp_dir.path().join(name);
120    extract_example(name, &model_path)?;
121    handle_run_command(&model_path, opts, settings)
122}