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::example::patches::{get_patch_names, get_patches};
4use crate::example::{Example, get_example_names};
5use crate::patch::ModelPatch;
6use anyhow::{Context, Result};
7use clap::Subcommand;
8use std::fs;
9use std::path::{Path, PathBuf};
10use tempfile::TempDir;
11
12/// The available subcommands for managing example models.
13#[derive(Subcommand)]
14pub enum ExampleSubcommands {
15    /// List available examples.
16    List {
17        /// Whether to list patched models.
18        #[arg(long, hide = true)]
19        patch: bool,
20    },
21    /// Provide information about the specified example.
22    Info {
23        /// The name of the example.
24        name: String,
25    },
26    /// Extract an example model configuration to a new directory.
27    Extract {
28        /// The name of the example to extract.
29        name: String,
30        /// The destination folder for the example.
31        new_path: Option<PathBuf>,
32        /// Whether the model to extract is a patched model.
33        #[arg(long, hide = true)]
34        patch: bool,
35    },
36    /// Run an example.
37    Run {
38        /// The name of the example to run.
39        name: String,
40        /// Whether the model to run is a patched model.
41        #[arg(long, hide = true)]
42        patch: bool,
43        /// Other run options
44        #[command(flatten)]
45        opts: RunOpts,
46    },
47}
48
49impl ExampleSubcommands {
50    /// Execute the supplied example subcommand
51    pub fn execute(self) -> Result<()> {
52        match self {
53            Self::List { patch } => handle_example_list_command(patch),
54            Self::Info { name } => handle_example_info_command(&name)?,
55            Self::Extract {
56                name,
57                patch,
58                new_path,
59            } => handle_example_extract_command(&name, new_path.as_deref(), patch)?,
60            Self::Run { name, patch, opts } => {
61                handle_example_run_command(&name, patch, &opts)?;
62            }
63        }
64
65        Ok(())
66    }
67}
68
69/// Handle the `example list` command.
70fn handle_example_list_command(patch: bool) {
71    if patch {
72        for name in get_patch_names() {
73            println!("{name}");
74        }
75    } else {
76        for name in get_example_names() {
77            println!("{name}");
78        }
79    }
80}
81
82/// Handle the `example info` command.
83fn handle_example_info_command(name: &str) -> Result<()> {
84    // If we can't load it, it's a bug, hence why we panic
85    let info = Example::from_name(name)?
86        .get_readme()
87        .unwrap_or_else(|_| panic!("Could not load README.txt for '{name}' example"));
88    print!("{info}");
89
90    Ok(())
91}
92
93/// Handle the `example extract` command
94fn handle_example_extract_command(name: &str, dest: Option<&Path>, patch: bool) -> Result<()> {
95    extract_example(name, patch, dest.unwrap_or(Path::new(name)))
96}
97
98/// Extract the specified example to a new directory.
99///
100/// If `patch` is `true`, then the corresponding patched example will be extracted.
101fn extract_example(name: &str, patch: bool, dest: &Path) -> Result<()> {
102    if patch {
103        let patches = get_patches(name)?;
104
105        // NB: All patched models are based on `simple`, for now
106        let example = Example::from_name("simple").unwrap();
107
108        // First extract the example to a temp dir
109        let example_tmp = TempDir::new().context("Failed to create temporary directory")?;
110        let example_path = example_tmp.path().join(name);
111        example
112            .extract(&example_path)
113            .context("Could not extract example")?;
114
115        // Patch example and put contents in dest
116        fs::create_dir(dest).context("Could not create output directory")?;
117        ModelPatch::new(example_path)
118            .with_file_patches(patches.to_owned())
119            .build(dest)
120            .context("Failed to patch example")
121    } else {
122        // Otherwise it's just a regular example
123        let example = Example::from_name(name)?;
124        example.extract(dest)
125    }
126}
127
128/// Handle the `example run` command.
129pub fn handle_example_run_command(name: &str, patch: bool, opts: &RunOpts) -> Result<()> {
130    let temp_dir = TempDir::new().context("Failed to create temporary directory")?;
131    let model_path = temp_dir.path().join(name);
132    extract_example(name, patch, &model_path)?;
133    handle_run_command(&model_path, opts)
134}