#[inline(always)]
fn byte_eq(a: u8, b: u8, ignore_case: bool) -> bool {
if ignore_case {
a.eq_ignore_ascii_case(&b)
} else {
a == b
}
}
#[inline(always)]
fn in_range(ch: u8, low: u8, high: u8, ignore_case: bool) -> bool {
if ignore_case {
let ch = ch.to_ascii_lowercase();
let l = low.to_ascii_lowercase();
let h = high.to_ascii_lowercase();
let (min, max) = if l <= h { (l, h) } else { (h, l) };
ch >= min && ch <= max
} else {
let (min, max) = if low <= high {
(low, high)
} else {
(high, low)
};
ch >= min && ch <= max
}
}
#[inline]
fn match_bracket(pat: &[u8], target_byte: u8, ignore_case: bool) -> (bool, usize) {
let mut i = 1;
let mut invert = false;
if i < pat.len() && pat[i] == b'^' {
invert = true;
i += 1;
}
let mut matched = false;
while i < pat.len() && pat[i] != b']' {
if pat[i] == b'\\' && i + 1 < pat.len() {
matched |= pat[i + 1] == target_byte;
i += 2;
} else if pat.len() - i >= 3 && pat[i + 1] == b'-' {
let start = pat[i];
let end = pat[i + 2];
matched |= in_range(target_byte, start, end, ignore_case);
i += 3;
} else {
matched |= byte_eq(pat[i], target_byte, ignore_case);
i += 1;
}
}
if i >= pat.len() {
return (matched != invert, pat.len());
}
(matched != invert, i + 1)
}
#[inline]
pub fn glob_match_opt(pattern: &[u8], target: &[u8], ignore_case: bool) -> bool {
if target.is_empty() {
return pattern.is_empty();
}
let mut p = 0;
let mut t = 0;
let mut star_p: Option<usize> = None;
let mut star_t = 0;
while t < target.len() {
if p < pattern.len() {
match pattern[p] {
b'*' => {
while p + 1 < pattern.len() && pattern[p + 1] == b'*' {
p += 1;
}
if p + 1 == pattern.len() {
return true;
}
star_p = Some(p);
p += 1;
star_t = t;
continue;
}
b'?' => {
p += 1;
t += 1;
continue;
}
b'[' => {
let (matched, consumed) = match_bracket(&pattern[p..], target[t], ignore_case);
if matched {
p += consumed;
t += 1;
continue;
}
}
b'\\' => {
let pat_byte = if p + 1 < pattern.len() {
pattern[p + 1]
} else {
b'\\'
};
if byte_eq(pat_byte, target[t], ignore_case) {
p += if p + 1 < pattern.len() { 2 } else { 1 };
t += 1;
continue;
}
}
c => {
if byte_eq(c, target[t], ignore_case) {
p += 1;
t += 1;
continue;
}
}
}
}
if let Some(sp) = star_p {
p = sp + 1;
star_t += 1;
t = star_t;
} else {
return false;
}
}
while p < pattern.len() && pattern[p] == b'*' {
p += 1;
}
p == pattern.len()
}
#[inline]
pub fn glob_match(pattern: &[u8], target: &[u8]) -> bool {
glob_match_opt(pattern, target, false)
}
#[inline]
pub fn glob_match_nocase(pattern: &[u8], target: &[u8]) -> bool {
glob_match_opt(pattern, target, true)
}
#[cfg(test)]
mod tests {
use super::{glob_match, glob_match_nocase, glob_match_opt};
#[test]
fn test_glob_basic() {
assert!(!glob_match(b"*", b""));
assert!(glob_match(b"*", b"hello"));
assert!(glob_match(b"", b""));
assert!(!glob_match(b"", b"a"));
assert!(glob_match(b"h?llo", b"hello"));
assert!(!glob_match(b"h?llo", b"hllo"));
}
#[test]
fn test_glob_brackets_and_escapes() {
assert!(glob_match(b"[a-z]ello", b"hello"));
assert!(!glob_match(b"[0-9]ello", b"hello"));
assert!(!glob_match(b"[!0-9]ello", b"hello"));
assert!(glob_match(b"[!0-9]ello", b"!ello"));
assert!(glob_match(b"[^0-9]ello", b"hello"));
assert!(glob_match(b"[\\]]", b"]"));
assert!(glob_match(b"[\\\\\\\\]", b"\\"));
assert!(glob_match(b"\\*hello", b"*hello"));
assert!(!glob_match(b"\\*hello", b"foo_hello"));
assert!(glob_match(b"[a-]", b"]"));
assert!(glob_match(b"[a-]", b"a"));
assert!(!glob_match(b"[a-]", b"b"));
assert!(glob_match(b"[a-]x]", b"x"));
assert!(glob_match(b"[a-]x]", b"]"));
assert!(!glob_match(b"[a-]x]", b"]x"));
assert!(glob_match(b"[z-a]", b"m"));
assert!(glob_match(b"[abc", b"a"));
assert!(!glob_match(b"[abc", b"d"));
assert!(!glob_match(b"[", b"["));
}
#[test]
fn test_glob_nocase() {
assert!(!glob_match(b"hello", b"HELLO"));
assert!(glob_match_nocase(b"hello", b"HELLO"));
assert!(glob_match_opt(b"h[a-z]llo", b"hEllo", true));
}
}