summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2019-10-05 10:44:30 +0300
committerLars Wirzenius <liw@liw.fi>2019-10-05 10:44:30 +0300
commit94a1206617cf3421ae859a67964244d7bec073b0 (patch)
treea6e7a87327c9aabca10bae50aea8410313b1f82d /src
parentd8e084df36507a29b1d6636da55fe5d4c41e6636 (diff)
downloadroadmap-94a1206617cf3421ae859a67964244d7bec073b0.tar.gz
Add: copy of GPL-3
Diffstat (limited to 'src')
-rw-r--r--src/#parser.rs#170
l---------src/.#parser.rs1
2 files changed, 171 insertions, 0 deletions
diff --git a/src/#parser.rs# b/src/#parser.rs#
new file mode 100644
index 0000000..32cdd73
--- /dev/null
+++ b/src/#parser.rs#
@@ -0,0 +1,170 @@
+use serde_yaml;
+use serde_yaml::Value;
+use std::collections::HashMap
+pub use crate::Roadmap;
+pub use crate::RoadmapResult;
+pub use crate::Status;
+pub use crate::Step;
+
+/// Create a new roadmap from a textual YAML representation.
+pub fn from_yaml(yaml: &str) -> RoadmapResult<Roadmap> {
+ let mut roadmap = Roadmap::new();
+ let map: HashMap<String, serde_yaml::Value> = serde_yaml::from_str(yaml)?;
+
+ for (name, value) in map {
+ let step = step_from_value(&name, &value)?;
+ roadmap.add_step(step);
+ }
+
+ roadmap.validate()?;
+ Ok(roadmap)
+}
+
+// Convert a Value into a Step, if possible.
+fn step_from_value(name: &str, value: &Value) -> RoadmapResult<Step> {
+ match value {
+ Value::Mapping(_) => {
+ let label = parse_label(&value);
+ let status = parse_status(&value)?;
+
+ let mut step = Step::new(name, label);
+ step.set_status(status);
+
+ for depname in parse_depends(&value)?.iter() {
+ step.add_dependency(depname);
+ }
+
+ Ok(step)
+ }
+ _ => Err("step is not a mapping".to_string().into()),
+ }
+}
+
+// Get a sequence of depenencies.
+fn parse_depends(map: &Value) -> RoadmapResult<Vec<&str>> {
+ 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(need_list_of_names.into()),
+ }
+
+ Ok(depends)
+}
+
+// Get label string from a Mapping element, or empty string.
+fn parse_label(map: &Value) -> &str {
+ parse_string("label", map)
+}
+
+// Get status string from a Mapping element. Default to unknown status.
+fn parse_status<'a>(map: &'a Value) -> RoadmapResult<Status> {
+ let text = parse_string("status", map);
+ match Status::from_text(text) {
+ Some(status) => Ok(status),
+ _ => Err(format!("unknown status: {:?}", text).into()),
+ }
+}
+
+// Get string value from a Mapping field, or empty string if field
+// isn't there.
+fn parse_string<'a>(key_name: &str, map: &'a Value) -> &'a str {
+ let key = Value::String(key_name.to_string());
+ match map.get(&key) {
+ Some(Value::String(s)) => s,
+ _ => "",
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::from_yaml;
+
+ #[test]
+ fn yaml_is_empty() {
+ let r = from_yaml("");
+ match r {
+ Ok(_) => panic!("expected a parse error"),
+ _ => (),
+ }
+ }
+
+ #[test]
+ fn yaml_is_list() {
+ let r = from_yaml("[]");
+ match r {
+ Ok(_) => panic!("expected a parse error"),
+ _ => (),
+ }
+ }
+
+ #[test]
+ fn yaml_map_entries_not_maps() {
+ let r = from_yaml("foo: []");
+ match r {
+ Ok(_) => panic!("expected a parse error"),
+ _ => (),
+ }
+ }
+
+ #[test]
+ fn yaml_unknown_dep() {
+ let r = from_yaml("foo: {depends: [bar]}");
+ match r {
+ Ok(_) => panic!("expected a parse error"),
+ _ => (),
+ }
+ }
+
+ #[test]
+ fn yaml_unknown_status() {
+ let r = from_yaml(r#"foo: {status: "bar"}"#);
+ match r {
+ Ok(_) => panic!("expected a parse error"),
+ _ => (),
+ }
+ }
+
+ #[test]
+ fn yaml_happy() {
+ let roadmap = from_yaml(
+ "
+first:
+ label: the first step
+second:
+ label: the second step
+ depends:
+ - first
+",
+ )
+ .unwrap();
+
+ let names: Vec<&str> = roadmap.step_names().collect();
+ assert_eq!(names.len(), 2);
+ assert!(names.contains(&"first"));
+ assert!(names.contains(&"second"));
+
+ let first = roadmap.get_step("first").unwrap();
+ assert_eq!(first.name(), "first");
+ assert_eq!(first.label(), "the first step");
+ let deps: Vec<&str> = first.dependencies().collect();
+ assert_eq!(deps.len(), 0);
+
+ let second = roadmap.get_step("second").unwrap();
+ assert_eq!(second.name(), "second");
+ assert_eq!(second.label(), "the second step");
+ let deps: Vec<&str> = second.dependencies().collect();
+ assert_eq!(deps, vec!["first"]);
+ }
+}
diff --git a/src/.#parser.rs b/src/.#parser.rs
new file mode 120000
index 0000000..5d257a5
--- /dev/null
+++ b/src/.#parser.rs
@@ -0,0 +1 @@
+liw@exolobe1.5169:1568966750 \ No newline at end of file