357 lines · 16.7 KB · Rust
RawDownload
use bitflags::bitflags;bitflags! {    /// The match mode employed in [`Pattern::matches()`][crate::Pattern::matches()].    #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]    pub struct Mode: u8 {        /// Let globs like `*` and `?` not match the slash `/` literal, which is useful when matching paths.        const NO_MATCH_SLASH_LITERAL = 1 << 0;        /// Match case insensitively for ascii characters only.        const IGNORE_CASE = 1 << 1;    }} pub(crate) mod function {    use bstr::{BStr, ByteSlice};     use crate::wildmatch::Mode;     #[derive(Eq, PartialEq)]    enum Result {        Match,        NoMatch,        AbortAll,        AbortToStarStar,    }     const STAR: u8 = b'*';    const BACKSLASH: u8 = b'\\';    const SLASH: u8 = b'/';    const BRACKET_OPEN: u8 = b'[';    const BRACKET_CLOSE: u8 = b']';    const COLON: u8 = b':';     const NEGATE_CLASS: u8 = b'!';     fn match_recursive(pattern: &BStr, text: &BStr, mode: Mode) -> Result {        use self::Result::*;        let possibly_lowercase = |c: &u8| {            if mode.contains(Mode::IGNORE_CASE) {                c.to_ascii_lowercase()            } else {                *c            }        };        let mut p = pattern.iter().map(possibly_lowercase).enumerate().peekable();        let mut t = text.iter().map(possibly_lowercase).enumerate();         while let Some((mut p_idx, mut p_ch)) = p.next() {            let (mut t_idx, mut t_ch) = match t.next() {                Some(c) => c,                None if p_ch != STAR => return AbortAll,                None => (text.len(), 0),            };             if p_ch == BACKSLASH {                match p.next() {                    Some((_p_idx, p_ch)) => {                        if p_ch != t_ch {                            return NoMatch;                        } else {                            continue;                        }                    }                    None => return NoMatch,                };            }            match p_ch {                b'?' => {                    if mode.contains(Mode::NO_MATCH_SLASH_LITERAL) && t_ch == SLASH {                        return NoMatch;                    } else {                        continue;                    }                }                STAR => {                    let mut match_slash = mode                        .contains(Mode::NO_MATCH_SLASH_LITERAL)                        .then(|| false)                        .unwrap_or(true);                    match p.next() {                        Some((next_p_idx, next_p_ch)) => {                            let next;                            if next_p_ch == STAR {                                let leading_slash_idx = p_idx.checked_sub(1);                                while p.next_if(|(_, c)| *c == STAR).is_some() {}                                next = p.next();                                if !mode.contains(Mode::NO_MATCH_SLASH_LITERAL) {                                    match_slash = true;                                } else if leading_slash_idx.map_or(true, |idx| pattern[idx] == SLASH)                                    && next.map_or(true, |(_, c)| {                                        c == SLASH || (c == BACKSLASH && p.peek().map(|t| t.1) == Some(SLASH))                                    })                                {                                    if next.map_or(NoMatch, |(idx, _)| {                                        match_recursive(pattern[idx + 1..].as_bstr(), text[t_idx..].as_bstr(), mode)                                    }) == Match                                    {                                        return Match;                                    }                                    match_slash = true;                                } else {                                    match_slash = false;                                }                            } else {                                next = Some((next_p_idx, next_p_ch));                            }                             match next {                                None => {                                    return if !match_slash && text[t_idx..].contains(&SLASH) {                                        NoMatch                                    } else {                                        Match                                    };                                }                                Some((next_p_idx, next_p_ch)) => {                                    p_idx = next_p_idx;                                    p_ch = next_p_ch;                                    if !match_slash && p_ch == SLASH {                                        match text[t_idx..].find_byte(SLASH) {                                            Some(distance_to_slash) => {                                                for _ in t.by_ref().take(distance_to_slash) {}                                                continue;                                            }                                            None => return NoMatch,                                        }                                    }                                }                            }                        }                        None => {                            return if !match_slash && text[t_idx..].contains(&SLASH) {                                NoMatch                            } else {                                Match                            }                        }                    }                     return loop {                        if !crate::parse::GLOB_CHARACTERS.contains(&p_ch) {                            loop {                                if (!match_slash && t_ch == SLASH) || t_ch == p_ch {                                    break;                                }                                match t.next() {                                    Some(t) => {                                        t_idx = t.0;                                        t_ch = t.1;                                    }                                    None => break,                                };                            }                            if t_ch != p_ch {                                return NoMatch;                            }                        }                        let res = match_recursive(pattern[p_idx..].as_bstr(), text[t_idx..].as_bstr(), mode);                        if res != NoMatch {                            if !match_slash || res != AbortToStarStar {                                return res;                            }                        } else if !match_slash && t_ch == SLASH {                            return AbortToStarStar;                        }                        match t.next() {                            Some(t) => {                                t_idx = t.0;                                t_ch = t.1;                            }                            None => break AbortAll,                        };                    };                }                BRACKET_OPEN => {                    match p.next() {                        Some(t) => {                            p_idx = t.0;                            p_ch = t.1;                        }                        None => return AbortAll,                    };                     if p_ch == b'^' {                        p_ch = NEGATE_CLASS;                    }                    let negated = p_ch == NEGATE_CLASS;                    let mut next = if negated { p.next() } else { Some((p_idx, p_ch)) };                    let mut prev_p_ch = 0;                    let mut matched = false;                    loop {                        match next {                            None => return AbortAll,                            Some((p_idx, mut p_ch)) => match p_ch {                                BACKSLASH => match p.next() {                                    Some((_, p_ch)) => {                                        if p_ch == t_ch {                                            matched = true                                        } else {                                            prev_p_ch = p_ch;                                        }                                    }                                    None => return AbortAll,                                },                                b'-' if prev_p_ch != 0                                    && p.peek().is_some()                                    && p.peek().map(|t| t.1) != Some(BRACKET_CLOSE) =>                                {                                    p_ch = p.next().expect("peeked").1;                                    if p_ch == BACKSLASH {                                        p_ch = match p.next() {                                            Some(t) => t.1,                                            None => return AbortAll,                                        };                                    }                                    if t_ch <= p_ch && t_ch >= prev_p_ch {                                        matched = true;                                    } else if mode.contains(Mode::IGNORE_CASE) && t_ch.is_ascii_lowercase() {                                        let t_ch_upper = t_ch.to_ascii_uppercase();                                        if (t_ch_upper <= p_ch.to_ascii_uppercase()                                            && t_ch_upper >= prev_p_ch.to_ascii_uppercase())                                            || (t_ch_upper <= prev_p_ch.to_ascii_uppercase()                                                && t_ch_upper >= p_ch.to_ascii_uppercase())                                        {                                            matched = true;                                        }                                    }                                    prev_p_ch = 0;                                }                                BRACKET_OPEN if matches!(p.peek(), Some((_, COLON))) => {                                    p.next();                                    while p.peek().map_or(false, |t| t.1 != BRACKET_CLOSE) {                                        p.next();                                    }                                    let closing_bracket_idx = match p.next() {                                        Some((idx, _)) => idx,                                        None => return AbortAll,                                    };                                    const BRACKET__COLON__BRACKET: usize = 3;                                    if closing_bracket_idx - p_idx < BRACKET__COLON__BRACKET                                        || pattern[closing_bracket_idx - 1] != COLON                                    {                                        if t_ch == BRACKET_OPEN {                                            matched = true                                        }                                        p = pattern[p_idx + 1..]                                            .iter()                                            .map(possibly_lowercase)                                            .enumerate()                                            .peekable();                                    } else {                                        let class = &pattern.as_ref()[p_idx + 2..closing_bracket_idx - 1];                                        match class {                                            b"alnum" => {                                                if t_ch.is_ascii_alphanumeric() {                                                    matched = true;                                                }                                            }                                            b"alpha" => {                                                if t_ch.is_ascii_alphabetic() {                                                    matched = true;                                                }                                            }                                            b"blank" => {                                                if t_ch.is_ascii_whitespace() {                                                    matched = true;                                                }                                            }                                            b"cntrl" => {                                                if t_ch.is_ascii_control() {                                                    matched = true;                                                }                                            }                                            b"digit" => {                                                if t_ch.is_ascii_digit() {                                                    matched = true;                                                }                                            }                                             b"graph" => {                                                if t_ch.is_ascii_graphic() {                                                    matched = true;                                                }                                            }                                            b"lower" => {                                                if t_ch.is_ascii_lowercase() {                                                    matched = true;                                                }                                            }                                            b"print" => {                                                if (0x20u8..=0x7e).contains(&t_ch) {                                                    matched = true;                                                }                                            }                                            b"punct" => {                                                if t_ch.is_ascii_punctuation() {                                                    matched = true;                                                }                                            }                                            b"space" => {                                                if t_ch == b' ' {                                                    matched = true;                                                }                                            }                                            b"upper" => {                                                if t_ch.is_ascii_uppercase()                                                    || mode.contains(Mode::IGNORE_CASE) && t_ch.is_ascii_lowercase()                                                {                                                    matched = true;                                                }                                            }                                            b"xdigit" => {                                                if t_ch.is_ascii_hexdigit() {                                                    matched = true;                                                }                                            }                                            _ => return AbortAll,                                        };                                        prev_p_ch = 0;                                    }                                }                                _ => {                                    prev_p_ch = p_ch;                                    if p_ch == t_ch {                                        matched = true;                                    }                                }                            },                        };                        next = p.next();                        if let Some((_, BRACKET_CLOSE)) = next {                            break;                        }                    }                    if matched == negated || mode.contains(Mode::NO_MATCH_SLASH_LITERAL) && t_ch == SLASH {                        return NoMatch;                    }                    continue;                }                non_glob_ch => {                    if non_glob_ch != t_ch {                        return NoMatch;                    } else {                        continue;                    }                }            }        }        t.next().map(|_| NoMatch).unwrap_or(Match)    }     /// Employ pattern matching to see if `value` matches `pattern`.    ///    /// `mode` can be used to adjust the way the matching is performed.    pub fn wildmatch(pattern: &BStr, value: &BStr, mode: Mode) -> bool {        match_recursive(pattern, value, mode) == Result::Match    }}