From 22e89fd9f7eeff83ee97a51d40263e9403f8ccf8 Mon Sep 17 00:00:00 2001 From: Ryan Marcus Date: Tue, 19 Dec 2023 10:45:47 +0100 Subject: [PATCH] Adds `const` generic args to support alternative wildcard characters * Introduced new generic arguments variant of `WildMatch` to specify wildcard characters: `WildMatchPattern` * Changed `WildMatch` to be a typedef based on `WildMatchPattern`, which uses the default wildcard characters * Added test with SQL-style characters (e.g., `%` and `_`) --- src/lib.rs | 87 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 27 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dc80529..f35f97e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,16 +24,32 @@ //! assert!(!WildMatch::new("????").matches("cat")); //! assert!(!WildMatch::new("?").matches("cat")); //! ``` +//! +//! You can specify custom `char` values for the single and multi-character +//! wildcards. For example, to use `%` as the multi-character wildcard and +//! `_` as the single-character wildcard: +//! ```rust +//! # extern crate wildmatch; use wildmatch::WildMatchPattern; +//! assert!(WildMatchPattern::<'%', '_'>::new("%cat%").matches("dog_cat_dog")); +//! ``` use std::fmt; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +/// A wildcard matcher using `*` as the multi-character wildcard and `?` as +/// the single-character wildcard. +pub type WildMatch = WildMatchPattern<'*', '?'>; + /// Wildcard matcher used to match strings. +/// +/// `MULTI_WILDCARD` is the character used to represent a +/// multiple-character wildcard (e.g., `*`), and `SINGLE_WILDCARD` is the +/// character used to represent a single-character wildcard (e.g., `?`). #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Default)] -pub struct WildMatch { +pub struct WildMatchPattern { pattern: Vec, max_questionmarks: usize, } @@ -45,13 +61,15 @@ struct State { has_wildcard: bool, } -impl fmt::Display for WildMatch { +impl fmt::Display + for WildMatchPattern +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use std::fmt::Write; for state in &self.pattern { if state.has_wildcard { - f.write_char('*')?; + f.write_char(MULTI_WILDCARD)?; } if let Some(c) = state.next_char { f.write_char(c)?; @@ -61,31 +79,31 @@ impl fmt::Display for WildMatch { } } -impl WildMatch { +impl + WildMatchPattern +{ /// Constructor with pattern which can be used for matching. - pub fn new(pattern: &str) -> WildMatch { + pub fn new(pattern: &str) -> WildMatchPattern { let mut simplified: Vec = Vec::with_capacity(pattern.len()); let mut prev_was_star = false; let mut max_questionmarks: usize = 0; let mut questionmarks: usize = 0; for current_char in pattern.chars() { - match current_char { - '*' => { - prev_was_star = true; - max_questionmarks = std::cmp::max(max_questionmarks, questionmarks); - questionmarks = 0; - } - _ => { - if current_char == '?' { - questionmarks += 1; - } - let s = State { - next_char: Some(current_char), - has_wildcard: prev_was_star, - }; - simplified.push(s); - prev_was_star = false; + if current_char == MULTI_WILDCARD { + prev_was_star = true; + max_questionmarks = std::cmp::max(max_questionmarks, questionmarks); + questionmarks = 0; + } else { + if current_char == SINGLE_WILDCARD { + questionmarks += 1; } + + let s = State { + next_char: Some(current_char), + has_wildcard: prev_was_star, + }; + simplified.push(s); + prev_was_star = false; } } @@ -97,7 +115,7 @@ impl WildMatch { simplified.push(final_state); } - WildMatch { + Self { pattern: simplified, max_questionmarks, } @@ -122,7 +140,7 @@ impl WildMatch { None => { return false; } - Some(p) if p.next_char == Some('?') => { + Some(p) if p.next_char == Some(SINGLE_WILDCARD) => { if p.has_wildcard { last_wildcard_idx = pattern_idx; } @@ -151,12 +169,12 @@ impl WildMatch { let current_idx = pattern_idx; pattern_idx = last_wildcard_idx; for prev_state in self.pattern[last_wildcard_idx + 1..current_idx].iter() { - if self.pattern[pattern_idx].next_char == Some('?') { + if self.pattern[pattern_idx].next_char == Some(SINGLE_WILDCARD) { pattern_idx += 1; continue; } let mut prev_input_char = prev_state.next_char; - if prev_input_char == Some('?') { + if prev_input_char == Some(SINGLE_WILDCARD) { prev_input_char = Some(questionmark_matches[questionmark_idx]); questionmark_idx += 1; } @@ -174,7 +192,7 @@ impl WildMatch { } // Match last char again - if self.pattern[pattern_idx].next_char == Some('?') + if self.pattern[pattern_idx].next_char == Some(SINGLE_WILDCARD) || self.pattern[pattern_idx].next_char == Some(input_char) { pattern_idx += 1; @@ -186,7 +204,9 @@ impl WildMatch { } } -impl<'a> PartialEq<&'a str> for WildMatch { +impl<'a, const MULTI_WILDCARD: char, const SINGLE_WILDCARD: char> PartialEq<&'a str> + for WildMatchPattern +{ fn eq(&self, &other: &&'a str) -> bool { self.matches(other) } @@ -296,6 +316,19 @@ mod tests { assert!(m.matches(TEXT)); } + #[test] + fn complex_pattern_alternative_wildcards() { + const TEXT: &str = "Lorem ipsum dolor sit amet, \ + consetetur sadipscing elitr, sed diam nonumy eirmod tempor \ + invidunt ut labore et dolore magna aliquyam erat, sed diam \ + voluptua. At vero eos et accusam et justo duo dolores et ea \ + rebum. Stet clita kasd gubergren, no sea takimata sanctus est \ + Lorem ipsum dolor sit amet."; + const COMPLEX_PATTERN: &str = "Lorem_ipsum%dolore%ea% _____ata%."; + let m = WildMatchPattern::<'%', '_'>::new(COMPLEX_PATTERN); + assert!(m.matches(TEXT)); + } + #[test] fn compare_via_equal() { let m = WildMatch::new("c?*");