From c2580f9361d0f4d08b4ad77f999860c5c673c3b8 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Thu, 3 Oct 2019 21:49:26 +0300 Subject: Change: drop unnecessary crate:: prefixes Also, rename roadmap.rs to map.rs to avoid naming conflict. --- src/lib.rs | 10 +-- src/map.rs | 271 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/roadmap.rs | 271 --------------------------------------------------------- 3 files changed, 276 insertions(+), 276 deletions(-) create mode 100644 src/map.rs delete mode 100644 src/roadmap.rs diff --git a/src/lib.rs b/src/lib.rs index 4eb712a..c9caef1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,13 +32,13 @@ //! ``` mod status; -pub use crate::status::Status; +pub use status::Status; mod step; -pub use crate::step::Step; +pub use step::Step; -mod roadmap; -pub use crate::roadmap::Roadmap; +mod map; +pub use map::Roadmap; mod parser; -pub use crate::parser::from_yaml; +pub use parser::from_yaml; diff --git a/src/map.rs b/src/map.rs new file mode 100644 index 0000000..d131e7d --- /dev/null +++ b/src/map.rs @@ -0,0 +1,271 @@ +use textwrap::fill; + +pub use crate::from_yaml; +pub use crate::Status; +pub use crate::Step; + +/// All the steps to get to the end goal. +#[derive(Clone,Debug)] +pub struct Roadmap { + steps: Vec, +} + +impl Roadmap { + /// Create a new, empty roadmap. + pub fn new() -> Roadmap { + Roadmap { steps: vec![] } + } + + /// Count number of steps that nothing depends on. + pub fn count_goals(&self) -> usize { + self.steps + .iter() + .filter(|step| self.is_goal(step)) + .count() + } + + /// Return list of step names. + pub fn step_names(&self) -> impl Iterator { + self.steps.iter().map(|step| step.name()) + } + + /// Get a step, given its name. + pub fn get_step(&self, name: &str) -> Option<&Step> { + self.steps.iter().filter(|step| step.name() == name).next() + } + + /// Add a step to the roadmap. This may fail, if there's a step + /// with that name already. + pub fn add_step(&mut self, step: &Step) -> Result<(), Box> { + self.steps.push(step.clone()); + Ok(()) + } + + // Get iterator over refs to steps. + pub fn iter(&self) -> impl Iterator { + self.steps.iter() + } + + // Get iterator over mut refs to steps. + pub fn iter_mut(&mut self) -> impl Iterator { + self.steps.iter_mut() + } + + /// Compute status of any step for which it has not been specified + /// in the input. + pub fn set_missing_statuses(&mut self) { + let new_steps: Vec = self + .steps + .iter() + .map(|step| { + let mut step = step.clone(); + if self.is_unset(&step) { + if self.is_goal(&step) { + step.set_status(Status::Goal); + } else if self.is_blocked(&step) { + step.set_status(Status::Blocked); + } else if self.is_ready(&step) { + step.set_status(Status::Ready); + } + } + step + }) + .collect(); + + if self.steps != new_steps { + self.steps = new_steps; + self.set_missing_statuses(); + } + } + + /// Is status unset? + pub fn is_unset(&self, step: &Step) -> bool { + step.status() == Status::Unknown + } + + /// Should unset status be ready? In other words, if there are any + /// dependencies, they are all finished. + pub fn is_ready(&self, step: &Step) -> bool { + self.dep_statuses(step) + .iter() + .all(|&status| status == Status::Finished) + } + + /// Should unset status be blocked? In other words, if there are + /// any dependencies, that aren't finished. + pub fn is_blocked(&self, step: &Step) -> bool { + self.dep_statuses(step) + .iter() + .any(|&status| status != Status::Finished) + } + + // Return vector of all statuses of all dependencies + fn dep_statuses(&self, step: &Step) -> Vec<&Status> { + step.dependencies() + .map(|depname| { + if let Some(step) = self.get_step(depname) { + step.status() + } else { + &Status::Unknown + } + }) + .collect() + } + + /// Should status be goal? In other words, does any other step + /// depend on this one? + pub fn is_goal(&self, step: &Step) -> bool { + let has_parent = self.steps.iter().any(|other| other.depends_on(step.name())); + !has_parent + } + + /// Get a Graphviz dot language representation of a roadmap. This + /// is the textual representation, and the caller needs to use the + /// Graphviz dot(1) tool to create an image from it. + pub fn as_dot(&self, label_width: usize) -> Result> { + let labels = self.steps.iter().map(|step| { + format!( + "{} [label=\"{}\" style=filled fillcolor=\"{}\" shape=\"{}\"];\n", + step.name(), + fill(&step.label(), label_width).replace("\n", "\\n"), + Roadmap::get_status_color(step), + Roadmap::get_status_shape(step), + ) + }); + + let mut dot = String::new(); + dot.push_str("digraph \"roadmap\" {\n"); + for line in labels { + dot.push_str(&line); + } + + for step in self.iter() { + for dep in step.dependencies() { + let line = format!("{} -> {};\n", dep, step.name()); + dot.push_str(&line); + } + } + + dot.push_str("}\n"); + + Ok(dot) + } + + fn get_status_color(step: &Step) -> &str { + match step.status() { + Status::Blocked => "#f4bada", + Status::Finished => "#eeeeee", + Status::Ready => "#ffffff", + Status::Next => "#0cc00", + Status::Goal => "#00eeee", + Status::Unknown => "#ff0000", + } + } + + fn get_status_shape(step: &Step) -> &str { + match step.status() { + Status::Blocked => "rectangle", + Status::Finished => "circle", + Status::Ready => "ellipse", + Status::Next => "ellipse", + Status::Goal => "diamond", + Status::Unknown => "house", + } + } +} + +#[cfg(test)] +mod tests { + use super::{from_yaml, Roadmap, Status, Step}; + + #[test] + fn new_roadmap() { + let roadmap = Roadmap::new(); + assert_eq!(roadmap.step_names().count(), 0); + } + + #[test] + fn add_step_to_roadmap() { + let mut roadmap = Roadmap::new(); + let first = Step::new("first", "the first step"); + roadmap.add_step(&first).unwrap(); + let names: Vec<&str> = roadmap.step_names().collect(); + assert_eq!(names, vec!["first"]); + } + + #[test] + fn get_step_from_roadmap() { + let mut roadmap = Roadmap::new(); + let first = Step::new("first", "the first step"); + roadmap.add_step(&first).unwrap(); + let gotit = roadmap.get_step("first").unwrap(); + assert_eq!(gotit.name(), "first"); + assert_eq!(gotit.label(), "the first step"); + } + + #[test] + fn set_missing_goal_status() { + let mut r = from_yaml( + " +goal: + depends: + - finished + - blocked + +finished: + status: finished + +ready: + depends: + - finished + +next: + status: next + +blocked: + depends: + - ready + - next +", + ) + .unwrap(); + r.set_missing_statuses(); + assert_eq!(r.get_step("goal").unwrap().status(), Status::Goal); + assert_eq!(r.get_step("finished").unwrap().status(), Status::Finished); + assert_eq!(r.get_step("ready").unwrap().status(), Status::Ready); + assert_eq!(r.get_step("next").unwrap().status(), Status::Next); + assert_eq!(r.get_step("blocked").unwrap().status(), Status::Blocked); + } + + #[test] + fn empty_dot() { + let roadmap = Roadmap::new(); + assert_eq!( + roadmap.as_dot(999).unwrap(), + "digraph \"roadmap\" { +} +" + ); + } + + #[test] + fn simple_dot() { + let mut roadmap = Roadmap::new(); + let mut first = Step::new("first", ""); + first.set_status(Status::Ready); + let mut second = Step::new("second", ""); + second.add_dependency("first"); + second.set_status(Status::Goal); + roadmap.add_step(&first).unwrap(); + roadmap.add_step(&second).unwrap(); + assert_eq!( + roadmap.as_dot(999).unwrap(), + "digraph \"roadmap\" { +first [label=\"\" style=filled fillcolor=\"#ffffff\" shape=\"ellipse\"]; +second [label=\"\" style=filled fillcolor=\"#00eeee\" shape=\"diamond\"]; +first -> second; +} +" + ); + } +} diff --git a/src/roadmap.rs b/src/roadmap.rs deleted file mode 100644 index d131e7d..0000000 --- a/src/roadmap.rs +++ /dev/null @@ -1,271 +0,0 @@ -use textwrap::fill; - -pub use crate::from_yaml; -pub use crate::Status; -pub use crate::Step; - -/// All the steps to get to the end goal. -#[derive(Clone,Debug)] -pub struct Roadmap { - steps: Vec, -} - -impl Roadmap { - /// Create a new, empty roadmap. - pub fn new() -> Roadmap { - Roadmap { steps: vec![] } - } - - /// Count number of steps that nothing depends on. - pub fn count_goals(&self) -> usize { - self.steps - .iter() - .filter(|step| self.is_goal(step)) - .count() - } - - /// Return list of step names. - pub fn step_names(&self) -> impl Iterator { - self.steps.iter().map(|step| step.name()) - } - - /// Get a step, given its name. - pub fn get_step(&self, name: &str) -> Option<&Step> { - self.steps.iter().filter(|step| step.name() == name).next() - } - - /// Add a step to the roadmap. This may fail, if there's a step - /// with that name already. - pub fn add_step(&mut self, step: &Step) -> Result<(), Box> { - self.steps.push(step.clone()); - Ok(()) - } - - // Get iterator over refs to steps. - pub fn iter(&self) -> impl Iterator { - self.steps.iter() - } - - // Get iterator over mut refs to steps. - pub fn iter_mut(&mut self) -> impl Iterator { - self.steps.iter_mut() - } - - /// Compute status of any step for which it has not been specified - /// in the input. - pub fn set_missing_statuses(&mut self) { - let new_steps: Vec = self - .steps - .iter() - .map(|step| { - let mut step = step.clone(); - if self.is_unset(&step) { - if self.is_goal(&step) { - step.set_status(Status::Goal); - } else if self.is_blocked(&step) { - step.set_status(Status::Blocked); - } else if self.is_ready(&step) { - step.set_status(Status::Ready); - } - } - step - }) - .collect(); - - if self.steps != new_steps { - self.steps = new_steps; - self.set_missing_statuses(); - } - } - - /// Is status unset? - pub fn is_unset(&self, step: &Step) -> bool { - step.status() == Status::Unknown - } - - /// Should unset status be ready? In other words, if there are any - /// dependencies, they are all finished. - pub fn is_ready(&self, step: &Step) -> bool { - self.dep_statuses(step) - .iter() - .all(|&status| status == Status::Finished) - } - - /// Should unset status be blocked? In other words, if there are - /// any dependencies, that aren't finished. - pub fn is_blocked(&self, step: &Step) -> bool { - self.dep_statuses(step) - .iter() - .any(|&status| status != Status::Finished) - } - - // Return vector of all statuses of all dependencies - fn dep_statuses(&self, step: &Step) -> Vec<&Status> { - step.dependencies() - .map(|depname| { - if let Some(step) = self.get_step(depname) { - step.status() - } else { - &Status::Unknown - } - }) - .collect() - } - - /// Should status be goal? In other words, does any other step - /// depend on this one? - pub fn is_goal(&self, step: &Step) -> bool { - let has_parent = self.steps.iter().any(|other| other.depends_on(step.name())); - !has_parent - } - - /// Get a Graphviz dot language representation of a roadmap. This - /// is the textual representation, and the caller needs to use the - /// Graphviz dot(1) tool to create an image from it. - pub fn as_dot(&self, label_width: usize) -> Result> { - let labels = self.steps.iter().map(|step| { - format!( - "{} [label=\"{}\" style=filled fillcolor=\"{}\" shape=\"{}\"];\n", - step.name(), - fill(&step.label(), label_width).replace("\n", "\\n"), - Roadmap::get_status_color(step), - Roadmap::get_status_shape(step), - ) - }); - - let mut dot = String::new(); - dot.push_str("digraph \"roadmap\" {\n"); - for line in labels { - dot.push_str(&line); - } - - for step in self.iter() { - for dep in step.dependencies() { - let line = format!("{} -> {};\n", dep, step.name()); - dot.push_str(&line); - } - } - - dot.push_str("}\n"); - - Ok(dot) - } - - fn get_status_color(step: &Step) -> &str { - match step.status() { - Status::Blocked => "#f4bada", - Status::Finished => "#eeeeee", - Status::Ready => "#ffffff", - Status::Next => "#0cc00", - Status::Goal => "#00eeee", - Status::Unknown => "#ff0000", - } - } - - fn get_status_shape(step: &Step) -> &str { - match step.status() { - Status::Blocked => "rectangle", - Status::Finished => "circle", - Status::Ready => "ellipse", - Status::Next => "ellipse", - Status::Goal => "diamond", - Status::Unknown => "house", - } - } -} - -#[cfg(test)] -mod tests { - use super::{from_yaml, Roadmap, Status, Step}; - - #[test] - fn new_roadmap() { - let roadmap = Roadmap::new(); - assert_eq!(roadmap.step_names().count(), 0); - } - - #[test] - fn add_step_to_roadmap() { - let mut roadmap = Roadmap::new(); - let first = Step::new("first", "the first step"); - roadmap.add_step(&first).unwrap(); - let names: Vec<&str> = roadmap.step_names().collect(); - assert_eq!(names, vec!["first"]); - } - - #[test] - fn get_step_from_roadmap() { - let mut roadmap = Roadmap::new(); - let first = Step::new("first", "the first step"); - roadmap.add_step(&first).unwrap(); - let gotit = roadmap.get_step("first").unwrap(); - assert_eq!(gotit.name(), "first"); - assert_eq!(gotit.label(), "the first step"); - } - - #[test] - fn set_missing_goal_status() { - let mut r = from_yaml( - " -goal: - depends: - - finished - - blocked - -finished: - status: finished - -ready: - depends: - - finished - -next: - status: next - -blocked: - depends: - - ready - - next -", - ) - .unwrap(); - r.set_missing_statuses(); - assert_eq!(r.get_step("goal").unwrap().status(), Status::Goal); - assert_eq!(r.get_step("finished").unwrap().status(), Status::Finished); - assert_eq!(r.get_step("ready").unwrap().status(), Status::Ready); - assert_eq!(r.get_step("next").unwrap().status(), Status::Next); - assert_eq!(r.get_step("blocked").unwrap().status(), Status::Blocked); - } - - #[test] - fn empty_dot() { - let roadmap = Roadmap::new(); - assert_eq!( - roadmap.as_dot(999).unwrap(), - "digraph \"roadmap\" { -} -" - ); - } - - #[test] - fn simple_dot() { - let mut roadmap = Roadmap::new(); - let mut first = Step::new("first", ""); - first.set_status(Status::Ready); - let mut second = Step::new("second", ""); - second.add_dependency("first"); - second.set_status(Status::Goal); - roadmap.add_step(&first).unwrap(); - roadmap.add_step(&second).unwrap(); - assert_eq!( - roadmap.as_dot(999).unwrap(), - "digraph \"roadmap\" { -first [label=\"\" style=filled fillcolor=\"#ffffff\" shape=\"ellipse\"]; -second [label=\"\" style=filled fillcolor=\"#00eeee\" shape=\"diamond\"]; -first -> second; -} -" - ); - } -} -- cgit v1.2.1