diff options
author | Matthias Schiffer <mschiffer@universe-factory.net> | 2021-01-24 19:27:26 +0100 |
---|---|---|
committer | Matthias Schiffer <mschiffer@universe-factory.net> | 2021-01-24 19:27:26 +0100 |
commit | 9e60d16555765a6cfc053798f56e5b914ea1834e (patch) | |
tree | 0ba9b72df22a17c4dbd363d195a2f14417362c24 /src | |
parent | 01e3de22a53d30f21a829bf5d7cebc45c4d61ea8 (diff) | |
download | rebel-9e60d16555765a6cfc053798f56e5b914ea1834e.tar rebel-9e60d16555765a6cfc053798f56e5b914ea1834e.zip |
Read tasks from files
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 144 | ||||
-rw-r--r-- | src/recipe.rs | 82 |
2 files changed, 150 insertions, 76 deletions
diff --git a/src/main.rs b/src/main.rs index 98d1b8d..8c5708f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,96 +1,88 @@ -use std::collections::{HashMap, HashSet}; -// use std::io::{self, Write}; -use std::io; -// use std::process::Command; - -struct Task { - // action: String, - deps: Vec<String>, +use im::{HashMap, HashSet}; +use std::{fmt, path::Path, rc::Rc}; + +mod recipe; + +#[derive(Debug)] +enum Error { + TaskNotFound(String), + DependencyCycle(String), } -// impl Task { -// fn run(&self) -> io::Result<()> { -// let output = Command::new("sh").arg("-c").arg(&self.action).output()?; - -// let stdout = io::stdout(); -// let mut handle = stdout.lock(); -// handle.write_all(&output.stdout)?; - -// Ok(()) -// } -// } - -fn collect_subtasks<'a>( - queued: &HashSet<&'a str>, - tasks: &'a HashMap<String, Task>, - id: &str, -) -> io::Result<HashSet<&'a str>> { - let (task_id, task) = match tasks.get_key_value(id) { - Some(t) => t, - None => { - return Err(io::Error::new(io::ErrorKind::NotFound, "Task not found")); +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), } - }; - - if queued.contains(id) { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Dependency cycle", - )); } +} + +impl std::error::Error for Error {} + +type Result<T> = std::result::Result<T, Error>; + +#[derive(Default)] +struct Context { + tasks: HashMap<String, Rc<recipe::Task>>, +} + +impl Context { + fn collect_subtasks<'a>( + &'a self, + queued: &HashSet<&'a str>, + id: &str, + ) -> Result<HashSet<&'a str>> { + let (task_id, task) = match self.tasks.get_key_value(id) { + Some(t) => t, + None => { + return Err(Error::TaskNotFound(id.to_string())); + } + }; - let mut queued_sub = queued.clone(); - queued_sub.insert(task_id); + 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); + let mut subtasks: HashSet<&'a str> = HashSet::new(); + subtasks.insert(task_id); - for dep in &task.deps { - let deptasks = collect_subtasks(&queued_sub, tasks, dep)?; - subtasks = subtasks.union(&deptasks).copied().collect(); + for dep in &task.depends { + let deptasks = self.collect_subtasks(&queued_sub, dep)?; + subtasks = subtasks.union(deptasks); + } + + Ok(subtasks) } - Ok(subtasks) + fn collect_tasks(&self, id: &str) -> Result<HashSet<&str>> { + self.collect_subtasks(&HashSet::new(), id) + } } -fn collect_tasks<'a>(tasks: &'a HashMap<String, Task>, id: &str) -> io::Result<HashSet<&'a str>> { - collect_subtasks(&HashSet::new(), tasks, 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)); + } + } -fn main() -> io::Result<()> { - let mut tasks = HashMap::new(); - tasks.insert( - String::from("ls"), - Task { - // action: String::from("ls"), - deps: vec!["foo".to_owned(), "bar".to_owned()], - }, - ); - tasks.insert( - String::from("foo"), - Task { - // action: String::from("echo foo"), - deps: vec!["bar".to_owned()], - }, - ); - tasks.insert( - String::from("bar"), - Task { - // action: String::from("echo bar"), - deps: vec![], - }, - ); - - let queue = collect_tasks(&tasks, "ls")?; - let (runnable, queued) = queue + let queue = ctx.collect_tasks("ls:build")?; + let (runnable, queued): (HashSet<&str>, HashSet<&str>) = queue .into_iter() - .partition::<HashSet<_>, _>(|id| tasks.get(*id).unwrap().deps.is_empty()); + .partition(|id| ctx.tasks.get(*id).unwrap().depends.is_empty()); for t in &runnable { - println!("Runnable: {}", t); + println!("Runnable: {} ({:?})", t, ctx.tasks.get(*t).unwrap().run); } for t in &queued { - println!("Queued: {}", t); + println!("Queued: {} ({:?})", t, ctx.tasks.get(*t).unwrap().run); } Ok(()) diff --git a/src/recipe.rs b/src/recipe.rs new file mode 100644 index 0000000..d06b62b --- /dev/null +++ b/src/recipe.rs @@ -0,0 +1,82 @@ +use im::HashMap; +use serde::Deserialize; +use std::{fmt, fs::File, io, path::Path}; +use walkdir::WalkDir; + +#[derive(Clone, Debug, Deserialize)] +pub struct Task { + #[serde(default)] + pub depends: Vec<String>, + pub run: String, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Recipe { + pub tasks: HashMap<String, Task>, +} + +#[derive(Debug)] +pub enum Error { + IOError(io::Error), + YAMLError(serde_yaml::Error), +} + +impl From<io::Error> for Error { + fn from(err: io::Error) -> Self { + Error::IOError(err) + } +} + +impl From<serde_yaml::Error> for Error { + fn from(err: serde_yaml::Error) -> Self { + Error::YAMLError(err) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::IOError(err) => write!(f, "IO error: {}", err), + Error::YAMLError(err) => write!(f, "YAML error: {}", err), + } + } +} + +impl std::error::Error for Error {} + +pub type Result<T> = std::result::Result<T, Error>; + +pub fn read_recipe(path: &Path) -> Result<Recipe> { + let f = File::open(path)?; + + let recipe: Recipe = serde_yaml::from_reader(f)?; + + Ok(recipe) +} + +fn is_yml(path: &Path) -> bool { + path.extension() == Some("yml".as_ref()) +} + +pub fn read_recipes(path: &Path) -> Result<HashMap<String, Recipe>> { + let mut ret = HashMap::new(); + + for entry in WalkDir::new(path) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| { + let path = e.path(); + path.is_file() && is_yml(path) + }) { + let path = entry.path(); + let basename = match path.file_stem().map(|n| n.to_str()) { + Some(Some(v)) => v, + _ => continue, + }; + + let recipe = read_recipe(path)?; + ret.insert(basename.to_string(), recipe); + } + + Ok(ret) +} |