tinted_builder_rust/operations/build/
utils.rs1use anyhow::{anyhow, Result};
2use serde::Deserialize;
3use std::collections::HashMap;
4use std::fs::read_to_string;
5use std::path::{Path, PathBuf};
6use tinted_builder::{Scheme, SchemeSystem};
7use wax::{Glob, Program};
8
9#[derive(Debug, Clone)]
11pub enum SchemeFile {
12 Yaml(PathBuf),
13 Yml(PathBuf),
14}
15
16impl SchemeFile {
17 pub fn new(path: impl AsRef<Path>) -> Result<Self> {
23 let extension = path
24 .as_ref()
25 .extension()
26 .unwrap_or_default()
27 .to_str()
28 .unwrap_or_default();
29
30 match extension {
31 "yaml" => Ok(Self::Yaml(path.as_ref().to_path_buf())),
32 "yml" => Ok(Self::Yml(path.as_ref().to_path_buf())),
33 _ => Err(anyhow!(
34 "E111: Invalid scheme file extension: {}",
35 path.as_ref().display()
36 )),
37 }
38 }
39
40 pub fn get_scheme(&self) -> Result<Scheme> {
49 match self {
50 Self::Yaml(path) | Self::Yml(path) => {
51 let scheme_str = read_to_string(path)?;
52 let scheme: serde_yaml::Value = serde_yaml::from_str(&scheme_str)?;
53
54 if let serde_yaml::Value::Mapping(map) = scheme {
55 match map.get("system") {
56 Some(serde_yaml::Value::String(system_str))
57 if system_str == &SchemeSystem::Base24.to_string() =>
58 {
59 let scheme_inner =
60 serde_yaml::from_value(serde_yaml::Value::Mapping(map))?;
61 let scheme = Scheme::Base24(scheme_inner);
62
63 Ok(scheme)
64 }
65 Some(_) => {
66 let scheme_inner =
67 serde_yaml::from_value(serde_yaml::Value::Mapping(map))?;
68 let scheme = Scheme::Base16(scheme_inner);
69
70 Ok(scheme)
71 }
72 None => {
73 if let Some(scheme_meta) = map.get("scheme") {
74 if let Some(system) = scheme_meta.get("system") {
75 if system == &SchemeSystem::Tinted8.to_string() {
76 let scheme_inner = serde_yaml::from_value(
77 serde_yaml::Value::Mapping(map),
78 )?;
79 let scheme = Scheme::Tinted8(scheme_inner);
80
81 Ok(scheme)
82 } else {
83 Err(anyhow!("E110: Unknown or unsupported scheme system"))
84 }
85 } else {
86 Err(anyhow!("E111: Missing required field `scheme.system`"))
87 }
88 } else {
89 Err(anyhow!("E111: Missing required field `system`"))
90 }
91 }
92 }
93 } else {
94 Err(anyhow!("E112: Unable to parse scheme file"))
95 }
96 }
97 }
98 }
99
100 #[must_use]
102 pub fn get_path(&self) -> PathBuf {
103 match self {
104 Self::Yaml(path) | Self::Yml(path) => path.clone(),
105 }
106 }
107}
108
109#[derive(Debug, Deserialize)]
111pub struct TemplateConfig {
112 pub filename: Option<String>,
113
114 #[serde(rename = "supported-systems")]
115 pub supported_systems: Option<Vec<SchemeSystem>>,
116
117 pub supports: Option<HashMap<String, String>>,
118
119 pub options: Option<HashMap<String, String>>,
120
121 #[deprecated]
122 pub extension: Option<String>,
123
124 #[deprecated]
125 pub output: Option<String>,
126}
127
128#[derive(Debug)]
130pub struct ParsedFilename {
131 pub directory: PathBuf,
132 pub filestem: String,
133 pub file_extension: Option<String>,
134}
135
136impl ParsedFilename {
137 #[must_use]
139 pub fn get_path(&self) -> PathBuf {
140 let directory = &self.directory;
141 let filestem = &self.filestem;
142 let file_extension = &self
143 .file_extension
144 .as_ref()
145 .map(|ext| format!(".{ext}"))
146 .unwrap_or_default();
147
148 directory.join(format!("{filestem}{file_extension}"))
149 }
150}
151
152pub fn get_scheme_files(
176 dirpath: impl AsRef<Path>,
177 ignores: &[String],
178 is_recursive: bool,
179) -> Result<Vec<SchemeFile>> {
180 let glob_ignores: Vec<Glob> = ignores
181 .iter()
182 .map(|s| Glob::new(s))
183 .collect::<Result<_, _>>()?;
184
185 let mut scheme_paths: Vec<SchemeFile> = vec![];
186
187 for item in dirpath.as_ref().read_dir()? {
188 let file_path = item?.path();
189 if glob_ignores.iter().any(|g| g.is_match(file_path.as_path())) {
191 continue;
192 }
193
194 if file_path.is_dir() && is_recursive {
195 let inner_scheme_paths_result = get_scheme_files(&file_path, ignores, true);
196
197 if let Ok(inner_scheme_paths) = inner_scheme_paths_result {
198 scheme_paths.extend(inner_scheme_paths);
199 }
200
201 continue;
202 }
203
204 if file_path.is_file() {
206 let scheme_file_type_result = SchemeFile::new(&file_path);
207
208 match scheme_file_type_result {
209 Ok(scheme_file_type) => scheme_paths.push(scheme_file_type),
210 Err(err) => {
211 return Err(err);
213 }
214 }
215 }
216 }
217
218 scheme_paths.sort_by_key(SchemeFile::get_path);
219
220 Ok(scheme_paths)
221}
222
223pub fn parse_filename(template_path: impl AsRef<Path>, filepath: &str) -> ParsedFilename {
231 let p = Path::new(filepath);
232
233 let directory: PathBuf = p.parent().map_or_else(
234 || template_path.as_ref().to_path_buf(),
235 |dir| template_path.as_ref().join(dir),
236 );
237
238 let filestem = p
240 .file_stem()
241 .and_then(|s| s.to_str())
242 .filter(|s| !s.is_empty())
243 .map(String::from)
244 .unwrap_or_default();
245
246 let file_extension = p.extension().and_then(|e| e.to_str()).map(String::from);
247
248 ParsedFilename {
249 directory,
250 filestem,
251 file_extension,
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use std::path::Path;
259
260 #[test]
261 fn test_parse_filename_with_directory_and_extension() {
262 let template_path = Path::new("/home/user/templates");
263 let result = parse_filename(template_path, "some-directory/name/file.txt");
264
265 assert_eq!(result.directory, template_path.join("some-directory/name"));
266 assert_eq!(result.filestem, "file");
267 assert_eq!(result.file_extension, Some("txt".to_string()));
268 }
269
270 #[test]
271 fn test_parse_filename_with_filename_and_extension() {
272 let template_path = Path::new("/home/user/templates");
273 let result = parse_filename(template_path, "filename.ext");
274
275 assert_eq!(result.directory, template_path);
276 assert_eq!(result.filestem, "filename");
277 assert_eq!(result.file_extension, Some("ext".to_string()));
278 }
279
280 #[test]
281 fn test_parse_filename_with_only_filename() {
282 let template_path = Path::new("/home/user/templates");
283 let result = parse_filename(template_path, "file");
284
285 assert_eq!(result.directory, template_path);
286 assert_eq!(result.filestem, "file");
287 assert_eq!(result.file_extension, None);
288 }
289
290 #[test]
291 fn test_parse_filename_with_directory_and_no_extension() {
292 let template_path = Path::new("/home/user/templates");
293 let result = parse_filename(template_path, "some-directory/file");
294
295 assert_eq!(result.directory, template_path.join("some-directory"));
296 assert_eq!(result.filestem, "file");
297 assert_eq!(result.file_extension, None);
298 }
299}