From 2ee090748a209a621460e8616105bfdb9c8e853e Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 12 Oct 2019 11:37:21 +0300 Subject: Change: finished steps are now octagons, circles were too big --- legend.svg | 92 +++++++++++++++++++++++++++++++------------------------------- src/map.rs | 2 +- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/legend.svg b/legend.svg index a7856d6..645c64d 100644 --- a/legend.svg +++ b/legend.svg @@ -4,75 +4,75 @@ - - + + roadmap - - + + +goal + +This is the end goal: +if we reach here, there +is nothing more to be +done in the project + + + +finished + +This task is finished; +the arrow indicates what +follows this task (unless +it's blocked) + + + +finished->goal + + + + + next - -This task is chosen -to be done next + +This task is chosen +to be done next blocked - -This task is blocked -and can't be done until -something happens + +This task is blocked +and can't be done until +something happens next->blocked - - - - - -finished - -This task is finished; -the arrow indicates what -follows this task (unless -it's blocked) - - - -goal - -This is the end goal: -if we reach here, there -is nothing more to be -done in the project - - - -finished->goal - - + + blocked->goal - - + + ready - -This task is ready -to be done: it is not -blocked by anything + +This task is ready +to be done: it is not +blocked by anything ready->blocked - - + + diff --git a/src/map.rs b/src/map.rs index be81216..ff7e4f9 100644 --- a/src/map.rs +++ b/src/map.rs @@ -191,7 +191,7 @@ impl Roadmap { fn get_status_shape(step: &Step) -> &str { match step.status() { Status::Blocked => "rectangle", - Status::Finished => "circle", + Status::Finished => "octagon", Status::Ready => "ellipse", Status::Next => "ellipse", Status::Goal => "diamond", -- cgit v1.2.1 From 7245b6aba123b6497e39c298400a778a26dbcd60 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 12 Oct 2019 11:53:16 +0300 Subject: Change: update legend.svg --- legend.svg | 92 +++++++++++++++++++++++++++++++------------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/legend.svg b/legend.svg index 645c64d..b358188 100644 --- a/legend.svg +++ b/legend.svg @@ -4,75 +4,75 @@ - + roadmap - + goal - -This is the end goal: -if we reach here, there -is nothing more to be -done in the project - - - -finished - -This task is finished; -the arrow indicates what -follows this task (unless -it's blocked) - - - -finished->goal - - + +This is the end goal: +if we reach here, there +is nothing more to be +done in the project - + next - -This task is chosen -to be done next + +This task is chosen +to be done next blocked - -This task is blocked -and can't be done until -something happens + +This task is blocked +and can't be done until +something happens next->blocked - - - - - -blocked->goal - - + + - + ready - -This task is ready -to be done: it is not -blocked by anything + +This task is ready +to be done: it is not +blocked by anything ready->blocked - - + + + + + +blocked->goal + + + + + +finished + +This task is finished; +the arrow indicates what +follows this task (unless +it's blocked) + + + +finished->goal + + -- cgit v1.2.1 From 57dcf6f8da0060d29b96cb542a9f679ae57d10f8 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 12 Oct 2019 11:54:07 +0300 Subject: Change: update version number --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7ada85b..771bd52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "roadmap" -version = "0.1.0" +version = "0.1.1" authors = ["Lars Wirzenius "] edition = "2018" license = "GPL-3.0-or-later" -- cgit v1.2.1 From 97654c1795dfb9e84792713911475310e4bc1a86 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Fri, 20 Mar 2020 08:25:39 +0200 Subject: Refactor: add Roadmap.goals --- src/map.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/map.rs b/src/map.rs index ff7e4f9..77b2feb 100644 --- a/src/map.rs +++ b/src/map.rs @@ -25,9 +25,14 @@ impl Roadmap { Roadmap { steps: vec![] } } + // Find steps that nothing depends on. + fn goals(&self) -> Vec<&Step> { + self.steps.iter().filter(|step| self.is_goal(step)).collect() + } + /// Count number of steps that nothing depends on. pub fn count_goals(&self) -> usize { - self.steps.iter().filter(|step| self.is_goal(step)).count() + self.goals().len() } /// Iterate over step names. -- cgit v1.2.1 From dcbcbd9457d4bd4d21bcf8dbcc9e416fff463d92 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Fri, 20 Mar 2020 08:46:16 +0200 Subject: Change: make error message for wrong number of goals more helpful --- legend.yaml | 5 +++++ src/map.rs | 17 ++++++++++++++--- src/step.rs | 7 +++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/legend.yaml b/legend.yaml index fc17ba8..951d369 100644 --- a/legend.yaml +++ b/legend.yaml @@ -1,3 +1,8 @@ +goal2: {} +goal3: {} +goal4: {} +goal5: {} + goal: label: | This is the end goal: diff --git a/src/map.rs b/src/map.rs index 77b2feb..c1294bf 100644 --- a/src/map.rs +++ b/src/map.rs @@ -27,7 +27,10 @@ impl Roadmap { // Find steps that nothing depends on. fn goals(&self) -> Vec<&Step> { - self.steps.iter().filter(|step| self.is_goal(step)).collect() + self.steps + .iter() + .filter(|step| self.is_goal(step)) + .collect() } /// Count number of steps that nothing depends on. @@ -125,10 +128,18 @@ impl Roadmap { // Validate that the parsed, constructed roadmap is valid. pub fn validate(&self) -> RoadmapResult<()> { // Is there exactly one goal? - match self.count_goals() { + let goals = self.goals(); + let n = goals.len(); + match n { 0 => return Err(format!("the roadmap doesn't have a goal").into()), 1 => (), - _ => return Err(format!("must have exactly one goal for roadmap").into()), + _ => { + let mut names: Vec<&str> = goals.iter().map(|s| s.name()).collect(); + names.sort(); + let names = names.join(", "); + let msg = format!("only one goal allowed, found {}: {}", n, names); + return Err(msg.into()); + } } // Does every dependency exist? diff --git a/src/step.rs b/src/step.rs index 1ed495b..92b8eec 100644 --- a/src/step.rs +++ b/src/step.rs @@ -1,4 +1,5 @@ use super::Status; +use std::fmt; /// A roadmap step. /// @@ -62,6 +63,12 @@ impl Step { } } +impl fmt::Display for Step { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } +} + #[cfg(test)] mod tests { use super::{Status, Step}; -- cgit v1.2.1 From 5e5891771efd0530b1e648c1acb6d673b032e432 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Fri, 20 Mar 2020 09:27:36 +0200 Subject: Change: use anyhow and thiserror for error handling --- Cargo.toml | 2 ++ legend.yaml | 5 ----- src/bin/roadmap2dot.rs | 3 ++- src/err.rs | 33 +++++++++++++++++++++++++++++++++ src/lib.rs | 3 +++ src/map.rs | 26 +++++++++++++------------- src/parser.rs | 12 ++++++------ 7 files changed, 59 insertions(+), 25 deletions(-) create mode 100644 src/err.rs diff --git a/Cargo.toml b/Cargo.toml index 771bd52..3a6827a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,5 @@ description = "model a project roadmap as a directed acyclic graph" serde_yaml = "0.8.9" structopt = "0.3" textwrap = "0.11.0" +thiserror = "1.0" +anyhow = "1.0" diff --git a/legend.yaml b/legend.yaml index 951d369..fc17ba8 100644 --- a/legend.yaml +++ b/legend.yaml @@ -1,8 +1,3 @@ -goal2: {} -goal3: {} -goal4: {} -goal5: {} - goal: label: | This is the end goal: diff --git a/src/bin/roadmap2dot.rs b/src/bin/roadmap2dot.rs index 244a300..b180da8 100644 --- a/src/bin/roadmap2dot.rs +++ b/src/bin/roadmap2dot.rs @@ -16,6 +16,7 @@ use std::fs::File; use std::io::Read; use std::path::PathBuf; use structopt::StructOpt; +use anyhow::Result; const LABEL_WIDTH: usize = 30; @@ -30,7 +31,7 @@ struct Opt { filename: PathBuf, } -fn main() -> Result<(), Box> { +fn main() -> Result<()> { let opt = Opt::from_args(); let mut text = String::new(); let mut f = File::open(opt.filename)?; diff --git a/src/err.rs b/src/err.rs new file mode 100644 index 0000000..6a224a1 --- /dev/null +++ b/src/err.rs @@ -0,0 +1,33 @@ +use serde_yaml; +use thiserror::Error; + +/// Errors that can be returned for roadmaps. +#[derive(Error, Debug)] +pub enum RoadmapError { + #[error("roadmap has no goals, must have exactly one")] + NoGoals, + + #[error("too many goals, must have exactly one: found {count:}: {}", .names.join(", "))] + ManyGoals { + count: usize, + names: Vec, + }, + + #[error("step {name:} depends on missing {missing:}")] + MissingDep { + name: String, + missing: String, + }, + + #[error("step is not a mapping")] + StepNotMapping, + + #[error("'depends' must be a list of step names")] + DependsNotNames, + + #[error("unknown status: {0}")] + UnknownStatus(String), + + #[error(transparent)] + SerdeError(#[from] serde_yaml::Error), +} diff --git a/src/lib.rs b/src/lib.rs index d3f672b..63497bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,9 @@ //! # } //! ``` +mod err; +pub use err::RoadmapError; + mod status; pub use status::Status; diff --git a/src/map.rs b/src/map.rs index c1294bf..671318e 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,12 +1,12 @@ -use std::error::Error; use textwrap::fill; pub use crate::from_yaml; +pub use crate::RoadmapError; pub use crate::Status; pub use crate::Step; /// Error in Roadmap, from parsing or otherwise. -pub type RoadmapResult = Result>; +pub type RoadmapResult = Result; /// Represent a full project roadmap. /// @@ -131,14 +131,14 @@ impl Roadmap { let goals = self.goals(); let n = goals.len(); match n { - 0 => return Err(format!("the roadmap doesn't have a goal").into()), + 0 => return Err(RoadmapError::NoGoals), 1 => (), _ => { - let mut names: Vec<&str> = goals.iter().map(|s| s.name()).collect(); - names.sort(); - let names = names.join(", "); - let msg = format!("only one goal allowed, found {}: {}", n, names); - return Err(msg.into()); + let names: Vec = goals.iter().map(|s| s.name().into()).collect(); + return Err(RoadmapError::ManyGoals { + count: n, + names: names, + }); } } @@ -146,11 +146,11 @@ impl Roadmap { for step in self.iter() { for depname in step.dependencies() { match self.get_step(depname) { - None => { - return Err( - format!("step {} depends on missing {}", step.name(), depname).into(), - ) - } + None => + return Err(RoadmapError::MissingDep { + name: step.name().into(), + missing: depname.into(), + }), Some(_) => (), } } diff --git a/src/parser.rs b/src/parser.rs index 7da2254..7602b94 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,6 +3,7 @@ use serde_yaml::Value; use std::collections::HashMap; pub use crate::Roadmap; +pub use crate::RoadmapError; pub use crate::RoadmapResult; pub use crate::Status; pub use crate::Step; @@ -37,7 +38,7 @@ fn step_from_value(name: &str, value: &Value) -> RoadmapResult { Ok(step) } - _ => Err("step is not a mapping".to_string().into()), + _ => Err(RoadmapError::StepNotMapping), } } @@ -46,19 +47,18 @@ fn parse_depends(map: &Value) -> RoadmapResult> { let key_name = "depends"; let key = Value::String(key_name.to_string()); let mut depends: Vec<&str> = vec![]; - let need_list_of_names = format!("'depends' must be a list of step names"); - + match map.get(&key) { None => (), Some(Value::Sequence(deps)) => { for depname in deps.iter() { match depname { Value::String(depname) => depends.push(depname), - _ => return Err(need_list_of_names.into()), + _ => return Err(RoadmapError::DependsNotNames), } } } - _ => return Err(need_list_of_names.into()), + _ => return Err(RoadmapError::DependsNotNames), } Ok(depends) @@ -74,7 +74,7 @@ fn parse_status<'a>(map: &'a Value) -> RoadmapResult { let text = parse_string("status", map); match Status::from_text(text) { Some(status) => Ok(status), - _ => Err(format!("unknown status: {:?}", text).into()), + _ => Err(RoadmapError::UnknownStatus(text.into())), } } -- cgit v1.2.1