use std::{collections::HashMap, fs::File, path::Path, result}; use scoped_tls_hkt::scoped_thread_local; use serde::{Deserialize, Deserializer}; use walkdir::WalkDir; use common::{error::*, types::*}; use crate::task::{RecipeMeta, TaskDef}; scoped_thread_local!(static CURRENT_RECIPE: str); fn current_recipe() -> String { CURRENT_RECIPE.with(|current| current.to_string()) } pub fn deserialize_task_id<'de, D>(deserializer: D) -> result::Result where D: Deserializer<'de>, { #[derive(Deserialize)] struct RecipeTaskID { recipe: Option, task: String, } let RecipeTaskID { recipe, task } = RecipeTaskID::deserialize(deserializer)?; Ok(TaskID { recipe: recipe.unwrap_or_else(current_recipe), task, }) } #[derive(Debug, Deserialize)] struct Recipe { #[serde(default)] pub meta: RecipeMeta, pub tasks: HashMap, } fn read_recipe(path: &Path) -> Result { let f = File::open(path).context("IO error")?; let recipe: Recipe = serde_yaml::from_reader(f) .map_err(Error::new) .context("YAML error")?; Ok(recipe) } fn is_yml(path: &Path) -> bool { path.extension() == Some("yml".as_ref()) } pub fn read_recipes>(path: P) -> Result> { let mut tasks = HashMap::new(); for entry in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) { let path = entry.path(); if !path.is_file() || !is_yml(path) { continue; } let basename: &str = match path.file_stem().map(|n| n.to_str()) { Some(Some(v)) => v, _ => continue, }; let recipe = CURRENT_RECIPE.set(basename, || read_recipe(path))?; let mut meta = recipe.meta; if meta.name.is_empty() { meta.name = basename.to_string(); } for (label, mut task) in recipe.tasks { let task_id = TaskID { recipe: basename.to_string(), task: label, }; task.meta = meta.clone(); tasks.insert(task_id, task); } } Ok(tasks) }