1#![doc = include_str!("../README.md")]
2#![no_std]
3extern crate alloc;
4
5mod engine;
6mod error;
7mod glob;
8mod literal;
9mod map;
10mod parse;
11mod set;
12mod strategy;
13mod tinyset;
14
15pub use crate::error::{Error, ErrorKind};
16pub use crate::glob::{Candidate, Glob, GlobBuilder, GlobMatcher};
17pub use crate::map::{GlobMap, GlobMapBuilder};
18pub use crate::set::{GlobSet, GlobSetBuilder};
19pub use crate::tinyset::{TinyGlobSet, TinyGlobSetBuilder};
20
21use alloc::string::String;
22
23pub fn escape(s: &str) -> String {
28 let mut escaped = String::with_capacity(s.len());
29 for c in s.chars() {
30 match c {
31 '*' | '?' | '[' | ']' | '{' | '}' | '\\' | '!' | '^' | ',' => {
32 escaped.push('\\');
33 escaped.push(c);
34 }
35 _ => escaped.push(c),
36 }
37 }
38 escaped
39}
40
41#[cfg(test)]
42#[allow(clippy::unwrap_used)]
43mod tests {
44 use super::*;
45
46 #[test]
47 fn escape_special_chars() {
48 assert_eq!(escape("*.rs"), "\\*.rs");
49 assert_eq!(escape("[foo]"), "\\[foo\\]");
50 assert_eq!(escape("{a,b}"), "\\{a\\,b\\}");
51 assert_eq!(escape("a\\b"), "a\\\\b");
52 assert_eq!(escape("!negated"), "\\!negated");
53 }
54
55 #[test]
56 fn escape_round_trip() {
57 let original = "hello*world?[test]{a,b}";
58 let escaped = escape(original);
59 let glob = Glob::new(&escaped).unwrap();
60 let matcher = glob.compile_matcher();
61 assert!(matcher.is_match(original));
62 }
63
64 #[test]
65 fn escape_no_special() {
66 assert_eq!(escape("hello.txt"), "hello.txt");
67 assert_eq!(escape("src/main.rs"), "src/main.rs");
68 }
69}