summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers+gitlab@digital-scurf.org>2021-07-28 21:37:03 +0000
committerDaniel Silverstone <dsilvers+gitlab@digital-scurf.org>2021-07-28 21:37:03 +0000
commita54a051c2b24420dbee5cd901718d0bd4cc5d5a4 (patch)
treea489e4839c5b35e302d96de618fbf86ee15b8d0e
parent5a6e40e10eeb8ee106d0fae4b9e25b779bebfe57 (diff)
parent258ee4398bde99009663ff23521cbfd8324312a0 (diff)
downloadsubplot-a54a051c2b24420dbee5cd901718d0bd4cc5d5a4.tar.gz
Merge branch 'all-bindings' into 'main'
feat: when more than one bindings match a step, list all of them Closes #120 See merge request subplot/subplot!188
-rw-r--r--src/bindings.rs28
-rw-r--r--src/error.rs6
-rw-r--r--src/lib.rs1
-rw-r--r--src/matches.rs44
-rw-r--r--subplot.md21
5 files changed, 77 insertions, 23 deletions
diff --git a/src/bindings.rs b/src/bindings.rs
index d17460f..2e927cd 100644
--- a/src/bindings.rs
+++ b/src/bindings.rs
@@ -1,4 +1,5 @@
use super::MatchedStep;
+use super::MatchedSteps;
use super::PartialStep;
use super::ScenarioStep;
use super::StepKind;
@@ -170,7 +171,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.kind(), &self.function, self.cleanup(), &self.types);
+ let mut m = MatchedStep::new(self, &self.types);
if caps.len() == 1 {
m.append_part(PartialStep::uncaptured(step_text));
return Some(m);
@@ -480,16 +481,23 @@ impl Bindings {
/// Find the binding matching a given scenario step, if there is
/// exactly one.
pub fn find(&self, step: &ScenarioStep) -> Result<MatchedStep> {
- let mut matches = self
+ let mut matches: Vec<MatchedStep> = self
.bindings()
.iter()
- .filter_map(|b| b.match_with_step(step));
- match matches.next() {
- None => Err(SubplotError::BindingUnknown(step.to_string())),
- Some(matched) => match matches.next() {
- None => Ok(matched),
- Some(_) => Err(SubplotError::BindingNotUnique(step.to_string())),
- },
+ .filter_map(|b| b.match_with_step(step))
+ .collect();
+ if matches.len() > 1 {
+ // Too many matching bindings.
+ Err(SubplotError::BindingNotUnique(
+ step.to_string(),
+ MatchedSteps::new(matches),
+ ))
+ } else if let Some(m) = matches.pop() {
+ // Exactly one matching binding.
+ Ok(m)
+ } else {
+ // No matching bindings.
+ Err(SubplotError::BindingUnknown(step.to_string()))
}
}
@@ -719,7 +727,7 @@ mod test_bindings {
);
assert!(matches!(
bindings.find(&step),
- Err(SubplotError::BindingNotUnique(_))
+ Err(SubplotError::BindingNotUnique(_, _))
));
}
diff --git a/src/error.rs b/src/error.rs
index eb70e28..532714b 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,3 +1,5 @@
+use crate::matches::MatchedSteps;
+
use std::path::PathBuf;
use std::process::Output;
@@ -46,8 +48,8 @@ pub enum SubplotError {
///
/// THis may be due to bindings being too general, or having unusual
/// overlaps in their matching
- #[error("more than one binding matches: {0}")]
- BindingNotUnique(String),
+ #[error("more than one binding matches step {0}:\n{1}")]
+ BindingNotUnique(String, MatchedSteps),
/// A binding in the bindings file doesn't specify a known keyword.
#[error("binding doesn't specify known keyword: {0}")]
diff --git a/src/lib.rs b/src/lib.rs
index 960e3ef..e2b1431 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -60,6 +60,7 @@ pub use parser::parse_scenario_snippet;
mod matches;
pub use matches::MatchedScenario;
pub use matches::MatchedStep;
+pub use matches::MatchedSteps;
pub use matches::PartialStep;
pub use matches::StepSnippet;
diff --git a/src/matches.rs b/src/matches.rs
index 472b475..88d2c55 100644
--- a/src/matches.rs
+++ b/src/matches.rs
@@ -1,3 +1,4 @@
+use crate::Binding;
use crate::Result;
use crate::Scenario;
use crate::StepKind;
@@ -34,6 +35,30 @@ impl MatchedScenario {
}
}
+/// A list of matched steps.
+#[derive(Debug)]
+pub struct MatchedSteps {
+ matches: Vec<MatchedStep>,
+}
+
+impl MatchedSteps {
+ /// Create new set of steps that match a scenario step.
+ pub fn new(matches: Vec<MatchedStep>) -> Self {
+ Self { matches }
+ }
+}
+
+impl std::fmt::Display for MatchedSteps {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
+ let matches: Vec<String> = self
+ .matches
+ .iter()
+ .map(|m| format!("{}", m.pattern()))
+ .collect();
+ write!(f, "{}", matches.join("\n"))
+ }
+}
+
/// A matched binding and scenario step, with captured parts.
///
/// A MatchedStep is a sequence of partial steps, each representing
@@ -41,6 +66,7 @@ impl MatchedScenario {
#[derive(Debug, Serialize, Deserialize)]
pub struct MatchedStep {
kind: StepKind,
+ pattern: String,
text: String,
parts: Vec<PartialStep>,
function: String,
@@ -50,18 +76,14 @@ pub struct MatchedStep {
impl MatchedStep {
/// Return a new empty match. Empty means it has no step parts.
- pub fn new(
- kind: StepKind,
- function: &str,
- cleanup: Option<&str>,
- types: &HashMap<String, CaptureType>,
- ) -> MatchedStep {
+ pub fn new(binding: &Binding, types: &HashMap<String, CaptureType>) -> MatchedStep {
MatchedStep {
- kind,
+ kind: binding.kind(),
+ pattern: binding.pattern().to_string(),
text: "".to_string(),
parts: vec![],
- function: function.to_string(),
- cleanup: cleanup.map(String::from),
+ function: binding.function().to_string(),
+ cleanup: binding.cleanup().map(String::from),
types: types.clone(),
}
}
@@ -87,6 +109,10 @@ impl MatchedStep {
self.parts.iter()
}
+ fn pattern(&self) -> String {
+ self.pattern.to_string()
+ }
+
fn text(&self) -> String {
let mut t = String::new();
for part in self.parts() {
diff --git a/subplot.md b/subplot.md
index 6ddc2c7..07c07de 100644
--- a/subplot.md
+++ b/subplot.md
@@ -2561,7 +2561,9 @@ then command fails
title: Two bindings match
template: python
bindings:
-- badbindings.yaml
+- twobindings.yaml
+functions:
+- a_function.py
...
# Broken scenario because step has two possible bindings
@@ -2570,12 +2572,27 @@ given a binding
```
~~~~
+~~~{#twobindings.yaml .file .yaml}
+- given: a {xyzzy}
+ function: a_function
+- given: a {plugh}
+ function: a_function
+~~~
+
+~~~{#a_function.py .file .python}
+def a_function(ctx):
+ assert 0
+~~~
+
```scenario
given file twobindings.md
-and file badbindings.yaml
+and file twobindings.yaml
+given file a_function.py
and an installed subplot
when I try to run subplot codegen --run twobindings.md -o test.py
then command fails
+then stderr contains "xyzzy"
+then stderr contains "plugh"
```