muse2/
example.rs

1//! Code for working with example models
2use std::fs;
3use std::path::Path;
4
5use anyhow::{Context, Result};
6use include_dir::{Dir, DirEntry, include_dir};
7
8pub mod patches;
9
10/// The directory containing the example models.
11const EXAMPLES_DIR: Dir = include_dir!("examples");
12
13/// Get the names of all examples
14pub fn get_example_names() -> impl Iterator<Item = &'static str> {
15    EXAMPLES_DIR.dirs().map(|dir| {
16        dir.path()
17            .as_os_str()
18            .to_str()
19            .expect("Invalid unicode in path")
20    })
21}
22
23/// A bundled example model
24pub struct Example(Dir<'static>);
25
26impl Example {
27    /// Get the example with the specified name
28    pub fn from_name(name: &str) -> Result<Self> {
29        let dir = EXAMPLES_DIR
30            .get_dir(name)
31            .with_context(|| format!("Example '{name}' not found"))?;
32
33        Ok(Self(dir.clone()))
34    }
35
36    /// Get the contents of the readme file for this example
37    pub fn get_readme(&self) -> Result<&'static str> {
38        self.0
39            .get_file(self.0.path().join("README.txt"))
40            .context("Missing file")?
41            .contents_utf8()
42            .context("File not UTF-8 encoded")
43    }
44
45    /// Extract this example to a specified destination.
46    ///
47    /// Returns an error if the destination directory already exists or copying the files fails.
48    pub fn extract(&self, new_path: &Path) -> Result<()> {
49        // Copy the contents of the subdirectory to the destination
50        fs::create_dir(new_path)?;
51        for entry in self.0.entries() {
52            match entry {
53                DirEntry::Dir(_) => panic!("Subdirectories in examples not supported"),
54                DirEntry::File(f) => {
55                    let file_name = f.path().file_name().unwrap();
56                    let file_path = new_path.join(file_name);
57                    fs::write(&file_path, f.contents())?;
58                }
59            }
60        }
61
62        Ok(())
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn all_examples_have_readme() {
72        for example in get_example_names() {
73            let readme = Example::from_name(example)
74                .unwrap()
75                .get_readme()
76                .with_context(|| format!("Could not load readme for {example}"))
77                .unwrap();
78
79            assert!(!readme.trim().is_empty());
80        }
81    }
82}