summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2019-09-24 11:01:46 +0300
committerLars Wirzenius <liw@liw.fi>2019-09-24 12:07:05 +0300
commit52c6b4372ee27843443af88eee561b8e14bd7b76 (patch)
tree0e9c7a7ef675a5e45daeb41bfdf817f437b15da7 /src
parentf01608de3b69d9a647ffdb4ca86a447d553a111f (diff)
downloadroadmap-52c6b4372ee27843443af88eee561b8e14bd7b76.tar.gz
Add: set_missing_statuses to set status of steps not set in input
Diffstat (limited to 'src')
-rw-r--r--src/bin/roadmap2dot.rs10
-rw-r--r--src/lib.rs102
2 files changed, 103 insertions, 9 deletions
diff --git a/src/bin/roadmap2dot.rs b/src/bin/roadmap2dot.rs
index 2526555..25cbc34 100644
--- a/src/bin/roadmap2dot.rs
+++ b/src/bin/roadmap2dot.rs
@@ -33,15 +33,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut text = String::new();
let mut f = File::open(opt.filename)?;
f.read_to_string(&mut text)?;
-// eprintln!("text:\n{}\n", text);
- let r = Roadmap::from_yaml(&text)?;
-// eprintln!("");
- for name in r.step_names() {
- let step = r.get_step(name).unwrap();
- eprintln!("step {} ({}) status {}", name, step.name(), step.status());
- }
- eprintln!("");
+ let mut r = Roadmap::from_yaml(&text)?;
+ r.set_missing_statuses();
println!("{}", r.as_dot(LABEL_WIDTH).unwrap());
Ok(())
diff --git a/src/lib.rs b/src/lib.rs
index affe137..553a29d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -35,7 +35,7 @@ use std::collections::HashMap;
use textwrap::fill;
/// A step in a roadmap.
-#[derive(Clone)]
+#[derive(Clone,Debug,PartialEq)]
pub struct Step {
name: String,
status: String,
@@ -83,6 +83,11 @@ impl Step {
pub fn add_dependency(&mut self, name: &str) {
self.depends.push(String::from(name));
}
+
+ /// Does this step depend on given other step?
+ pub fn depends_on(&self, other_name: &str) -> bool {
+ self.depends.iter().any(|depname| depname == other_name)
+ }
}
/// All the steps to get to the end goal.
@@ -201,6 +206,70 @@ impl Roadmap {
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<Step> = self.steps.iter()
+ .map(|step| {
+ let mut step = step.clone();
+ if self.is_unset(&step) {
+ if self.is_goal(&step) {
+ step.set_status("goal");
+ } else if self.is_blocked(&step) {
+ step.set_status("blocked");
+ } else if self.is_ready(&step) {
+ step.set_status("ready");
+ }
+ }
+ step
+ })
+ .collect();
+
+ if self.steps != new_steps {
+ self.steps = new_steps;
+ self.set_missing_statuses();
+ }
+ }
+
+ // Is status unset?
+ fn is_unset(&self, step: &Step) -> bool {
+ step.status() == ""
+ }
+
+ // Should unset status be ready? In other words, if there are any
+ // dependencies, they are all finished.
+ fn is_ready(&self, step: &Step) -> bool {
+ self.dep_statuses(step).iter().all(|status| status == &"finished")
+ }
+
+ // Should unset status be blocked? In other words, if there are
+ // any dependencies, that aren't finished.
+ fn is_blocked(&self, step: &Step) -> bool {
+ self.dep_statuses(step).iter().any(|status| status != &"finished")
+ }
+
+ // Return vector of all statuses of all dependencies
+ fn dep_statuses<'a>(&'a self, step: &Step) -> Vec<&'a str> {
+ step.dependencies()
+ .map(|depname| {
+ if let Some(step) = self.get_step(depname) {
+ step.status()
+ } else {
+ &""
+ }
+ })
+ .collect()
+ }
+
+ // Should unset status be goal? In other words, does any other
+ // step depend on this one?
+ 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.
@@ -310,6 +379,37 @@ mod tests {
}
#[test]
+ fn set_missing_goal_status() {
+ let mut r = Roadmap::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(), "goal");
+ assert_eq!(r.get_step("finished").unwrap().status(), "finished");
+ assert_eq!(r.get_step("ready").unwrap().status(), "ready");
+ assert_eq!(r.get_step("next").unwrap().status(), "next");
+ assert_eq!(r.get_step("blocked").unwrap().status(), "blocked");
+ }
+
+ #[test]
fn empty_dot() {
let roadmap = Roadmap::new();
assert_eq!(