1use std::fmt::Write;
2use std::path::{Path, is_separator};
3
4use regex_automata::meta::Regex;
5
6use crate::{Candidate, Error, ErrorKind, new_regex};
7
8#[derive(Clone, Debug, Eq, PartialEq)]
16pub(crate) enum MatchStrategy {
17 Literal(String),
20 BasenameLiteral(String),
23 Extension(String),
26 Prefix(String),
29 Suffix {
35 suffix: String,
37 component: bool,
39 },
40 RequiredExtension(String),
45 Regex,
47}
48
49impl MatchStrategy {
50 pub(crate) fn new(pat: &Glob) -> MatchStrategy {
52 if let Some(lit) = pat.basename_literal() {
53 MatchStrategy::BasenameLiteral(lit)
54 } else if let Some(lit) = pat.literal() {
55 MatchStrategy::Literal(lit)
56 } else if let Some(ext) = pat.ext() {
57 MatchStrategy::Extension(ext)
58 } else if let Some(prefix) = pat.prefix() {
59 MatchStrategy::Prefix(prefix)
60 } else if let Some((suffix, component)) = pat.suffix() {
61 MatchStrategy::Suffix { suffix, component }
62 } else if let Some(ext) = pat.required_ext() {
63 MatchStrategy::RequiredExtension(ext)
64 } else {
65 MatchStrategy::Regex
66 }
67 }
68}
69
70#[derive(Clone, Eq)]
75#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
76pub struct Glob {
77 glob: String,
78 re: String,
79 opts: GlobOptions,
80 tokens: Tokens,
81}
82
83impl AsRef<Glob> for Glob {
84 fn as_ref(&self) -> &Glob {
85 self
86 }
87}
88
89impl PartialEq for Glob {
90 fn eq(&self, other: &Glob) -> bool {
91 self.glob == other.glob && self.opts == other.opts
92 }
93}
94
95impl std::hash::Hash for Glob {
96 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
97 self.glob.hash(state);
98 self.opts.hash(state);
99 }
100}
101
102impl std::fmt::Debug for Glob {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 if f.alternate() {
105 f.debug_struct("Glob")
106 .field("glob", &self.glob)
107 .field("re", &self.re)
108 .field("opts", &self.opts)
109 .field("tokens", &self.tokens)
110 .finish()
111 } else {
112 f.debug_tuple("Glob").field(&self.glob).finish()
113 }
114 }
115}
116
117impl std::fmt::Display for Glob {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 self.glob.fmt(f)
120 }
121}
122
123impl std::str::FromStr for Glob {
124 type Err = Error;
125
126 fn from_str(glob: &str) -> Result<Self, Self::Err> {
127 Self::new(glob)
128 }
129}
130
131#[derive(Clone, Debug)]
133pub struct GlobMatcher {
134 pat: Glob,
136 re: Regex,
138}
139
140impl GlobMatcher {
141 pub fn is_match<P: AsRef<Path>>(&self, path: P) -> bool {
143 self.is_match_candidate(&Candidate::new(path.as_ref()))
144 }
145
146 pub fn is_match_candidate(&self, path: &Candidate<'_>) -> bool {
148 self.re.is_match(&path.path)
149 }
150
151 pub fn glob(&self) -> &Glob {
153 &self.pat
154 }
155}
156
157#[cfg(test)]
159#[derive(Clone, Debug)]
160struct GlobStrategic {
161 strategy: MatchStrategy,
163 re: Regex,
165}
166
167#[cfg(test)]
168impl GlobStrategic {
169 fn is_match<P: AsRef<Path>>(&self, path: P) -> bool {
171 self.is_match_candidate(&Candidate::new(path.as_ref()))
172 }
173
174 fn is_match_candidate(&self, candidate: &Candidate<'_>) -> bool {
176 let byte_path = &*candidate.path;
177
178 match self.strategy {
179 MatchStrategy::Literal(ref lit) => lit.as_bytes() == byte_path,
180 MatchStrategy::BasenameLiteral(ref lit) => {
181 lit.as_bytes() == &*candidate.basename
182 }
183 MatchStrategy::Extension(ref ext) => {
184 ext.as_bytes() == &*candidate.ext
185 }
186 MatchStrategy::Prefix(ref pre) => {
187 starts_with(pre.as_bytes(), byte_path)
188 }
189 MatchStrategy::Suffix { ref suffix, component } => {
190 if component && byte_path == &suffix.as_bytes()[1..] {
191 return true;
192 }
193 ends_with(suffix.as_bytes(), byte_path)
194 }
195 MatchStrategy::RequiredExtension(ref ext) => {
196 let ext = ext.as_bytes();
197 &*candidate.ext == ext && self.re.is_match(byte_path)
198 }
199 MatchStrategy::Regex => self.re.is_match(byte_path),
200 }
201 }
202}
203
204#[derive(Clone, Debug)]
211pub struct GlobBuilder<'a> {
212 glob: &'a str,
214 opts: GlobOptions,
216}
217
218#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
219#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
220struct GlobOptions {
221 case_insensitive: bool,
223 literal_separator: bool,
226 backslash_escape: bool,
229 empty_alternates: bool,
232 allow_unclosed_class: bool,
237}
238
239impl GlobOptions {
240 fn default() -> GlobOptions {
241 GlobOptions {
242 case_insensitive: false,
243 literal_separator: false,
244 backslash_escape: !is_separator('\\'),
245 empty_alternates: false,
246 allow_unclosed_class: false,
247 }
248 }
249}
250
251#[derive(Clone, Debug, Default, Eq, PartialEq)]
252#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
253struct Tokens(Vec<Token>);
254
255impl std::ops::Deref for Tokens {
256 type Target = Vec<Token>;
257 fn deref(&self) -> &Vec<Token> {
258 &self.0
259 }
260}
261
262impl std::ops::DerefMut for Tokens {
263 fn deref_mut(&mut self) -> &mut Vec<Token> {
264 &mut self.0
265 }
266}
267
268#[derive(Clone, Debug, Eq, PartialEq)]
269#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
270enum Token {
271 Literal(char),
272 Any,
273 ZeroOrMore,
274 RecursivePrefix,
275 RecursiveSuffix,
276 RecursiveZeroOrMore,
277 Class { negated: bool, ranges: Vec<(char, char)> },
278 Alternates(Vec<Tokens>),
279}
280
281impl Glob {
282 pub fn new(glob: &str) -> Result<Glob, Error> {
284 GlobBuilder::new(glob).build()
285 }
286
287 pub fn compile_matcher(&self) -> GlobMatcher {
289 let re =
290 new_regex(&self.re).expect("regex compilation shouldn't fail");
291 GlobMatcher { pat: self.clone(), re }
292 }
293
294 #[cfg(test)]
300 fn compile_strategic_matcher(&self) -> GlobStrategic {
301 let strategy = MatchStrategy::new(self);
302 let re =
303 new_regex(&self.re).expect("regex compilation shouldn't fail");
304 GlobStrategic { strategy, re }
305 }
306
307 pub fn glob(&self) -> &str {
309 &self.glob
310 }
311
312 pub fn regex(&self) -> &str {
327 &self.re
328 }
329
330 fn literal(&self) -> Option<String> {
335 if self.opts.case_insensitive {
336 return None;
337 }
338 let mut lit = String::new();
339 for t in &*self.tokens {
340 let Token::Literal(c) = *t else { return None };
341 lit.push(c);
342 }
343 if lit.is_empty() { None } else { Some(lit) }
344 }
345
346 fn ext(&self) -> Option<String> {
354 if self.opts.case_insensitive {
355 return None;
356 }
357 let start = match *self.tokens.get(0)? {
358 Token::RecursivePrefix => 1,
359 _ => 0,
360 };
361 match *self.tokens.get(start)? {
362 Token::ZeroOrMore => {
363 if start == 0 && self.opts.literal_separator {
367 return None;
368 }
369 }
370 _ => return None,
371 }
372 match *self.tokens.get(start + 1)? {
373 Token::Literal('.') => {}
374 _ => return None,
375 }
376 let mut lit = ".".to_string();
377 for t in self.tokens[start + 2..].iter() {
378 match *t {
379 Token::Literal('.') | Token::Literal('/') => return None,
380 Token::Literal(c) => lit.push(c),
381 _ => return None,
382 }
383 }
384 if lit.is_empty() { None } else { Some(lit) }
385 }
386
387 fn required_ext(&self) -> Option<String> {
391 if self.opts.case_insensitive {
392 return None;
393 }
394 let mut ext: Vec<char> = vec![]; for t in self.tokens.iter().rev() {
398 match *t {
399 Token::Literal('/') => return None,
400 Token::Literal(c) => {
401 ext.push(c);
402 if c == '.' {
403 break;
404 }
405 }
406 _ => return None,
407 }
408 }
409 if ext.last() != Some(&'.') {
410 None
411 } else {
412 ext.reverse();
413 Some(ext.into_iter().collect())
414 }
415 }
416
417 fn prefix(&self) -> Option<String> {
420 if self.opts.case_insensitive {
421 return None;
422 }
423 let (end, need_sep) = match *self.tokens.last()? {
424 Token::ZeroOrMore => {
425 if self.opts.literal_separator {
426 return None;
433 }
434 (self.tokens.len() - 1, false)
435 }
436 Token::RecursiveSuffix => (self.tokens.len() - 1, true),
437 _ => (self.tokens.len(), false),
438 };
439 let mut lit = String::new();
440 for t in &self.tokens[0..end] {
441 let Token::Literal(c) = *t else { return None };
442 lit.push(c);
443 }
444 if need_sep {
445 lit.push('/');
446 }
447 if lit.is_empty() { None } else { Some(lit) }
448 }
449
450 fn suffix(&self) -> Option<(String, bool)> {
463 if self.opts.case_insensitive {
464 return None;
465 }
466 let mut lit = String::new();
467 let (start, entire) = match *self.tokens.get(0)? {
468 Token::RecursivePrefix => {
469 if let Some(&Token::Literal(_)) = self.tokens.get(1) {
472 lit.push('/');
473 (1, true)
474 } else {
475 (1, false)
476 }
477 }
478 _ => (0, false),
479 };
480 let start = match *self.tokens.get(start)? {
481 Token::ZeroOrMore => {
482 if self.opts.literal_separator {
486 return None;
487 }
488 start + 1
489 }
490 _ => start,
491 };
492 for t in &self.tokens[start..] {
493 let Token::Literal(c) = *t else { return None };
494 lit.push(c);
495 }
496 if lit.is_empty() || lit == "/" { None } else { Some((lit, entire)) }
497 }
498
499 fn basename_tokens(&self) -> Option<&[Token]> {
511 if self.opts.case_insensitive {
512 return None;
513 }
514 let start = match *self.tokens.get(0)? {
515 Token::RecursivePrefix => 1,
516 _ => {
517 return None;
521 }
522 };
523 if self.tokens[start..].is_empty() {
524 return None;
525 }
526 for t in self.tokens[start..].iter() {
527 match *t {
528 Token::Literal('/') => return None,
529 Token::Literal(_) => {} Token::Any | Token::ZeroOrMore => {
531 if !self.opts.literal_separator {
532 return None;
536 }
537 }
538 Token::RecursivePrefix
539 | Token::RecursiveSuffix
540 | Token::RecursiveZeroOrMore => {
541 return None;
542 }
543 Token::Class { .. } | Token::Alternates(..) => {
544 return None;
548 }
549 }
550 }
551 Some(&self.tokens[start..])
552 }
553
554 fn basename_literal(&self) -> Option<String> {
560 let tokens = self.basename_tokens()?;
561 let mut lit = String::new();
562 for t in tokens {
563 let Token::Literal(c) = *t else { return None };
564 lit.push(c);
565 }
566 Some(lit)
567 }
568}
569
570impl<'a> GlobBuilder<'a> {
571 pub fn new(glob: &'a str) -> GlobBuilder<'a> {
575 GlobBuilder { glob, opts: GlobOptions::default() }
576 }
577
578 pub fn build(&self) -> Result<Glob, Error> {
580 let mut p = Parser {
581 glob: &self.glob,
582 alternates_stack: Vec::new(),
583 branches: vec![Tokens::default()],
584 chars: self.glob.chars().peekable(),
585 prev: None,
586 cur: None,
587 found_unclosed_class: false,
588 opts: &self.opts,
589 };
590 p.parse()?;
591 if p.branches.is_empty() {
592 unreachable!()
596 } else if p.branches.len() > 1 {
597 Err(Error {
598 glob: Some(self.glob.to_string()),
599 kind: ErrorKind::UnclosedAlternates,
600 })
601 } else {
602 let tokens = p.branches.pop().unwrap();
603 Ok(Glob {
604 glob: self.glob.to_string(),
605 re: tokens.to_regex_with(&self.opts),
606 opts: self.opts,
607 tokens,
608 })
609 }
610 }
611
612 pub fn case_insensitive(&mut self, yes: bool) -> &mut GlobBuilder<'a> {
616 self.opts.case_insensitive = yes;
617 self
618 }
619
620 pub fn literal_separator(&mut self, yes: bool) -> &mut GlobBuilder<'a> {
624 self.opts.literal_separator = yes;
625 self
626 }
627
628 pub fn backslash_escape(&mut self, yes: bool) -> &mut GlobBuilder<'a> {
637 self.opts.backslash_escape = yes;
638 self
639 }
640
641 pub fn empty_alternates(&mut self, yes: bool) -> &mut GlobBuilder<'a> {
648 self.opts.empty_alternates = yes;
649 self
650 }
651
652 pub fn allow_unclosed_class(&mut self, yes: bool) -> &mut GlobBuilder<'a> {
664 self.opts.allow_unclosed_class = yes;
665 self
666 }
667}
668
669impl Tokens {
670 fn to_regex_with(&self, options: &GlobOptions) -> String {
674 let mut re = String::new();
675 re.push_str("(?-u)");
676 if options.case_insensitive {
677 re.push_str("(?i)");
678 }
679 re.push('^');
680 if self.len() == 1 && self[0] == Token::RecursivePrefix {
683 re.push_str(".*");
684 re.push('$');
685 return re;
686 }
687 self.tokens_to_regex(options, &self, &mut re);
688 re.push('$');
689 re
690 }
691
692 fn tokens_to_regex(
693 &self,
694 options: &GlobOptions,
695 tokens: &[Token],
696 re: &mut String,
697 ) {
698 for tok in tokens.iter() {
699 match *tok {
700 Token::Literal(c) => {
701 re.push_str(&char_to_escaped_literal(c));
702 }
703 Token::Any => {
704 if options.literal_separator {
705 re.push_str("[^/]");
706 } else {
707 re.push_str(".");
708 }
709 }
710 Token::ZeroOrMore => {
711 if options.literal_separator {
712 re.push_str("[^/]*");
713 } else {
714 re.push_str(".*");
715 }
716 }
717 Token::RecursivePrefix => {
718 re.push_str("(?:/?|.*/)");
719 }
720 Token::RecursiveSuffix => {
721 re.push_str("/.*");
722 }
723 Token::RecursiveZeroOrMore => {
724 re.push_str("(?:/|/.*/)");
725 }
726 Token::Class { negated, ref ranges } => {
727 re.push('[');
728 if negated {
729 re.push('^');
730 }
731 for r in ranges {
732 if r.0 == r.1 {
733 re.push_str(&char_to_escaped_literal(r.0));
735 } else {
736 re.push_str(&char_to_escaped_literal(r.0));
737 re.push('-');
738 re.push_str(&char_to_escaped_literal(r.1));
739 }
740 }
741 re.push(']');
742 }
743 Token::Alternates(ref patterns) => {
744 let mut parts = vec![];
745 for pat in patterns {
746 let mut altre = String::new();
747 self.tokens_to_regex(options, &pat, &mut altre);
748 if !altre.is_empty() || options.empty_alternates {
749 parts.push(altre);
750 }
751 }
752
753 if !parts.is_empty() {
756 re.push_str("(?:");
757 re.push_str(&parts.join("|"));
758 re.push(')');
759 }
760 }
761 }
762 }
763 }
764}
765
766fn char_to_escaped_literal(c: char) -> String {
769 let mut buf = [0; 4];
770 let bytes = c.encode_utf8(&mut buf).as_bytes();
771 bytes_to_escaped_literal(bytes)
772}
773
774fn bytes_to_escaped_literal(bs: &[u8]) -> String {
777 let mut s = String::with_capacity(bs.len());
778 for &b in bs {
779 if b <= 0x7F {
780 regex_syntax::escape_into(
781 char::from(b).encode_utf8(&mut [0; 4]),
782 &mut s,
783 );
784 } else {
785 write!(&mut s, "\\x{:02x}", b).unwrap();
786 }
787 }
788 s
789}
790
791struct Parser<'a> {
792 glob: &'a str,
794 alternates_stack: Vec<usize>,
796 branches: Vec<Tokens>,
799 chars: std::iter::Peekable<std::str::Chars<'a>>,
801 prev: Option<char>,
803 cur: Option<char>,
805 found_unclosed_class: bool,
814 opts: &'a GlobOptions,
816}
817
818impl<'a> Parser<'a> {
819 fn error(&self, kind: ErrorKind) -> Error {
820 Error { glob: Some(self.glob.to_string()), kind }
821 }
822
823 fn parse(&mut self) -> Result<(), Error> {
824 while let Some(c) = self.bump() {
825 match c {
826 '?' => self.push_token(Token::Any)?,
827 '*' => self.parse_star()?,
828 '[' if !self.found_unclosed_class => self.parse_class()?,
829 '{' => self.push_alternate()?,
830 '}' => self.pop_alternate()?,
831 ',' => self.parse_comma()?,
832 '\\' => self.parse_backslash()?,
833 c => self.push_token(Token::Literal(c))?,
834 }
835 }
836 Ok(())
837 }
838
839 fn push_alternate(&mut self) -> Result<(), Error> {
840 self.alternates_stack.push(self.branches.len());
841 self.branches.push(Tokens::default());
842 Ok(())
843 }
844
845 fn pop_alternate(&mut self) -> Result<(), Error> {
846 let Some(start) = self.alternates_stack.pop() else {
847 return Err(self.error(ErrorKind::UnopenedAlternates));
848 };
849 assert!(start <= self.branches.len());
850 let alts = Token::Alternates(self.branches.drain(start..).collect());
851 self.push_token(alts)?;
852 Ok(())
853 }
854
855 fn push_token(&mut self, tok: Token) -> Result<(), Error> {
856 if let Some(ref mut pat) = self.branches.last_mut() {
857 return Ok(pat.push(tok));
858 }
859 Err(self.error(ErrorKind::UnopenedAlternates))
860 }
861
862 fn pop_token(&mut self) -> Result<Token, Error> {
863 if let Some(ref mut pat) = self.branches.last_mut() {
864 return Ok(pat.pop().unwrap());
865 }
866 Err(self.error(ErrorKind::UnopenedAlternates))
867 }
868
869 fn have_tokens(&self) -> Result<bool, Error> {
870 match self.branches.last() {
871 None => Err(self.error(ErrorKind::UnopenedAlternates)),
872 Some(ref pat) => Ok(!pat.is_empty()),
873 }
874 }
875
876 fn parse_comma(&mut self) -> Result<(), Error> {
877 if self.alternates_stack.is_empty() {
881 self.push_token(Token::Literal(','))
882 } else {
883 Ok(self.branches.push(Tokens::default()))
884 }
885 }
886
887 fn parse_backslash(&mut self) -> Result<(), Error> {
888 if self.opts.backslash_escape {
889 match self.bump() {
890 None => Err(self.error(ErrorKind::DanglingEscape)),
891 Some(c) => self.push_token(Token::Literal(c)),
892 }
893 } else if is_separator('\\') {
894 self.push_token(Token::Literal('/'))
896 } else {
897 self.push_token(Token::Literal('\\'))
898 }
899 }
900
901 fn parse_star(&mut self) -> Result<(), Error> {
902 let prev = self.prev;
903 if self.peek() != Some('*') {
904 self.push_token(Token::ZeroOrMore)?;
905 return Ok(());
906 }
907 assert!(self.bump() == Some('*'));
908 if !self.have_tokens()? {
909 if !self.peek().map_or(true, is_separator) {
910 self.push_token(Token::ZeroOrMore)?;
911 self.push_token(Token::ZeroOrMore)?;
912 } else {
913 self.push_token(Token::RecursivePrefix)?;
914 assert!(self.bump().map_or(true, is_separator));
915 }
916 return Ok(());
917 }
918
919 if !prev.map(is_separator).unwrap_or(false) {
920 if self.branches.len() <= 1
921 || (prev != Some(',') && prev != Some('{'))
922 {
923 self.push_token(Token::ZeroOrMore)?;
924 self.push_token(Token::ZeroOrMore)?;
925 return Ok(());
926 }
927 }
928 let is_suffix = match self.peek() {
929 None => {
930 assert!(self.bump().is_none());
931 true
932 }
933 Some(',') | Some('}') if self.branches.len() >= 2 => true,
934 Some(c) if is_separator(c) => {
935 assert!(self.bump().map(is_separator).unwrap_or(false));
936 false
937 }
938 _ => {
939 self.push_token(Token::ZeroOrMore)?;
940 self.push_token(Token::ZeroOrMore)?;
941 return Ok(());
942 }
943 };
944 match self.pop_token()? {
945 Token::RecursivePrefix => {
946 self.push_token(Token::RecursivePrefix)?;
947 }
948 Token::RecursiveSuffix => {
949 self.push_token(Token::RecursiveSuffix)?;
950 }
951 _ => {
952 if is_suffix {
953 self.push_token(Token::RecursiveSuffix)?;
954 } else {
955 self.push_token(Token::RecursiveZeroOrMore)?;
956 }
957 }
958 }
959 Ok(())
960 }
961
962 fn parse_class(&mut self) -> Result<(), Error> {
963 let saved_chars = self.chars.clone();
965 let saved_prev = self.prev;
966 let saved_cur = self.cur;
967
968 fn add_to_last_range(
969 glob: &str,
970 r: &mut (char, char),
971 add: char,
972 ) -> Result<(), Error> {
973 r.1 = add;
974 if r.1 < r.0 {
975 Err(Error {
976 glob: Some(glob.to_string()),
977 kind: ErrorKind::InvalidRange(r.0, r.1),
978 })
979 } else {
980 Ok(())
981 }
982 }
983 let mut ranges = vec![];
984 let negated = match self.chars.peek() {
985 Some(&'!') | Some(&'^') => {
986 let bump = self.bump();
987 assert!(bump == Some('!') || bump == Some('^'));
988 true
989 }
990 _ => false,
991 };
992 let mut first = true;
993 let mut in_range = false;
994 loop {
995 let Some(c) = self.bump() else {
996 return if self.opts.allow_unclosed_class == true {
997 self.chars = saved_chars;
998 self.cur = saved_cur;
999 self.prev = saved_prev;
1000 self.found_unclosed_class = true;
1001
1002 self.push_token(Token::Literal('['))
1003 } else {
1004 Err(self.error(ErrorKind::UnclosedClass))
1005 };
1006 };
1007 match c {
1008 ']' => {
1009 if first {
1010 ranges.push((']', ']'));
1011 } else {
1012 break;
1013 }
1014 }
1015 '-' => {
1016 if first {
1017 ranges.push(('-', '-'));
1018 } else if in_range {
1019 let r = ranges.last_mut().unwrap();
1022 add_to_last_range(&self.glob, r, '-')?;
1023 in_range = false;
1024 } else {
1025 assert!(!ranges.is_empty());
1026 in_range = true;
1027 }
1028 }
1029 c => {
1030 if in_range {
1031 add_to_last_range(
1034 &self.glob,
1035 ranges.last_mut().unwrap(),
1036 c,
1037 )?;
1038 } else {
1039 ranges.push((c, c));
1040 }
1041 in_range = false;
1042 }
1043 }
1044 first = false;
1045 }
1046 if in_range {
1047 ranges.push(('-', '-'));
1050 }
1051 self.push_token(Token::Class { negated, ranges })
1052 }
1053
1054 fn bump(&mut self) -> Option<char> {
1055 self.prev = self.cur;
1056 self.cur = self.chars.next();
1057 self.cur
1058 }
1059
1060 fn peek(&mut self) -> Option<char> {
1061 self.chars.peek().map(|&ch| ch)
1062 }
1063}
1064
1065#[cfg(test)]
1066fn starts_with(needle: &[u8], haystack: &[u8]) -> bool {
1067 needle.len() <= haystack.len() && needle == &haystack[..needle.len()]
1068}
1069
1070#[cfg(test)]
1071fn ends_with(needle: &[u8], haystack: &[u8]) -> bool {
1072 if needle.len() > haystack.len() {
1073 return false;
1074 }
1075 needle == &haystack[haystack.len() - needle.len()..]
1076}
1077
1078#[cfg(test)]
1079mod tests {
1080 use super::Token::*;
1081 use super::{Glob, GlobBuilder, Token};
1082 use crate::{ErrorKind, GlobSetBuilder};
1083
1084 #[derive(Clone, Copy, Debug, Default)]
1085 struct Options {
1086 casei: Option<bool>,
1087 litsep: Option<bool>,
1088 bsesc: Option<bool>,
1089 ealtre: Option<bool>,
1090 unccls: Option<bool>,
1091 }
1092
1093 macro_rules! syntax {
1094 ($name:ident, $pat:expr, $tokens:expr) => {
1095 #[test]
1096 fn $name() {
1097 let pat = Glob::new($pat).unwrap();
1098 assert_eq!($tokens, pat.tokens.0);
1099 }
1100 };
1101 }
1102
1103 macro_rules! syntaxerr {
1104 ($name:ident, $pat:expr, $err:expr) => {
1105 #[test]
1106 fn $name() {
1107 let err = Glob::new($pat).unwrap_err();
1108 assert_eq!(&$err, err.kind());
1109 }
1110 };
1111 }
1112
1113 macro_rules! toregex {
1114 ($name:ident, $pat:expr, $re:expr) => {
1115 toregex!($name, $pat, $re, Options::default());
1116 };
1117 ($name:ident, $pat:expr, $re:expr, $options:expr) => {
1118 #[test]
1119 fn $name() {
1120 let mut builder = GlobBuilder::new($pat);
1121 if let Some(casei) = $options.casei {
1122 builder.case_insensitive(casei);
1123 }
1124 if let Some(litsep) = $options.litsep {
1125 builder.literal_separator(litsep);
1126 }
1127 if let Some(bsesc) = $options.bsesc {
1128 builder.backslash_escape(bsesc);
1129 }
1130 if let Some(ealtre) = $options.ealtre {
1131 builder.empty_alternates(ealtre);
1132 }
1133 if let Some(unccls) = $options.unccls {
1134 builder.allow_unclosed_class(unccls);
1135 }
1136
1137 let pat = builder.build().unwrap();
1138 assert_eq!(format!("(?-u){}", $re), pat.regex());
1139 }
1140 };
1141 }
1142
1143 macro_rules! matches {
1144 ($name:ident, $pat:expr, $path:expr) => {
1145 matches!($name, $pat, $path, Options::default());
1146 };
1147 ($name:ident, $pat:expr, $path:expr, $options:expr) => {
1148 #[test]
1149 fn $name() {
1150 let mut builder = GlobBuilder::new($pat);
1151 if let Some(casei) = $options.casei {
1152 builder.case_insensitive(casei);
1153 }
1154 if let Some(litsep) = $options.litsep {
1155 builder.literal_separator(litsep);
1156 }
1157 if let Some(bsesc) = $options.bsesc {
1158 builder.backslash_escape(bsesc);
1159 }
1160 if let Some(ealtre) = $options.ealtre {
1161 builder.empty_alternates(ealtre);
1162 }
1163 let pat = builder.build().unwrap();
1164 let matcher = pat.compile_matcher();
1165 let strategic = pat.compile_strategic_matcher();
1166 let set = GlobSetBuilder::new().add(pat).build().unwrap();
1167 assert!(matcher.is_match($path));
1168 assert!(strategic.is_match($path));
1169 assert!(set.is_match($path));
1170 }
1171 };
1172 }
1173
1174 macro_rules! nmatches {
1175 ($name:ident, $pat:expr, $path:expr) => {
1176 nmatches!($name, $pat, $path, Options::default());
1177 };
1178 ($name:ident, $pat:expr, $path:expr, $options:expr) => {
1179 #[test]
1180 fn $name() {
1181 let mut builder = GlobBuilder::new($pat);
1182 if let Some(casei) = $options.casei {
1183 builder.case_insensitive(casei);
1184 }
1185 if let Some(litsep) = $options.litsep {
1186 builder.literal_separator(litsep);
1187 }
1188 if let Some(bsesc) = $options.bsesc {
1189 builder.backslash_escape(bsesc);
1190 }
1191 if let Some(ealtre) = $options.ealtre {
1192 builder.empty_alternates(ealtre);
1193 }
1194 let pat = builder.build().unwrap();
1195 let matcher = pat.compile_matcher();
1196 let strategic = pat.compile_strategic_matcher();
1197 let set = GlobSetBuilder::new().add(pat).build().unwrap();
1198 assert!(!matcher.is_match($path));
1199 assert!(!strategic.is_match($path));
1200 assert!(!set.is_match($path));
1201 }
1202 };
1203 }
1204
1205 fn s(string: &str) -> String {
1206 string.to_string()
1207 }
1208
1209 fn class(s: char, e: char) -> Token {
1210 Class { negated: false, ranges: vec![(s, e)] }
1211 }
1212
1213 fn classn(s: char, e: char) -> Token {
1214 Class { negated: true, ranges: vec![(s, e)] }
1215 }
1216
1217 fn rclass(ranges: &[(char, char)]) -> Token {
1218 Class { negated: false, ranges: ranges.to_vec() }
1219 }
1220
1221 fn rclassn(ranges: &[(char, char)]) -> Token {
1222 Class { negated: true, ranges: ranges.to_vec() }
1223 }
1224
1225 syntax!(literal1, "a", vec![Literal('a')]);
1226 syntax!(literal2, "ab", vec![Literal('a'), Literal('b')]);
1227 syntax!(any1, "?", vec![Any]);
1228 syntax!(any2, "a?b", vec![Literal('a'), Any, Literal('b')]);
1229 syntax!(seq1, "*", vec![ZeroOrMore]);
1230 syntax!(seq2, "a*b", vec![Literal('a'), ZeroOrMore, Literal('b')]);
1231 syntax!(
1232 seq3,
1233 "*a*b*",
1234 vec![ZeroOrMore, Literal('a'), ZeroOrMore, Literal('b'), ZeroOrMore,]
1235 );
1236 syntax!(rseq1, "**", vec![RecursivePrefix]);
1237 syntax!(rseq2, "**/", vec![RecursivePrefix]);
1238 syntax!(rseq3, "/**", vec![RecursiveSuffix]);
1239 syntax!(rseq4, "/**/", vec![RecursiveZeroOrMore]);
1240 syntax!(
1241 rseq5,
1242 "a/**/b",
1243 vec![Literal('a'), RecursiveZeroOrMore, Literal('b'),]
1244 );
1245 syntax!(cls1, "[a]", vec![class('a', 'a')]);
1246 syntax!(cls2, "[!a]", vec![classn('a', 'a')]);
1247 syntax!(cls3, "[a-z]", vec![class('a', 'z')]);
1248 syntax!(cls4, "[!a-z]", vec![classn('a', 'z')]);
1249 syntax!(cls5, "[-]", vec![class('-', '-')]);
1250 syntax!(cls6, "[]]", vec![class(']', ']')]);
1251 syntax!(cls7, "[*]", vec![class('*', '*')]);
1252 syntax!(cls8, "[!!]", vec![classn('!', '!')]);
1253 syntax!(cls9, "[a-]", vec![rclass(&[('a', 'a'), ('-', '-')])]);
1254 syntax!(cls10, "[-a-z]", vec![rclass(&[('-', '-'), ('a', 'z')])]);
1255 syntax!(cls11, "[a-z-]", vec![rclass(&[('a', 'z'), ('-', '-')])]);
1256 syntax!(
1257 cls12,
1258 "[-a-z-]",
1259 vec![rclass(&[('-', '-'), ('a', 'z'), ('-', '-')]),]
1260 );
1261 syntax!(cls13, "[]-z]", vec![class(']', 'z')]);
1262 syntax!(cls14, "[--z]", vec![class('-', 'z')]);
1263 syntax!(cls15, "[ --]", vec![class(' ', '-')]);
1264 syntax!(cls16, "[0-9a-z]", vec![rclass(&[('0', '9'), ('a', 'z')])]);
1265 syntax!(cls17, "[a-z0-9]", vec![rclass(&[('a', 'z'), ('0', '9')])]);
1266 syntax!(cls18, "[!0-9a-z]", vec![rclassn(&[('0', '9'), ('a', 'z')])]);
1267 syntax!(cls19, "[!a-z0-9]", vec![rclassn(&[('a', 'z'), ('0', '9')])]);
1268 syntax!(cls20, "[^a]", vec![classn('a', 'a')]);
1269 syntax!(cls21, "[^a-z]", vec![classn('a', 'z')]);
1270
1271 syntaxerr!(err_unclosed1, "[", ErrorKind::UnclosedClass);
1272 syntaxerr!(err_unclosed2, "[]", ErrorKind::UnclosedClass);
1273 syntaxerr!(err_unclosed3, "[!", ErrorKind::UnclosedClass);
1274 syntaxerr!(err_unclosed4, "[!]", ErrorKind::UnclosedClass);
1275 syntaxerr!(err_range1, "[z-a]", ErrorKind::InvalidRange('z', 'a'));
1276 syntaxerr!(err_range2, "[z--]", ErrorKind::InvalidRange('z', '-'));
1277 syntaxerr!(err_alt1, "{a,b", ErrorKind::UnclosedAlternates);
1278 syntaxerr!(err_alt2, "{a,{b,c}", ErrorKind::UnclosedAlternates);
1279 syntaxerr!(err_alt3, "a,b}", ErrorKind::UnopenedAlternates);
1280 syntaxerr!(err_alt4, "{a,b}}", ErrorKind::UnopenedAlternates);
1281
1282 const CASEI: Options = Options {
1283 casei: Some(true),
1284 litsep: None,
1285 bsesc: None,
1286 ealtre: None,
1287 unccls: None,
1288 };
1289 const SLASHLIT: Options = Options {
1290 casei: None,
1291 litsep: Some(true),
1292 bsesc: None,
1293 ealtre: None,
1294 unccls: None,
1295 };
1296 const NOBSESC: Options = Options {
1297 casei: None,
1298 litsep: None,
1299 bsesc: Some(false),
1300 ealtre: None,
1301 unccls: None,
1302 };
1303 const BSESC: Options = Options {
1304 casei: None,
1305 litsep: None,
1306 bsesc: Some(true),
1307 ealtre: None,
1308 unccls: None,
1309 };
1310 const EALTRE: Options = Options {
1311 casei: None,
1312 litsep: None,
1313 bsesc: Some(true),
1314 ealtre: Some(true),
1315 unccls: None,
1316 };
1317 const UNCCLS: Options = Options {
1318 casei: None,
1319 litsep: None,
1320 bsesc: None,
1321 ealtre: None,
1322 unccls: Some(true),
1323 };
1324
1325 toregex!(allow_unclosed_class_single, r"[", r"^\[$", &UNCCLS);
1326 toregex!(allow_unclosed_class_many, r"[abc", r"^\[abc$", &UNCCLS);
1327 toregex!(allow_unclosed_class_empty1, r"[]", r"^\[\]$", &UNCCLS);
1328 toregex!(allow_unclosed_class_empty2, r"[][", r"^\[\]\[$", &UNCCLS);
1329 toregex!(allow_unclosed_class_negated_unclosed, r"[!", r"^\[!$", &UNCCLS);
1330 toregex!(allow_unclosed_class_negated_empty, r"[!]", r"^\[!\]$", &UNCCLS);
1331 toregex!(
1332 allow_unclosed_class_brace1,
1333 r"{[abc,xyz}",
1334 r"^(?:\[abc|xyz)$",
1335 &UNCCLS
1336 );
1337 toregex!(
1338 allow_unclosed_class_brace2,
1339 r"{[abc,[xyz}",
1340 r"^(?:\[abc|\[xyz)$",
1341 &UNCCLS
1342 );
1343 toregex!(
1344 allow_unclosed_class_brace3,
1345 r"{[abc],[xyz}",
1346 r"^(?:[abc]|\[xyz)$",
1347 &UNCCLS
1348 );
1349
1350 toregex!(re_empty, "", "^$");
1351
1352 toregex!(re_casei, "a", "(?i)^a$", &CASEI);
1353
1354 toregex!(re_slash1, "?", r"^[^/]$", SLASHLIT);
1355 toregex!(re_slash2, "*", r"^[^/]*$", SLASHLIT);
1356
1357 toregex!(re1, "a", "^a$");
1358 toregex!(re2, "?", "^.$");
1359 toregex!(re3, "*", "^.*$");
1360 toregex!(re4, "a?", "^a.$");
1361 toregex!(re5, "?a", "^.a$");
1362 toregex!(re6, "a*", "^a.*$");
1363 toregex!(re7, "*a", "^.*a$");
1364 toregex!(re8, "[*]", r"^[\*]$");
1365 toregex!(re9, "[+]", r"^[\+]$");
1366 toregex!(re10, "+", r"^\+$");
1367 toregex!(re11, "☃", r"^\xe2\x98\x83$");
1368 toregex!(re12, "**", r"^.*$");
1369 toregex!(re13, "**/", r"^.*$");
1370 toregex!(re14, "**/*", r"^(?:/?|.*/).*$");
1371 toregex!(re15, "**/**", r"^.*$");
1372 toregex!(re16, "**/**/*", r"^(?:/?|.*/).*$");
1373 toregex!(re17, "**/**/**", r"^.*$");
1374 toregex!(re18, "**/**/**/*", r"^(?:/?|.*/).*$");
1375 toregex!(re19, "a/**", r"^a/.*$");
1376 toregex!(re20, "a/**/**", r"^a/.*$");
1377 toregex!(re21, "a/**/**/**", r"^a/.*$");
1378 toregex!(re22, "a/**/b", r"^a(?:/|/.*/)b$");
1379 toregex!(re23, "a/**/**/b", r"^a(?:/|/.*/)b$");
1380 toregex!(re24, "a/**/**/**/b", r"^a(?:/|/.*/)b$");
1381 toregex!(re25, "**/b", r"^(?:/?|.*/)b$");
1382 toregex!(re26, "**/**/b", r"^(?:/?|.*/)b$");
1383 toregex!(re27, "**/**/**/b", r"^(?:/?|.*/)b$");
1384 toregex!(re28, "a**", r"^a.*.*$");
1385 toregex!(re29, "**a", r"^.*.*a$");
1386 toregex!(re30, "a**b", r"^a.*.*b$");
1387 toregex!(re31, "***", r"^.*.*.*$");
1388 toregex!(re32, "/a**", r"^/a.*.*$");
1389 toregex!(re33, "/**a", r"^/.*.*a$");
1390 toregex!(re34, "/a**b", r"^/a.*.*b$");
1391 toregex!(re35, "{a,b}", r"^(?:a|b)$");
1392 toregex!(re36, "{a,{b,c}}", r"^(?:a|(?:b|c))$");
1393 toregex!(re37, "{{a,b},{c,d}}", r"^(?:(?:a|b)|(?:c|d))$");
1394
1395 matches!(match1, "a", "a");
1396 matches!(match2, "a*b", "a_b");
1397 matches!(match3, "a*b*c", "abc");
1398 matches!(match4, "a*b*c", "a_b_c");
1399 matches!(match5, "a*b*c", "a___b___c");
1400 matches!(match6, "abc*abc*abc", "abcabcabcabcabcabcabc");
1401 matches!(match7, "a*a*a*a*a*a*a*a*a", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
1402 matches!(match8, "a*b[xyz]c*d", "abxcdbxcddd");
1403 matches!(match9, "*.rs", ".rs");
1404 matches!(match10, "☃", "☃");
1405
1406 matches!(matchrec1, "some/**/needle.txt", "some/needle.txt");
1407 matches!(matchrec2, "some/**/needle.txt", "some/one/needle.txt");
1408 matches!(matchrec3, "some/**/needle.txt", "some/one/two/needle.txt");
1409 matches!(matchrec4, "some/**/needle.txt", "some/other/needle.txt");
1410 matches!(matchrec5, "**", "abcde");
1411 matches!(matchrec6, "**", "");
1412 matches!(matchrec7, "**", ".asdf");
1413 matches!(matchrec8, "**", "/x/.asdf");
1414 matches!(matchrec9, "some/**/**/needle.txt", "some/needle.txt");
1415 matches!(matchrec10, "some/**/**/needle.txt", "some/one/needle.txt");
1416 matches!(matchrec11, "some/**/**/needle.txt", "some/one/two/needle.txt");
1417 matches!(matchrec12, "some/**/**/needle.txt", "some/other/needle.txt");
1418 matches!(matchrec13, "**/test", "one/two/test");
1419 matches!(matchrec14, "**/test", "one/test");
1420 matches!(matchrec15, "**/test", "test");
1421 matches!(matchrec16, "/**/test", "/one/two/test");
1422 matches!(matchrec17, "/**/test", "/one/test");
1423 matches!(matchrec18, "/**/test", "/test");
1424 matches!(matchrec19, "**/.*", ".abc");
1425 matches!(matchrec20, "**/.*", "abc/.abc");
1426 matches!(matchrec21, "**/foo/bar", "foo/bar");
1427 matches!(matchrec22, ".*/**", ".abc/abc");
1428 matches!(matchrec23, "test/**", "test/");
1429 matches!(matchrec24, "test/**", "test/one");
1430 matches!(matchrec25, "test/**", "test/one/two");
1431 matches!(matchrec26, "some/*/needle.txt", "some/one/needle.txt");
1432
1433 matches!(matchrange1, "a[0-9]b", "a0b");
1434 matches!(matchrange2, "a[0-9]b", "a9b");
1435 matches!(matchrange3, "a[!0-9]b", "a_b");
1436 matches!(matchrange4, "[a-z123]", "1");
1437 matches!(matchrange5, "[1a-z23]", "1");
1438 matches!(matchrange6, "[123a-z]", "1");
1439 matches!(matchrange7, "[abc-]", "-");
1440 matches!(matchrange8, "[-abc]", "-");
1441 matches!(matchrange9, "[-a-c]", "b");
1442 matches!(matchrange10, "[a-c-]", "b");
1443 matches!(matchrange11, "[-]", "-");
1444 matches!(matchrange12, "a[^0-9]b", "a_b");
1445
1446 matches!(matchpat1, "*hello.txt", "hello.txt");
1447 matches!(matchpat2, "*hello.txt", "gareth_says_hello.txt");
1448 matches!(matchpat3, "*hello.txt", "some/path/to/hello.txt");
1449 matches!(matchpat4, "*hello.txt", "some\\path\\to\\hello.txt");
1450 matches!(matchpat5, "*hello.txt", "/an/absolute/path/to/hello.txt");
1451 matches!(matchpat6, "*some/path/to/hello.txt", "some/path/to/hello.txt");
1452 matches!(
1453 matchpat7,
1454 "*some/path/to/hello.txt",
1455 "a/bigger/some/path/to/hello.txt"
1456 );
1457
1458 matches!(matchescape, "_[[]_[]]_[?]_[*]_!_", "_[_]_?_*_!_");
1459
1460 matches!(matchcasei1, "aBcDeFg", "aBcDeFg", CASEI);
1461 matches!(matchcasei2, "aBcDeFg", "abcdefg", CASEI);
1462 matches!(matchcasei3, "aBcDeFg", "ABCDEFG", CASEI);
1463 matches!(matchcasei4, "aBcDeFg", "AbCdEfG", CASEI);
1464
1465 matches!(matchalt1, "a,b", "a,b");
1466 matches!(matchalt2, ",", ",");
1467 matches!(matchalt3, "{a,b}", "a");
1468 matches!(matchalt4, "{a,b}", "b");
1469 matches!(matchalt5, "{**/src/**,foo}", "abc/src/bar");
1470 matches!(matchalt6, "{**/src/**,foo}", "foo");
1471 matches!(matchalt7, "{[}],foo}", "}");
1472 matches!(matchalt8, "{foo}", "foo");
1473 matches!(matchalt9, "{}", "");
1474 matches!(matchalt10, "{,}", "");
1475 matches!(matchalt11, "{*.foo,*.bar,*.wat}", "test.foo");
1476 matches!(matchalt12, "{*.foo,*.bar,*.wat}", "test.bar");
1477 matches!(matchalt13, "{*.foo,*.bar,*.wat}", "test.wat");
1478 matches!(matchalt14, "foo{,.txt}", "foo.txt");
1479 nmatches!(matchalt15, "foo{,.txt}", "foo");
1480 matches!(matchalt16, "foo{,.txt}", "foo", EALTRE);
1481 matches!(matchalt17, "{a,b{c,d}}", "bc");
1482 matches!(matchalt18, "{a,b{c,d}}", "bd");
1483 matches!(matchalt19, "{a,b{c,d}}", "a");
1484
1485 matches!(matchslash1, "abc/def", "abc/def", SLASHLIT);
1486 #[cfg(unix)]
1487 nmatches!(matchslash2, "abc?def", "abc/def", SLASHLIT);
1488 #[cfg(not(unix))]
1489 nmatches!(matchslash2, "abc?def", "abc\\def", SLASHLIT);
1490 nmatches!(matchslash3, "abc*def", "abc/def", SLASHLIT);
1491 matches!(matchslash4, "abc[/]def", "abc/def", SLASHLIT); #[cfg(unix)]
1493 nmatches!(matchslash5, "abc\\def", "abc/def", SLASHLIT);
1494 #[cfg(not(unix))]
1495 matches!(matchslash5, "abc\\def", "abc/def", SLASHLIT);
1496
1497 matches!(matchbackslash1, "\\[", "[", BSESC);
1498 matches!(matchbackslash2, "\\?", "?", BSESC);
1499 matches!(matchbackslash3, "\\*", "*", BSESC);
1500 matches!(matchbackslash4, "\\[a-z]", "\\a", NOBSESC);
1501 matches!(matchbackslash5, "\\?", "\\a", NOBSESC);
1502 matches!(matchbackslash6, "\\*", "\\\\", NOBSESC);
1503 #[cfg(unix)]
1504 matches!(matchbackslash7, "\\a", "a");
1505 #[cfg(not(unix))]
1506 matches!(matchbackslash8, "\\a", "/a");
1507
1508 nmatches!(matchnot1, "a*b*c", "abcd");
1509 nmatches!(matchnot2, "abc*abc*abc", "abcabcabcabcabcabcabca");
1510 nmatches!(matchnot3, "some/**/needle.txt", "some/other/notthis.txt");
1511 nmatches!(matchnot4, "some/**/**/needle.txt", "some/other/notthis.txt");
1512 nmatches!(matchnot5, "/**/test", "test");
1513 nmatches!(matchnot6, "/**/test", "/one/notthis");
1514 nmatches!(matchnot7, "/**/test", "/notthis");
1515 nmatches!(matchnot8, "**/.*", "ab.c");
1516 nmatches!(matchnot9, "**/.*", "abc/ab.c");
1517 nmatches!(matchnot10, ".*/**", "a.bc");
1518 nmatches!(matchnot11, ".*/**", "abc/a.bc");
1519 nmatches!(matchnot12, "a[0-9]b", "a_b");
1520 nmatches!(matchnot13, "a[!0-9]b", "a0b");
1521 nmatches!(matchnot14, "a[!0-9]b", "a9b");
1522 nmatches!(matchnot15, "[!-]", "-");
1523 nmatches!(matchnot16, "*hello.txt", "hello.txt-and-then-some");
1524 nmatches!(matchnot17, "*hello.txt", "goodbye.txt");
1525 nmatches!(
1526 matchnot18,
1527 "*some/path/to/hello.txt",
1528 "some/path/to/hello.txt-and-then-some"
1529 );
1530 nmatches!(
1531 matchnot19,
1532 "*some/path/to/hello.txt",
1533 "some/other/path/to/hello.txt"
1534 );
1535 nmatches!(matchnot20, "a", "foo/a");
1536 nmatches!(matchnot21, "./foo", "foo");
1537 nmatches!(matchnot22, "**/foo", "foofoo");
1538 nmatches!(matchnot23, "**/foo/bar", "foofoo/bar");
1539 nmatches!(matchnot24, "/*.c", "mozilla-sha1/sha1.c");
1540 nmatches!(matchnot25, "*.c", "mozilla-sha1/sha1.c", SLASHLIT);
1541 nmatches!(
1542 matchnot26,
1543 "**/m4/ltoptions.m4",
1544 "csharp/src/packages/repositories.config",
1545 SLASHLIT
1546 );
1547 nmatches!(matchnot27, "a[^0-9]b", "a0b");
1548 nmatches!(matchnot28, "a[^0-9]b", "a9b");
1549 nmatches!(matchnot29, "[^-]", "-");
1550 nmatches!(matchnot30, "some/*/needle.txt", "some/needle.txt");
1551 nmatches!(
1552 matchrec31,
1553 "some/*/needle.txt",
1554 "some/one/two/needle.txt",
1555 SLASHLIT
1556 );
1557 nmatches!(
1558 matchrec32,
1559 "some/*/needle.txt",
1560 "some/one/two/three/needle.txt",
1561 SLASHLIT
1562 );
1563 nmatches!(matchrec33, ".*/**", ".abc");
1564 nmatches!(matchrec34, "foo/**", "foo");
1565
1566 macro_rules! extract {
1567 ($which:ident, $name:ident, $pat:expr, $expect:expr) => {
1568 extract!($which, $name, $pat, $expect, Options::default());
1569 };
1570 ($which:ident, $name:ident, $pat:expr, $expect:expr, $options:expr) => {
1571 #[test]
1572 fn $name() {
1573 let mut builder = GlobBuilder::new($pat);
1574 if let Some(casei) = $options.casei {
1575 builder.case_insensitive(casei);
1576 }
1577 if let Some(litsep) = $options.litsep {
1578 builder.literal_separator(litsep);
1579 }
1580 if let Some(bsesc) = $options.bsesc {
1581 builder.backslash_escape(bsesc);
1582 }
1583 if let Some(ealtre) = $options.ealtre {
1584 builder.empty_alternates(ealtre);
1585 }
1586 let pat = builder.build().unwrap();
1587 assert_eq!($expect, pat.$which());
1588 }
1589 };
1590 }
1591
1592 macro_rules! literal {
1593 ($($tt:tt)*) => { extract!(literal, $($tt)*); }
1594 }
1595
1596 macro_rules! basetokens {
1597 ($($tt:tt)*) => { extract!(basename_tokens, $($tt)*); }
1598 }
1599
1600 macro_rules! ext {
1601 ($($tt:tt)*) => { extract!(ext, $($tt)*); }
1602 }
1603
1604 macro_rules! required_ext {
1605 ($($tt:tt)*) => { extract!(required_ext, $($tt)*); }
1606 }
1607
1608 macro_rules! prefix {
1609 ($($tt:tt)*) => { extract!(prefix, $($tt)*); }
1610 }
1611
1612 macro_rules! suffix {
1613 ($($tt:tt)*) => { extract!(suffix, $($tt)*); }
1614 }
1615
1616 macro_rules! baseliteral {
1617 ($($tt:tt)*) => { extract!(basename_literal, $($tt)*); }
1618 }
1619
1620 literal!(extract_lit1, "foo", Some(s("foo")));
1621 literal!(extract_lit2, "foo", None, CASEI);
1622 literal!(extract_lit3, "/foo", Some(s("/foo")));
1623 literal!(extract_lit4, "/foo/", Some(s("/foo/")));
1624 literal!(extract_lit5, "/foo/bar", Some(s("/foo/bar")));
1625 literal!(extract_lit6, "*.foo", None);
1626 literal!(extract_lit7, "foo/bar", Some(s("foo/bar")));
1627 literal!(extract_lit8, "**/foo/bar", None);
1628
1629 basetokens!(
1630 extract_basetoks1,
1631 "**/foo",
1632 Some(&*vec![Literal('f'), Literal('o'), Literal('o'),])
1633 );
1634 basetokens!(extract_basetoks2, "**/foo", None, CASEI);
1635 basetokens!(
1636 extract_basetoks3,
1637 "**/foo",
1638 Some(&*vec![Literal('f'), Literal('o'), Literal('o'),]),
1639 SLASHLIT
1640 );
1641 basetokens!(extract_basetoks4, "*foo", None, SLASHLIT);
1642 basetokens!(extract_basetoks5, "*foo", None);
1643 basetokens!(extract_basetoks6, "**/fo*o", None);
1644 basetokens!(
1645 extract_basetoks7,
1646 "**/fo*o",
1647 Some(&*vec![Literal('f'), Literal('o'), ZeroOrMore, Literal('o'),]),
1648 SLASHLIT
1649 );
1650
1651 ext!(extract_ext1, "**/*.rs", Some(s(".rs")));
1652 ext!(extract_ext2, "**/*.rs.bak", None);
1653 ext!(extract_ext3, "*.rs", Some(s(".rs")));
1654 ext!(extract_ext4, "a*.rs", None);
1655 ext!(extract_ext5, "/*.c", None);
1656 ext!(extract_ext6, "*.c", None, SLASHLIT);
1657 ext!(extract_ext7, "*.c", Some(s(".c")));
1658
1659 required_ext!(extract_req_ext1, "*.rs", Some(s(".rs")));
1660 required_ext!(extract_req_ext2, "/foo/bar/*.rs", Some(s(".rs")));
1661 required_ext!(extract_req_ext3, "/foo/bar/*.rs", Some(s(".rs")));
1662 required_ext!(extract_req_ext4, "/foo/bar/.rs", Some(s(".rs")));
1663 required_ext!(extract_req_ext5, ".rs", Some(s(".rs")));
1664 required_ext!(extract_req_ext6, "./rs", None);
1665 required_ext!(extract_req_ext7, "foo", None);
1666 required_ext!(extract_req_ext8, ".foo/", None);
1667 required_ext!(extract_req_ext9, "foo/", None);
1668
1669 prefix!(extract_prefix1, "/foo", Some(s("/foo")));
1670 prefix!(extract_prefix2, "/foo/*", Some(s("/foo/")));
1671 prefix!(extract_prefix3, "**/foo", None);
1672 prefix!(extract_prefix4, "foo/**", Some(s("foo/")));
1673
1674 suffix!(extract_suffix1, "**/foo/bar", Some((s("/foo/bar"), true)));
1675 suffix!(extract_suffix2, "*/foo/bar", Some((s("/foo/bar"), false)));
1676 suffix!(extract_suffix3, "*/foo/bar", None, SLASHLIT);
1677 suffix!(extract_suffix4, "foo/bar", Some((s("foo/bar"), false)));
1678 suffix!(extract_suffix5, "*.foo", Some((s(".foo"), false)));
1679 suffix!(extract_suffix6, "*.foo", None, SLASHLIT);
1680 suffix!(extract_suffix7, "**/*_test", Some((s("_test"), false)));
1681
1682 baseliteral!(extract_baselit1, "**/foo", Some(s("foo")));
1683 baseliteral!(extract_baselit2, "foo", None);
1684 baseliteral!(extract_baselit3, "*foo", None);
1685 baseliteral!(extract_baselit4, "*/foo", None);
1686}