use im::{HashMap, HashSet}; use std::{fmt, path::Path, rc::Rc}; mod recipe; #[derive(Debug)] enum Error { TaskNotFound(String), DependencyCycle(String), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::TaskNotFound(id) => write!(f, "Task not found: {}", id), Error::DependencyCycle(id) => write!(f, "DependencyCycle: {}", id), } } } impl std::error::Error for Error {} type Result = std::result::Result; #[derive(Default)] struct Context { tasks: HashMap>, } impl Context { fn collect_subtasks<'a>( &'a self, queued: &HashSet<&'a str>, id: &str, ) -> Result> { let (task_id, task) = match self.tasks.get_key_value(id) { Some(t) => t, None => { return Err(Error::TaskNotFound(id.to_string())); } }; if queued.contains(id) { return Err(Error::DependencyCycle(id.to_string())); } let queued_sub = queued.update(task_id); let mut subtasks: HashSet<&'a str> = HashSet::new(); subtasks.insert(task_id); for dep in &task.depends { let deptasks = self.collect_subtasks(&queued_sub, dep)?; subtasks = subtasks.union(deptasks); } Ok(subtasks) } fn collect_tasks(&self, id: &str) -> Result> { self.collect_subtasks(&HashSet::new(), id) } } fn main() -> Result<()> { let recipes = recipe::read_recipes(Path::new("examples")).unwrap(); let mut ctx = Context::default(); for (recipe_name, recipe) in recipes { for (task_name, task) in recipe.tasks { let full_name = format!("{}:{}", recipe_name, task_name); ctx.tasks.insert(full_name, Rc::new(task)); } } let queue = ctx.collect_tasks("ls:build")?; let (runnable, queued): (HashSet<&str>, HashSet<&str>) = queue .into_iter() .partition(|id| ctx.tasks.get(*id).unwrap().depends.is_empty()); for t in &runnable { println!("Runnable: {} ({:?})", t, ctx.tasks.get(*t).unwrap().run); } for t in &queued { println!("Queued: {} ({:?})", t, ctx.tasks.get(*t).unwrap().run); } Ok(()) }