Commit a54a051c authored by Daniel Silverstone's avatar Daniel Silverstone
Browse files

Merge branch 'all-bindings' into 'main'

feat: when more than one bindings match a step, list all of them

Closes #120

See merge request !188
parents 5a6e40e1 258ee439
Pipeline #344512192 passed with stage
in 10 minutes and 17 seconds
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(_, _))
));
}
......
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}")]
......
......@@ -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;
......
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() {
......
......@@ -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"
```
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment