diff options
Diffstat (limited to 'crates/rebel/src/recipe.rs')
-rw-r--r-- | crates/rebel/src/recipe.rs | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/crates/rebel/src/recipe.rs b/crates/rebel/src/recipe.rs new file mode 100644 index 0000000..28cc84c --- /dev/null +++ b/crates/rebel/src/recipe.rs @@ -0,0 +1,167 @@ +use std::{collections::HashMap, ffi::OsStr, fs::File, path::Path}; + +use serde::{de::DeserializeOwned, Deserialize}; +use walkdir::WalkDir; + +use rebel_common::error::*; +use rebel_resolve::task::{TaskDef, TaskMeta}; + +#[derive(Clone, Debug, Deserialize, Default)] +pub struct RecipeMeta { + pub name: Option<String>, + pub version: Option<String>, +} + +#[derive(Debug, Deserialize)] +struct Recipe { + #[serde(default)] + pub meta: RecipeMeta, + pub tasks: HashMap<String, TaskDef>, +} + +#[derive(Debug, Deserialize)] +struct Subrecipe { + pub tasks: HashMap<String, TaskDef>, +} + +fn read_yaml<T: DeserializeOwned>(path: &Path) -> Result<T> { + let f = File::open(path).context("IO error")?; + + let value: T = serde_yaml::from_reader(f) + .map_err(Error::new) + .context("YAML error")?; + + Ok(value) +} + +const RECIPE_NAME: &str = "build"; +const RECIPE_PREFIX: &str = "build."; + +fn recipe_name(path: &Path) -> Option<&str> { + if path.extension() != Some("yml".as_ref()) { + return None; + } + + let stem = path.file_stem()?.to_str()?; + if stem == RECIPE_NAME { + return Some(""); + } + stem.strip_prefix(RECIPE_PREFIX) +} + +fn handle_recipe_tasks( + tasks: &mut HashMap<String, HashMap<String, Vec<TaskDef>>>, + recipe_tasks: HashMap<String, TaskDef>, + meta: &TaskMeta, +) { + let task_map = match tasks.get_mut(&meta.recipe) { + Some(task_map) => task_map, + None => tasks.entry(meta.recipe.clone()).or_default(), + }; + + for (label, mut task) in recipe_tasks { + task.meta = meta.clone(); + task_map.entry(label).or_default().push(task); + } +} + +fn read_recipe_tasks( + path: &Path, + basename: &str, + tasks: &mut HashMap<String, HashMap<String, Vec<TaskDef>>>, +) -> Result<RecipeMeta> { + let recipe_def = read_yaml::<Recipe>(path)?; + + let name = recipe_def + .meta + .name + .as_deref() + .unwrap_or(basename) + .to_string(); + + let meta = TaskMeta { + basename: basename.to_string(), + recipename: "".to_string(), + recipe: basename.to_string(), + name, + version: recipe_def.meta.version.clone(), + }; + + handle_recipe_tasks(tasks, recipe_def.tasks, &meta); + + Ok(recipe_def.meta) +} + +fn read_subrecipe_tasks( + path: &Path, + basename: &str, + recipename: &str, + recipe_meta: &RecipeMeta, + tasks: &mut HashMap<String, HashMap<String, Vec<TaskDef>>>, +) -> Result<()> { + let recipe = format!("{basename}/{recipename}"); + let recipe_def = read_yaml::<Subrecipe>(path)?; + + let name = recipe_meta.name.as_deref().unwrap_or(basename).to_string(); + + let meta = TaskMeta { + basename: basename.to_string(), + recipename: recipename.to_string(), + recipe: recipe.clone(), + name, + version: recipe_meta.version.clone(), + }; + + handle_recipe_tasks(tasks, recipe_def.tasks, &meta); + + Ok(()) +} + +pub fn read_recipes<P: AsRef<Path>>( + path: P, +) -> Result<HashMap<String, HashMap<String, Vec<TaskDef>>>> { + let mut tasks = HashMap::<String, HashMap<String, Vec<TaskDef>>>::new(); + let mut recipe_metas = HashMap::<String, RecipeMeta>::new(); + + for entry in WalkDir::new(path) + .sort_by(|a, b| { + // Files are sorted first by stem, then by extension, so that + // recipe.yml will always be read before recipe.NAME.yml + let stem_cmp = a.path().file_stem().cmp(&b.path().file_stem()); + let ext_cmp = a.path().extension().cmp(&b.path().extension()); + stem_cmp.then(ext_cmp) + }) + .into_iter() + .filter_map(|e| e.ok()) + { + let path = entry.path(); + if !path.is_file() { + continue; + } + + let Some(recipename) = recipe_name(path) else { + continue; + }; + let Some(basename) = path + .parent() + .and_then(Path::file_name) + .and_then(OsStr::to_str) + else { + continue; + }; + + if recipename.is_empty() { + recipe_metas.insert( + basename.to_string(), + read_recipe_tasks(path, basename, &mut tasks)?, + ); + } else { + let Some(recipe_meta) = recipe_metas.get(basename) else { + continue; + }; + read_subrecipe_tasks(path, basename, recipename, recipe_meta, &mut tasks)?; + } + } + + Ok(tasks) +} |