diff options
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | legend.svg | 96 | ||||
-rw-r--r-- | src/bin/roadmap2dot.rs | 4 | ||||
-rw-r--r-- | src/lib.rs | 49 |
4 files changed, 83 insertions, 67 deletions
@@ -9,3 +9,4 @@ edition = "2018" [dependencies] serde_yaml = "0.8.9" structopt = "0.3" +textwrap = "0.11.0" @@ -9,70 +9,70 @@ <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 343.2864)"> <title>roadmap</title> <polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-343.2864 521.5757,-343.2864 521.5757,4 -4,4"/> -<!-- ready --> +<!-- goal --> <g id="node1" class="node"> -<title>ready</title> -<ellipse fill="none" stroke="#000000" cx="90.5097" cy="-301.8097" rx="90.5193" ry="37.4533"/> -<text text-anchor="middle" x="90.5097" y="-313.1097" font-family="Times,serif" font-size="14.00" fill="#000000">This task is ready </text> -<text text-anchor="middle" x="90.5097" y="-298.1097" 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="-283.1097" font-family="Times,serif" font-size="14.00" fill="#000000">blocked by anything</text> -</g> -<!-- blocked --> -<g id="node4" class="node"> -<title>blocked</title> -<ellipse fill="none" stroke="#000000" cx="188.5097" cy="-180.2498" rx="98.9899" ry="37.4533"/> -<text text-anchor="middle" x="188.5097" y="-191.5498" font-family="Times,serif" font-size="14.00" fill="#000000">This task is blocked</text> -<text text-anchor="middle" x="188.5097" y="-176.5498" 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="-161.5498" 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="M119.3829,-265.9952C129.955,-252.8814 142.0401,-237.8909 153.1175,-224.1505"/> -<polygon fill="#000000" stroke="#000000" points="155.9527,-226.2102 159.5042,-216.2284 150.5031,-221.8168 155.9527,-226.2102"/> +<title>goal</title> +<ellipse fill="none" stroke="#000000" cx="217.066" cy="-48.0833" rx="98.0761" ry="48.1667"/> +<text text-anchor="middle" x="217.066" y="-66.8833" font-family="Times,serif" font-size="14.00" fill="#000000">This is the end goal:</text> +<text text-anchor="middle" x="217.066" y="-51.8833" font-family="Times,serif" font-size="14.00" fill="#000000">if we reach here, there</text> +<text text-anchor="middle" x="217.066" y="-36.8833" font-family="Times,serif" font-size="14.00" fill="#000000">is nothing more to be</text> +<text text-anchor="middle" x="217.066" y="-21.8833" font-family="Times,serif" font-size="14.00" fill="#000000">done in the project</text> </g> <!-- finished --> <g id="node2" class="node"> <title>finished</title> -<ellipse fill="none" stroke="#000000" cx="411.5097" cy="-180.2498" rx="106.1321" ry="48.1667"/> -<text text-anchor="middle" x="411.5097" y="-199.0498" font-family="Times,serif" font-size="14.00" fill="#000000">This task is finished;</text> -<text text-anchor="middle" x="411.5097" y="-184.0498" font-family="Times,serif" font-size="14.00" fill="#000000">the arrow indicates what</text> -<text text-anchor="middle" x="411.5097" y="-169.0498" font-family="Times,serif" font-size="14.00" fill="#000000">follows this task (unless</text> -<text text-anchor="middle" x="411.5097" y="-154.0498" font-family="Times,serif" font-size="14.00" fill="#000000">it's blocked)</text> -</g> -<!-- goal --> -<g id="node3" class="node"> -<title>goal</title> -<ellipse fill="none" stroke="#000000" cx="299.5097" cy="-48.0833" rx="98.0761" ry="48.1667"/> -<text text-anchor="middle" x="299.5097" y="-66.8833" font-family="Times,serif" font-size="14.00" fill="#000000">This is the end goal:</text> -<text text-anchor="middle" x="299.5097" y="-51.8833" font-family="Times,serif" font-size="14.00" fill="#000000">if we reach here, there</text> -<text text-anchor="middle" x="299.5097" y="-36.8833" font-family="Times,serif" font-size="14.00" fill="#000000">is nothing more to be</text> -<text text-anchor="middle" x="299.5097" y="-21.8833" font-family="Times,serif" font-size="14.00" fill="#000000">done in the project</text> +<ellipse fill="none" stroke="#000000" cx="106.066" cy="-180.2498" rx="106.1321" ry="48.1667"/> +<text text-anchor="middle" x="106.066" y="-199.0498" font-family="Times,serif" font-size="14.00" fill="#000000">This task is finished;</text> +<text text-anchor="middle" x="106.066" y="-184.0498" font-family="Times,serif" font-size="14.00" fill="#000000">the arrow indicates what</text> +<text text-anchor="middle" x="106.066" y="-169.0498" font-family="Times,serif" font-size="14.00" fill="#000000">follows this task (unless</text> +<text text-anchor="middle" x="106.066" y="-154.0498" 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="M373.2789,-135.1352C363.805,-123.9555 353.5742,-111.8826 343.8363,-100.3913"/> -<polygon fill="#000000" stroke="#000000" points="346.324,-97.9131 337.1887,-92.5467 340.9836,-102.4387 346.324,-97.9131"/> -</g> -<!-- blocked->goal --> -<g id="edge2" class="edge"> -<title>blocked->goal</title> -<path fill="none" stroke="#000000" d="M218.5479,-144.4836C229.8159,-131.0669 242.8717,-115.5215 255.1657,-100.8832"/> -<polygon fill="#000000" stroke="#000000" points="258.15,-102.772 261.9011,-92.8634 252.7896,-98.2701 258.15,-102.772"/> +<path fill="none" stroke="#000000" d="M143.9555,-135.1352C153.2427,-124.0771 163.2638,-112.145 172.8203,-100.7662"/> +<polygon fill="#000000" stroke="#000000" points="175.5962,-102.9031 179.3473,-92.9945 170.2359,-98.4012 175.5962,-102.9031"/> </g> <!-- next --> -<g id="node5" class="node"> +<g id="node3" class="node"> <title>next</title> -<ellipse fill="none" stroke="#000000" cx="286.5097" cy="-301.8097" rx="87.8629" ry="26.7407"/> -<text text-anchor="middle" x="286.5097" y="-305.6097" font-family="Times,serif" font-size="14.00" fill="#000000">This task is chosen </text> -<text text-anchor="middle" x="286.5097" y="-290.6097" font-family="Times,serif" font-size="14.00" fill="#000000">to be done next</text> +<ellipse fill="none" stroke="#000000" cx="231.066" cy="-301.8097" rx="87.8629" ry="26.7407"/> +<text text-anchor="middle" x="231.066" y="-305.6097" font-family="Times,serif" font-size="14.00" fill="#000000">This task is chosen </text> +<text text-anchor="middle" x="231.066" y="-290.6097" font-family="Times,serif" font-size="14.00" fill="#000000">to be done next</text> +</g> +<!-- blocked --> +<g id="node4" class="node"> +<title>blocked</title> +<ellipse fill="none" stroke="#000000" cx="329.066" cy="-180.2498" rx="98.9899" ry="37.4533"/> +<text text-anchor="middle" x="329.066" y="-191.5498" font-family="Times,serif" font-size="14.00" fill="#000000">This task is blocked</text> +<text text-anchor="middle" x="329.066" y="-176.5498" font-family="Times,serif" font-size="14.00" fill="#000000">and can't be done until</text> +<text text-anchor="middle" x="329.066" y="-161.5498" font-family="Times,serif" font-size="14.00" fill="#000000">something happens</text> </g> <!-- next->blocked --> <g id="edge4" class="edge"> <title>next->blocked</title> -<path fill="none" stroke="#000000" d="M265.2451,-275.433C253.2474,-260.551 237.9419,-241.5659 224.1613,-224.4724"/> -<polygon fill="#000000" stroke="#000000" points="226.5411,-221.8476 217.5399,-216.2592 221.0915,-226.241 226.5411,-221.8476"/> +<path fill="none" stroke="#000000" d="M252.3306,-275.433C264.3282,-260.551 279.6337,-241.5659 293.4144,-224.4724"/> +<polygon fill="#000000" stroke="#000000" points="296.4842,-226.241 300.0357,-216.2592 291.0346,-221.8476 296.4842,-226.241"/> +</g> +<!-- blocked->goal --> +<g id="edge2" class="edge"> +<title>blocked->goal</title> +<path fill="none" stroke="#000000" d="M298.7572,-144.4836C287.3877,-131.0669 274.2143,-115.5215 261.8095,-100.8832"/> +<polygon fill="#000000" stroke="#000000" points="264.1487,-98.2297 255.0134,-92.8634 258.8083,-102.7553 264.1487,-98.2297"/> +</g> +<!-- ready --> +<g id="node5" class="node"> +<title>ready</title> +<ellipse fill="none" stroke="#000000" cx="427.066" cy="-301.8097" rx="90.5193" ry="37.4533"/> +<text text-anchor="middle" x="427.066" y="-313.1097" font-family="Times,serif" font-size="14.00" fill="#000000">This task is ready </text> +<text text-anchor="middle" x="427.066" y="-298.1097" font-family="Times,serif" font-size="14.00" fill="#000000">to be done: it is not</text> +<text text-anchor="middle" x="427.066" y="-283.1097" font-family="Times,serif" font-size="14.00" fill="#000000">blocked by anything</text> +</g> +<!-- ready->blocked --> +<g id="edge3" class="edge"> +<title>ready->blocked</title> +<path fill="none" stroke="#000000" d="M398.1928,-265.9952C387.6207,-252.8814 375.5356,-237.8909 364.4582,-224.1505"/> +<polygon fill="#000000" stroke="#000000" points="367.0726,-221.8168 358.0715,-216.2284 361.623,-226.2102 367.0726,-221.8168"/> </g> </g> </svg> diff --git a/src/bin/roadmap2dot.rs b/src/bin/roadmap2dot.rs index 4e40bc6..16ec0f0 100644 --- a/src/bin/roadmap2dot.rs +++ b/src/bin/roadmap2dot.rs @@ -17,6 +17,8 @@ use std::path::PathBuf; use std::fs::File; use std::io::Read; +const LABEL_WIDTH: usize = 30; + #[derive(Debug, StructOpt)] #[structopt(name = "roadmap2dot", about = "Create a dot graph of a roadmap in YAML")] struct Opt { @@ -33,7 +35,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { f.read_to_string(&mut text)?; let r = Roadmap::from_yaml(&text)?; - println!("{}", r.as_dot().unwrap()); + println!("{}", r.as_dot(LABEL_WIDTH).unwrap()); Ok(()) } @@ -24,14 +24,15 @@ //! assert_eq!(n.len(), 2); //! assert!(n.contains(&"first")); //! assert!(n.contains(&"endgoal")); -//! +//! //! # Ok(()) //! # } //! ``` -use std::collections::HashMap; use serde_yaml; use serde_yaml::Value; +use std::collections::HashMap; +use textwrap::fill; /// A step in a roadmap. #[derive(Clone)] @@ -62,7 +63,7 @@ impl Step { } /// Return vector of names of dependencies for a step. - pub fn dependencies(&self) -> impl Iterator<Item=&String> { + pub fn dependencies(&self) -> impl Iterator<Item = &String> { self.depends.iter() } @@ -115,7 +116,7 @@ impl Roadmap { return Ok(step); } Ok(Step::new(name, "")) - }, + } _ => Err("step is not a mapping"), } } @@ -147,22 +148,26 @@ impl Roadmap { } // Get iterator over refs to steps. - pub fn iter(&self) -> impl Iterator<Item=&Step> { + pub fn iter(&self) -> impl Iterator<Item = &Step> { self.steps.iter() } // Get iterator over mut refs to steps. - pub fn iter_mut(&mut self) -> impl Iterator<Item=&mut Step> { + pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Step> { self.steps.iter_mut() } /// 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) -> Result<String, Box<dyn std::error::Error>> { - let labels = self.steps.iter().map(|step| - format!("{} [label=\"{}\"];\n", step.name(), step.label()) - ); + pub fn as_dot(self, label_width: usize) -> Result<String, Box<dyn std::error::Error>> { + let labels = self.steps.iter().map(|step| { + format!( + "{} [label=\"{}\"];\n", + step.name(), + fill(&step.label(), label_width).replace("\n", "\\n") + ) + }); let mut dot = String::new(); dot.push_str("digraph \"roadmap\" {\n"); @@ -183,10 +188,9 @@ impl Roadmap { } } - #[cfg(test)] mod tests { - use super::{Roadmap,Step}; + use super::{Roadmap, Step}; #[test] fn new_step() { @@ -232,9 +236,12 @@ mod tests { #[test] fn empty_dot() { let roadmap = Roadmap::new(); - assert_eq!(roadmap.as_dot().unwrap(), "digraph \"roadmap\" { + assert_eq!( + roadmap.as_dot(999).unwrap(), + "digraph \"roadmap\" { } -"); +" + ); } #[test] @@ -245,12 +252,15 @@ mod tests { second.add_dependency("first"); roadmap.add_step(&first).unwrap(); roadmap.add_step(&second).unwrap(); - assert_eq!(roadmap.as_dot().unwrap(), "digraph \"roadmap\" { + assert_eq!( + roadmap.as_dot(999).unwrap(), + "digraph \"roadmap\" { first [label=\"\"]; second [label=\"\"]; first -> second; } -"); +" + ); } #[test] @@ -261,14 +271,17 @@ first -> second; #[test] fn from_nonempty_yaml() { - let roadmap = Roadmap::from_yaml(" + let roadmap = Roadmap::from_yaml( + " first: label: the first step second: label: the second step depends: - first -").unwrap(); +", + ) + .unwrap(); let names = roadmap.step_names(); assert_eq!(names.len(), 2); |