tinted_builder_rust/operations/build/
utils.rs1use anyhow::{anyhow, Result};
2use regex::Regex;
3use serde::Deserialize;
4use std::fs::read_to_string;
5use std::path::{Path, PathBuf};
6use tinted_builder::{Scheme, SchemeSystem};
7
8#[derive(Debug, Clone)]
9pub enum SchemeFile {
10 Yaml(PathBuf),
11 Yml(PathBuf),
12}
13
14impl SchemeFile {
15 pub fn new(path: impl AsRef<Path>) -> Result<Self> {
16 let extension = path
17 .as_ref()
18 .extension()
19 .unwrap_or_default()
20 .to_str()
21 .unwrap_or_default();
22
23 match extension {
24 "yaml" => Ok(Self::Yaml(path.as_ref().to_path_buf())),
25 "yml" => Ok(Self::Yml(path.as_ref().to_path_buf())),
26 _ => Err(anyhow!("Invalid file extension: {}", extension.to_string())),
27 }
28 }
29
30 pub fn get_scheme(&self) -> Result<Scheme> {
31 match self {
32 Self::Yaml(path) | Self::Yml(path) => {
33 let scheme_str = read_to_string(path)?;
34 let scheme: serde_yaml::Value = serde_yaml::from_str(&scheme_str)?;
35
36 if let serde_yaml::Value::Mapping(map) = scheme {
37 match map.get("system") {
38 Some(serde_yaml::Value::String(system_str))
39 if system_str == &SchemeSystem::Base24.to_string() =>
40 {
41 let scheme_inner =
42 serde_yaml::from_value(serde_yaml::Value::Mapping(map))?;
43 let scheme = Scheme::Base24(scheme_inner);
44
45 Ok(scheme)
46 }
47 None | Some(_) => {
48 let scheme_inner =
49 serde_yaml::from_value(serde_yaml::Value::Mapping(map))?;
50 let scheme = Scheme::Base16(scheme_inner);
51
52 Ok(scheme)
53 }
54 }
55 } else {
56 Err(anyhow!("Unable to get scheme from SchemeFile"))
57 }
58 }
59 }
60 }
61
62 pub fn get_path(&self) -> Option<PathBuf> {
63 match self {
64 Self::Yaml(path) | Self::Yml(path) => Some(path.to_path_buf()),
65 }
66 }
67}
68
69#[derive(Debug, Deserialize)]
70pub(crate) struct TemplateConfig {
71 pub filename: Option<String>,
72
73 #[serde(rename = "supported-systems")]
74 pub supported_systems: Option<Vec<SchemeSystem>>,
75
76 #[deprecated]
77 pub extension: Option<String>,
78
79 #[deprecated]
80 pub output: Option<String>,
81}
82
83#[derive(Debug)]
84pub(crate) struct ParsedFilename {
85 pub directory: PathBuf,
86 pub filestem: String,
87 pub file_extension: Option<String>,
88}
89
90impl ParsedFilename {
91 pub fn get_path(&self) -> PathBuf {
92 let directory = &self.directory;
93 let filestem = &self.filestem;
94 let file_extension = &self
95 .file_extension
96 .as_ref()
97 .map(|ext| format!(".{}", ext))
98 .unwrap_or_default();
99
100 directory.join(format!("{}{}", filestem, file_extension))
101 }
102}
103
104pub fn get_scheme_files(dirpath: impl AsRef<Path>, is_recursive: bool) -> Result<Vec<SchemeFile>> {
127 let mut scheme_paths: Vec<SchemeFile> = vec![];
128
129 for item in dirpath.as_ref().read_dir()? {
130 let file_path = item?.path();
131 let file_stem = file_path
132 .file_stem()
133 .unwrap_or_default()
134 .to_str()
135 .unwrap_or_default();
136
137 if file_stem.starts_with('.') {
139 continue;
140 }
141
142 if file_path.is_dir() && is_recursive {
143 let inner_scheme_paths_result = get_scheme_files(&file_path, true);
144
145 if let Ok(inner_scheme_paths) = inner_scheme_paths_result {
146 scheme_paths.extend(inner_scheme_paths);
147 }
148
149 continue;
150 }
151
152 let scheme_file_type_result = SchemeFile::new(&file_path);
153
154 match scheme_file_type_result {
155 Ok(scheme_file_type) => {
156 scheme_paths.push(scheme_file_type);
157 }
158 Err(_) => continue,
159 }
160 }
161
162 scheme_paths.sort_by_key(|k| k.get_path());
163
164 Ok(scheme_paths)
165}
166
167pub(crate) fn parse_filename(
175 template_path: impl AsRef<Path>,
176 filepath: &str,
177) -> Result<ParsedFilename> {
178 let re = Regex::new(r"^(?P<directory>.*/)?(?P<filestem>[^/\.]+)(?:\.(?P<extension>[^/]+))?$")
179 .unwrap();
180
181 if let Some(captures) = re.captures(filepath) {
182 let directory = captures
184 .name("directory")
185 .map(|d| template_path.as_ref().join(d.as_str()))
186 .unwrap_or_else(|| template_path.as_ref().to_path_buf());
187 let filestem = captures.name("filestem").unwrap().as_str().to_string();
188 let file_extension = captures
189 .name("extension")
190 .map(|ext| ext.as_str().to_string());
191
192 if filestem.is_empty() {
193 Err(anyhow!(
194 "Config property \"filename\" requires a filestem: {}",
195 &filepath
196 ))
197 } else {
198 Ok(ParsedFilename {
200 directory,
201 filestem,
202 file_extension,
203 })
204 }
205 } else {
206 Err(anyhow!("Unable to parse template: {}", &filepath))
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use std::path::Path;
214
215 #[test]
216 fn test_parse_filename_with_directory_and_extension() {
217 let template_path = Path::new("/home/user/templates");
218 let result = parse_filename(template_path, "some-directory/name/file.txt").unwrap();
219
220 assert_eq!(result.directory, template_path.join("some-directory/name"));
221 assert_eq!(result.filestem, "file");
222 assert_eq!(result.file_extension, Some("txt".to_string()));
223 }
224
225 #[test]
226 fn test_parse_filename_with_filename_and_extension() {
227 let template_path = Path::new("/home/user/templates");
228 let result = parse_filename(template_path, "filename.ext").unwrap();
229
230 assert_eq!(result.directory, template_path);
231 assert_eq!(result.filestem, "filename");
232 assert_eq!(result.file_extension, Some("ext".to_string()));
233 }
234
235 #[test]
236 fn test_parse_filename_with_only_filename() {
237 let template_path = Path::new("/home/user/templates");
238 let result = parse_filename(template_path, "file").unwrap();
239
240 assert_eq!(result.directory, template_path);
241 assert_eq!(result.filestem, "file");
242 assert_eq!(result.file_extension, None);
243 }
244
245 #[test]
246 fn test_parse_filename_with_directory_and_no_extension() {
247 let template_path = Path::new("/home/user/templates");
248 let result = parse_filename(template_path, "some-directory/file").unwrap();
249
250 assert_eq!(result.directory, template_path.join("some-directory"));
251 assert_eq!(result.filestem, "file");
252 assert_eq!(result.file_extension, None);
253 }
254
255 #[test]
256 fn test_parse_filename_invalid_filestem() {
257 let template_path = Path::new("/home/user/templates");
258 let filename = "/invalid/path/";
259 let err_message = parse_filename(template_path, filename)
260 .unwrap_err()
261 .to_string();
262
263 assert!(
264 err_message.contains(format!("Unable to parse template: {}", &filename).as_str()),
265 "Unexpected error message: {}",
266 err_message
267 );
268 }
269}