diff --git a/CHANGES.md b/CHANGES.md index e2eb72b..6db0c4b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,38 @@ +# fm 0.4.0 (2024-08-30) + +* Add the `..~` "group match" wildcard, which searches until it finds a match + for a group of lines. This means that fm now backtracks! The behaviour of the + `...` interline wildcard is unchanged, and is preferred, because it has more + predictable performance and leads to stronger tests. + +* Report names matched when matching fails. When matching fails, it can be hard + to work out what went wrong, particularly when name matching is used. fm now + reports the set of names and their values at the point of failure, which + helps debug matching problems. + +* Add support for "ignorable" name matchers. Sometimes one needs to match a + particular pattern, but the specific text matched is irrelevant. As a one-off + this was easily handled, but if you had multiple places where you wanted to + do this, you had to use a fresh name for each such instance, which is at best + obfuscatory and at worst error prone. Ignorable name matchers allow you to + use the same name (e.g. `_`) multiple times, each matching the same pattern, + but not comparing the text matched with other instances of the ignorable + name. + +* Report the text line where pattern failed to match from. Previously when + `...` failed, it told you how-far-it-got before realising it had failed, + rather than the more useful where-it-started. + +* Remove "distinct name matching" in favour of the more powerful "name + matching validator". Distinct name matching can now be expressed as: + + ``` + .name_matching_validator(|names| { + names.values().collect::>().len() == names.len() + }) + ``` + + # fm 0.3.0 (2024-03-20) * Add `OutputFormatter`s. The default output formatting has now changed from a diff --git a/Cargo.toml b/Cargo.toml index d34d27d..a735aba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "fm" description = "Non-backtracking fuzzy text matcher" repository = "https://github.com/softdevteam/fm/" -version = "0.3.0" +version = "0.4.0" authors = ["Edd Barrett ", "Laurence Tratt "] readme = "README.md" license = "Apache-2.0/MIT" diff --git a/README.md b/README.md index 52821bf..3ed17cd 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,10 @@ either: The interline wildcards are: - * The *prefix match* wildcard `...` matches until it finds a match for the + * The *prefix match* wildcard `...` searches until it finds a match for the line immediately after the interline operator ("the prefix"), at which point the search is anchored. This wildcard does not backtrack. - * The *group match* wildcard `..~` matches until it finds a match for the + * The *group match* wildcard `..~` searches until it finds a match for the next group, at which point the search is anchored. This wildcard backtracks, though never further than one group. diff --git a/src/lib.rs b/src/lib.rs index 3480db5..f7cc6ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,10 +3,7 @@ #![allow(clippy::type_complexity)] use std::{ - collections::{ - hash_map::{Entry, HashMap}, - HashSet, - }, + collections::hash_map::{Entry, HashMap}, default::Default, error::Error, fmt, @@ -180,26 +177,6 @@ impl<'a> FMBuilder<'a> { self } - /// If `yes`, then different names cannot match the same text value. For example if `$1` binds - /// to `a` then `$2` will refuse to match against `a` (though `$1` will continue to match - /// against only `a`). Note that ignorable name matches (see [Self::name_matcher_ignore]) are - /// never subject to distinct name matching. Defaults to `false`. - /// - /// # Warning - /// - /// If you call this function with `true` then later with `false`, the latter will not take - /// effect. - #[deprecated(since = "0.3.1", note = "Please use name_matching_validator instead")] - pub fn distinct_name_matching(self, yes: bool) -> Self { - if yes { - self.name_matching_validator(|names| { - names.values().collect::>().len() == names.len() - }) - } else { - self - } - } - /// Add a name matching validator: this takes a [HashMap] of `(key, value)` pairs and must /// return `true` if this is a valid set of pairs or false otherwise. Name matching validators /// allow you to customise what names are valid matches. For example, if you want distinct @@ -246,6 +223,7 @@ impl<'a> FMBuilder<'a> { /// If `yes`, then: /// 1. Blank lines at the start/end of the pattern and text are ignored. /// 2. Leading/trailing whitespace is ignored on each line of the pattern and text. + /// /// Defaults to "yes". pub fn trim_whitespace(mut self, yes: bool) -> Self { self.options.trim_whitespace = yes; @@ -693,7 +671,7 @@ impl Error for FMatchError { /// If `trim` is set to true: /// 1. Leading/trailing blank space within lines is trimmed. /// 2. Leading/trailing blank lines (including blank space) are trimmed. -fn line_trimmer<'a>(trim: bool, s: &'a str) -> (Vec<&'a str>, usize) { +fn line_trimmer(trim: bool, s: &str) -> (Vec<&str>, usize) { let mut lines = s.lines().collect::>(); if !trim { return (lines, 1); @@ -1184,28 +1162,6 @@ mod tests { assert!(!helper("$1 $2", "a a")); } - /// This test can be removed when [FMBuilder::distinct_name_matching] is removed. - #[test] - fn distinct_names_deprecated() { - let nameptn_re = Regex::new(r"\$.+?\b").unwrap(); - let name_re = Regex::new(r".+?\b").unwrap(); - let helper = |ptn: &str, text: &str| -> bool { - #[allow(deprecated)] - FMBuilder::new(ptn) - .unwrap() - .name_matcher(nameptn_re.clone(), name_re.clone()) - .distinct_name_matching(true) - .build() - .unwrap() - .matches(text) - .is_ok() - }; - - assert!(helper("$1 $1", "a a")); - assert!(!helper("$1 $1", "a b")); - assert!(!helper("$1 $2", "a a")); - } - #[test] fn error_display() { let ptn_re = Regex::new("\\$.+?\\b").unwrap();