From a5cf066b03ec0e23e8a4e0303d1f06da1b3f792a Mon Sep 17 00:00:00 2001 From: Yinwhe Date: Wed, 9 Feb 2022 17:43:07 +0800 Subject: [PATCH] dagrs add TaskTrait --- dagrs/src/dag_engine.rs | 52 +++++++--------------- dagrs/src/graph.rs | 12 +++++ dagrs/src/main.rs | 15 ++++--- dagrs/src/task.rs | 97 ++++++++++++++++++++++++++++++++++------- 4 files changed, 118 insertions(+), 58 deletions(-) diff --git a/dagrs/src/dag_engine.rs b/dagrs/src/dag_engine.rs index 6a3f5540..5df227c3 100644 --- a/dagrs/src/dag_engine.rs +++ b/dagrs/src/dag_engine.rs @@ -3,28 +3,27 @@ use crate::{ error_handler::{DagError, FormatErrorMark}, graph::Graph, - task::Task, + task::TaskTrait, }; -use std::{collections::HashMap, fs::File, io::Read}; -use yaml_rust::YamlLoader; +use std::collections::HashMap; /// dagrs's function is wrapped in DagEngine struct -pub struct DagEngine { +pub struct DagEngine { /// Store all tasks' infos - tasks: HashMap, + tasks: HashMap, /// Store dependency relations rely_graph: Graph, } -impl DagEngine { +impl DagEngine { /// Allocate a new DagEngine - /// + /// /// # Example /// ``` /// let dagrs = DagEngine::new(); /// ``` /// This function is usually used with `run`. - pub fn new() -> DagEngine { + pub fn new() -> DagEngine { DagEngine { tasks: HashMap::new(), rely_graph: Graph::new(), @@ -32,50 +31,31 @@ impl DagEngine { } /// Do dagrs's job - /// + /// /// # Example /// ``` /// let dagrs = DagEngine::new(); /// dagrs.run("test/test_dag.yaml"); /// ``` - pub fn run(&mut self, tasks_list: &str) -> Result { - self.read_tasks(tasks_list)?; + pub fn run(&mut self, task_info_file: &str) -> Result { + self.read_tasks(task_info_file); self.create_graph()?; Ok(self.check_dag()) } /// Read tasks into engine throuh yaml - /// + /// /// # Example /// ``` /// let yaml_tasks = dagrs.read_task("test/test.yaml"); /// ``` /// This operation will read all info in yaml file into `dagrs.tasks` if no error occurs. - fn read_tasks(&mut self, filename: &str) -> Result<(), DagError> { - let mut yaml_cont = String::new(); - - let mut yaml_file = File::open(filename)?; - yaml_file.read_to_string(&mut yaml_cont)?; - - // Parse Yaml - let yaml_tasks = YamlLoader::load_from_str(&yaml_cont)?; - let yaml_tasks = yaml_tasks[0]["dagrs"] - .as_hash() - .ok_or(DagError::format_error("", FormatErrorMark::StartWordError))?; - - // Read tasks - for (v, w) in yaml_tasks { - let id = v.as_str().unwrap(); // .ok_or(DagError::form("task id error"))?; - let task = Task::from_yaml(id, w)?; - - self.tasks.insert(id.to_owned(), task); - } - - Ok(()) + fn read_tasks(&mut self, filename: &str) { + self.tasks = T::from_file(filename); } /// create rely map between tasks - /// + /// /// # Example /// ``` /// dagrs.create_graph(); @@ -109,7 +89,7 @@ impl DagEngine { } /// Check whether it's DAG or not - /// + /// /// # Example /// ``` /// dagrs.check_dag(); @@ -118,4 +98,4 @@ impl DagEngine { fn check_dag(&self) -> bool { self.rely_graph.topo_sort() } -} \ No newline at end of file +} diff --git a/dagrs/src/graph.rs b/dagrs/src/graph.rs index bc2225d1..5e407ae0 100644 --- a/dagrs/src/graph.rs +++ b/dagrs/src/graph.rs @@ -95,6 +95,18 @@ impl Graph { /// or it will print `Loop Detected`. /// /// **Note**: this function can only be called after graph's initialization (add nodes and edges, etc.) is done. + /// + /// # Principle + /// Reference: [Topological Sorting](https://www.jianshu.com/p/b59db381561a) + /// + /// 1. For a grapg g, we record the indgree of every node. + /// + /// 2. Each time we start from a node with zero indegree, name it N0, and N0 can be executed since it has no dependency. + /// + /// 3. And then we decrease the indegree of N0's children (those tasks depend on N0), this would create some new zero indegree nodes. + /// + /// 4. Just repeat step 2, 3 until no more zero degree nodes can be generated. + /// If all tasks have been executed, then it's a DAG, or there must be a loop in the graph. pub fn topo_sort(&self) -> bool { let mut queue = Vec::new(); let mut indegree = self.indegree.clone(); diff --git a/dagrs/src/main.rs b/dagrs/src/main.rs index 6bd582bc..f3b00952 100644 --- a/dagrs/src/main.rs +++ b/dagrs/src/main.rs @@ -68,6 +68,7 @@ pub mod task; use clap::Parser; use dag_engine::DagEngine; +use task::Task; #[derive(Parser)] #[clap(version)] @@ -80,7 +81,7 @@ struct Args { fn main() { let args = Args::parse(); - let mut dagrs = DagEngine::new(); + let mut dagrs: DagEngine = DagEngine::new(); if let Err(e) = dagrs.run(&args.filepath) { println!("[Error] {}", e); @@ -89,26 +90,26 @@ fn main() { #[test] fn test_dag() { - let res = DagEngine::new().run("test/test_dag.yaml").unwrap(); + let res = DagEngine::::new().run("test/test_dag.yaml").unwrap(); assert_eq!(res, true) } #[test] fn test_loop() { - let res = DagEngine::new().run("test/test_loop1.yaml").unwrap(); + let res = DagEngine::::new().run("test/test_loop1.yaml").unwrap(); assert_eq!(res, false) } #[test] fn test_complex_loop() { - let res = DagEngine::new().run("test/test_loop2.yaml").unwrap(); + let res = DagEngine::::new().run("test/test_loop2.yaml").unwrap(); assert_eq!(res, false) } #[test] fn test_format_error1() { use error_handler::{DagError, FormatErrorMark}; - let res = DagEngine::new().run("test/test_error1.yaml"); + let res = DagEngine::::new().run("test/test_error1.yaml"); assert_eq!( res, Err(DagError::format_error("a".into(), FormatErrorMark::NoName)) @@ -118,7 +119,7 @@ fn test_format_error1() { #[test] fn test_format_error2() { use error_handler::{DagError, FormatErrorMark}; - let res = DagEngine::new().run("test/test_error2.yaml"); + let res = DagEngine::::new().run("test/test_error2.yaml"); assert_eq!( res, Err(DagError::format_error("".into(), FormatErrorMark::StartWordError)) @@ -129,7 +130,7 @@ fn test_format_error2() { #[test] fn test_rely_error() { use error_handler::{DagError, FormatErrorMark}; - let res = DagEngine::new().run("test/test_error3.yaml"); + let res = DagEngine::::new().run("test/test_error3.yaml"); assert_eq!( res, Err(DagError::format_error("a".into(), FormatErrorMark::RelyIDIllegal)) diff --git a/dagrs/src/task.rs b/dagrs/src/task.rs index 892418a9..9cb341d7 100644 --- a/dagrs/src/task.rs +++ b/dagrs/src/task.rs @@ -1,7 +1,19 @@ //! Task Implementations, used to store task infos +use std::{collections::HashMap, fs::File, io::Read, process::exit}; + use crate::error_handler::{DagError, FormatErrorMark}; -use yaml_rust::Yaml; +use yaml_rust::{Yaml, YamlLoader}; + +/// Struct that implement this trait can be taken as a task accepted by DagEngine. +pub trait TaskTrait where Self:Sized { + /// Get the ID of a task. + fn get_ID(&self) -> String; + /// Get the dependency list of a task. + fn get_rely_list(&self) -> &Vec; + /// Parse all tasks from file and form a hash map (ID to task struct mapping). + fn from_file(filename: &str) -> HashMap; +} /// Task Struct #[derive(Debug)] @@ -14,6 +26,51 @@ pub struct Task { pub relys: Vec, } +impl TaskTrait for Task { + /// Get the ID of a task + /// + /// # Example + /// ``` + /// let id = task.get_ID(); + /// println!("{}", id); + /// ``` + fn get_ID(&self) -> String { + self.ID.to_owned() + } + + /// Get dependency tasks list + /// + /// # Example + /// Usually used like: + /// ``` + /// let relys = tasks.get_rely_list(); + /// for rely_task in relys{ + /// ... + /// } + /// ``` + fn get_rely_list(&self) -> &Vec { + &self.relys + } + + /// Read all tasks from file, and return a hash map recording ID to Task Struct + /// + /// # Example + /// ``` + /// let tasks = Task::from_file("test/test_dag.yaml") + /// ``` + fn from_file(filename: &str) -> HashMap { + let res = Task::read_tasks(filename); + if let Err(e) = res { + println!("[Error] {}", e); + exit(0); + } else { + res.unwrap() + } + } + + +} + impl Task { /// Parse Task from Yaml /// @@ -32,7 +89,7 @@ impl Task { /// ... /// } /// ``` - pub fn from_yaml(id: &str, info: &Yaml) -> Result { + fn from_yaml(id: &str, info: &Yaml) -> Result { // Get name first let name = info["name"] @@ -59,18 +116,28 @@ impl Task { }) } + /// Read all tasks from yaml file. + fn read_tasks(filename: &str) -> Result, DagError> { + let mut yaml_cont = String::new(); - /// Get dependency tasks list - /// - /// # Example - /// Usually used like: - /// ``` - /// let relys = tasks.get_rely_list(); - /// for rely_task in relys{ - /// ... - /// } - /// ``` - pub fn get_rely_list(&self) -> &Vec { - &self.relys + let mut yaml_file = File::open(filename)?; + yaml_file.read_to_string(&mut yaml_cont)?; + + // Parse Yaml + let yaml_tasks = YamlLoader::load_from_str(&yaml_cont)?; + let yaml_tasks = yaml_tasks[0]["dagrs"] + .as_hash() + .ok_or(DagError::format_error("", FormatErrorMark::StartWordError))?; + + let mut tasks = HashMap::new(); + // Read tasks + for (v, w) in yaml_tasks { + let id = v.as_str().unwrap(); // .ok_or(DagError::form("task id error"))?; + let task = Task::from_yaml(id, w)?; + + tasks.insert(id.to_owned(), task); + } + + Ok(tasks) } -} +} \ No newline at end of file -- Gitee