summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMatthias Schiffer <mschiffer@universe-factory.net>2021-01-24 19:27:26 +0100
committerMatthias Schiffer <mschiffer@universe-factory.net>2021-01-24 19:27:26 +0100
commit9e60d16555765a6cfc053798f56e5b914ea1834e (patch)
tree0ba9b72df22a17c4dbd363d195a2f14417362c24 /src
parent01e3de22a53d30f21a829bf5d7cebc45c4d61ea8 (diff)
downloadrebel-9e60d16555765a6cfc053798f56e5b914ea1834e.tar
rebel-9e60d16555765a6cfc053798f56e5b914ea1834e.zip
Read tasks from files
Diffstat (limited to 'src')
-rw-r--r--src/main.rs144
-rw-r--r--src/recipe.rs82
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)
+}