diff options
author | Lars Wirzenius <liw@liw.fi> | 2019-09-24 11:01:46 +0300 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2019-09-24 12:07:05 +0300 |
commit | 52c6b4372ee27843443af88eee561b8e14bd7b76 (patch) | |
tree | 0e9c7a7ef675a5e45daeb41bfdf817f437b15da7 | |
parent | f01608de3b69d9a647ffdb4ca86a447d553a111f (diff) | |
download | roadmap-52c6b4372ee27843443af88eee561b8e14bd7b76.tar.gz |
Add: set_missing_statuses to set status of steps not set in input
-rw-r--r-- | legend.svg | 92 | ||||
-rw-r--r-- | legend.yaml | 3 | ||||
-rw-r--r-- | src/bin/roadmap2dot.rs | 10 | ||||
-rw-r--r-- | src/lib.rs | 102 |
4 files changed, 149 insertions, 58 deletions
@@ -9,70 +9,70 @@ <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 499.0854)"> <title>roadmap</title> <polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-499.0854 492.5757,-499.0854 492.5757,4 -4,4"/> -<!-- goal --> +<!-- ready --> <g id="node1" class="node"> -<title>goal</title> -<polygon fill="#00eeee" stroke="#000000" points="203.066,-136 64.066,-68 203.066,0 342.066,-68 203.066,-136"/> -<text text-anchor="middle" x="203.066" y="-86.8" font-family="Times,serif" font-size="14.00" fill="#000000">This is the end goal:</text> -<text text-anchor="middle" x="203.066" y="-71.8" font-family="Times,serif" font-size="14.00" fill="#000000">if we reach here, there</text> -<text text-anchor="middle" x="203.066" y="-56.8" font-family="Times,serif" font-size="14.00" fill="#000000">is nothing more to be</text> -<text text-anchor="middle" x="203.066" y="-41.8" font-family="Times,serif" font-size="14.00" fill="#000000">done in the project</text> -</g> -<!-- next --> -<g id="node2" class="node"> -<title>next</title> -<ellipse fill="#0cc000" stroke="#000000" cx="202.066" cy="-457.6087" rx="87.8629" ry="26.7407"/> -<text text-anchor="middle" x="202.066" y="-461.4087" font-family="Times,serif" font-size="14.00" fill="#000000">This task is chosen </text> -<text text-anchor="middle" x="202.066" y="-446.4087" font-family="Times,serif" font-size="14.00" fill="#000000">to be done next</text> +<title>ready</title> +<ellipse fill="#ffffff" stroke="#000000" cx="90.5097" cy="-457.6087" rx="90.5193" ry="37.4533"/> +<text text-anchor="middle" x="90.5097" y="-468.9087" font-family="Times,serif" font-size="14.00" fill="#000000">This task is ready </text> +<text text-anchor="middle" x="90.5097" y="-453.9087" font-family="Times,serif" font-size="14.00" fill="#000000">to be done: it is not</text> +<text text-anchor="middle" x="90.5097" y="-438.9087" font-family="Times,serif" font-size="14.00" fill="#000000">blocked by anything</text> </g> <!-- blocked --> <g id="node4" class="node"> <title>blocked</title> -<polygon fill="#f4bada" stroke="#000000" points="370.066,-304.566 230.066,-304.566 230.066,-251.566 370.066,-251.566 370.066,-304.566"/> -<text text-anchor="middle" x="300.066" y="-289.366" font-family="Times,serif" font-size="14.00" fill="#000000">This task is blocked</text> -<text text-anchor="middle" x="300.066" y="-274.366" font-family="Times,serif" font-size="14.00" fill="#000000">and can't be done until</text> -<text text-anchor="middle" x="300.066" y="-259.366" font-family="Times,serif" font-size="14.00" fill="#000000">something happens</text> +<polygon fill="#f4bada" stroke="#000000" points="258.5097,-304.566 118.5097,-304.566 118.5097,-251.566 258.5097,-251.566 258.5097,-304.566"/> +<text text-anchor="middle" x="188.5097" y="-289.366" font-family="Times,serif" font-size="14.00" fill="#000000">This task is blocked</text> +<text text-anchor="middle" x="188.5097" y="-274.366" font-family="Times,serif" font-size="14.00" fill="#000000">and can't be done until</text> +<text text-anchor="middle" x="188.5097" y="-259.366" font-family="Times,serif" font-size="14.00" fill="#000000">something happens</text> +</g> +<!-- ready->blocked --> +<g id="edge3" class="edge"> +<title>ready->blocked</title> +<path fill="none" stroke="#000000" d="M110.5779,-420.8423C127.6802,-389.5098 152.1083,-344.7558 169.0375,-313.7404"/> +<polygon fill="#000000" stroke="#000000" points="172.3152,-315.0405 174.0342,-304.5861 166.1709,-311.6868 172.3152,-315.0405"/> +</g> +<!-- next --> +<g id="node2" class="node"> +<title>next</title> +<ellipse fill="#0cc000" stroke="#000000" cx="286.5097" cy="-457.6087" rx="87.8629" ry="26.7407"/> +<text text-anchor="middle" x="286.5097" y="-461.4087" font-family="Times,serif" font-size="14.00" fill="#000000">This task is chosen </text> +<text text-anchor="middle" x="286.5097" y="-446.4087" font-family="Times,serif" font-size="14.00" fill="#000000">to be done next</text> </g> <!-- next->blocked --> <g id="edge4" class="edge"> <title>next->blocked</title> -<path fill="none" stroke="#000000" d="M216.7376,-430.7293C233.7461,-399.5686 261.8441,-348.0911 280.65,-313.6375"/> -<polygon fill="#000000" stroke="#000000" points="283.7354,-315.29 285.4544,-304.8355 277.5911,-311.9362 283.7354,-315.29"/> +<path fill="none" stroke="#000000" d="M271.8381,-430.7293C254.8296,-399.5686 226.7316,-348.0911 207.9257,-313.6375"/> +<polygon fill="#000000" stroke="#000000" points="210.9846,-311.9362 203.1213,-304.8355 204.8403,-315.29 210.9846,-311.9362"/> </g> -<!-- finished --> +<!-- goal --> <g id="node3" class="node"> -<title>finished</title> -<ellipse fill="#eeeeee" stroke="#000000" cx="106.066" cy="-278.066" rx="106.1321" ry="106.1321"/> -<text text-anchor="middle" x="106.066" y="-296.866" font-family="Times,serif" font-size="14.00" fill="#000000">This task is finished;</text> -<text text-anchor="middle" x="106.066" y="-281.866" font-family="Times,serif" font-size="14.00" fill="#000000">the arrow indicates what</text> -<text text-anchor="middle" x="106.066" y="-266.866" font-family="Times,serif" font-size="14.00" fill="#000000">follows this task (unless</text> -<text text-anchor="middle" x="106.066" y="-251.866" font-family="Times,serif" font-size="14.00" fill="#000000">it's blocked)</text> -</g> -<!-- finished->goal --> -<g id="edge1" class="edge"> -<title>finished->goal</title> -<path fill="none" stroke="#000000" d="M150.5915,-181.6404C158.1804,-165.2057 165.8999,-148.488 173.0002,-133.1114"/> -<polygon fill="#000000" stroke="#000000" points="176.3197,-134.2713 177.3344,-123.7251 169.9645,-131.3367 176.3197,-134.2713"/> +<title>goal</title> +<polygon fill="#00eeee" stroke="#000000" points="285.5097,-136 146.5097,-68 285.5097,0 424.5097,-68 285.5097,-136"/> +<text text-anchor="middle" x="285.5097" y="-86.8" font-family="Times,serif" font-size="14.00" fill="#000000">This is the end goal:</text> +<text text-anchor="middle" x="285.5097" y="-71.8" font-family="Times,serif" font-size="14.00" fill="#000000">if we reach here, there</text> +<text text-anchor="middle" x="285.5097" y="-56.8" font-family="Times,serif" font-size="14.00" fill="#000000">is nothing more to be</text> +<text text-anchor="middle" x="285.5097" y="-41.8" font-family="Times,serif" font-size="14.00" fill="#000000">done in the project</text> </g> <!-- blocked->goal --> <g id="edge2" class="edge"> <title>blocked->goal</title> -<path fill="none" stroke="#000000" d="M287.7809,-251.461C274.0653,-221.7581 251.3723,-172.6136 232.9239,-132.6612"/> -<polygon fill="#000000" stroke="#000000" points="236.0663,-131.1174 228.6964,-123.5058 229.7111,-134.052 236.0663,-131.1174"/> +<path fill="none" stroke="#000000" d="M200.7948,-251.461C214.5104,-221.7581 237.2033,-172.6136 255.6517,-132.6612"/> +<polygon fill="#000000" stroke="#000000" points="258.8646,-134.052 259.8793,-123.5058 252.5094,-131.1174 258.8646,-134.052"/> </g> -<!-- ready --> +<!-- finished --> <g id="node5" class="node"> -<title>ready</title> -<ellipse fill="#ffffff" stroke="#000000" cx="398.066" cy="-457.6087" rx="90.5193" ry="37.4533"/> -<text text-anchor="middle" x="398.066" y="-468.9087" font-family="Times,serif" font-size="14.00" fill="#000000">This task is ready </text> -<text text-anchor="middle" x="398.066" y="-453.9087" font-family="Times,serif" font-size="14.00" fill="#000000">to be done: it is not</text> -<text text-anchor="middle" x="398.066" y="-438.9087" font-family="Times,serif" font-size="14.00" fill="#000000">blocked by anything</text> +<title>finished</title> +<ellipse fill="#eeeeee" stroke="#000000" cx="382.5097" cy="-278.066" rx="106.1321" ry="106.1321"/> +<text text-anchor="middle" x="382.5097" y="-296.866" font-family="Times,serif" font-size="14.00" fill="#000000">This task is finished;</text> +<text text-anchor="middle" x="382.5097" y="-281.866" font-family="Times,serif" font-size="14.00" fill="#000000">the arrow indicates what</text> +<text text-anchor="middle" x="382.5097" y="-266.866" font-family="Times,serif" font-size="14.00" fill="#000000">follows this task (unless</text> +<text text-anchor="middle" x="382.5097" y="-251.866" font-family="Times,serif" font-size="14.00" fill="#000000">it's blocked)</text> </g> -<!-- ready->blocked --> -<g id="edge3" class="edge"> -<title>ready->blocked</title> -<path fill="none" stroke="#000000" d="M377.9978,-420.8423C360.8955,-389.5098 336.4674,-344.7558 319.5382,-313.7404"/> -<polygon fill="#000000" stroke="#000000" points="322.4048,-311.6868 314.5415,-304.5861 316.2605,-315.0405 322.4048,-311.6868"/> +<!-- finished->goal --> +<g id="edge1" class="edge"> +<title>finished->goal</title> +<path fill="none" stroke="#000000" d="M337.9842,-181.6404C330.3953,-165.2057 322.6758,-148.488 315.5755,-133.1114"/> +<polygon fill="#000000" stroke="#000000" points="318.6112,-131.3367 311.2413,-123.7251 312.256,-134.2713 318.6112,-131.3367"/> </g> </g> </svg> diff --git a/legend.yaml b/legend.yaml index e23663c..fc17ba8 100644 --- a/legend.yaml +++ b/legend.yaml @@ -1,5 +1,4 @@ goal: - status: goal label: | This is the end goal: if we reach here, there @@ -18,7 +17,6 @@ finished: it's blocked) ready: - status: ready label: | This task is ready to be done: it is not @@ -31,7 +29,6 @@ next: to be done next blocked: - status: blocked label: | This task is blocked and can't be done until 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(()) @@ -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!( |