1use alloc::{
7 borrow::Cow,
8 string::{String, ToString},
9 vec::Vec,
10};
11use core::fmt::Write;
12#[cfg(feature = "std")]
13use std::path::is_separator;
14
15use miden_debug_types::Uri;
16
17#[cfg(not(feature = "std"))]
18fn is_separator(c: char) -> bool {
19 matches!(c, '/')
20}
21
22#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
24#[error("{}", self.format_error())]
25pub struct Error {
26 glob: Option<String>,
28 kind: ErrorKind,
30}
31
32impl Error {
33 pub fn glob(&self) -> Option<&str> {
35 self.glob.as_deref()
36 }
37
38 pub fn kind(&self) -> &ErrorKind {
40 &self.kind
41 }
42
43 fn format_error(&self) -> String {
44 if let Some(glob) = self.glob() {
45 format!("error parsing glob '{glob}': {}", self.kind)
46 } else {
47 format!("{}", self.kind)
48 }
49 }
50}
51
52#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
54#[non_exhaustive]
55pub enum ErrorKind {
56 #[error("unclosed character class; missing ']'")]
58 UnclosedClass,
59 #[error("unclosed character range")]
63 InvalidRange(char, char),
64 #[error("unopened alternate group; missing '{{' (maybe escape '}}' with '[}}]'?)")]
66 UnopenedAlternates,
67 #[error("unclosed alternate group; missing '}}' (maybe escape '{{' with '[{{]'?)")]
69 UnclosedAlternates,
70 #[error("dangling '\\'")]
72 DanglingEscape,
73 #[error("{0}")]
75 Regex(String),
76}
77
78#[derive(Clone, Eq)]
83pub struct Glob {
84 glob: String,
85 re: String,
86 opts: GlobOptions,
87 tokens: Tokens,
88}
89
90impl AsRef<Glob> for Glob {
91 fn as_ref(&self) -> &Glob {
92 self
93 }
94}
95
96impl PartialEq for Glob {
97 fn eq(&self, other: &Glob) -> bool {
98 self.glob == other.glob && self.opts == other.opts
99 }
100}
101
102#[cfg(feature = "std")]
103impl std::hash::Hash for Glob {
104 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
105 self.glob.hash(state);
106 self.opts.hash(state);
107 }
108}
109
110impl core::fmt::Debug for Glob {
111 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
112 if f.alternate() {
113 f.debug_struct("Glob")
114 .field("glob", &self.glob)
115 .field("re", &self.re)
116 .field("opts", &self.opts)
117 .field("tokens", &self.tokens)
118 .finish()
119 } else {
120 f.debug_tuple("Glob").field(&self.glob).finish()
121 }
122 }
123}
124
125impl core::fmt::Display for Glob {
126 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
127 self.glob.fmt(f)
128 }
129}
130
131impl core::str::FromStr for Glob {
132 type Err = Error;
133
134 fn from_str(glob: &str) -> Result<Self, Self::Err> {
135 Self::new(glob)
136 }
137}
138
139#[derive(Clone, Debug)]
141pub struct GlobMatcher {
142 pat: Glob,
144 re: regex::bytes::Regex,
146}
147
148impl Eq for GlobMatcher {}
149impl PartialEq for GlobMatcher {
150 fn eq(&self, other: &Self) -> bool {
151 self.pat == other.pat
152 }
153}
154
155impl GlobMatcher {
156 pub fn is_match(&self, path: &Uri) -> bool {
158 self.is_match_candidate(&Candidate::new(path))
159 }
160
161 pub fn is_match_candidate(&self, path: &Candidate<'_>) -> bool {
163 self.re.is_match(path.path.as_bytes())
164 }
165
166 pub fn glob(&self) -> &Glob {
168 &self.pat
169 }
170}
171
172#[derive(Debug, Clone)]
179pub struct Candidate<'a> {
180 path: Cow<'a, str>,
181}
182
183impl<'a> Candidate<'a> {
184 pub fn new(uri: &'a Uri) -> Candidate<'a> {
186 let path = normalize_path(uri);
187 Candidate { path }
188 }
189}
190
191#[cfg(unix)]
194pub(crate) fn normalize_path(uri: &Uri) -> Cow<'_, str> {
195 Cow::Borrowed(match uri.scheme() {
197 Some("file") => uri.as_str().strip_prefix("file://").unwrap(),
198 Some("stdin") => match uri.as_str().strip_prefix("stdin://").unwrap() {
199 "" => "stdin",
200 other => other,
201 },
202 Some(_) => uri.as_str().split_once("://").unwrap().1,
204 None => uri.as_str(),
205 })
206}
207
208#[cfg(not(unix))]
211pub(crate) fn normalize_path(uri: &Uri) -> Cow<'_, str> {
212 let path = match uri.scheme() {
213 Some("stdin") => {
214 return Cow::Borrowed(match uri.as_str().strip_prefix("stdin://").unwrap() {
215 "" => "stdin",
216 other => other,
217 });
218 }
219 Some(scheme) if scheme == "file" || scheme.chars().count() == 1 => {
220 uri.as_str().split_once("://").unwrap().1
221 }
222 Some(_) => return Cow::Borrowed(uri.as_str().split_once("://").unwrap().1),
223 None => uri.as_str(),
224 };
225 let mut output = String::with_capacity(path.len());
226 for c in path.chars() {
227 if matches!(c, '/') || !is_separator(c) {
228 output.push(c);
229 continue;
230 }
231 output.push('/');
232 }
233 Cow::Owned(output)
234}
235
236#[derive(Clone, Debug)]
243pub struct GlobBuilder<'a> {
244 glob: &'a str,
246 opts: GlobOptions,
248}
249
250#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
251struct GlobOptions {
252 case_insensitive: bool,
254 literal_separator: bool,
257 backslash_escape: bool,
260 empty_alternates: bool,
263 allow_unclosed_class: bool,
268}
269
270impl GlobOptions {
271 fn default() -> GlobOptions {
272 GlobOptions {
273 case_insensitive: false,
274 literal_separator: false,
275 backslash_escape: !is_separator('\\'),
276 empty_alternates: false,
277 allow_unclosed_class: false,
278 }
279 }
280}
281
282#[derive(Clone, Debug, Default, Eq, PartialEq)]
283struct Tokens(Vec<Token>);
284
285impl core::ops::Deref for Tokens {
286 type Target = Vec<Token>;
287
288 fn deref(&self) -> &Vec<Token> {
289 &self.0
290 }
291}
292
293impl core::ops::DerefMut for Tokens {
294 fn deref_mut(&mut self) -> &mut Vec<Token> {
295 &mut self.0
296 }
297}
298
299#[derive(Clone, Debug, Eq, PartialEq)]
300enum Token {
301 Literal(char),
302 Any,
303 ZeroOrMore,
304 RecursivePrefix,
305 RecursiveSuffix,
306 RecursiveZeroOrMore,
307 Class {
308 negated: bool,
309 ranges: Vec<(char, char)>,
310 },
311 Alternates(Vec<Tokens>),
312}
313
314impl Glob {
315 pub fn new(glob: &str) -> Result<Glob, Error> {
317 GlobBuilder::new(glob).build()
318 }
319
320 pub fn compile_matcher(&self) -> GlobMatcher {
322 let mut re = regex::bytes::RegexBuilder::new(&self.re);
323 re.unicode(false).dot_matches_new_line(true);
324
325 let re = re.build().expect("regex compilation shouldn't fail");
326 GlobMatcher {
327 pat: self.clone(),
328 re,
329 }
330 }
331
332 pub fn glob(&self) -> &str {
334 &self.glob
335 }
336
337 pub fn regex(&self) -> &str {
352 &self.re
353 }
354}
355
356impl<'a> GlobBuilder<'a> {
357 pub fn new(glob: &'a str) -> GlobBuilder<'a> {
361 GlobBuilder {
362 glob,
363 opts: GlobOptions::default(),
364 }
365 }
366
367 pub fn build(&self) -> Result<Glob, Error> {
369 let mut p = Parser {
370 glob: self.glob,
371 alternates_stack: Vec::new(),
372 branches: vec![Tokens::default()],
373 chars: self.glob.chars().peekable(),
374 prev: None,
375 cur: None,
376 found_unclosed_class: false,
377 opts: &self.opts,
378 };
379 p.parse()?;
380 if p.branches.is_empty() {
381 unreachable!()
385 } else if p.branches.len() > 1 {
386 Err(Error {
387 glob: Some(self.glob.to_string()),
388 kind: ErrorKind::UnclosedAlternates,
389 })
390 } else {
391 let tokens = p.branches.pop().unwrap();
392 Ok(Glob {
393 glob: self.glob.to_string(),
394 re: tokens.to_regex_with(&self.opts),
395 opts: self.opts,
396 tokens,
397 })
398 }
399 }
400
401 pub fn case_insensitive(&mut self, yes: bool) -> &mut GlobBuilder<'a> {
405 self.opts.case_insensitive = yes;
406 self
407 }
408
409 pub fn literal_separator(&mut self, yes: bool) -> &mut GlobBuilder<'a> {
413 self.opts.literal_separator = yes;
414 self
415 }
416
417 pub fn backslash_escape(&mut self, yes: bool) -> &mut GlobBuilder<'a> {
426 self.opts.backslash_escape = yes;
427 self
428 }
429
430 pub fn empty_alternates(&mut self, yes: bool) -> &mut GlobBuilder<'a> {
437 self.opts.empty_alternates = yes;
438 self
439 }
440
441 pub fn allow_unclosed_class(&mut self, yes: bool) -> &mut GlobBuilder<'a> {
453 self.opts.allow_unclosed_class = yes;
454 self
455 }
456}
457
458impl Tokens {
459 fn to_regex_with(&self, options: &GlobOptions) -> String {
463 let mut re = String::new();
464 re.push_str("(?-u)");
465 if options.case_insensitive {
466 re.push_str("(?i)");
467 }
468 re.push('^');
469 if self.len() == 1 && self[0] == Token::RecursivePrefix {
472 re.push_str(".*");
473 re.push('$');
474 return re;
475 }
476 self.tokens_to_regex(options, self, &mut re);
477 re.push('$');
478 re
479 }
480
481 fn tokens_to_regex(&self, options: &GlobOptions, tokens: &[Token], re: &mut String) {
482 for tok in tokens.iter() {
483 match *tok {
484 Token::Literal(c) => {
485 re.push_str(&char_to_escaped_literal(c));
486 }
487 Token::Any => {
488 if options.literal_separator {
489 re.push_str("[^/]");
490 } else {
491 re.push('.');
492 }
493 }
494 Token::ZeroOrMore => {
495 if options.literal_separator {
496 re.push_str("[^/]*");
497 } else {
498 re.push_str(".*");
499 }
500 }
501 Token::RecursivePrefix => {
502 re.push_str("(?:/?|.*/)");
503 }
504 Token::RecursiveSuffix => {
505 re.push_str("/.*");
506 }
507 Token::RecursiveZeroOrMore => {
508 re.push_str("(?:/|/.*/)");
509 }
510 Token::Class {
511 negated,
512 ref ranges,
513 } => {
514 re.push('[');
515 if negated {
516 re.push('^');
517 }
518 for r in ranges {
519 if r.0 == r.1 {
520 re.push_str(&char_to_escaped_literal(r.0));
522 } else {
523 re.push_str(&char_to_escaped_literal(r.0));
524 re.push('-');
525 re.push_str(&char_to_escaped_literal(r.1));
526 }
527 }
528 re.push(']');
529 }
530 Token::Alternates(ref patterns) => {
531 let mut parts = vec![];
532 for pat in patterns {
533 let mut altre = String::new();
534 self.tokens_to_regex(options, pat, &mut altre);
535 if !altre.is_empty() || options.empty_alternates {
536 parts.push(altre);
537 }
538 }
539
540 if !parts.is_empty() {
543 re.push_str("(?:");
544 re.push_str(&parts.join("|"));
545 re.push(')');
546 }
547 }
548 }
549 }
550 }
551}
552
553fn char_to_escaped_literal(c: char) -> String {
556 let mut buf = [0; 4];
557 let bytes = c.encode_utf8(&mut buf).as_bytes();
558 bytes_to_escaped_literal(bytes)
559}
560
561fn bytes_to_escaped_literal(bs: &[u8]) -> String {
564 let mut s = String::with_capacity(bs.len());
565 for &b in bs {
566 if b <= 0x7f {
567 regex_syntax::escape_into(char::from(b).encode_utf8(&mut [0; 4]), &mut s);
568 } else {
569 write!(&mut s, "\\x{:02x}", b).unwrap();
570 }
571 }
572 s
573}
574
575struct Parser<'a> {
576 glob: &'a str,
578 alternates_stack: Vec<usize>,
580 branches: Vec<Tokens>,
583 chars: core::iter::Peekable<core::str::Chars<'a>>,
585 prev: Option<char>,
587 cur: Option<char>,
589 found_unclosed_class: bool,
598 opts: &'a GlobOptions,
600}
601
602impl<'a> Parser<'a> {
603 fn error(&self, kind: ErrorKind) -> Error {
604 Error {
605 glob: Some(self.glob.to_string()),
606 kind,
607 }
608 }
609
610 fn parse(&mut self) -> Result<(), Error> {
611 while let Some(c) = self.bump() {
612 match c {
613 '?' => self.push_token(Token::Any)?,
614 '*' => self.parse_star()?,
615 '[' if !self.found_unclosed_class => self.parse_class()?,
616 '{' => self.push_alternate()?,
617 '}' => self.pop_alternate()?,
618 ',' => self.parse_comma()?,
619 '\\' => self.parse_backslash()?,
620 c => self.push_token(Token::Literal(c))?,
621 }
622 }
623 Ok(())
624 }
625
626 fn push_alternate(&mut self) -> Result<(), Error> {
627 self.alternates_stack.push(self.branches.len());
628 self.branches.push(Tokens::default());
629 Ok(())
630 }
631
632 fn pop_alternate(&mut self) -> Result<(), Error> {
633 let Some(start) = self.alternates_stack.pop() else {
634 return Err(self.error(ErrorKind::UnopenedAlternates));
635 };
636 assert!(start <= self.branches.len());
637 let alts = Token::Alternates(self.branches.drain(start..).collect());
638 self.push_token(alts)?;
639 Ok(())
640 }
641
642 fn push_token(&mut self, tok: Token) -> Result<(), Error> {
643 if let Some(ref mut pat) = self.branches.last_mut() {
644 pat.push(tok);
645 return Ok(());
646 }
647 Err(self.error(ErrorKind::UnopenedAlternates))
648 }
649
650 fn pop_token(&mut self) -> Result<Token, Error> {
651 if let Some(ref mut pat) = self.branches.last_mut() {
652 return Ok(pat.pop().unwrap());
653 }
654 Err(self.error(ErrorKind::UnopenedAlternates))
655 }
656
657 fn have_tokens(&self) -> Result<bool, Error> {
658 match self.branches.last() {
659 None => Err(self.error(ErrorKind::UnopenedAlternates)),
660 Some(pat) => Ok(!pat.is_empty()),
661 }
662 }
663
664 fn parse_comma(&mut self) -> Result<(), Error> {
665 if self.alternates_stack.is_empty() {
669 self.push_token(Token::Literal(','))
670 } else {
671 self.branches.push(Tokens::default());
672 Ok(())
673 }
674 }
675
676 fn parse_backslash(&mut self) -> Result<(), Error> {
677 if self.opts.backslash_escape {
678 match self.bump() {
679 None => Err(self.error(ErrorKind::DanglingEscape)),
680 Some(c) => self.push_token(Token::Literal(c)),
681 }
682 } else if is_separator('\\') {
683 self.push_token(Token::Literal('/'))
685 } else {
686 self.push_token(Token::Literal('\\'))
687 }
688 }
689
690 fn parse_star(&mut self) -> Result<(), Error> {
691 let prev = self.prev;
692 if self.peek() != Some('*') {
693 self.push_token(Token::ZeroOrMore)?;
694 return Ok(());
695 }
696 assert!(self.bump() == Some('*'));
697 if !self.have_tokens()? {
698 if !self.peek().is_none_or(is_separator) {
699 self.push_token(Token::ZeroOrMore)?;
700 self.push_token(Token::ZeroOrMore)?;
701 } else {
702 self.push_token(Token::RecursivePrefix)?;
703 assert!(self.bump().is_none_or(is_separator));
704 }
705 return Ok(());
706 }
707
708 if !prev.map(is_separator).unwrap_or(false)
709 && (self.branches.len() <= 1 || (prev != Some(',') && prev != Some('{')))
710 {
711 self.push_token(Token::ZeroOrMore)?;
712 self.push_token(Token::ZeroOrMore)?;
713 return Ok(());
714 }
715 let is_suffix = match self.peek() {
716 None => {
717 assert!(self.bump().is_none());
718 true
719 }
720 Some(',') | Some('}') if self.branches.len() >= 2 => true,
721 Some(c) if is_separator(c) => {
722 assert!(self.bump().map(is_separator).unwrap_or(false));
723 false
724 }
725 _ => {
726 self.push_token(Token::ZeroOrMore)?;
727 self.push_token(Token::ZeroOrMore)?;
728 return Ok(());
729 }
730 };
731 match self.pop_token()? {
732 Token::RecursivePrefix => {
733 self.push_token(Token::RecursivePrefix)?;
734 }
735 Token::RecursiveSuffix => {
736 self.push_token(Token::RecursiveSuffix)?;
737 }
738 _ => {
739 if is_suffix {
740 self.push_token(Token::RecursiveSuffix)?;
741 } else {
742 self.push_token(Token::RecursiveZeroOrMore)?;
743 }
744 }
745 }
746 Ok(())
747 }
748
749 fn parse_class(&mut self) -> Result<(), Error> {
750 let saved_chars = self.chars.clone();
752 let saved_prev = self.prev;
753 let saved_cur = self.cur;
754
755 fn add_to_last_range(glob: &str, r: &mut (char, char), add: char) -> Result<(), Error> {
756 r.1 = add;
757 if r.1 < r.0 {
758 Err(Error {
759 glob: Some(glob.to_string()),
760 kind: ErrorKind::InvalidRange(r.0, r.1),
761 })
762 } else {
763 Ok(())
764 }
765 }
766 let mut ranges = vec![];
767 let negated = match self.chars.peek() {
768 Some(&'!') | Some(&'^') => {
769 let bump = self.bump();
770 assert!(bump == Some('!') || bump == Some('^'));
771 true
772 }
773 _ => false,
774 };
775 let mut first = true;
776 let mut in_range = false;
777 loop {
778 let Some(c) = self.bump() else {
779 return if self.opts.allow_unclosed_class {
780 self.chars = saved_chars;
781 self.cur = saved_cur;
782 self.prev = saved_prev;
783 self.found_unclosed_class = true;
784
785 self.push_token(Token::Literal('['))
786 } else {
787 Err(self.error(ErrorKind::UnclosedClass))
788 };
789 };
790 match c {
791 ']' => {
792 if first {
793 ranges.push((']', ']'));
794 } else {
795 break;
796 }
797 }
798 '-' => {
799 if first {
800 ranges.push(('-', '-'));
801 } else if in_range {
802 let r = ranges.last_mut().unwrap();
805 add_to_last_range(self.glob, r, '-')?;
806 in_range = false;
807 } else {
808 assert!(!ranges.is_empty());
809 in_range = true;
810 }
811 }
812 c => {
813 if in_range {
814 add_to_last_range(self.glob, ranges.last_mut().unwrap(), c)?;
817 } else {
818 ranges.push((c, c));
819 }
820 in_range = false;
821 }
822 }
823 first = false;
824 }
825 if in_range {
826 ranges.push(('-', '-'));
829 }
830 self.push_token(Token::Class { negated, ranges })
831 }
832
833 fn bump(&mut self) -> Option<char> {
834 self.prev = self.cur;
835 self.cur = self.chars.next();
836 self.cur
837 }
838
839 fn peek(&mut self) -> Option<char> {
840 self.chars.peek().copied()
841 }
842}
843
844#[cfg(test)]
845mod tests {
846 use miden_debug_types::Uri;
847
848 use super::{ErrorKind, Glob, GlobBuilder, Token, Token::*};
849
850 #[derive(Clone, Copy, Debug, Default)]
851 struct Options {
852 casei: Option<bool>,
853 litsep: Option<bool>,
854 bsesc: Option<bool>,
855 ealtre: Option<bool>,
856 unccls: Option<bool>,
857 }
858
859 macro_rules! syntax {
860 ($name:ident, $pat:expr, $tokens:expr) => {
861 #[test]
862 fn $name() {
863 let pat = Glob::new($pat).unwrap();
864 assert_eq!($tokens, pat.tokens.0);
865 }
866 };
867 }
868
869 macro_rules! syntaxerr {
870 ($name:ident, $pat:expr, $err:expr) => {
871 #[test]
872 fn $name() {
873 let err = Glob::new($pat).unwrap_err();
874 assert_eq!(&$err, err.kind());
875 }
876 };
877 }
878
879 macro_rules! toregex {
880 ($name:ident, $pat:expr, $re:expr) => {
881 toregex!($name, $pat, $re, Options::default());
882 };
883 ($name:ident, $pat:expr, $re:expr, $options:expr) => {
884 #[test]
885 fn $name() {
886 let mut builder = GlobBuilder::new($pat);
887 if let Some(casei) = $options.casei {
888 builder.case_insensitive(casei);
889 }
890 if let Some(litsep) = $options.litsep {
891 builder.literal_separator(litsep);
892 }
893 if let Some(bsesc) = $options.bsesc {
894 builder.backslash_escape(bsesc);
895 }
896 if let Some(ealtre) = $options.ealtre {
897 builder.empty_alternates(ealtre);
898 }
899 if let Some(unccls) = $options.unccls {
900 builder.allow_unclosed_class(unccls);
901 }
902
903 let pat = builder.build().unwrap();
904 assert_eq!(format!("(?-u){}", $re), pat.regex());
905 }
906 };
907 }
908
909 macro_rules! matches {
910 ($name:ident, $pat:expr, $path:expr) => {
911 matches!($name, $pat, $path, Options::default());
912 };
913 ($name:ident, $pat:expr, $path:expr, $options:expr) => {
914 #[test]
915 fn $name() {
916 let mut builder = GlobBuilder::new($pat);
917 if let Some(casei) = $options.casei {
918 builder.case_insensitive(casei);
919 }
920 if let Some(litsep) = $options.litsep {
921 builder.literal_separator(litsep);
922 }
923 if let Some(bsesc) = $options.bsesc {
924 builder.backslash_escape(bsesc);
925 }
926 if let Some(ealtre) = $options.ealtre {
927 builder.empty_alternates(ealtre);
928 }
929 let pat = builder.build().unwrap();
930 let matcher = pat.compile_matcher();
931 let path = Uri::from($path);
932 assert!(matcher.is_match(&path));
933 }
934 };
935 }
936
937 macro_rules! nmatches {
938 ($name:ident, $pat:expr, $path:expr) => {
939 nmatches!($name, $pat, $path, Options::default());
940 };
941 ($name:ident, $pat:expr, $path:expr, $options:expr) => {
942 #[test]
943 fn $name() {
944 let mut builder = GlobBuilder::new($pat);
945 if let Some(casei) = $options.casei {
946 builder.case_insensitive(casei);
947 }
948 if let Some(litsep) = $options.litsep {
949 builder.literal_separator(litsep);
950 }
951 if let Some(bsesc) = $options.bsesc {
952 builder.backslash_escape(bsesc);
953 }
954 if let Some(ealtre) = $options.ealtre {
955 builder.empty_alternates(ealtre);
956 }
957 let pat = builder.build().unwrap();
958 let matcher = pat.compile_matcher();
959 let path = Uri::from($path);
960 assert!(!matcher.is_match(&path));
961 }
962 };
963 }
964
965 fn class(s: char, e: char) -> Token {
966 Class {
967 negated: false,
968 ranges: vec![(s, e)],
969 }
970 }
971
972 fn classn(s: char, e: char) -> Token {
973 Class {
974 negated: true,
975 ranges: vec![(s, e)],
976 }
977 }
978
979 fn rclass(ranges: &[(char, char)]) -> Token {
980 Class {
981 negated: false,
982 ranges: ranges.to_vec(),
983 }
984 }
985
986 fn rclassn(ranges: &[(char, char)]) -> Token {
987 Class {
988 negated: true,
989 ranges: ranges.to_vec(),
990 }
991 }
992
993 syntax!(literal1, "a", vec![Literal('a')]);
994 syntax!(literal2, "ab", vec![Literal('a'), Literal('b')]);
995 syntax!(any1, "?", vec![Any]);
996 syntax!(any2, "a?b", vec![Literal('a'), Any, Literal('b')]);
997 syntax!(seq1, "*", vec![ZeroOrMore]);
998 syntax!(seq2, "a*b", vec![Literal('a'), ZeroOrMore, Literal('b')]);
999 syntax!(
1000 seq3,
1001 "*a*b*",
1002 vec![ZeroOrMore, Literal('a'), ZeroOrMore, Literal('b'), ZeroOrMore,]
1003 );
1004 syntax!(rseq1, "**", vec![RecursivePrefix]);
1005 syntax!(rseq2, "**/", vec![RecursivePrefix]);
1006 syntax!(rseq3, "/**", vec![RecursiveSuffix]);
1007 syntax!(rseq4, "/**/", vec![RecursiveZeroOrMore]);
1008 syntax!(rseq5, "a/**/b", vec![Literal('a'), RecursiveZeroOrMore, Literal('b'),]);
1009 syntax!(cls1, "[a]", vec![class('a', 'a')]);
1010 syntax!(cls2, "[!a]", vec![classn('a', 'a')]);
1011 syntax!(cls3, "[a-z]", vec![class('a', 'z')]);
1012 syntax!(cls4, "[!a-z]", vec![classn('a', 'z')]);
1013 syntax!(cls5, "[-]", vec![class('-', '-')]);
1014 syntax!(cls6, "[]]", vec![class(']', ']')]);
1015 syntax!(cls7, "[*]", vec![class('*', '*')]);
1016 syntax!(cls8, "[!!]", vec![classn('!', '!')]);
1017 syntax!(cls9, "[a-]", vec![rclass(&[('a', 'a'), ('-', '-')])]);
1018 syntax!(cls10, "[-a-z]", vec![rclass(&[('-', '-'), ('a', 'z')])]);
1019 syntax!(cls11, "[a-z-]", vec![rclass(&[('a', 'z'), ('-', '-')])]);
1020 syntax!(cls12, "[-a-z-]", vec![rclass(&[('-', '-'), ('a', 'z'), ('-', '-')]),]);
1021 syntax!(cls13, "[]-z]", vec![class(']', 'z')]);
1022 syntax!(cls14, "[--z]", vec![class('-', 'z')]);
1023 syntax!(cls15, "[ --]", vec![class(' ', '-')]);
1024 syntax!(cls16, "[0-9a-z]", vec![rclass(&[('0', '9'), ('a', 'z')])]);
1025 syntax!(cls17, "[a-z0-9]", vec![rclass(&[('a', 'z'), ('0', '9')])]);
1026 syntax!(cls18, "[!0-9a-z]", vec![rclassn(&[('0', '9'), ('a', 'z')])]);
1027 syntax!(cls19, "[!a-z0-9]", vec![rclassn(&[('a', 'z'), ('0', '9')])]);
1028 syntax!(cls20, "[^a]", vec![classn('a', 'a')]);
1029 syntax!(cls21, "[^a-z]", vec![classn('a', 'z')]);
1030
1031 syntaxerr!(err_unclosed1, "[", ErrorKind::UnclosedClass);
1032 syntaxerr!(err_unclosed2, "[]", ErrorKind::UnclosedClass);
1033 syntaxerr!(err_unclosed3, "[!", ErrorKind::UnclosedClass);
1034 syntaxerr!(err_unclosed4, "[!]", ErrorKind::UnclosedClass);
1035 syntaxerr!(err_range1, "[z-a]", ErrorKind::InvalidRange('z', 'a'));
1036 syntaxerr!(err_range2, "[z--]", ErrorKind::InvalidRange('z', '-'));
1037 syntaxerr!(err_alt1, "{a,b", ErrorKind::UnclosedAlternates);
1038 syntaxerr!(err_alt2, "{a,{b,c}", ErrorKind::UnclosedAlternates);
1039 syntaxerr!(err_alt3, "a,b}", ErrorKind::UnopenedAlternates);
1040 syntaxerr!(err_alt4, "{a,b}}", ErrorKind::UnopenedAlternates);
1041
1042 const CASEI: Options = Options {
1043 casei: Some(true),
1044 litsep: None,
1045 bsesc: None,
1046 ealtre: None,
1047 unccls: None,
1048 };
1049 const SLASHLIT: Options = Options {
1050 casei: None,
1051 litsep: Some(true),
1052 bsesc: None,
1053 ealtre: None,
1054 unccls: None,
1055 };
1056 const NOBSESC: Options = Options {
1057 casei: None,
1058 litsep: None,
1059 bsesc: Some(false),
1060 ealtre: None,
1061 unccls: None,
1062 };
1063 const BSESC: Options = Options {
1064 casei: None,
1065 litsep: None,
1066 bsesc: Some(true),
1067 ealtre: None,
1068 unccls: None,
1069 };
1070 const EALTRE: Options = Options {
1071 casei: None,
1072 litsep: None,
1073 bsesc: Some(true),
1074 ealtre: Some(true),
1075 unccls: None,
1076 };
1077 const UNCCLS: Options = Options {
1078 casei: None,
1079 litsep: None,
1080 bsesc: None,
1081 ealtre: None,
1082 unccls: Some(true),
1083 };
1084
1085 toregex!(allow_unclosed_class_single, r"[", r"^\[$", &UNCCLS);
1086 toregex!(allow_unclosed_class_many, r"[abc", r"^\[abc$", &UNCCLS);
1087 toregex!(allow_unclosed_class_empty1, r"[]", r"^\[\]$", &UNCCLS);
1088 toregex!(allow_unclosed_class_empty2, r"[][", r"^\[\]\[$", &UNCCLS);
1089 toregex!(allow_unclosed_class_negated_unclosed, r"[!", r"^\[!$", &UNCCLS);
1090 toregex!(allow_unclosed_class_negated_empty, r"[!]", r"^\[!\]$", &UNCCLS);
1091 toregex!(allow_unclosed_class_brace1, r"{[abc,xyz}", r"^(?:\[abc|xyz)$", &UNCCLS);
1092 toregex!(allow_unclosed_class_brace2, r"{[abc,[xyz}", r"^(?:\[abc|\[xyz)$", &UNCCLS);
1093 toregex!(allow_unclosed_class_brace3, r"{[abc],[xyz}", r"^(?:[abc]|\[xyz)$", &UNCCLS);
1094
1095 toregex!(re_empty, "", "^$");
1096
1097 toregex!(re_casei, "a", "(?i)^a$", &CASEI);
1098
1099 toregex!(re_slash1, "?", r"^[^/]$", SLASHLIT);
1100 toregex!(re_slash2, "*", r"^[^/]*$", SLASHLIT);
1101
1102 toregex!(re1, "a", "^a$");
1103 toregex!(re2, "?", "^.$");
1104 toregex!(re3, "*", "^.*$");
1105 toregex!(re4, "a?", "^a.$");
1106 toregex!(re5, "?a", "^.a$");
1107 toregex!(re6, "a*", "^a.*$");
1108 toregex!(re7, "*a", "^.*a$");
1109 toregex!(re8, "[*]", r"^[\*]$");
1110 toregex!(re9, "[+]", r"^[\+]$");
1111 toregex!(re10, "+", r"^\+$");
1112 toregex!(re11, "☃", r"^\xe2\x98\x83$");
1113 toregex!(re12, "**", r"^.*$");
1114 toregex!(re13, "**/", r"^.*$");
1115 toregex!(re14, "**/*", r"^(?:/?|.*/).*$");
1116 toregex!(re15, "**/**", r"^.*$");
1117 toregex!(re16, "**/**/*", r"^(?:/?|.*/).*$");
1118 toregex!(re17, "**/**/**", r"^.*$");
1119 toregex!(re18, "**/**/**/*", r"^(?:/?|.*/).*$");
1120 toregex!(re19, "a/**", r"^a/.*$");
1121 toregex!(re20, "a/**/**", r"^a/.*$");
1122 toregex!(re21, "a/**/**/**", r"^a/.*$");
1123 toregex!(re22, "a/**/b", r"^a(?:/|/.*/)b$");
1124 toregex!(re23, "a/**/**/b", r"^a(?:/|/.*/)b$");
1125 toregex!(re24, "a/**/**/**/b", r"^a(?:/|/.*/)b$");
1126 toregex!(re25, "**/b", r"^(?:/?|.*/)b$");
1127 toregex!(re26, "**/**/b", r"^(?:/?|.*/)b$");
1128 toregex!(re27, "**/**/**/b", r"^(?:/?|.*/)b$");
1129 toregex!(re28, "a**", r"^a.*.*$");
1130 toregex!(re29, "**a", r"^.*.*a$");
1131 toregex!(re30, "a**b", r"^a.*.*b$");
1132 toregex!(re31, "***", r"^.*.*.*$");
1133 toregex!(re32, "/a**", r"^/a.*.*$");
1134 toregex!(re33, "/**a", r"^/.*.*a$");
1135 toregex!(re34, "/a**b", r"^/a.*.*b$");
1136 toregex!(re35, "{a,b}", r"^(?:a|b)$");
1137 toregex!(re36, "{a,{b,c}}", r"^(?:a|(?:b|c))$");
1138 toregex!(re37, "{{a,b},{c,d}}", r"^(?:(?:a|b)|(?:c|d))$");
1139
1140 matches!(match1, "a", "a");
1141 matches!(match2, "a*b", "a_b");
1142 matches!(match3, "a*b*c", "abc");
1143 matches!(match4, "a*b*c", "a_b_c");
1144 matches!(match5, "a*b*c", "a___b___c");
1145 matches!(match6, "abc*abc*abc", "abcabcabcabcabcabcabc");
1146 matches!(match7, "a*a*a*a*a*a*a*a*a", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
1147 matches!(match8, "a*b[xyz]c*d", "abxcdbxcddd");
1148 matches!(match9, "*.rs", ".rs");
1149 matches!(match10, "☃", "☃");
1150
1151 matches!(matchrec1, "some/**/needle.txt", "some/needle.txt");
1152 matches!(matchrec2, "some/**/needle.txt", "some/one/needle.txt");
1153 matches!(matchrec3, "some/**/needle.txt", "some/one/two/needle.txt");
1154 matches!(matchrec4, "some/**/needle.txt", "some/other/needle.txt");
1155 matches!(matchrec5, "**", "abcde");
1156 matches!(matchrec6, "**", "");
1157 matches!(matchrec7, "**", ".asdf");
1158 matches!(matchrec8, "**", "/x/.asdf");
1159 matches!(matchrec9, "some/**/**/needle.txt", "some/needle.txt");
1160 matches!(matchrec10, "some/**/**/needle.txt", "some/one/needle.txt");
1161 matches!(matchrec11, "some/**/**/needle.txt", "some/one/two/needle.txt");
1162 matches!(matchrec12, "some/**/**/needle.txt", "some/other/needle.txt");
1163 matches!(matchrec13, "**/test", "one/two/test");
1164 matches!(matchrec14, "**/test", "one/test");
1165 matches!(matchrec15, "**/test", "test");
1166 matches!(matchrec16, "/**/test", "/one/two/test");
1167 matches!(matchrec17, "/**/test", "/one/test");
1168 matches!(matchrec18, "/**/test", "/test");
1169 matches!(matchrec19, "**/.*", ".abc");
1170 matches!(matchrec20, "**/.*", "abc/.abc");
1171 matches!(matchrec21, "**/foo/bar", "foo/bar");
1172 matches!(matchrec22, ".*/**", ".abc/abc");
1173 matches!(matchrec23, "test/**", "test/");
1174 matches!(matchrec24, "test/**", "test/one");
1175 matches!(matchrec25, "test/**", "test/one/two");
1176 matches!(matchrec26, "some/*/needle.txt", "some/one/needle.txt");
1177
1178 matches!(matchrange1, "a[0-9]b", "a0b");
1179 matches!(matchrange2, "a[0-9]b", "a9b");
1180 matches!(matchrange3, "a[!0-9]b", "a_b");
1181 matches!(matchrange4, "[a-z123]", "1");
1182 matches!(matchrange5, "[1a-z23]", "1");
1183 matches!(matchrange6, "[123a-z]", "1");
1184 matches!(matchrange7, "[abc-]", "-");
1185 matches!(matchrange8, "[-abc]", "-");
1186 matches!(matchrange9, "[-a-c]", "b");
1187 matches!(matchrange10, "[a-c-]", "b");
1188 matches!(matchrange11, "[-]", "-");
1189 matches!(matchrange12, "a[^0-9]b", "a_b");
1190
1191 matches!(matchpat1, "*hello.txt", "hello.txt");
1192 matches!(matchpat2, "*hello.txt", "gareth_says_hello.txt");
1193 matches!(matchpat3, "*hello.txt", "some/path/to/hello.txt");
1194 matches!(matchpat4, "*hello.txt", "some\\path\\to\\hello.txt");
1195 matches!(matchpat5, "*hello.txt", "/an/absolute/path/to/hello.txt");
1196 matches!(matchpat6, "*some/path/to/hello.txt", "some/path/to/hello.txt");
1197 matches!(matchpat7, "*some/path/to/hello.txt", "a/bigger/some/path/to/hello.txt");
1198
1199 matches!(matchescape, "_[[]_[]]_[?]_[*]_!_", "_[_]_?_*_!_");
1200
1201 matches!(matchcasei1, "aBcDeFg", "aBcDeFg", CASEI);
1202 matches!(matchcasei2, "aBcDeFg", "abcdefg", CASEI);
1203 matches!(matchcasei3, "aBcDeFg", "ABCDEFG", CASEI);
1204 matches!(matchcasei4, "aBcDeFg", "AbCdEfG", CASEI);
1205
1206 matches!(matchalt1, "a,b", "a,b");
1207 matches!(matchalt2, ",", ",");
1208 matches!(matchalt3, "{a,b}", "a");
1209 matches!(matchalt4, "{a,b}", "b");
1210 matches!(matchalt5, "{**/src/**,foo}", "abc/src/bar");
1211 matches!(matchalt6, "{**/src/**,foo}", "foo");
1212 matches!(matchalt7, "{[}],foo}", "}");
1213 matches!(matchalt8, "{foo}", "foo");
1214 matches!(matchalt9, "{}", "");
1215 matches!(matchalt10, "{,}", "");
1216 matches!(matchalt11, "{*.foo,*.bar,*.wat}", "test.foo");
1217 matches!(matchalt12, "{*.foo,*.bar,*.wat}", "test.bar");
1218 matches!(matchalt13, "{*.foo,*.bar,*.wat}", "test.wat");
1219 matches!(matchalt14, "foo{,.txt}", "foo.txt");
1220 nmatches!(matchalt15, "foo{,.txt}", "foo");
1221 matches!(matchalt16, "foo{,.txt}", "foo", EALTRE);
1222 matches!(matchalt17, "{a,b{c,d}}", "bc");
1223 matches!(matchalt18, "{a,b{c,d}}", "bd");
1224 matches!(matchalt19, "{a,b{c,d}}", "a");
1225
1226 matches!(matchslash1, "abc/def", "abc/def", SLASHLIT);
1227 #[cfg(unix)]
1228 nmatches!(matchslash2, "abc?def", "abc/def", SLASHLIT);
1229 #[cfg(not(unix))]
1230 nmatches!(matchslash2, "abc?def", "abc\\def", SLASHLIT);
1231 nmatches!(matchslash3, "abc*def", "abc/def", SLASHLIT);
1232 matches!(matchslash4, "abc[/]def", "abc/def", SLASHLIT); #[cfg(unix)]
1234 nmatches!(matchslash5, "abc\\def", "abc/def", SLASHLIT);
1235 #[cfg(not(unix))]
1236 matches!(matchslash5, "abc\\def", "abc/def", SLASHLIT);
1237
1238 matches!(matchbackslash1, "\\[", "[", BSESC);
1239 matches!(matchbackslash2, "\\?", "?", BSESC);
1240 matches!(matchbackslash3, "\\*", "*", BSESC);
1241 matches!(matchbackslash4, "\\[a-z]", "\\a", NOBSESC);
1242 matches!(matchbackslash5, "\\?", "\\a", NOBSESC);
1243 matches!(matchbackslash6, "\\*", "\\\\", NOBSESC);
1244 #[cfg(unix)]
1245 matches!(matchbackslash7, "\\a", "a");
1246 #[cfg(not(unix))]
1247 matches!(matchbackslash8, "\\a", "/a");
1248
1249 nmatches!(matchnot1, "a*b*c", "abcd");
1250 nmatches!(matchnot2, "abc*abc*abc", "abcabcabcabcabcabcabca");
1251 nmatches!(matchnot3, "some/**/needle.txt", "some/other/notthis.txt");
1252 nmatches!(matchnot4, "some/**/**/needle.txt", "some/other/notthis.txt");
1253 nmatches!(matchnot5, "/**/test", "test");
1254 nmatches!(matchnot6, "/**/test", "/one/notthis");
1255 nmatches!(matchnot7, "/**/test", "/notthis");
1256 nmatches!(matchnot8, "**/.*", "ab.c");
1257 nmatches!(matchnot9, "**/.*", "abc/ab.c");
1258 nmatches!(matchnot10, ".*/**", "a.bc");
1259 nmatches!(matchnot11, ".*/**", "abc/a.bc");
1260 nmatches!(matchnot12, "a[0-9]b", "a_b");
1261 nmatches!(matchnot13, "a[!0-9]b", "a0b");
1262 nmatches!(matchnot14, "a[!0-9]b", "a9b");
1263 nmatches!(matchnot15, "[!-]", "-");
1264 nmatches!(matchnot16, "*hello.txt", "hello.txt-and-then-some");
1265 nmatches!(matchnot17, "*hello.txt", "goodbye.txt");
1266 nmatches!(matchnot18, "*some/path/to/hello.txt", "some/path/to/hello.txt-and-then-some");
1267 nmatches!(matchnot19, "*some/path/to/hello.txt", "some/other/path/to/hello.txt");
1268 nmatches!(matchnot20, "a", "foo/a");
1269 nmatches!(matchnot21, "./foo", "foo");
1270 nmatches!(matchnot22, "**/foo", "foofoo");
1271 nmatches!(matchnot23, "**/foo/bar", "foofoo/bar");
1272 nmatches!(matchnot24, "/*.c", "mozilla-sha1/sha1.c");
1273 nmatches!(matchnot25, "*.c", "mozilla-sha1/sha1.c", SLASHLIT);
1274 nmatches!(
1275 matchnot26,
1276 "**/m4/ltoptions.m4",
1277 "csharp/src/packages/repositories.config",
1278 SLASHLIT
1279 );
1280 nmatches!(matchnot27, "a[^0-9]b", "a0b");
1281 nmatches!(matchnot28, "a[^0-9]b", "a9b");
1282 nmatches!(matchnot29, "[^-]", "-");
1283 nmatches!(matchnot30, "some/*/needle.txt", "some/needle.txt");
1284 nmatches!(matchrec31, "some/*/needle.txt", "some/one/two/needle.txt", SLASHLIT);
1285 nmatches!(matchrec32, "some/*/needle.txt", "some/one/two/three/needle.txt", SLASHLIT);
1286 nmatches!(matchrec33, ".*/**", ".abc");
1287 nmatches!(matchrec34, "foo/**", "foo");
1288}