use crate::job::{Job, Jobs}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; #[derive(Serialize, Deserialize, Default)] #[serde(default)] pub struct Project { pub components: Vec, pub groups: Vec, pub env: HashMap, pub tasks: Vec, } impl Project { fn get_absolute_task(&self, name: &str) -> Option { self.tasks .iter() .find(|t| t.name == name) .map(|task| { let job = self.build_project_task_job(task); let mut jobs = task .before .iter() .map(|name| self.get_absolute_task(name)) .flatten() .map(|jobs| jobs.to_vec()) .flatten() .collect::>(); jobs.push(job); Jobs::new(jobs) }) .or_else(|| { self.components.iter().find_map(|component| { component .tasks .iter() .find(|t| format!("{}:{}", component.name, t.name) == name) .map(|task| { let job = self.build_component_task_job(component, task); let mut jobs = task .before .iter() .map(|name| self.get_absolute_task(name)) .flatten() .map(|jobs| jobs.to_vec()) .flatten() .collect::>(); jobs.push(job); Jobs::new(jobs) }) }) }) } fn get_relative_task(&self, task_name: &str, component_name: &str) -> Option { if let Some(component) = self.components.iter().find(|c| c.name == component_name) { component .tasks .iter() .find(|t| t.name == task_name) .map(|task| { let job = self.build_component_task_job(component, task); let mut jobs = task .before .iter() .map(|name| self.get_by_name(name)) .flatten() .map(|jobs| jobs.to_vec()) .flatten() .collect::>(); jobs.push(job); Jobs::new(jobs) }) } else { None } } fn get_component(&self, name: &str) -> Option { self.components.iter().find(|c| c.name == name).map(|c| { let component_job = self.build_component_job(c); let mut absolute_tasks = c .before .iter() .map(|name| self.get_absolute_task(name)) .flatten() .map(|jobs| jobs.to_vec()) .flatten() .collect::>(); let mut relative_tasks = c .before .iter() .map(|name| self.get_relative_task(name, &c.name)) .flatten() .map(|jobs| jobs.to_vec()) .flatten() .collect::>(); absolute_tasks.append(&mut relative_tasks); absolute_tasks.push(component_job); Jobs::new(absolute_tasks) }) } fn build_component_job(&self, c: &Component) -> Job { let mut env = self.env.clone(); env.extend(c.env.clone()); Job { name: c.name.clone(), env, ..Job::default() } } fn build_project_task_job(&self, task: &TaskDefinition) -> Job { let mut env = self.env.clone(); env.extend(task.env.clone()); Job { name: task.name.clone(), ..Job::default() } } fn build_component_task_job(&self, component: &Component, task: &TaskDefinition) -> Job { let mut env = self.env.clone(); env.extend(component.env.clone()); env.extend(task.env.clone()); Job { name: format!("{}:{}", component.name.clone(), task.name.clone()), env, ..Job::default() } } pub fn get_by_name(&self, name: &str) -> Option { self.get_component(name) .or_else(|| self.get_absolute_task(name)) } pub fn from_str(s: &str) -> Self { serde_yaml::from_str(s).unwrap() } } #[derive(Serialize, Deserialize, Default)] #[serde(default)] pub struct Group { name: String, components: Vec, env: HashMap, } #[derive(Serialize, Deserialize)] #[serde(default)] pub struct Component { pub env: HashMap, pub name: String, pub command: String, pub path: Option, pub retry: bool, pub keep_alive: bool, pub retry_delay: u64, pub before: Vec, pub tasks: Vec, } impl Default for Component { fn default() -> Self { Self { env: HashMap::new(), name: "UNNAMED".to_string(), command: String::new(), path: None, retry: false, keep_alive: true, retry_delay: 2, before: vec![], tasks: vec![], } } } #[derive(Serialize, Deserialize, Default)] #[serde(default)] pub struct TaskDefinition { pub env: HashMap, pub name: String, pub command: String, pub path: Option, pub retry: bool, pub keep_alive: bool, pub retry_delay: u64, pub before: Vec, } #[cfg(test)] mod tests { use super::Project; #[test] fn component_by_name() { let project = Project::from_str( r#" components: - name: test-component "#, ); let jobs = project.get_by_name("test-component").unwrap(); assert_eq!(jobs.len(), 1); } #[test] fn project_task() { let project = Project::from_str( r#" tasks: - name: task1 "#, ); let jobs = project.get_by_name("task1").unwrap(); assert_eq!(jobs.len(), 1); } #[test] fn component_task() { let project = Project::from_str( r#" components: - name: c1 tasks: - name: task1 "#, ); let jobs = project.get_by_name("c1:task1").unwrap(); assert_eq!(jobs.len(), 1); } #[test] fn component_dependent_absolute_component_task() { let project = Project::from_str( r#" components: - name: main-cmp before: - main-cmp:sub-task tasks: - name: sub-task "#, ); let jobs = project.get_by_name("main-cmp").unwrap(); assert_eq!(jobs.len(), 2); assert_eq!(jobs.first().unwrap().name, "main-cmp:sub-task"); } #[test] fn component_dependent_relative_component_task() { let project = Project::from_str( r#" components: - name: main-cmp before: - sub-task tasks: - name: sub-task "#, ); let jobs = project.get_by_name("main-cmp").unwrap(); assert_eq!(jobs.len(), 2); assert_eq!(jobs.first().unwrap().name, "main-cmp:sub-task"); } #[test] fn complicated_dependencies() { let project = Project::from_str( r#" components: - name: ui before: - build-ui tasks: - name: build-ui - name: server before: - setup tasks: - name: setup - name: build before: - server:setup tasks: - name: build before: - ui:build-ui - server:build "#, ); let jobs = project.get_by_name("build").unwrap(); assert_eq!(jobs.get(0).unwrap().name, "ui:build-ui"); assert_eq!(jobs.get(1).unwrap().name, "server:setup"); assert_eq!(jobs.get(2).unwrap().name, "server:build"); assert_eq!(jobs.get(3).unwrap().name, "build"); assert_eq!(jobs.len(), 4); } #[test] fn component_env() { let project = Project::from_str( r#" env: foo: one sub: two components: - name: main-cmp env: sub: three "#, ); let jobs = project.get_by_name("main-cmp").unwrap(); let job = jobs.first().unwrap(); assert_eq!(job.env.get("foo"), Some(&String::from("one"))); assert_eq!(job.env.get("sub"), Some(&String::from("three"))); } #[test] fn task_env() { let project = Project::from_str( r#" env: root: ten foo: one sub: two components: - name: main-cmp env: sub: three tasks: - name: t env: foo: four "#, ); let jobs = project.get_by_name("main-cmp:t").unwrap(); let job = jobs.first().unwrap(); assert_eq!(job.env.get("foo"), Some(&String::from("four"))); assert_eq!(job.env.get("sub"), Some(&String::from("three"))); assert_eq!(job.env.get("root"), Some(&String::from("ten"))); } }