summaryrefslogtreecommitdiff
path: root/src/bindings.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bindings.rs')
-rw-r--r--src/bindings.rs201
1 files changed, 165 insertions, 36 deletions
diff --git a/src/bindings.rs b/src/bindings.rs
index b937edb..825b895 100644
--- a/src/bindings.rs
+++ b/src/bindings.rs
@@ -3,6 +3,7 @@ use super::MatchedSteps;
use super::PartialStep;
use super::ScenarioStep;
use super::StepKind;
+use crate::Warning;
use crate::{resource, SubplotError};
use serde::{Deserialize, Serialize};
@@ -167,6 +168,7 @@ pub struct Binding {
impls: HashMap<String, Arc<BindingImpl>>,
types: HashMap<String, CaptureType>,
doc: Option<String>,
+ filename: Arc<Path>,
}
impl Binding {
@@ -175,19 +177,14 @@ impl Binding {
kind: StepKind,
pattern: &str,
case_sensitive: bool,
- mut types: HashMap<String, CaptureType>,
+ types: HashMap<String, CaptureType>,
doc: Option<String>,
+ filename: Arc<Path>,
) -> Result<Binding, SubplotError> {
let regex = RegexBuilder::new(&format!("^{pattern}$"))
.case_insensitive(!case_sensitive)
.build()
.map_err(|err| SubplotError::Regex(pattern.to_string(), err))?;
- // For every named capture, ensure we have a known type for it.
- // If the type is missing from the map, we default to `text` which is
- // the .* pattern
- for capture in regex.capture_names().flatten() {
- types.entry(capture.into()).or_insert(CaptureType::Text);
- }
Ok(Binding {
kind,
@@ -196,6 +193,7 @@ impl Binding {
impls: HashMap::new(),
types,
doc,
+ filename,
})
}
@@ -283,15 +281,14 @@ impl Binding {
let cap = cap.as_str();
// These unwraps are safe because we ensured the map is complete
// in the constructor, and that all the types are known.
- let ty = self.types.get(name).unwrap();
- let rx = &KIND_PATTERNS.get(ty).unwrap();
+ let kind = self.types.get(name).copied().unwrap_or(CaptureType::Text);
+ let rx = KIND_PATTERNS.get(&kind).unwrap();
if !rx.is_match(cap) {
// This capture doesn't match the kind so it's not
// valid for this binding.
return None;
}
- let kind = self.types.get(name).unwrap_or(&CaptureType::Text);
- PartialStep::text(name, cap, *kind)
+ PartialStep::text(name, cap, kind)
}
};
@@ -307,6 +304,39 @@ impl Binding {
Some(m)
}
+
+ fn filename(&self) -> &Path {
+ &self.filename
+ }
+
+ fn check(&self, warnings: &mut crate::Warnings) -> Result<(), SubplotError> {
+ fn nth(i: usize) -> &'static str {
+ match i % 10 {
+ 1 => "st",
+ 2 => "nd",
+ 3 => "rd",
+ _ => "th",
+ }
+ }
+ for (nr, capture) in self.regex.capture_names().enumerate().skip(1) {
+ if let Some(name) = capture {
+ if !self.types.contains_key(name) {
+ warnings.push(Warning::MissingCaptureType(
+ self.filename().to_owned(),
+ format!("{}: {}", self.kind(), self.pattern()),
+ name.to_string(),
+ ));
+ }
+ } else {
+ warnings.push(Warning::MissingCaptureName(
+ self.filename().to_owned(),
+ format!("{}: {}", self.kind(), self.pattern()),
+ format!("{nr}{}", nth(nr)),
+ ));
+ }
+ }
+ Ok(())
+ }
}
impl PartialEq for Binding {
@@ -325,10 +355,25 @@ mod test_binding {
use crate::ScenarioStep;
use crate::StepKind;
use std::collections::HashMap;
+ use std::path::Path;
+ use std::path::PathBuf;
+ use std::sync::Arc;
+
+ fn path() -> Arc<Path> {
+ PathBuf::new().into()
+ }
#[test]
fn creates_new() {
- let b = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new(), None).unwrap();
+ let b = Binding::new(
+ StepKind::Given,
+ "I am Tomjon",
+ false,
+ HashMap::new(),
+ None,
+ path(),
+ )
+ .unwrap();
assert_eq!(b.kind(), StepKind::Given);
assert!(b.regex().is_match("I am Tomjon"));
assert!(!b.regex().is_match("I am Tomjon of Lancre"));
@@ -337,20 +382,45 @@ mod test_binding {
#[test]
fn equal() {
- let a = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new(), None).unwrap();
- let b = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new(), None).unwrap();
+ let a = Binding::new(
+ StepKind::Given,
+ "I am Tomjon",
+ false,
+ HashMap::new(),
+ None,
+ path(),
+ )
+ .unwrap();
+ let b = Binding::new(
+ StepKind::Given,
+ "I am Tomjon",
+ false,
+ HashMap::new(),
+ None,
+ path(),
+ )
+ .unwrap();
assert_eq!(a, b);
}
#[test]
fn not_equal() {
- let a = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new(), None).unwrap();
+ let a = Binding::new(
+ StepKind::Given,
+ "I am Tomjon",
+ false,
+ HashMap::new(),
+ None,
+ path(),
+ )
+ .unwrap();
let b = Binding::new(
StepKind::Given,
"I am Tomjon of Lancre",
false,
HashMap::new(),
None,
+ path(),
)
.unwrap();
assert_ne!(a, b);
@@ -359,21 +429,21 @@ mod test_binding {
#[test]
fn does_not_match_with_wrong_kind() {
let step = ScenarioStep::new(StepKind::Given, "given", "yo", Location::Unknown);
- let b = Binding::new(StepKind::When, "yo", false, HashMap::new(), None).unwrap();
+ let b = Binding::new(StepKind::When, "yo", false, HashMap::new(), None, path()).unwrap();
assert!(b.match_with_step("", &step).is_none());
}
#[test]
fn does_not_match_with_wrong_text() {
let step = ScenarioStep::new(StepKind::Given, "given", "foo", Location::Unknown);
- let b = Binding::new(StepKind::Given, "bar", false, HashMap::new(), None).unwrap();
+ let b = Binding::new(StepKind::Given, "bar", false, HashMap::new(), None, path()).unwrap();
assert!(b.match_with_step("", &step).is_none());
}
#[test]
fn match_with_fixed_pattern() {
let step = ScenarioStep::new(StepKind::Given, "given", "foo", Location::Unknown);
- let b = Binding::new(StepKind::Given, "foo", false, HashMap::new(), None).unwrap();
+ let b = Binding::new(StepKind::Given, "foo", false, HashMap::new(), None, path()).unwrap();
let m = b.match_with_step("", &step).unwrap();
assert_eq!(m.kind(), StepKind::Given);
let mut parts = m.parts();
@@ -396,6 +466,7 @@ mod test_binding {
false,
HashMap::new(),
None,
+ path(),
)
.unwrap();
let m = b.match_with_step("", &step).unwrap();
@@ -413,9 +484,25 @@ mod test_binding {
#[test]
fn case_sensitive_mismatch() {
let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown);
- let b = Binding::new(StepKind::Given, r"i am tomjon", false, HashMap::new(), None).unwrap();
+ let b = Binding::new(
+ StepKind::Given,
+ r"i am tomjon",
+ false,
+ HashMap::new(),
+ None,
+ path(),
+ )
+ .unwrap();
assert!(b.match_with_step("", &step).is_some());
- let b = Binding::new(StepKind::Given, r"i am tomjon", true, HashMap::new(), None).unwrap();
+ let b = Binding::new(
+ StepKind::Given,
+ r"i am tomjon",
+ true,
+ HashMap::new(),
+ None,
+ path(),
+ )
+ .unwrap();
assert!(b.match_with_step("", &step).is_none());
}
}
@@ -493,11 +580,11 @@ impl Bindings {
}
/// Add bindings from a YAML string
- pub fn add_from_yaml(&mut self, yaml: &str) -> Result<(), SubplotError> {
+ pub fn add_from_yaml(&mut self, yaml: &str, filename: Arc<Path>) -> Result<(), SubplotError> {
let bindings: Vec<ParsedBindingWrapper> =
serde_yaml::from_str(yaml).map_err(SubplotError::Metadata)?;
for wrapper in bindings {
- self.add(from_hashmap(&wrapper.binding)?);
+ self.add(from_hashmap(&wrapper.binding, Arc::clone(&filename))?);
}
Ok(())
}
@@ -539,12 +626,12 @@ impl Bindings {
where
P: AsRef<Path> + Debug,
{
- let yaml = resource::read_as_string(filename.as_ref(), template)
- .map_err(|e| SubplotError::BindingsFileNotFound(filename.as_ref().into(), e))?;
+ let filename = filename.as_ref();
+ let yaml = resource::read_as_string(filename, template)
+ .map_err(|e| SubplotError::BindingsFileNotFound(filename.into(), e))?;
trace!("Loaded file content");
- self.add_from_yaml(&yaml).map_err(|e| {
- SubplotError::BindingFileParseError(filename.as_ref().to_owned(), Box::new(e))
- })?;
+ self.add_from_yaml(&yaml, filename.to_owned().into())
+ .map_err(|e| SubplotError::BindingFileParseError(filename.to_owned(), Box::new(e)))?;
Ok(())
}
@@ -556,9 +643,17 @@ impl Bindings {
.filter(|b| b.kind() == kind && b.pattern() == pattern);
m.count() == 1
}
+
+ /// Check these bindings for any warnings which users might need to know about
+ pub fn check(&self, warnings: &mut crate::Warnings) -> Result<(), SubplotError> {
+ for binding in self.bindings() {
+ binding.check(warnings)?;
+ }
+ Ok(())
+ }
}
-fn from_hashmap(parsed: &ParsedBinding) -> Result<Binding, SubplotError> {
+fn from_hashmap(parsed: &ParsedBinding, filename: Arc<Path>) -> Result<Binding, SubplotError> {
let given: i32 = parsed.given.is_some().into();
let when: i32 = parsed.when.is_some().into();
let then: i32 = parsed.then.is_some().into();
@@ -601,6 +696,7 @@ fn from_hashmap(parsed: &ParsedBinding) -> Result<Binding, SubplotError> {
parsed.case_sensitive,
types,
parsed.doc.clone(),
+ filename,
)?;
trace!("Binding parsed OK");
for (template, pimpl) in &parsed.impls {
@@ -622,6 +718,13 @@ mod test_bindings {
use crate::SubplotError;
use std::collections::HashMap;
+ use std::path::Path;
+ use std::path::PathBuf;
+ use std::sync::Arc;
+
+ fn path() -> Arc<Path> {
+ PathBuf::new().into()
+ }
#[test]
fn has_no_bindings_initially() {
@@ -637,6 +740,7 @@ mod test_bindings {
false,
HashMap::new(),
None,
+ path(),
)
.unwrap();
let mut bindings = Bindings::new();
@@ -672,7 +776,7 @@ mod test_bindings {
total: word
";
let mut bindings = Bindings::new();
- bindings.add_from_yaml(yaml).unwrap();
+ bindings.add_from_yaml(yaml, path()).unwrap();
println!("test: {bindings:?}");
assert!(bindings.has(StepKind::Given, "I am Tomjon"));
assert!(bindings.has(StepKind::When, "I declare myself king"));
@@ -692,7 +796,7 @@ mod test_bindings {
python:
FUNCTION: set_name
";
- match Bindings::new().add_from_yaml(yaml) {
+ match Bindings::new().add_from_yaml(yaml, path()) {
Ok(_) => unreachable!(),
Err(SubplotError::BindingHasManyKeywords(_)) => (),
Err(e) => panic!("Incorrect error: {}", e),
@@ -709,7 +813,7 @@ mod test_bindings {
types:
age: number
";
- match Bindings::new().add_from_yaml(yaml) {
+ match Bindings::new().add_from_yaml(yaml, path()) {
Ok(_) => unreachable!(),
Err(SubplotError::SimplePatternKindMismatch(_)) => (),
Err(e) => panic!("Incorrect error: {}", e),
@@ -719,8 +823,15 @@ mod test_bindings {
#[test]
fn does_not_find_match_for_unmatching_kind() {
let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown);
- let binding =
- Binding::new(StepKind::When, r"I am Tomjon", false, HashMap::new(), None).unwrap();
+ let binding = Binding::new(
+ StepKind::When,
+ r"I am Tomjon",
+ false,
+ HashMap::new(),
+ None,
+ path(),
+ )
+ .unwrap();
let mut bindings = Bindings::new();
bindings.add(binding);
assert!(matches!(
@@ -738,6 +849,7 @@ mod test_bindings {
false,
HashMap::new(),
None,
+ path(),
)
.unwrap();
let mut bindings = Bindings::new();
@@ -753,7 +865,15 @@ mod test_bindings {
let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown);
let mut bindings = Bindings::default();
bindings.add(
- Binding::new(StepKind::Given, r"I am Tomjon", false, HashMap::new(), None).unwrap(),
+ Binding::new(
+ StepKind::Given,
+ r"I am Tomjon",
+ false,
+ HashMap::new(),
+ None,
+ path(),
+ )
+ .unwrap(),
);
bindings.add(
Binding::new(
@@ -763,6 +883,7 @@ mod test_bindings {
false,
HashMap::new(),
None,
+ path(),
)
.unwrap(),
);
@@ -775,8 +896,15 @@ mod test_bindings {
#[test]
fn finds_match_for_fixed_string_pattern() {
let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown);
- let binding =
- Binding::new(StepKind::Given, r"I am Tomjon", false, HashMap::new(), None).unwrap();
+ let binding = Binding::new(
+ StepKind::Given,
+ r"I am Tomjon",
+ false,
+ HashMap::new(),
+ None,
+ path(),
+ )
+ .unwrap();
let mut bindings = Bindings::new();
bindings.add(binding);
let m = bindings.find("", &step).unwrap();
@@ -799,6 +927,7 @@ mod test_bindings {
false,
HashMap::new(),
None,
+ path(),
)
.unwrap();
let mut bindings = Bindings::new();