fast-fs 0.2.1

High-speed async file system traversal library with batteries-included file browser component
Documentation
// <FILE>crates/fast-fs/src/nav/fnc_glob_match.rs</FILE> - <DESC>Simple glob pattern matching for filter</DESC>
// <VERS>VERSION: 0.1.0</VERS>
// <WCTX>Extracting from cls_browser.rs for OFPF compliance</WCTX>
// <CLOG>Initial extraction of glob_match function</CLOG>

//! Simple glob pattern matching for file filtering
//!
//! Supports `*` (match any characters) and `?` (match single character).
//! Case-insensitive matching.

/// Match a filename against a glob pattern
///
/// This is an internal function used by the browser's filter feature.
///
/// # Supported Patterns
/// - `*` matches zero or more characters
/// - `?` matches exactly one character
/// - All other characters match literally (case-insensitive)
///
/// # Examples
///
/// ```text
/// glob_match("*.rs", "main.rs")   // true
/// glob_match("test_*", "test_foo") // true
/// glob_match("?.txt", "a.txt")     // true
/// glob_match("?.txt", "ab.txt")    // false
/// ```
pub fn glob_match(pattern: &str, name: &str) -> bool {
    let pattern = pattern.to_lowercase();
    let name = name.to_lowercase();
    glob_match_impl(&pattern, &name)
}

fn glob_match_impl(pattern: &str, name: &str) -> bool {
    let mut p = pattern.chars().peekable();
    let mut n = name.chars().peekable();

    while let Some(pc) = p.next() {
        match pc {
            '*' => {
                // * at end matches everything
                if p.peek().is_none() {
                    return true;
                }
                // Try matching rest of pattern at each position
                let rest_pattern: String = p.clone().collect();
                let mut rest_name: String = n.clone().collect();
                while !rest_name.is_empty() {
                    if glob_match_impl(&rest_pattern, &rest_name) {
                        return true;
                    }
                    rest_name = rest_name.chars().skip(1).collect();
                }
                // Also try matching with empty string
                return glob_match_impl(&rest_pattern, "");
            }
            '?' => {
                // ? must match exactly one character
                if n.next().is_none() {
                    return false;
                }
            }
            c => {
                // Literal character must match
                if n.next() != Some(c) {
                    return false;
                }
            }
        }
    }

    // Pattern exhausted - name must also be exhausted
    n.peek().is_none()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_exact_match() {
        assert!(glob_match("foo.txt", "foo.txt"));
        assert!(glob_match("FOO.TXT", "foo.txt")); // case insensitive
        assert!(!glob_match("foo.txt", "bar.txt"));
    }

    #[test]
    fn test_star_wildcard() {
        assert!(glob_match("*.rs", "main.rs"));
        assert!(glob_match("*.rs", "test.rs"));
        assert!(!glob_match("*.rs", "main.txt"));
        assert!(glob_match("test_*", "test_foo"));
        assert!(glob_match("test_*", "test_"));
        assert!(glob_match("*", "anything"));
        assert!(glob_match("*.*", "file.txt"));
    }

    #[test]
    fn test_question_wildcard() {
        assert!(glob_match("?.txt", "a.txt"));
        assert!(!glob_match("?.txt", "ab.txt"));
        assert!(glob_match("??.rs", "ab.rs"));
        assert!(!glob_match("??.rs", "abc.rs"));
    }

    #[test]
    fn test_combined_wildcards() {
        assert!(glob_match("*.?", "file.c"));
        assert!(glob_match("test_*.rs", "test_foo.rs"));
        assert!(glob_match("*_test.rs", "foo_test.rs"));
    }

    #[test]
    fn test_empty_cases() {
        assert!(glob_match("*", ""));
        assert!(!glob_match("?", ""));
        assert!(glob_match("", ""));
        assert!(!glob_match("a", ""));
    }
}

// <FILE>crates/fast-fs/src/nav/fnc_glob_match.rs</FILE>
// <VERS>END OF VERSION: 0.1.0</VERS>