diff options
author | Daniel Silverstone <dsilvers@digital-scurf.org> | 2021-08-14 10:48:26 +0100 |
---|---|---|
committer | Daniel Silverstone <dsilvers@digital-scurf.org> | 2021-09-07 17:32:20 +0100 |
commit | d5c69db746b5c4ba938248a7d1c285256c6e6412 (patch) | |
tree | c0945c1eba87cc2702ea574a94c5cfa5ae61762c /src | |
parent | 4436b7ee28b0318a96d98833d85d712c8a18850d (diff) | |
download | subplot-d5c69db746b5c4ba938248a7d1c285256c6e6412.tar.gz |
subplot: Properly support polyglot bindings
Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/subplot.rs | 17 | ||||
-rw-r--r-- | src/bindings.rs | 39 | ||||
-rw-r--r-- | src/codegen.rs | 2 | ||||
-rw-r--r-- | src/doc.rs | 32 | ||||
-rw-r--r-- | src/matches.rs | 24 | ||||
-rw-r--r-- | src/typeset.rs | 2 |
6 files changed, 79 insertions, 37 deletions
diff --git a/src/bin/subplot.rs b/src/bin/subplot.rs index 22eda3b..034cbb2 100644 --- a/src/bin/subplot.rs +++ b/src/bin/subplot.rs @@ -310,7 +310,9 @@ impl Docgen { } let mut doc = cli::load_document(&self.input, style)?; doc.lint()?; - if !doc.check_named_files_exist()? { + let template = doc.meta().template_name().unwrap_or("").to_string(); + if !doc.check_named_files_exist(&template)? || !doc.check_matched_steps_have_impl(&template) + { eprintln!("Continuing despite warnings"); } @@ -408,17 +410,18 @@ impl Codegen { let _enter = span.enter(); let mut doc = cli::load_document(&self.filename, Style::default())?; doc.lint()?; - if !doc.check_named_files_exist()? { - eprintln!("Unable to continue"); - std::process::exit(1); - } - - let spec = template_spec(&doc)?; let template = doc .meta() .template_name() .ok_or_else(|| anyhow::anyhow!("No template name given"))? .to_string(); + if !doc.check_named_files_exist(&template)? || !doc.check_matched_steps_have_impl(&template) + { + eprintln!("Unable to continue"); + std::process::exit(1); + } + + let spec = template_spec(&doc)?; generate_test_program(&mut doc, &spec, &self.output, &template)?; if self.run { diff --git a/src/bindings.rs b/src/bindings.rs index 9282155..91a2c8d 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -207,8 +207,8 @@ impl Binding { } /// Retrieve a particular implementation by name - pub fn step_impl(&self, _template: &str) -> Option<Arc<BindingImpl>> { - self.impls.values().next().cloned() + pub fn step_impl(&self, template: &str) -> Option<Arc<BindingImpl>> { + self.impls.get(template).cloned() } /// Return the compiled regular expression for the pattern of the @@ -219,8 +219,13 @@ impl Binding { &self.regex } + /// Return the type bindings for this binding. + pub fn types(&self) -> impl Iterator<Item = (&str, CaptureType)> { + self.types.iter().map(|(s, c)| (s.as_str(), *c)) + } + /// Try to match defined binding against a parsed scenario step. - pub fn match_with_step(&self, step: &ScenarioStep) -> Option<MatchedStep> { + pub fn match_with_step(&self, template: &str, step: &ScenarioStep) -> Option<MatchedStep> { if self.kind() != step.kind() { return None; } @@ -229,7 +234,7 @@ impl Binding { let caps = self.regex.captures(step_text)?; // If there is only one capture, it's the whole string. - let mut m = MatchedStep::new(self, &self.types); + let mut m = MatchedStep::new(self, template); if caps.len() == 1 { m.append_part(PartialStep::uncaptured(step_text)); return Some(m); @@ -336,21 +341,21 @@ mod test_binding { fn does_not_match_with_wrong_kind() { let step = ScenarioStep::new(StepKind::Given, "given", "yo"); let b = Binding::new(StepKind::When, "yo", false, HashMap::new()).unwrap(); - assert!(b.match_with_step(&step).is_none()); + assert!(b.match_with_step("", &step).is_none()); } #[test] fn does_not_match_with_wrong_text() { let step = ScenarioStep::new(StepKind::Given, "given", "foo"); let b = Binding::new(StepKind::Given, "bar", false, HashMap::new()).unwrap(); - assert!(b.match_with_step(&step).is_none()); + assert!(b.match_with_step("", &step).is_none()); } #[test] fn match_with_fixed_pattern() { let step = ScenarioStep::new(StepKind::Given, "given", "foo"); let b = Binding::new(StepKind::Given, "foo", false, HashMap::new()).unwrap(); - let m = b.match_with_step(&step).unwrap(); + let m = b.match_with_step("", &step).unwrap(); assert_eq!(m.kind(), StepKind::Given); let mut parts = m.parts(); let p = parts.next().unwrap(); @@ -368,7 +373,7 @@ mod test_binding { HashMap::new(), ) .unwrap(); - let m = b.match_with_step(&step).unwrap(); + let m = b.match_with_step("", &step).unwrap(); assert_eq!(m.kind(), StepKind::Given); let mut parts = m.parts(); assert_eq!(parts.next().unwrap(), &PartialStep::uncaptured("I am ")); @@ -381,9 +386,9 @@ mod test_binding { fn case_sensitive_mismatch() { let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon"); let b = Binding::new(StepKind::Given, r"i am tomjon", false, HashMap::new()).unwrap(); - assert!(b.match_with_step(&step).is_some()); + assert!(b.match_with_step("", &step).is_some()); let b = Binding::new(StepKind::Given, r"i am tomjon", true, HashMap::new()).unwrap(); - assert!(b.match_with_step(&step).is_none()); + assert!(b.match_with_step("", &step).is_none()); } } @@ -479,11 +484,11 @@ impl Bindings { /// Find the binding matching a given scenario step, if there is /// exactly one. - pub fn find(&self, step: &ScenarioStep) -> Result<MatchedStep> { + pub fn find(&self, template: &str, step: &ScenarioStep) -> Result<MatchedStep> { let mut matches: Vec<MatchedStep> = self .bindings() .iter() - .filter_map(|b| b.match_with_step(step)) + .filter_map(|b| b.match_with_step(template, step)) .collect(); if matches.len() > 1 { // Too many matching bindings. @@ -688,7 +693,7 @@ mod test_bindings { let mut bindings = Bindings::new(); bindings.add(binding); assert!(matches!( - bindings.find(&step), + bindings.find("", &step), Err(SubplotError::BindingUnknown(_)) )); } @@ -706,7 +711,7 @@ mod test_bindings { let mut bindings = Bindings::new(); bindings.add(binding); assert!(matches!( - bindings.find(&step), + bindings.find("", &step), Err(SubplotError::BindingUnknown(_)) )); } @@ -727,7 +732,7 @@ mod test_bindings { .unwrap(), ); assert!(matches!( - bindings.find(&step), + bindings.find("", &step), Err(SubplotError::BindingNotUnique(_, _)) )); } @@ -738,7 +743,7 @@ mod test_bindings { let binding = Binding::new(StepKind::Given, r"I am Tomjon", false, HashMap::new()).unwrap(); let mut bindings = Bindings::new(); bindings.add(binding); - let m = bindings.find(&step).unwrap(); + let m = bindings.find("", &step).unwrap(); assert_eq!(m.kind(), StepKind::Given); let mut parts = m.parts(); let p = parts.next().unwrap(); @@ -761,7 +766,7 @@ mod test_bindings { .unwrap(); let mut bindings = Bindings::new(); bindings.add(binding); - let m = bindings.find(&step).unwrap(); + let m = bindings.find("", &step).unwrap(); assert_eq!(m.kind(), StepKind::Given); let mut parts = m.parts(); let p = parts.next().unwrap(); diff --git a/src/codegen.rs b/src/codegen.rs index f48f049..89ea591 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -39,7 +39,7 @@ pub fn generate_test_program( fn context(doc: &mut Document, template: &str) -> Result<Context, SubplotError> { let mut context = Context::new(); - context.insert("scenarios", &doc.matched_scenarios()?); + context.insert("scenarios", &doc.matched_scenarios(template)?); context.insert("files", doc.files()); let funcs_filenames = doc.meta().functions_filenames(); @@ -268,14 +268,14 @@ impl<'a> Document { /// Check that all named files (in matched steps) are actually present in the /// document. - pub fn check_named_files_exist(&mut self) -> Result<bool> { + pub fn check_named_files_exist(&mut self, template: &str) -> Result<bool> { let filenames: HashSet<_> = self .files() .iter() .map(|f| f.filename().to_lowercase()) .collect(); let mut okay = true; - let scenarios = match self.matched_scenarios() { + let scenarios = match self.matched_scenarios(template) { Ok(scenarios) => scenarios, Err(_) => return Ok(true), // We can't do the check, so say it's okay. }; @@ -296,6 +296,30 @@ impl<'a> Document { Ok(okay) } + /// Check that all matched steps actually have function implementations + pub fn check_matched_steps_have_impl(&mut self, template: &str) -> bool { + let mut okay = true; + let scenarios = match self.matched_scenarios(template) { + Ok(scenarios) => scenarios, + Err(_) => return true, // No matches means no missing impls + }; + for scenario in scenarios { + let mut said_scenario = false; + for step in scenario.steps() { + if step.function().is_none() { + if !said_scenario { + eprintln!("Scenario: '{}'", scenario.title()); + eprintln!(" Template: '{}'", template); + said_scenario = true; + } + eprintln!(" Step missing implementation: '{}'", step.text()); + okay = false; + } + } + } + okay + } + /// Typeset a Subplot document. pub fn typeset(&mut self) { let mut visitor = @@ -322,12 +346,12 @@ impl<'a> Document { } /// Return matched scenarios in a document. - pub fn matched_scenarios(&mut self) -> Result<Vec<MatchedScenario>> { + pub fn matched_scenarios(&mut self, template: &str) -> Result<Vec<MatchedScenario>> { let scenarios = self.scenarios()?; let bindings = self.meta().bindings(); scenarios .iter() - .map(|scen| MatchedScenario::new(scen, bindings)) + .map(|scen| MatchedScenario::new(template, scen, bindings)) .collect() } } diff --git a/src/matches.rs b/src/matches.rs index db1bbda..f8e527a 100644 --- a/src/matches.rs +++ b/src/matches.rs @@ -17,11 +17,11 @@ pub struct MatchedScenario { impl MatchedScenario { /// Construct a new matched scenario - pub fn new(scen: &Scenario, bindings: &Bindings) -> Result<MatchedScenario> { + pub fn new(template: &str, scen: &Scenario, bindings: &Bindings) -> Result<MatchedScenario> { let steps: Result<Vec<MatchedStep>> = scen .steps() .iter() - .map(|step| bindings.find(step)) + .map(|step| bindings.find(template, step)) .collect(); Ok(MatchedScenario { title: scen.title().to_string(), @@ -33,6 +33,11 @@ impl MatchedScenario { pub fn steps(&self) -> &[MatchedStep] { &self.steps } + + /// Retrieve the scenario title + pub fn title(&self) -> &str { + &self.title + } } /// A list of matched steps. @@ -72,8 +77,8 @@ pub struct MatchedStep { impl MatchedStep { /// Return a new empty match. Empty means it has no step parts. - pub fn new(binding: &Binding, types: &HashMap<String, CaptureType>) -> MatchedStep { - let bimpl = binding.step_impl(""); + pub fn new(binding: &Binding, template: &str) -> MatchedStep { + let bimpl = binding.step_impl(template); MatchedStep { kind: binding.kind(), pattern: binding.pattern().to_string(), @@ -81,7 +86,7 @@ impl MatchedStep { parts: vec![], function: bimpl.clone().map(|b| b.function().to_owned()), cleanup: bimpl.and_then(|b| b.cleanup().map(String::from)), - types: types.clone(), + types: binding.types().map(|(s, c)| (s.to_string(), c)).collect(), } } @@ -98,7 +103,7 @@ impl MatchedStep { /// Append a partial step to the match. pub fn append_part(&mut self, part: PartialStep) { self.parts.push(part); - self.text = self.text(); + self.text = self.update_text(); } /// Iterate over all partial steps in the match. @@ -110,7 +115,7 @@ impl MatchedStep { self.pattern.to_string() } - fn text(&self) -> String { + fn update_text(&self) -> String { let mut t = String::new(); for part in self.parts() { t.push_str(part.as_text()); @@ -118,6 +123,11 @@ impl MatchedStep { t } + /// Return the step's text + pub fn text(&self) -> &str { + &self.text + } + /// Return the typemap for the matched step pub fn types(&self) -> &HashMap<String, CaptureType> { &self.types diff --git a/src/typeset.rs b/src/typeset.rs index 73cc1a2..97780a4 100644 --- a/src/typeset.rs +++ b/src/typeset.rs @@ -81,7 +81,7 @@ fn step( } let step = step.unwrap(); - let m = match bindings.find(&step) { + let m = match bindings.find("", &step) { Ok(m) => m, Err(e) => { eprintln!("Could not select binding: {:?}", e); |