plottery_project/
cargo_project_template.rs

1use anyhow::Result;
2use path_absolutize::Absolutize;
3use rust_embed::RustEmbed;
4use std::path::{Path, PathBuf};
5use std::vec;
6use std::{fs::File, io::Write};
7use vfs::{EmbeddedFS, FileSystem};
8
9#[derive(RustEmbed, Debug)]
10#[folder = "cargo_project_template/"]
11struct CargoProjectTemplate;
12
13fn get_fs() -> EmbeddedFS<CargoProjectTemplate> {
14    EmbeddedFS::<CargoProjectTemplate>::new()
15}
16
17#[derive(Debug, Clone)]
18pub enum LibSource {
19    PlotteryHome,
20    Path { path: PathBuf },
21    Cargo,
22}
23
24struct Replacement {
25    from: String,
26    to: String,
27}
28
29fn get_plottery_subcrate(
30    plottery_home_subdir: &str,
31    lib_source: &LibSource,
32    crate_name: &str,
33) -> Result<String> {
34    let lib_source_for_toml = match lib_source {
35        LibSource::PlotteryHome => {
36            let plottery_home = match std::env::var("PLOTTERY_HOME") {
37                Ok(home) => Path::new(&home).to_path_buf(),
38                Err(_) => {
39                    return Err(anyhow::anyhow!(
40                        "Environment variable PLOTTERY_HOME is not set"
41                    ));
42                }
43            };
44            let plottery_home_lib = plottery_home
45                .join(plottery_home_subdir)
46                .absolutize()
47                .unwrap()
48                .to_path_buf();
49            if !plottery_home_lib.exists() {
50                return Err(anyhow::anyhow!(
51                    "PLOTTERY_HOME/lib does not exist at: {}",
52                    plottery_home_lib.to_string_lossy()
53                ));
54            }
55            let path = plottery_home_lib
56                .absolutize()
57                .unwrap()
58                .to_string_lossy()
59                .to_string();
60            format!("{} = {{ path = \"{}\" }}", crate_name, path)
61        }
62        LibSource::Path {
63            path: plottery_home_path,
64        } => {
65            let sub_crate_path = plottery_home_path
66                .join(plottery_home_subdir)
67                .to_string_lossy()
68                .to_string();
69            format!("{} = {{ path = \"{}\" }}", crate_name, sub_crate_path)
70        }
71        LibSource::Cargo => format!("{} = \">=0.1\"", crate_name),
72    };
73    Ok(lib_source_for_toml)
74}
75
76pub fn generate_cargo_project_to_disk(
77    out_dir: PathBuf,
78    project_name: &str,
79    lib_source: LibSource,
80) -> Result<()> {
81    let fs = get_fs();
82    std::fs::create_dir_all(&out_dir)?;
83
84    let plottery_lib_include = get_plottery_subcrate("lib", &lib_source, "plottery_lib")?;
85    let plottery_project_include =
86        get_plottery_subcrate("project", &lib_source, "plottery_project")?;
87
88    let string_replacements = vec![
89        Replacement {
90            from: "{{plottery-lib-include}}".to_string(),
91            to: plottery_lib_include,
92        },
93        Replacement {
94            from: "{{plottery-project-include}}".to_string(),
95            to: plottery_project_include,
96        },
97        Replacement {
98            from: "{{project-name}}".to_string(),
99            to: project_name.to_owned(),
100        },
101    ];
102    let file_name_replacements = vec![Replacement {
103        from: "Cargo_template.toml".to_string(),
104        to: "Cargo.toml".to_string(),
105    }];
106
107    write_dir_to_disk_recurse(
108        &fs,
109        "".to_string(),
110        &out_dir,
111        &string_replacements,
112        &file_name_replacements,
113    )?;
114
115    Ok(())
116}
117
118fn write_dir_to_disk_recurse(
119    fs: &EmbeddedFS<CargoProjectTemplate>,
120    sub_dir: String,
121    out_dir: &PathBuf,
122    string_replacements: &[Replacement],
123    file_name_replacements: &[Replacement],
124) -> Result<()> {
125    for element in fs.read_dir(&sub_dir).unwrap() {
126        let sub_element = format!("{}/{}", &sub_dir, &element);
127        let out_element = out_dir.join(sub_element.strip_prefix('/').unwrap()); // needs to be relative or else join replaces the whole path with sub_element
128        if is_file(fs, &sub_element) {
129            write_file_to_disk(
130                fs,
131                &sub_element,
132                &out_element,
133                string_replacements,
134                file_name_replacements,
135            )?;
136        } else {
137            std::fs::create_dir_all(out_element)?;
138            write_dir_to_disk_recurse(
139                fs,
140                sub_element.clone(),
141                out_dir,
142                string_replacements,
143                file_name_replacements,
144            )?;
145        }
146    }
147    Ok(())
148}
149
150fn write_file_to_disk(
151    fs: &EmbeddedFS<CargoProjectTemplate>,
152    sub_element: &str,
153    out_element: &Path,
154    string_replacements: &[Replacement],
155    file_name_replacements: &[Replacement],
156) -> Result<()> {
157    let mut file = fs.open_file(sub_element)?;
158    let ext = match Path::new(&sub_element).extension() {
159        Some(ext) => ext.to_str().unwrap_or(""),
160        None => "",
161    };
162
163    let mut buf = Vec::new();
164    file.read_to_end(&mut buf)?;
165
166    if ext == "rs" || ext == "toml" {
167        let mut buf_str = String::from_utf8(buf)?;
168        for replacement in string_replacements {
169            buf_str = buf_str.replace(&replacement.from, &replacement.to);
170        }
171        buf = buf_str.into_bytes();
172    }
173
174    let mut file_name = out_element
175        .file_name()
176        .expect("Failed to get file name")
177        .to_str()
178        .unwrap();
179
180    for replacement in file_name_replacements {
181        if file_name == replacement.from {
182            file_name = &replacement.to;
183            break;
184        }
185    }
186
187    let out_file_path = out_element.with_file_name(file_name);
188
189    let mut out_file = File::create(out_file_path)?;
190    out_file.write_all(&buf)?;
191
192    Ok(())
193}
194
195fn is_file(fs: &EmbeddedFS<CargoProjectTemplate>, sub_dir: &str) -> bool {
196    fs.open_file(sub_dir).is_ok()
197}