summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers@digital-scurf.org>2021-08-14 10:48:26 +0100
committerDaniel Silverstone <dsilvers@digital-scurf.org>2021-09-07 17:32:20 +0100
commitd5c69db746b5c4ba938248a7d1c285256c6e6412 (patch)
treec0945c1eba87cc2702ea574a94c5cfa5ae61762c /src
parent4436b7ee28b0318a96d98833d85d712c8a18850d (diff)
downloadsubplot-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.rs17
-rw-r--r--src/bindings.rs39
-rw-r--r--src/codegen.rs2
-rw-r--r--src/doc.rs32
-rw-r--r--src/matches.rs24
-rw-r--r--src/typeset.rs2
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();
diff --git a/src/doc.rs b/src/doc.rs
index 1fa9b3b..5d9d3b1 100644
--- a/src/doc.rs
+++ b/src/doc.rs
@@ -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);