pub(crate) fn matches(pattern: &str, text: &str) -> bool {
let pattern: Vec<char> = pattern.chars().collect();
let text: Vec<char> = text.chars().collect();
let (mut p, mut t) = (0usize, 0usize);
let mut star: Option<usize> = None;
let mut star_t = 0usize;
while t < text.len() {
if p < pattern.len() && (pattern[p] == '?' || pattern[p] == text[t]) {
p += 1;
t += 1;
} else if p < pattern.len() && pattern[p] == '*' {
star = Some(p);
star_t = t;
p += 1;
} else if let Some(sp) = star {
p = sp + 1;
star_t += 1;
t = star_t;
} else {
return false;
}
}
while p < pattern.len() && pattern[p] == '*' {
p += 1;
}
p == pattern.len()
}
#[cfg(test)]
mod tests {
use super::matches;
#[test]
fn literal() {
assert!(matches("myapp-linux-amd64", "myapp-linux-amd64"));
assert!(!matches("myapp-linux-amd64", "myapp-linux-arm64"));
assert!(!matches("myapp-linux-amd64", "myapp-linux-amd64.exe"));
}
#[test]
fn anchored_to_whole_name() {
assert!(matches("app-linux-amd64", "app-linux-amd64"));
assert!(!matches("app-linux-amd64", "app-linux-amd64.sha256"));
assert!(!matches("app-linux-amd64", "app-linux-amd64.sig"));
assert!(!matches("app-linux-amd64", "prefix-app-linux-amd64"));
assert!(!matches("app-linux-amd64", "app-linux-amd6"));
assert!(matches("app-linux-amd64*", "app-linux-amd64.sha256"));
assert!(matches("*app-linux-amd64", "prefix-app-linux-amd64"));
}
#[test]
fn star() {
assert!(matches("*", "anything"));
assert!(matches("*-linux-amd64", "myapp-linux-amd64"));
assert!(matches("myapp-*-amd64", "myapp-linux-amd64"));
assert!(matches("myapp-*", "myapp-windows-amd64.exe"));
assert!(matches("myapp-linux-amd64*", "myapp-linux-amd64"));
assert!(!matches("*-linux-amd64", "myapp-linux-arm64"));
}
#[test]
fn question_mark() {
assert!(matches("v?.?.?", "v1.2.3"));
assert!(matches("myapp-linux-amd6?", "myapp-linux-amd64"));
assert!(!matches("myapp-linux-amd6?", "myapp-linux-amd644"));
assert!(!matches("?", ""));
}
#[test]
fn empty() {
assert!(matches("", ""));
assert!(!matches("", "x"));
assert!(matches("**", ""));
}
}