summaryrefslogtreecommitdiff
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
parentf01608de3b69d9a647ffdb4ca86a447d553a111f (diff)
downloadroadmap-52c6b4372ee27843443af88eee561b8e14bd7b76.tar.gz
Add: set_missing_statuses to set status of steps not set in input
-rw-r--r--legend.svg92
-rw-r--r--legend.yaml3
-rw-r--r--src/bin/roadmap2dot.rs10
-rw-r--r--src/lib.rs102
4 files changed, 149 insertions, 58 deletions
diff --git a/legend.svg b/legend.svg
index d148406..b970dfe 100644
--- a/legend.svg
+++ b/legend.svg
@@ -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&#39;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&#39;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&#45;&gt;blocked -->
+<g id="edge3" class="edge">
+<title>ready&#45;&gt;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&#45;&gt;blocked -->
<g id="edge4" class="edge">
<title>next&#45;&gt;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&#39;s blocked)</text>
-</g>
-<!-- finished&#45;&gt;goal -->
-<g id="edge1" class="edge">
-<title>finished&#45;&gt;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&#45;&gt;goal -->
<g id="edge2" class="edge">
<title>blocked&#45;&gt;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&#39;s blocked)</text>
</g>
-<!-- ready&#45;&gt;blocked -->
-<g id="edge3" class="edge">
-<title>ready&#45;&gt;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&#45;&gt;goal -->
+<g id="edge1" class="edge">
+<title>finished&#45;&gt;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(())
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!(