From 8e8a6ee6249b365b3d50c64c442141d704e59f62 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 16 May 2020 08:33:26 +0300 Subject: feat: add optional "cleanup" function to bindings This adds an optional "cleanup" field to bindings, as the name of a function to be called when the scenario ends. As a result, the code generation templates will be able to generate code to call the cleanup functions for successful steps. The diff for this commit is a little extra long because the extra argument to various ::new functions makes lines so long, rustfmt breaks them into several lines. --- src/bindings.rs | 95 ++++++++++++++++++++++++++++++++++++++++++--------------- src/matches.rs | 4 ++- 2 files changed, 73 insertions(+), 26 deletions(-) diff --git a/src/bindings.rs b/src/bindings.rs index 946e8fb..67c45d6 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -17,22 +17,29 @@ use regex::{escape, Regex}; /// Contains the pattern used to match against scenario steps, /// combined with the step kind. The pattern is a regular expression /// as understood by the regex crate. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Binding { kind: StepKind, pattern: String, regex: Regex, function: String, + cleanup: Option, } impl Binding { /// Create a new Binding, from a step kind and a pattern. - pub fn new(kind: StepKind, pattern: &str, function: &str) -> Result { + pub fn new( + kind: StepKind, + pattern: &str, + function: &str, + cleanup: Option<&str>, + ) -> Result { Ok(Binding { kind, pattern: pattern.to_owned(), regex: Regex::new(&format!("^{}$", pattern))?, function: function.to_string(), + cleanup: cleanup.map(String::from), }) } @@ -51,6 +58,14 @@ impl Binding { &self.function } + /// Return name of function that implements cleanup. + pub fn cleanup(&self) -> Option<&str> { + match self.cleanup { + None => None, + Some(ref s) => Some(&s), + } + } + /// Return the compiled regular expression for the pattern of the /// binding. /// @@ -69,7 +84,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); + let mut m = MatchedStep::new(self.kind(), &self.function, self.cleanup()); if caps.len() == 1 { m.append_part(PartialStep::uncaptured(step_text)); return Some(m); @@ -125,12 +140,6 @@ impl PartialEq for Binding { impl Eq for Binding {} -impl Clone for Binding { - fn clone(&self) -> Binding { - Binding::new(self.kind, self.pattern.as_str(), &self.function).unwrap() - } -} - #[cfg(test)] mod test_binding { use super::Binding; @@ -139,47 +148,65 @@ mod test_binding { use crate::StepKind; #[test] - fn creates_new() { - let b = Binding::new(StepKind::Given, "I am Tomjon", "set_name").unwrap(); + fn creates_new_without_cleanup() { + let b = Binding::new(StepKind::Given, "I am Tomjon", "set_name", None).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")); + assert!(!b.regex().is_match("Hello, I am Tomjon")); + assert_eq!(b.function(), "set_name"); + assert_eq!(b.cleanup(), None); + } + + #[test] + fn creates_new_with_cleanup() { + let b = Binding::new( + StepKind::Given, + "I am Tomjon", + "set_name", + Some("unset_name"), + ) + .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")); assert!(!b.regex().is_match("Hello, I am Tomjon")); assert_eq!(b.function(), "set_name"); + assert_eq!(b.cleanup(), Some("unset_name")); } #[test] fn equal() { - let a = Binding::new(StepKind::Given, "I am Tomjon", "set_name").unwrap(); - let b = Binding::new(StepKind::Given, "I am Tomjon", "set_name").unwrap(); + let a = Binding::new(StepKind::Given, "I am Tomjon", "set_name", Some("unset")).unwrap(); + let b = Binding::new(StepKind::Given, "I am Tomjon", "set_name", Some("unset")).unwrap(); assert_eq!(a, b); } #[test] fn not_equal() { - let a = Binding::new(StepKind::Given, "I am Tomjon", "set_name").unwrap(); - let b = Binding::new(StepKind::Given, "I am Tomjon of Lancre", "set_name").unwrap(); + let a = Binding::new(StepKind::Given, "I am Tomjon", "set_name", None).unwrap(); + let b = Binding::new(StepKind::Given, "I am Tomjon of Lancre", "set_name", None).unwrap(); assert_ne!(a, b); } #[test] fn does_not_match_with_wrong_kind() { let step = ScenarioStep::new(StepKind::Given, "given", "yo"); - let b = Binding::new(StepKind::When, "yo", "do_yo").unwrap(); + let b = Binding::new(StepKind::When, "yo", "do_yo", None).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"); - let b = Binding::new(StepKind::Given, "bar", "yo").unwrap(); + let b = Binding::new(StepKind::Given, "bar", "yo", None).unwrap(); 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", "do_foo").unwrap(); + let b = Binding::new(StepKind::Given, "foo", "do_foo", None).unwrap(); let m = b.match_with_step(&step).unwrap(); assert_eq!(m.kind(), StepKind::Given); let mut parts = m.parts(); @@ -191,7 +218,13 @@ mod test_binding { #[test] fn match_with_regex() { let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon, I am"); - let b = Binding::new(StepKind::Given, r"I am (?P\S+), I am", "set_name").unwrap(); + let b = Binding::new( + StepKind::Given, + r"I am (?P\S+), I am", + "set_name", + None, + ) + .unwrap(); let m = b.match_with_step(&step).unwrap(); assert_eq!(m.kind(), StepKind::Given); let mut parts = m.parts(); @@ -220,6 +253,7 @@ struct ParsedBinding { when: Option, then: Option, function: String, + cleanup: Option, regex: Option, } @@ -331,7 +365,15 @@ fn from_hashmap(parsed: &ParsedBinding) -> Result { regex_from_simple_pattern(pattern)? }; - Ok(Binding::new(kind, &pattern, &parsed.function)?) + Ok(Binding::new( + kind, + &pattern, + &parsed.function, + match parsed.cleanup { + None => None, + Some(ref s) => Some(s), + }, + )?) } #[cfg(test)] @@ -351,7 +393,8 @@ mod test_bindings { #[test] fn adds_binding() { - let binding = Binding::new(StepKind::Given, r"I am (?P\S+)", "set_name").unwrap(); + let binding = + Binding::new(StepKind::Given, r"I am (?P\S+)", "set_name", None).unwrap(); let mut bindings = Bindings::new(); bindings.add(&binding); assert_eq!(bindings.bindings(), &[binding]); @@ -393,7 +436,7 @@ mod test_bindings { #[test] fn does_not_find_match_for_unmatching_kind() { let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon"); - let binding = Binding::new(StepKind::When, r"I am Tomjon", "set_foo").unwrap(); + let binding = Binding::new(StepKind::When, r"I am Tomjon", "set_foo", None).unwrap(); let mut bindings = Bindings::new(); bindings.add(&binding); assert!(bindings.find(&step).is_none()); @@ -402,7 +445,8 @@ mod test_bindings { #[test] fn does_not_find_match_for_unmatching_pattern() { let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon"); - let binding = Binding::new(StepKind::Given, r"I am Tomjon of Lancre", "set_foo").unwrap(); + let binding = + Binding::new(StepKind::Given, r"I am Tomjon of Lancre", "set_foo", None).unwrap(); let mut bindings = Bindings::new(); bindings.add(&binding); assert!(bindings.find(&step).is_none()); @@ -411,7 +455,7 @@ mod test_bindings { #[test] fn finds_match_for_fixed_string_pattern() { let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon"); - let binding = Binding::new(StepKind::Given, r"I am Tomjon", "set_name").unwrap(); + let binding = Binding::new(StepKind::Given, r"I am Tomjon", "set_name", None).unwrap(); let mut bindings = Bindings::new(); bindings.add(&binding); let m = bindings.find(&step).unwrap(); @@ -428,7 +472,8 @@ mod test_bindings { #[test] fn finds_match_for_regexp_pattern() { let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon"); - let binding = Binding::new(StepKind::Given, r"I am (?P\S+)", "set_name").unwrap(); + let binding = + Binding::new(StepKind::Given, r"I am (?P\S+)", "set_name", None).unwrap(); let mut bindings = Bindings::new(); bindings.add(&binding); let m = bindings.find(&step).unwrap(); diff --git a/src/matches.rs b/src/matches.rs index 89cf791..27a71e1 100644 --- a/src/matches.rs +++ b/src/matches.rs @@ -46,16 +46,18 @@ pub struct MatchedStep { text: String, parts: Vec, function: String, + cleanup: Option, } impl MatchedStep { /// Return a new empty match. Empty means it has no step parts. - pub fn new(kind: StepKind, function: &str) -> MatchedStep { + pub fn new(kind: StepKind, function: &str, cleanup: Option<&str>) -> MatchedStep { MatchedStep { kind, text: "".to_string(), parts: vec![], function: function.to_string(), + cleanup: cleanup.map(String::from), } } -- cgit v1.2.1