pub fn matches(pattern: &str, text: &str) -> bool {
let p: Vec<char> = pattern.chars().map(|c| c.to_ascii_lowercase()).collect();
let t: Vec<char> = text.chars().map(|c| c.to_ascii_lowercase()).collect();
let (mut pi, mut ti) = (0usize, 0usize);
let mut star: Option<usize> = None;
let mut resume = 0usize;
while ti < t.len() {
if pi < p.len() && p[pi] == '*' {
star = Some(pi);
pi += 1;
resume = ti;
} else if pi < p.len() && p[pi] == t[ti] {
pi += 1;
ti += 1;
} else if let Some(s) = star {
pi = s + 1;
resume += 1;
ti = resume;
} else {
return false;
}
}
p[pi..].iter().all(|&c| c == '*')
}
pub fn is_pattern(query: &str) -> bool {
query.contains('*')
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn star_matches_any_run_including_dots() {
assert!(matches("User.*", "User.email"));
assert!(matches("User.*", "User.posts.first")); assert!(matches("*.email", "User.email"));
assert!(matches("*", "anything"));
assert!(matches("*User*", "AdminUserProfile"));
}
#[test]
fn matching_is_anchored_and_case_insensitive() {
assert!(matches("user.*", "User.email"));
assert!(matches("USER.*", "user.email"));
assert!(!matches("User.*", "UserProfile.email"));
assert!(!matches("get*", "forget"));
assert!(!matches("*email", "User.emails"));
}
#[test]
fn a_starless_pattern_is_a_literal_equality() {
assert!(matches("User.email", "user.EMAIL"));
assert!(!matches("User.email", "User.email2"));
assert!(!matches("User", "User.email"));
}
#[test]
fn empty_and_star_only_edges() {
assert!(matches("", ""));
assert!(!matches("", "x"));
assert!(matches("**", "x"));
assert!(matches("a*", "a"));
}
#[test]
fn is_pattern_detects_a_star() {
assert!(is_pattern("User.*"));
assert!(!is_pattern("User.email"));
}
}