Skip to content

Commit

Permalink
Merge pull request #15 from hoijui/char_generalization
Browse files Browse the repository at this point in the history
Adds `const` generic args to support alternative wildcard characters
  • Loading branch information
becheran authored Dec 22, 2023
2 parents afab6be + 22e89fd commit caecc17
Showing 1 changed file with 60 additions and 27 deletions.
87 changes: 60 additions & 27 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<const MULTI_WILDCARD: char, const SINGLE_WILDCARD: char> {
pattern: Vec<State>,
max_questionmarks: usize,
}
Expand All @@ -45,13 +61,15 @@ struct State {
has_wildcard: bool,
}

impl fmt::Display for WildMatch {
impl<const MULTI_WILDCARD: char, const SINGLE_WILDCARD: char> fmt::Display
for WildMatchPattern<MULTI_WILDCARD, SINGLE_WILDCARD>
{
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)?;
Expand All @@ -61,31 +79,31 @@ impl fmt::Display for WildMatch {
}
}

impl WildMatch {
impl<const MULTI_WILDCARD: char, const SINGLE_WILDCARD: char>
WildMatchPattern<MULTI_WILDCARD, SINGLE_WILDCARD>
{
/// Constructor with pattern which can be used for matching.
pub fn new(pattern: &str) -> WildMatch {
pub fn new(pattern: &str) -> WildMatchPattern<MULTI_WILDCARD, SINGLE_WILDCARD> {
let mut simplified: Vec<State> = 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;
}
}

Expand All @@ -97,7 +115,7 @@ impl WildMatch {
simplified.push(final_state);
}

WildMatch {
Self {
pattern: simplified,
max_questionmarks,
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand All @@ -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<MULTI_WILDCARD, SINGLE_WILDCARD>
{
fn eq(&self, &other: &&'a str) -> bool {
self.matches(other)
}
Expand Down Expand Up @@ -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?*");
Expand Down

0 comments on commit caecc17

Please sign in to comment.