#[derive(Debug)]
pub struct WildMatch {
pattern: Vec<State>,
}
#[derive(Debug)]
struct State {
next_char: Option<char>,
has_wildcard: bool,
is_final_state: bool,
}
impl WildMatch {
pub fn new(pattern: &str) -> WildMatch {
let mut simplified: Vec<State> = Vec::new();
let mut prev_was_star = false;
for current_char in pattern.chars() {
match current_char {
'*' => {
prev_was_star = true;
}
_ => {
let s = State {
next_char: Some(current_char),
has_wildcard: prev_was_star,
is_final_state: false,
};
simplified.push(s);
prev_was_star = false;
}
}
}
if pattern.chars().count() > 0 {
let final_state = State {
next_char: None,
has_wildcard: prev_was_star,
is_final_state: true,
};
simplified.push(final_state);
}
WildMatch {
pattern: simplified,
}
}
pub fn is_match(&self, input: &str) -> bool {
let mut pattern_idx = 0;
for input_char in input.chars() {
match self.pattern.get(pattern_idx) {
None => {
return false;
}
Some(p) if p.next_char == Some('?') => {
pattern_idx += 1;
}
Some(p) if p.next_char == Some(input_char) => {
pattern_idx += 1;
}
Some(p) if p.has_wildcard => {
if p.is_final_state {
return true;
}
}
_ => {
while let Some(pattern) = self.pattern.get(pattern_idx) {
if pattern.has_wildcard {
break;
}
if pattern_idx == 0 {
return false;
};
pattern_idx -= 1;
}
}
}
}
return self.pattern.get(pattern_idx).unwrap().is_final_state;
}
}
#[cfg(test)]
mod tests {
use super::*;
use ntest::assert_false;
use ntest::test_case;
#[test_case("**")]
#[test_case("*")]
#[test_case("*?*")]
#[test_case("c*")]
#[test_case("c?*")]
#[test_case("???")]
#[test_case("c?t")]
#[test_case("cat")]
#[test_case("*cat")]
#[test_case("cat*")]
fn is_match(pattern: &str) {
let m = WildMatch::new(pattern);
assert!(m.is_match("cat"));
}
#[test_case("*d*")]
#[test_case("*d")]
#[test_case("d*")]
#[test_case("*c")]
#[test_case("?")]
#[test_case("??")]
#[test_case("????")]
#[test_case("?????")]
#[test_case("*????")]
#[test_case("cats")]
#[test_case("cat?")]
#[test_case("cacat")]
#[test_case("cat*dog")]
fn no_match(pattern: &str) {
let m = WildMatch::new(pattern);
assert_false!(m.is_match("cat"));
}
#[test_case("cat?", "wildcats")]
#[test_case("cat*", "wildcats")]
#[test_case("*x*", "wildcats")]
#[test_case("*a", "wildcats")]
#[test_case("", "wildcats")]
#[test_case(" ", "wildcats")]
#[test_case("???", "wildcats")]
fn no_match_long(pattern: &str, expected: &str) {
let m = WildMatch::new(pattern);
assert_false!(m.is_match(expected))
}
#[test_case("*cat*", "d&(*og_cat_dog")]
#[test_case("*?*", "d&(*og_cat_dog")]
#[test_case("*a*", "d&(*og_cat_dog")]
#[test_case("*", "*")]
#[test_case("*", "?")]
#[test_case("?", "?")]
#[test_case("wildcats", "wildcats")]
#[test_case("wild*cats", "wild?cats")]
#[test_case("wi*ca*s", "wildcats")]
#[test_case("wi*ca?s", "wildcats")]
#[test_case("*o?", "hog_cat_dog")]
#[test_case("*o?", "cat_dog")]
#[test_case("*at_dog", "cat_dog")]
#[test_case(" ", " ")]
#[test_case("* ", "\n ")]
#[test_case("\n", "\n", name="special_chars")]
fn match_long(pattern: &str, expected: &str) {
let m = WildMatch::new(pattern);
assert!(m.is_match(expected))
}
}