From 9e60d16555765a6cfc053798f56e5b914ea1834e Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Sun, 24 Jan 2021 19:27:26 +0100 Subject: Read tasks from files --- Cargo.lock | 206 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 ++ examples/bar.yml | 5 ++ examples/foo.yml | 4 ++ examples/ls.yml | 7 ++ src/main.rs | 144 ++++++++++++++++++-------------------- src/recipe.rs | 82 ++++++++++++++++++++++ 7 files changed, 376 insertions(+), 76 deletions(-) create mode 100644 examples/bar.yml create mode 100644 examples/foo.yml create mode 100644 examples/ls.yml create mode 100644 src/recipe.rs diff --git a/Cargo.lock b/Cargo.lock index e64fc04..ac42b1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,211 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "dtoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" + +[[package]] +name = "im" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111c1983f3c5bb72732df25cddacee9b546d08325fb584b5ebd38148be7b0246" +dependencies = [ + "bitmaps", + "rand_core", + "rand_xoshiro", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_xoshiro" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004" +dependencies = [ + "rand_core", +] + [[package]] name = "rebel" version = "0.1.0" +dependencies = [ + "im", + "serde", + "serde_yaml", + "walkdir", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6159e3c76cab06f6bc466244d43b35e77e9500cd685da87620addadc2a4c40b1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3fcab8778dc651bc65cfab2e4eb64996f3c912b74002fb379c94517e1f27c46" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "971be8f6e4d4a47163b405a3df70d14359186f9ab0f3a3ec37df144ca1ce089f" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + +[[package]] +name = "sized-chunks" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec31ceca5644fa6d444cc77548b88b67f46db6f7c71683b0f9336e671830d2f" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "syn" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07cb8b1b4ebf86a89ee88cbd201b022b94138c623644d035185c84d3f41b7e66" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml index 7712670..3626264 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +im = { version = "15", features = ["serde"] } +serde = { version = "1", features = ["derive"] } +serde_yaml = "0.8" +walkdir = "2" diff --git a/examples/bar.yml b/examples/bar.yml new file mode 100644 index 0000000..08be4a5 --- /dev/null +++ b/examples/bar.yml @@ -0,0 +1,5 @@ +tasks: + build: + run: | + echo bar + diff --git a/examples/foo.yml b/examples/foo.yml new file mode 100644 index 0000000..97d273a --- /dev/null +++ b/examples/foo.yml @@ -0,0 +1,4 @@ +tasks: + build: + run: | + echo foo diff --git a/examples/ls.yml b/examples/ls.yml new file mode 100644 index 0000000..adc4944 --- /dev/null +++ b/examples/ls.yml @@ -0,0 +1,7 @@ +tasks: + build: + depends: + - 'foo:build' + - 'bar:build' + run: | + ls 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, +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, - id: &str, -) -> io::Result> { - 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 = 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())); + } + }; - 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> { + self.collect_subtasks(&HashSet::new(), id) + } } -fn collect_tasks<'a>(tasks: &'a HashMap, id: &str) -> io::Result> { - 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::, _>(|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, + pub run: String, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Recipe { + pub tasks: HashMap, +} + +#[derive(Debug)] +pub enum Error { + IOError(io::Error), + YAMLError(serde_yaml::Error), +} + +impl From for Error { + fn from(err: io::Error) -> Self { + Error::IOError(err) + } +} + +impl From 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 = std::result::Result; + +pub fn read_recipe(path: &Path) -> Result { + 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> { + 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) +} -- cgit v1.2.3