1#![deny(missing_docs)]
113
114use std::{
115 borrow::Cow,
116 panic::{RefUnwindSafe, UnwindSafe},
117 path::Path,
118 sync::Arc,
119};
120
121use {
122 aho_corasick::AhoCorasick,
123 bstr::{B, ByteSlice, ByteVec},
124 regex_automata::{
125 PatternSet,
126 meta::Regex,
127 util::pool::{Pool, PoolGuard},
128 },
129};
130
131use crate::{
132 glob::MatchStrategy,
133 pathutil::{file_name, file_name_ext, normalize_path},
134};
135
136pub use crate::glob::{Glob, GlobBuilder, GlobMatcher};
137
138mod fnv;
139mod glob;
140mod pathutil;
141
142#[cfg(feature = "serde1")]
143mod serde_impl;
144
145#[cfg(feature = "log")]
146macro_rules! debug {
147 ($($token:tt)*) => (::log::debug!($($token)*);)
148}
149
150#[cfg(not(feature = "log"))]
151macro_rules! debug {
152 ($($token:tt)*) => {};
153}
154
155#[derive(Clone, Debug, Eq, PartialEq)]
157pub struct Error {
158 glob: Option<String>,
160 kind: ErrorKind,
162}
163
164#[derive(Clone, Debug, Eq, PartialEq)]
166#[non_exhaustive]
167pub enum ErrorKind {
168 InvalidRecursive,
176 UnclosedClass,
178 InvalidRange(char, char),
182 UnopenedAlternates,
184 UnclosedAlternates,
186 NestedAlternates,
192 DanglingEscape,
194 Regex(String),
196}
197
198impl std::error::Error for Error {
199 fn description(&self) -> &str {
200 self.kind.description()
201 }
202}
203
204impl Error {
205 pub fn glob(&self) -> Option<&str> {
207 self.glob.as_ref().map(|s| &**s)
208 }
209
210 pub fn kind(&self) -> &ErrorKind {
212 &self.kind
213 }
214}
215
216impl ErrorKind {
217 fn description(&self) -> &str {
218 match *self {
219 ErrorKind::InvalidRecursive => {
220 "invalid use of **; must be one path component"
221 }
222 ErrorKind::UnclosedClass => {
223 "unclosed character class; missing ']'"
224 }
225 ErrorKind::InvalidRange(_, _) => "invalid character range",
226 ErrorKind::UnopenedAlternates => {
227 "unopened alternate group; missing '{' \
228 (maybe escape '}' with '[}]'?)"
229 }
230 ErrorKind::UnclosedAlternates => {
231 "unclosed alternate group; missing '}' \
232 (maybe escape '{' with '[{]'?)"
233 }
234 ErrorKind::NestedAlternates => {
235 "nested alternate groups are not allowed"
236 }
237 ErrorKind::DanglingEscape => "dangling '\\'",
238 ErrorKind::Regex(ref err) => err,
239 }
240 }
241}
242
243impl std::fmt::Display for Error {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 match self.glob {
246 None => self.kind.fmt(f),
247 Some(ref glob) => {
248 write!(f, "error parsing glob '{}': {}", glob, self.kind)
249 }
250 }
251 }
252}
253
254impl std::fmt::Display for ErrorKind {
255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 match *self {
257 ErrorKind::InvalidRecursive
258 | ErrorKind::UnclosedClass
259 | ErrorKind::UnopenedAlternates
260 | ErrorKind::UnclosedAlternates
261 | ErrorKind::NestedAlternates
262 | ErrorKind::DanglingEscape
263 | ErrorKind::Regex(_) => write!(f, "{}", self.description()),
264 ErrorKind::InvalidRange(s, e) => {
265 write!(f, "invalid range; '{}' > '{}'", s, e)
266 }
267 }
268 }
269}
270
271fn new_regex(pat: &str) -> Result<Regex, Error> {
272 let syntax = regex_automata::util::syntax::Config::new()
273 .utf8(false)
274 .dot_matches_new_line(true);
275 let config = Regex::config()
276 .utf8_empty(false)
277 .nfa_size_limit(Some(10 * (1 << 20)))
278 .hybrid_cache_capacity(10 * (1 << 20));
279 Regex::builder().syntax(syntax).configure(config).build(pat).map_err(
280 |err| Error {
281 glob: Some(pat.to_string()),
282 kind: ErrorKind::Regex(err.to_string()),
283 },
284 )
285}
286
287fn new_regex_set(pats: Vec<String>) -> Result<Regex, Error> {
288 let syntax = regex_automata::util::syntax::Config::new()
289 .utf8(false)
290 .dot_matches_new_line(true);
291 let config = Regex::config()
292 .match_kind(regex_automata::MatchKind::All)
293 .utf8_empty(false)
294 .nfa_size_limit(Some(10 * (1 << 20)))
295 .hybrid_cache_capacity(10 * (1 << 20));
296 Regex::builder()
297 .syntax(syntax)
298 .configure(config)
299 .build_many(&pats)
300 .map_err(|err| Error {
301 glob: None,
302 kind: ErrorKind::Regex(err.to_string()),
303 })
304}
305
306#[derive(Clone, Debug)]
309pub struct GlobSet {
310 len: usize,
311 strats: Vec<GlobSetMatchStrategy>,
312}
313
314impl GlobSet {
315 #[inline]
319 pub fn builder() -> GlobSetBuilder {
320 GlobSetBuilder::new()
321 }
322
323 #[inline]
325 pub const fn empty() -> GlobSet {
326 GlobSet { len: 0, strats: vec![] }
327 }
328
329 #[inline]
331 pub fn is_empty(&self) -> bool {
332 self.len == 0
333 }
334
335 #[inline]
337 pub fn len(&self) -> usize {
338 self.len
339 }
340
341 pub fn is_match<P: AsRef<Path>>(&self, path: P) -> bool {
343 self.is_match_candidate(&Candidate::new(path.as_ref()))
344 }
345
346 pub fn is_match_candidate(&self, path: &Candidate<'_>) -> bool {
351 if self.is_empty() {
352 return false;
353 }
354 for strat in &self.strats {
355 if strat.is_match(path) {
356 return true;
357 }
358 }
359 false
360 }
361
362 pub fn matches_all<P: AsRef<Path>>(&self, path: P) -> bool {
380 self.matches_all_candidate(&Candidate::new(path.as_ref()))
381 }
382
383 pub fn matches_all_candidate(&self, path: &Candidate<'_>) -> bool {
391 for strat in &self.strats {
392 if !strat.is_match(path) {
393 return false;
394 }
395 }
396 true
397 }
398
399 pub fn matches<P: AsRef<Path>>(&self, path: P) -> Vec<usize> {
402 self.matches_candidate(&Candidate::new(path.as_ref()))
403 }
404
405 pub fn matches_candidate(&self, path: &Candidate<'_>) -> Vec<usize> {
411 let mut into = vec![];
412 if self.is_empty() {
413 return into;
414 }
415 self.matches_candidate_into(path, &mut into);
416 into
417 }
418
419 pub fn matches_into<P: AsRef<Path>>(
426 &self,
427 path: P,
428 into: &mut Vec<usize>,
429 ) {
430 self.matches_candidate_into(&Candidate::new(path.as_ref()), into);
431 }
432
433 pub fn matches_candidate_into(
443 &self,
444 path: &Candidate<'_>,
445 into: &mut Vec<usize>,
446 ) {
447 into.clear();
448 if self.is_empty() {
449 return;
450 }
451 for strat in &self.strats {
452 strat.matches_into(path, into);
453 }
454 into.sort();
455 into.dedup();
456 }
457
458 pub fn new<I, G>(globs: I) -> Result<GlobSet, Error>
462 where
463 I: IntoIterator<Item = G>,
464 G: AsRef<Glob>,
465 {
466 let mut it = globs.into_iter().peekable();
467 if it.peek().is_none() {
468 return Ok(GlobSet::empty());
469 }
470
471 let mut len = 0;
472 let mut lits = LiteralStrategy::new();
473 let mut base_lits = BasenameLiteralStrategy::new();
474 let mut exts = ExtensionStrategy::new();
475 let mut prefixes = MultiStrategyBuilder::new();
476 let mut suffixes = MultiStrategyBuilder::new();
477 let mut required_exts = RequiredExtensionStrategyBuilder::new();
478 let mut regexes = MultiStrategyBuilder::new();
479 for (i, p) in it.enumerate() {
480 len += 1;
481
482 let p = p.as_ref();
483 match MatchStrategy::new(p) {
484 MatchStrategy::Literal(lit) => {
485 lits.add(i, lit);
486 }
487 MatchStrategy::BasenameLiteral(lit) => {
488 base_lits.add(i, lit);
489 }
490 MatchStrategy::Extension(ext) => {
491 exts.add(i, ext);
492 }
493 MatchStrategy::Prefix(prefix) => {
494 prefixes.add(i, prefix);
495 }
496 MatchStrategy::Suffix { suffix, component } => {
497 if component {
498 lits.add(i, suffix[1..].to_string());
499 }
500 suffixes.add(i, suffix);
501 }
502 MatchStrategy::RequiredExtension(ext) => {
503 required_exts.add(i, ext, p.regex().to_owned());
504 }
505 MatchStrategy::Regex => {
506 debug!(
507 "glob `{:?}` converted to regex: `{:?}`",
508 p,
509 p.regex()
510 );
511 regexes.add(i, p.regex().to_owned());
512 }
513 }
514 }
515 debug!(
516 "built glob set; {} literals, {} basenames, {} extensions, \
517 {} prefixes, {} suffixes, {} required extensions, {} regexes",
518 lits.0.len(),
519 base_lits.0.len(),
520 exts.0.len(),
521 prefixes.literals.len(),
522 suffixes.literals.len(),
523 required_exts.0.len(),
524 regexes.literals.len()
525 );
526 let mut strats = Vec::with_capacity(7);
527 if !exts.0.is_empty() {
529 strats.push(GlobSetMatchStrategy::Extension(exts));
530 }
531 if !base_lits.0.is_empty() {
532 strats.push(GlobSetMatchStrategy::BasenameLiteral(base_lits));
533 }
534 if !lits.0.is_empty() {
535 strats.push(GlobSetMatchStrategy::Literal(lits));
536 }
537 if !suffixes.is_empty() {
538 strats.push(GlobSetMatchStrategy::Suffix(suffixes.suffix()));
539 }
540 if !prefixes.is_empty() {
541 strats.push(GlobSetMatchStrategy::Prefix(prefixes.prefix()));
542 }
543 if !required_exts.0.is_empty() {
544 strats.push(GlobSetMatchStrategy::RequiredExtension(
545 required_exts.build()?,
546 ));
547 }
548 if !regexes.is_empty() {
549 strats.push(GlobSetMatchStrategy::Regex(regexes.regex_set()?));
550 }
551
552 Ok(GlobSet { len, strats })
553 }
554}
555
556impl Default for GlobSet {
557 fn default() -> Self {
559 GlobSet::empty()
560 }
561}
562
563#[derive(Clone, Debug)]
566pub struct GlobSetBuilder {
567 pats: Vec<Glob>,
568}
569
570impl GlobSetBuilder {
571 pub fn new() -> GlobSetBuilder {
575 GlobSetBuilder { pats: vec![] }
576 }
577
578 pub fn build(&self) -> Result<GlobSet, Error> {
582 GlobSet::new(self.pats.iter())
583 }
584
585 pub fn add(&mut self, pat: Glob) -> &mut GlobSetBuilder {
587 self.pats.push(pat);
588 self
589 }
590}
591
592#[derive(Clone)]
599pub struct Candidate<'a> {
600 path: Cow<'a, [u8]>,
601 basename: Cow<'a, [u8]>,
602 ext: Cow<'a, [u8]>,
603}
604
605impl<'a> std::fmt::Debug for Candidate<'a> {
606 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
607 f.debug_struct("Candidate")
608 .field("path", &self.path.as_bstr())
609 .field("basename", &self.basename.as_bstr())
610 .field("ext", &self.ext.as_bstr())
611 .finish()
612 }
613}
614
615impl<'a> Candidate<'a> {
616 pub fn new<P: AsRef<Path> + ?Sized>(path: &'a P) -> Candidate<'a> {
618 Self::from_cow(Vec::from_path_lossy(path.as_ref()))
619 }
620
621 pub fn from_bytes<P: AsRef<[u8]> + ?Sized>(path: &'a P) -> Candidate<'a> {
630 Self::from_cow(Cow::Borrowed(path.as_ref()))
631 }
632
633 fn from_cow(path: Cow<'a, [u8]>) -> Candidate<'a> {
634 let path = normalize_path(path);
635 let basename = file_name(&path).unwrap_or(Cow::Borrowed(B("")));
636 let ext = file_name_ext(&basename).unwrap_or(Cow::Borrowed(B("")));
637 Candidate { path, basename, ext }
638 }
639
640 fn path_prefix(&self, max: usize) -> &[u8] {
641 if self.path.len() <= max { &*self.path } else { &self.path[..max] }
642 }
643
644 fn path_suffix(&self, max: usize) -> &[u8] {
645 if self.path.len() <= max {
646 &*self.path
647 } else {
648 &self.path[self.path.len() - max..]
649 }
650 }
651}
652
653#[derive(Clone, Debug)]
654enum GlobSetMatchStrategy {
655 Literal(LiteralStrategy),
656 BasenameLiteral(BasenameLiteralStrategy),
657 Extension(ExtensionStrategy),
658 Prefix(PrefixStrategy),
659 Suffix(SuffixStrategy),
660 RequiredExtension(RequiredExtensionStrategy),
661 Regex(RegexSetStrategy),
662}
663
664impl GlobSetMatchStrategy {
665 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
666 use self::GlobSetMatchStrategy::*;
667 match *self {
668 Literal(ref s) => s.is_match(candidate),
669 BasenameLiteral(ref s) => s.is_match(candidate),
670 Extension(ref s) => s.is_match(candidate),
671 Prefix(ref s) => s.is_match(candidate),
672 Suffix(ref s) => s.is_match(candidate),
673 RequiredExtension(ref s) => s.is_match(candidate),
674 Regex(ref s) => s.is_match(candidate),
675 }
676 }
677
678 fn matches_into(
679 &self,
680 candidate: &Candidate<'_>,
681 matches: &mut Vec<usize>,
682 ) {
683 use self::GlobSetMatchStrategy::*;
684 match *self {
685 Literal(ref s) => s.matches_into(candidate, matches),
686 BasenameLiteral(ref s) => s.matches_into(candidate, matches),
687 Extension(ref s) => s.matches_into(candidate, matches),
688 Prefix(ref s) => s.matches_into(candidate, matches),
689 Suffix(ref s) => s.matches_into(candidate, matches),
690 RequiredExtension(ref s) => s.matches_into(candidate, matches),
691 Regex(ref s) => s.matches_into(candidate, matches),
692 }
693 }
694}
695
696#[derive(Clone, Debug)]
697struct LiteralStrategy(fnv::HashMap<Vec<u8>, Vec<usize>>);
698
699impl LiteralStrategy {
700 fn new() -> LiteralStrategy {
701 LiteralStrategy(fnv::HashMap::default())
702 }
703
704 fn add(&mut self, global_index: usize, lit: String) {
705 self.0.entry(lit.into_bytes()).or_insert(vec![]).push(global_index);
706 }
707
708 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
709 self.0.contains_key(candidate.path.as_bytes())
710 }
711
712 #[inline(never)]
713 fn matches_into(
714 &self,
715 candidate: &Candidate<'_>,
716 matches: &mut Vec<usize>,
717 ) {
718 if let Some(hits) = self.0.get(candidate.path.as_bytes()) {
719 matches.extend(hits);
720 }
721 }
722}
723
724#[derive(Clone, Debug)]
725struct BasenameLiteralStrategy(fnv::HashMap<Vec<u8>, Vec<usize>>);
726
727impl BasenameLiteralStrategy {
728 fn new() -> BasenameLiteralStrategy {
729 BasenameLiteralStrategy(fnv::HashMap::default())
730 }
731
732 fn add(&mut self, global_index: usize, lit: String) {
733 self.0.entry(lit.into_bytes()).or_insert(vec![]).push(global_index);
734 }
735
736 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
737 if candidate.basename.is_empty() {
738 return false;
739 }
740 self.0.contains_key(candidate.basename.as_bytes())
741 }
742
743 #[inline(never)]
744 fn matches_into(
745 &self,
746 candidate: &Candidate<'_>,
747 matches: &mut Vec<usize>,
748 ) {
749 if candidate.basename.is_empty() {
750 return;
751 }
752 if let Some(hits) = self.0.get(candidate.basename.as_bytes()) {
753 matches.extend(hits);
754 }
755 }
756}
757
758#[derive(Clone, Debug)]
759struct ExtensionStrategy(fnv::HashMap<Vec<u8>, Vec<usize>>);
760
761impl ExtensionStrategy {
762 fn new() -> ExtensionStrategy {
763 ExtensionStrategy(fnv::HashMap::default())
764 }
765
766 fn add(&mut self, global_index: usize, ext: String) {
767 self.0.entry(ext.into_bytes()).or_insert(vec![]).push(global_index);
768 }
769
770 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
771 if candidate.ext.is_empty() {
772 return false;
773 }
774 self.0.contains_key(candidate.ext.as_bytes())
775 }
776
777 #[inline(never)]
778 fn matches_into(
779 &self,
780 candidate: &Candidate<'_>,
781 matches: &mut Vec<usize>,
782 ) {
783 if candidate.ext.is_empty() {
784 return;
785 }
786 if let Some(hits) = self.0.get(candidate.ext.as_bytes()) {
787 matches.extend(hits);
788 }
789 }
790}
791
792#[derive(Clone, Debug)]
793struct PrefixStrategy {
794 matcher: AhoCorasick,
795 map: Vec<usize>,
796 longest: usize,
797}
798
799impl PrefixStrategy {
800 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
801 let path = candidate.path_prefix(self.longest);
802 for m in self.matcher.find_overlapping_iter(path) {
803 if m.start() == 0 {
804 return true;
805 }
806 }
807 false
808 }
809
810 fn matches_into(
811 &self,
812 candidate: &Candidate<'_>,
813 matches: &mut Vec<usize>,
814 ) {
815 let path = candidate.path_prefix(self.longest);
816 for m in self.matcher.find_overlapping_iter(path) {
817 if m.start() == 0 {
818 matches.push(self.map[m.pattern()]);
819 }
820 }
821 }
822}
823
824#[derive(Clone, Debug)]
825struct SuffixStrategy {
826 matcher: AhoCorasick,
827 map: Vec<usize>,
828 longest: usize,
829}
830
831impl SuffixStrategy {
832 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
833 let path = candidate.path_suffix(self.longest);
834 for m in self.matcher.find_overlapping_iter(path) {
835 if m.end() == path.len() {
836 return true;
837 }
838 }
839 false
840 }
841
842 fn matches_into(
843 &self,
844 candidate: &Candidate<'_>,
845 matches: &mut Vec<usize>,
846 ) {
847 let path = candidate.path_suffix(self.longest);
848 for m in self.matcher.find_overlapping_iter(path) {
849 if m.end() == path.len() {
850 matches.push(self.map[m.pattern()]);
851 }
852 }
853 }
854}
855
856#[derive(Clone, Debug)]
857struct RequiredExtensionStrategy(fnv::HashMap<Vec<u8>, Vec<(usize, Regex)>>);
858
859impl RequiredExtensionStrategy {
860 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
861 if candidate.ext.is_empty() {
862 return false;
863 }
864 match self.0.get(candidate.ext.as_bytes()) {
865 None => false,
866 Some(regexes) => {
867 for &(_, ref re) in regexes {
868 if re.is_match(candidate.path.as_bytes()) {
869 return true;
870 }
871 }
872 false
873 }
874 }
875 }
876
877 #[inline(never)]
878 fn matches_into(
879 &self,
880 candidate: &Candidate<'_>,
881 matches: &mut Vec<usize>,
882 ) {
883 if candidate.ext.is_empty() {
884 return;
885 }
886 if let Some(regexes) = self.0.get(candidate.ext.as_bytes()) {
887 for &(global_index, ref re) in regexes {
888 if re.is_match(candidate.path.as_bytes()) {
889 matches.push(global_index);
890 }
891 }
892 }
893 }
894}
895
896#[derive(Clone, Debug)]
897struct RegexSetStrategy {
898 matcher: Regex,
899 map: Vec<usize>,
900 patset: Arc<Pool<PatternSet, PatternSetPoolFn>>,
909}
910
911type PatternSetPoolFn =
912 Box<dyn Fn() -> PatternSet + Send + Sync + UnwindSafe + RefUnwindSafe>;
913
914impl RegexSetStrategy {
915 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
916 self.matcher.is_match(candidate.path.as_bytes())
917 }
918
919 fn matches_into(
920 &self,
921 candidate: &Candidate<'_>,
922 matches: &mut Vec<usize>,
923 ) {
924 let input = regex_automata::Input::new(candidate.path.as_bytes());
925 let mut patset = self.patset.get();
926 patset.clear();
927 self.matcher.which_overlapping_matches(&input, &mut patset);
928 for i in patset.iter() {
929 matches.push(self.map[i]);
930 }
931 PoolGuard::put(patset);
932 }
933}
934
935#[derive(Clone, Debug)]
936struct MultiStrategyBuilder {
937 literals: Vec<String>,
938 map: Vec<usize>,
939 longest: usize,
940}
941
942impl MultiStrategyBuilder {
943 fn new() -> MultiStrategyBuilder {
944 MultiStrategyBuilder { literals: vec![], map: vec![], longest: 0 }
945 }
946
947 fn add(&mut self, global_index: usize, literal: String) {
948 if literal.len() > self.longest {
949 self.longest = literal.len();
950 }
951 self.map.push(global_index);
952 self.literals.push(literal);
953 }
954
955 fn prefix(self) -> PrefixStrategy {
956 PrefixStrategy {
957 matcher: AhoCorasick::new(&self.literals).unwrap(),
958 map: self.map,
959 longest: self.longest,
960 }
961 }
962
963 fn suffix(self) -> SuffixStrategy {
964 SuffixStrategy {
965 matcher: AhoCorasick::new(&self.literals).unwrap(),
966 map: self.map,
967 longest: self.longest,
968 }
969 }
970
971 fn regex_set(self) -> Result<RegexSetStrategy, Error> {
972 let matcher = new_regex_set(self.literals)?;
973 let pattern_len = matcher.pattern_len();
974 let create: PatternSetPoolFn =
975 Box::new(move || PatternSet::new(pattern_len));
976 Ok(RegexSetStrategy {
977 matcher,
978 map: self.map,
979 patset: Arc::new(Pool::new(create)),
980 })
981 }
982
983 fn is_empty(&self) -> bool {
984 self.literals.is_empty()
985 }
986}
987
988#[derive(Clone, Debug)]
989struct RequiredExtensionStrategyBuilder(
990 fnv::HashMap<Vec<u8>, Vec<(usize, String)>>,
991);
992
993impl RequiredExtensionStrategyBuilder {
994 fn new() -> RequiredExtensionStrategyBuilder {
995 RequiredExtensionStrategyBuilder(fnv::HashMap::default())
996 }
997
998 fn add(&mut self, global_index: usize, ext: String, regex: String) {
999 self.0
1000 .entry(ext.into_bytes())
1001 .or_insert(vec![])
1002 .push((global_index, regex));
1003 }
1004
1005 fn build(self) -> Result<RequiredExtensionStrategy, Error> {
1006 let mut exts = fnv::HashMap::default();
1007 for (ext, regexes) in self.0.into_iter() {
1008 exts.insert(ext.clone(), vec![]);
1009 for (global_index, regex) in regexes {
1010 let compiled = new_regex(®ex)?;
1011 exts.get_mut(&ext).unwrap().push((global_index, compiled));
1012 }
1013 }
1014 Ok(RequiredExtensionStrategy(exts))
1015 }
1016}
1017
1018pub fn escape(s: &str) -> String {
1036 let mut escaped = String::with_capacity(s.len());
1037 for c in s.chars() {
1038 match c {
1039 '?' | '*' | '[' | ']' | '{' | '}' => {
1042 escaped.push('[');
1043 escaped.push(c);
1044 escaped.push(']');
1045 }
1046 c => {
1047 escaped.push(c);
1048 }
1049 }
1050 }
1051 escaped
1052}
1053
1054#[cfg(test)]
1055mod tests {
1056 use crate::glob::Glob;
1057
1058 use super::{GlobSet, GlobSetBuilder};
1059
1060 #[test]
1061 fn set_works() {
1062 let mut builder = GlobSetBuilder::new();
1063 builder.add(Glob::new("src/**/*.rs").unwrap());
1064 builder.add(Glob::new("*.c").unwrap());
1065 builder.add(Glob::new("src/lib.rs").unwrap());
1066 let set = builder.build().unwrap();
1067
1068 assert!(set.is_match("foo.c"));
1069 assert!(set.is_match("src/foo.c"));
1070 assert!(!set.is_match("foo.rs"));
1071 assert!(!set.is_match("tests/foo.rs"));
1072 assert!(set.is_match("src/foo.rs"));
1073 assert!(set.is_match("src/grep/src/main.rs"));
1074
1075 let matches = set.matches("src/lib.rs");
1076 assert_eq!(2, matches.len());
1077 assert_eq!(0, matches[0]);
1078 assert_eq!(2, matches[1]);
1079 }
1080
1081 #[test]
1082 fn empty_set_works() {
1083 let set = GlobSetBuilder::new().build().unwrap();
1084 assert!(!set.is_match(""));
1085 assert!(!set.is_match("a"));
1086 assert!(set.matches_all("a"));
1087 }
1088
1089 #[test]
1090 fn default_set_is_empty_works() {
1091 let set: GlobSet = Default::default();
1092 assert!(!set.is_match(""));
1093 assert!(!set.is_match("a"));
1094 }
1095
1096 #[test]
1097 fn escape() {
1098 use super::escape;
1099 assert_eq!("foo", escape("foo"));
1100 assert_eq!("foo[*]", escape("foo*"));
1101 assert_eq!("[[][]]", escape("[]"));
1102 assert_eq!("[*][?]", escape("*?"));
1103 assert_eq!("src/[*][*]/[*].rs", escape("src/**/*.rs"));
1104 assert_eq!("bar[[]ab[]]baz", escape("bar[ab]baz"));
1105 assert_eq!("bar[[]!![]]!baz", escape("bar[!!]!baz"));
1106 }
1107
1108 #[test]
1112 fn set_does_not_remember() {
1113 let mut builder = GlobSetBuilder::new();
1114 builder.add(Glob::new("*foo*").unwrap());
1115 builder.add(Glob::new("*bar*").unwrap());
1116 builder.add(Glob::new("*quux*").unwrap());
1117 let set = builder.build().unwrap();
1118
1119 let matches = set.matches("ZfooZquuxZ");
1120 assert_eq!(2, matches.len());
1121 assert_eq!(0, matches[0]);
1122 assert_eq!(2, matches[1]);
1123
1124 let matches = set.matches("nada");
1125 assert_eq!(0, matches.len());
1126 }
1127
1128 #[test]
1129 fn debug() {
1130 let mut builder = GlobSetBuilder::new();
1131 builder.add(Glob::new("*foo*").unwrap());
1132 builder.add(Glob::new("*bar*").unwrap());
1133 builder.add(Glob::new("*quux*").unwrap());
1134 assert_eq!(
1135 format!("{builder:?}"),
1136 "GlobSetBuilder { pats: [Glob(\"*foo*\"), Glob(\"*bar*\"), Glob(\"*quux*\")] }",
1137 );
1138 }
1139}