1#![doc(
61 html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
62 html_favicon_url = "https://www.rust-lang.org/favicon.ico",
63 html_root_url = "https://docs.rs/glob/0.3.1"
64)]
65#![deny(missing_docs)]
66
67#[cfg(test)]
68#[macro_use]
69extern crate doc_comment;
70
71#[cfg(test)]
72doctest!("../README.md");
73
74use std::cmp;
75use std::cmp::Ordering;
76use std::error::Error;
77use std::fmt;
78use std::fs;
79use std::io;
80use std::path::{self, Component, Path, PathBuf};
81use std::str::FromStr;
82
83use CharSpecifier::{CharRange, SingleChar};
84use MatchResult::{EntirePatternDoesntMatch, Match, SubPatternDoesntMatch};
85use PatternToken::AnyExcept;
86use PatternToken::{AnyChar, AnyRecursiveSequence, AnySequence, AnyWithin, Char};
87
88pub trait Interruptible {
90 fn interrupted(&self) -> bool;
92}
93
94impl<I: Interruptible> Interruptible for &I {
95 #[inline]
96 fn interrupted(&self) -> bool {
97 (*self).interrupted()
98 }
99}
100
101pub struct Uninterruptible;
103
104impl Interruptible for Uninterruptible {
105 #[inline]
106 fn interrupted(&self) -> bool {
107 false
108 }
109}
110
111#[derive(Debug)]
121pub struct Paths<I = Uninterruptible> {
122 dir_patterns: Vec<Pattern>,
123 require_dir: bool,
124 options: MatchOptions,
125 todo: Vec<Result<(PathBuf, usize), GlobError>>,
126 scope: Option<PathBuf>,
127 interrupt: I,
128}
129
130impl Paths<Uninterruptible> {
131 pub fn single(path: &Path, relative_to: &Path) -> Self {
133 Paths {
134 dir_patterns: vec![Pattern::new("*").expect("hard coded pattern")],
135 require_dir: false,
136 options: MatchOptions::default(),
137 todo: vec![Ok((path.to_path_buf(), 0))],
138 scope: Some(relative_to.into()),
139 interrupt: Uninterruptible,
140 }
141 }
142}
143
144pub fn glob<I: Interruptible>(pattern: &str, interrupt: I) -> Result<Paths<I>, PatternError> {
202 glob_with(pattern, MatchOptions::default(), interrupt)
203}
204
205pub fn glob_with<I: Interruptible>(
219 pattern: &str,
220 options: MatchOptions,
221 interrupt: I,
222) -> Result<Paths<I>, PatternError> {
223 #[cfg(windows)]
224 fn check_windows_verbatim(p: &Path) -> bool {
225 match p.components().next() {
226 Some(Component::Prefix(ref p)) => {
227 p.kind().is_verbatim() && !matches!(p.kind(), std::path::Prefix::VerbatimDisk(_))
229 }
230 _ => false,
231 }
232 }
233 #[cfg(not(windows))]
234 fn check_windows_verbatim(_: &Path) -> bool {
235 false
236 }
237
238 #[cfg(windows)]
239 fn to_scope(p: &Path) -> PathBuf {
240 p.to_path_buf()
242 }
243 #[cfg(not(windows))]
244 fn to_scope(p: &Path) -> PathBuf {
245 p.to_path_buf()
246 }
247
248 Pattern::new(pattern)?;
250
251 let mut components = Path::new(pattern).components().peekable();
252 while let Some(&Component::Prefix(..)) | Some(&Component::RootDir) = components.peek() {
253 components.next();
254 }
255
256 let rest = components.map(|s| s.as_os_str()).collect::<PathBuf>();
257 let normalized_pattern = Path::new(pattern).iter().collect::<PathBuf>();
258 let root_len = normalized_pattern
259 .to_str()
260 .expect("internal error: expected string")
261 .len()
262 - rest
263 .to_str()
264 .expect("internal error: expected string")
265 .len();
266 let root = if root_len > 0 {
267 Some(Path::new(&pattern[..root_len]))
268 } else {
269 None
270 };
271
272 if root_len > 0
273 && check_windows_verbatim(root.expect("internal error: already checked for len > 0"))
274 {
275 return Ok(Paths {
279 dir_patterns: Vec::new(),
280 require_dir: false,
281 options,
282 todo: Vec::new(),
283 scope: None,
284 interrupt,
285 });
286 }
287
288 let scope = root.map_or_else(|| PathBuf::from("."), to_scope);
289
290 let mut dir_patterns = Vec::new();
291 let components =
292 pattern[cmp::min(root_len, pattern.len())..].split_terminator(path::is_separator);
293
294 for component in components {
295 dir_patterns.push(Pattern::new(component)?);
296 }
297
298 if root_len == pattern.len() {
299 dir_patterns.push(Pattern {
300 original: "".to_string(),
301 tokens: Vec::new(),
302 is_recursive: false,
303 });
304 }
305
306 let last_is_separator = pattern.chars().next_back().map(path::is_separator);
307 let require_dir = last_is_separator == Some(true);
308 let todo = Vec::new();
309
310 Ok(Paths {
311 dir_patterns,
312 require_dir,
313 options,
314 todo,
315 scope: Some(scope),
316 interrupt,
317 })
318}
319
320pub fn glob_with_parent<I: Interruptible>(
328 pattern: &str,
329 options: MatchOptions,
330 parent: &Path,
331 interrupt: I,
332) -> Result<Paths<I>, PatternError> {
333 match glob_with(pattern, options, interrupt) {
334 Ok(mut p) => {
335 p.scope = match p.scope {
336 None => Some(parent.to_path_buf()),
337 Some(s) if &s.to_string_lossy() == "." => Some(parent.to_path_buf()),
338 Some(s) => Some(s),
339 };
340 Ok(p)
341 }
342 Err(e) => Err(e),
343 }
344}
345
346const GLOB_CHARS: &[char] = &['*', '?', '['];
347
348pub fn is_glob(pattern: &str) -> bool {
364 pattern.contains(GLOB_CHARS)
365}
366
367#[derive(Debug)]
373pub struct GlobError {
374 path: PathBuf,
375 error: io::Error,
376}
377
378impl GlobError {
379 pub fn path(&self) -> &Path {
381 &self.path
382 }
383
384 pub fn error(&self) -> &io::Error {
386 &self.error
387 }
388
389 pub fn into_error(self) -> io::Error {
391 self.error
392 }
393}
394
395impl Error for GlobError {
396 #[allow(deprecated)]
397 fn description(&self) -> &str {
398 self.error.description()
399 }
400
401 fn cause(&self) -> Option<&dyn Error> {
402 Some(&self.error)
403 }
404}
405
406impl fmt::Display for GlobError {
407 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
408 write!(
409 f,
410 "attempting to read `{}` resulted in an error: {}",
411 self.path.display(),
412 self.error
413 )
414 }
415}
416
417fn is_dir(p: &Path) -> bool {
418 fs::metadata(p).map(|m| m.is_dir()).unwrap_or(false)
419}
420
421pub type GlobResult = Result<PathBuf, GlobError>;
426
427impl<I: Interruptible> Iterator for Paths<I> {
428 type Item = GlobResult;
429
430 fn next(&mut self) -> Option<GlobResult> {
431 if let Some(scope) = self.scope.take()
436 && !self.dir_patterns.is_empty()
437 {
438 assert!(self.dir_patterns.len() < !0);
440
441 if self.todo.len() != 1 {
443 fill_todo(
444 &mut self.todo,
445 &self.dir_patterns,
446 0,
447 &scope,
448 self.options,
449 &self.interrupt,
450 );
451 }
452 }
453
454 loop {
455 if self.dir_patterns.is_empty() || self.todo.is_empty() {
456 return None;
457 }
458
459 let (path, mut idx) = match self
460 .todo
461 .pop()
462 .expect("internal error: already checked for non-empty")
463 {
464 Ok(pair) => pair,
465 Err(e) => return Some(Err(e)),
466 };
467
468 if idx == !0 {
471 if self.require_dir && !is_dir(&path) {
472 continue;
473 }
474 return Some(Ok(path));
475 }
476
477 if self.dir_patterns[idx].is_recursive {
478 let mut next = idx;
479
480 while (next + 1) < self.dir_patterns.len()
482 && self.dir_patterns[next + 1].is_recursive
483 {
484 next += 1;
485 }
486
487 if is_dir(&path) {
488 if !self.options.recursive_match_hidden_dir
491 && path
492 .file_name()
493 .map(|name| name.to_string_lossy().starts_with('.'))
494 .unwrap_or(false)
495 {
496 continue;
497 }
498
499 fill_todo(
501 &mut self.todo,
502 &self.dir_patterns,
503 next,
504 &path,
505 self.options,
506 &self.interrupt,
507 );
508
509 if next == self.dir_patterns.len() - 1 {
510 return Some(Ok(path));
513 } else {
514 idx = next + 1;
516 }
517 } else if next == self.dir_patterns.len() - 1 {
518 continue;
521 } else {
522 idx = next + 1;
524 }
525 }
526
527 if self.dir_patterns[idx].matches_with(
529 {
530 match path.file_name().and_then(|s| s.to_str()) {
531 None => {
535 println!("warning: get non-utf8 filename {path:?}, ignored.");
536 continue;
537 }
538 Some(x) => x,
539 }
540 },
541 self.options,
542 ) {
543 if idx == self.dir_patterns.len() - 1 {
544 if !self.require_dir || is_dir(&path) {
549 return Some(Ok(path));
550 }
551 } else {
552 fill_todo(
553 &mut self.todo,
554 &self.dir_patterns,
555 idx + 1,
556 &path,
557 self.options,
558 &self.interrupt,
559 );
560 }
561 }
562 }
563 }
564}
565
566#[derive(Debug)]
568pub struct PatternError {
569 pub pos: usize,
571
572 pub msg: &'static str,
574}
575
576impl Error for PatternError {
577 fn description(&self) -> &str {
578 self.msg
579 }
580}
581
582impl fmt::Display for PatternError {
583 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
584 write!(
585 f,
586 "Pattern syntax error near position {}: {}",
587 self.pos, self.msg
588 )
589 }
590}
591
592#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
618pub struct Pattern {
619 original: String,
620 tokens: Vec<PatternToken>,
621 is_recursive: bool,
622}
623
624impl fmt::Display for Pattern {
626 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
627 self.original.fmt(f)
628 }
629}
630
631impl FromStr for Pattern {
632 type Err = PatternError;
633
634 fn from_str(s: &str) -> Result<Self, PatternError> {
635 Self::new(s)
636 }
637}
638
639#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
640enum PatternToken {
641 Char(char),
642 AnyChar,
643 AnySequence,
644 AnyRecursiveSequence,
645 AnyWithin(Vec<CharSpecifier>),
646 AnyExcept(Vec<CharSpecifier>),
647}
648
649#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
650enum CharSpecifier {
651 SingleChar(char),
652 CharRange(char, char),
653}
654
655#[derive(Copy, Clone, PartialEq)]
656enum MatchResult {
657 Match,
658 SubPatternDoesntMatch,
659 EntirePatternDoesntMatch,
660}
661
662const ERROR_WILDCARDS: &str = "wildcards are either regular `*` or recursive `**`";
663const ERROR_RECURSIVE_WILDCARDS: &str = "recursive wildcards must form a single path \
664 component";
665const ERROR_INVALID_RANGE: &str = "invalid range pattern";
666
667impl Pattern {
668 pub fn new(pattern: &str) -> Result<Self, PatternError> {
672 let chars = pattern.chars().collect::<Vec<_>>();
673 let mut tokens = Vec::new();
674 let mut is_recursive = false;
675 let mut i = 0;
676
677 while i < chars.len() {
678 match chars[i] {
679 '?' => {
680 tokens.push(AnyChar);
681 i += 1;
682 }
683 '*' => {
684 let old = i;
685
686 while i < chars.len() && chars[i] == '*' {
687 i += 1;
688 }
689
690 let count = i - old;
691
692 match count.cmp(&2) {
693 Ordering::Greater => {
694 return Err(PatternError {
695 pos: old + 2,
696 msg: ERROR_WILDCARDS,
697 });
698 }
699 Ordering::Equal => {
700 let is_valid = if i == 2 || path::is_separator(chars[i - count - 1]) {
704 if i < chars.len() && path::is_separator(chars[i]) {
706 i += 1;
707 true
708 } else if i == chars.len() {
711 true
712 } else {
714 return Err(PatternError {
715 pos: i,
716 msg: ERROR_RECURSIVE_WILDCARDS,
717 });
718 }
719 } else {
721 return Err(PatternError {
722 pos: old - 1,
723 msg: ERROR_RECURSIVE_WILDCARDS,
724 });
725 };
726
727 if is_valid {
728 let tokens_len = tokens.len();
732
733 if !(tokens_len > 1
734 && tokens[tokens_len - 1] == AnyRecursiveSequence)
735 {
736 is_recursive = true;
737 tokens.push(AnyRecursiveSequence);
738 }
739 }
740 }
741 Ordering::Less => {
742 tokens.push(AnySequence);
743 }
744 }
745 }
746 '[' => {
747 if i + 4 <= chars.len() && chars[i + 1] == '!' {
748 match chars[i + 3..].iter().position(|x| *x == ']') {
749 None => (),
750 Some(j) => {
751 let chars = &chars[i + 2..i + 3 + j];
752 let cs = parse_char_specifiers(chars);
753 tokens.push(AnyExcept(cs));
754 i += j + 4;
755 continue;
756 }
757 }
758 } else if i + 3 <= chars.len() && chars[i + 1] != '!' {
759 match chars[i + 2..].iter().position(|x| *x == ']') {
760 None => (),
761 Some(j) => {
762 let cs = parse_char_specifiers(&chars[i + 1..i + 2 + j]);
763 tokens.push(AnyWithin(cs));
764 i += j + 3;
765 continue;
766 }
767 }
768 }
769
770 return Err(PatternError {
772 pos: i,
773 msg: ERROR_INVALID_RANGE,
774 });
775 }
776 c => {
777 tokens.push(Char(c));
778 i += 1;
779 }
780 }
781 }
782
783 Ok(Self {
784 tokens,
785 original: pattern.to_string(),
786 is_recursive,
787 })
788 }
789
790 pub fn escape(s: &str) -> String {
794 let mut escaped = String::new();
795 for c in s.chars() {
796 match c {
797 '?' | '*' | '[' | ']' => {
800 escaped.push('[');
801 escaped.push(c);
802 escaped.push(']');
803 }
804 c => {
805 escaped.push(c);
806 }
807 }
808 }
809 escaped
810 }
811
812 pub fn matches(&self, str: &str) -> bool {
825 self.matches_with(str, MatchOptions::default())
826 }
827
828 pub fn matches_path(&self, path: &Path) -> bool {
831 path.to_str().is_some_and(|s| self.matches(s))
833 }
834
835 pub fn matches_with(&self, str: &str, options: MatchOptions) -> bool {
838 self.matches_from(true, str.chars(), 0, options) == Match
839 }
840
841 pub fn matches_path_with(&self, path: &Path, options: MatchOptions) -> bool {
844 path.to_str().is_some_and(|s| self.matches_with(s, options))
846 }
847
848 pub fn as_str(&self) -> &str {
850 &self.original
851 }
852
853 fn matches_from(
854 &self,
855 mut follows_separator: bool,
856 mut file: std::str::Chars,
857 i: usize,
858 options: MatchOptions,
859 ) -> MatchResult {
860 for (ti, token) in self.tokens[i..].iter().enumerate() {
861 match *token {
862 AnySequence | AnyRecursiveSequence => {
863 debug_assert!(match *token {
865 AnyRecursiveSequence => follows_separator,
866 _ => true,
867 });
868
869 match self.matches_from(follows_separator, file.clone(), i + ti + 1, options) {
871 SubPatternDoesntMatch => (), m => return m,
873 };
874
875 while let Some(c) = file.next() {
876 if follows_separator && options.require_literal_leading_dot && c == '.' {
877 return SubPatternDoesntMatch;
878 }
879 follows_separator = path::is_separator(c);
880 match *token {
881 AnyRecursiveSequence if !follows_separator => continue,
882 AnySequence
883 if options.require_literal_separator && follows_separator =>
884 {
885 return SubPatternDoesntMatch;
886 }
887 _ => (),
888 }
889 match self.matches_from(
890 follows_separator,
891 file.clone(),
892 i + ti + 1,
893 options,
894 ) {
895 SubPatternDoesntMatch => (), m => return m,
897 }
898 }
899 }
900 _ => {
901 let c = match file.next() {
902 Some(c) => c,
903 None => return EntirePatternDoesntMatch,
904 };
905
906 let is_sep = path::is_separator(c);
907
908 if !match *token {
909 AnyChar | AnyWithin(..) | AnyExcept(..)
910 if (options.require_literal_separator && is_sep)
911 || (follows_separator
912 && options.require_literal_leading_dot
913 && c == '.') =>
914 {
915 false
916 }
917 AnyChar => true,
918 AnyWithin(ref specifiers) => in_char_specifiers(specifiers, c, options),
919 AnyExcept(ref specifiers) => !in_char_specifiers(specifiers, c, options),
920 Char(c2) => chars_eq(c, c2, options.case_sensitive),
921 AnySequence | AnyRecursiveSequence => unreachable!(),
922 } {
923 return SubPatternDoesntMatch;
924 }
925 follows_separator = is_sep;
926 }
927 }
928 }
929
930 if file.next().is_none() {
932 Match
933 } else {
934 SubPatternDoesntMatch
935 }
936 }
937}
938
939fn fill_todo(
943 todo: &mut Vec<Result<(PathBuf, usize), GlobError>>,
944 patterns: &[Pattern],
945 idx: usize,
946 path: &Path,
947 options: MatchOptions,
948 interrupt: &impl Interruptible,
949) {
950 fn pattern_as_str(pattern: &Pattern) -> Option<String> {
952 let mut s = String::new();
953 for token in &pattern.tokens {
954 match *token {
955 Char(c) => s.push(c),
956 _ => return None,
957 }
958 }
959
960 Some(s)
961 }
962
963 let add = |todo: &mut Vec<_>, next_path: PathBuf| {
964 if idx + 1 == patterns.len() {
965 todo.push(Ok((next_path, !0)));
969 } else {
970 fill_todo(todo, patterns, idx + 1, &next_path, options, interrupt);
971 }
972 };
973
974 let pattern = &patterns[idx];
975 let is_dir = is_dir(path);
976 let curdir = path == Path::new(".");
977 match pattern_as_str(pattern) {
978 Some(s) => {
979 let special = "." == s || ".." == s;
985 let next_path = if curdir {
986 PathBuf::from(s)
987 } else {
988 path.join(&s)
989 };
990 if (special && is_dir)
991 || (!special
992 && (fs::metadata(&next_path).is_ok()
993 || fs::symlink_metadata(&next_path).is_ok()))
994 {
995 add(todo, next_path);
996 }
997 }
998 None if is_dir => {
999 let dirs = fs::read_dir(path).and_then(|d| {
1000 d.map(|e| {
1001 if interrupt.interrupted() {
1002 return Err(io::Error::from(io::ErrorKind::Interrupted));
1003 }
1004 e.map(|e| {
1005 if curdir {
1006 PathBuf::from(
1007 e.path()
1008 .file_name()
1009 .expect("internal error: missing filename"),
1010 )
1011 } else {
1012 e.path()
1013 }
1014 })
1015 })
1016 .collect::<Result<Vec<_>, _>>()
1017 });
1018 match dirs {
1019 Ok(mut children) => {
1020 children.sort_by(|p1, p2| p2.file_name().cmp(&p1.file_name()));
1031 todo.extend(children.into_iter().map(|x| Ok((x, idx))));
1032
1033 if !pattern.tokens.is_empty() && pattern.tokens[0] == Char('.') {
1039 for &special in &[".", ".."] {
1040 if pattern.matches_with(special, options) {
1041 add(todo, path.join(special));
1042 }
1043 }
1044 }
1045 }
1046 Err(e) => {
1047 todo.push(Err(GlobError {
1048 path: path.to_path_buf(),
1049 error: e,
1050 }));
1051 }
1052 }
1053 }
1054 None => {
1055 }
1057 }
1058}
1059
1060fn parse_char_specifiers(s: &[char]) -> Vec<CharSpecifier> {
1061 let mut cs = Vec::new();
1062 let mut i = 0;
1063 while i < s.len() {
1064 if i + 3 <= s.len() && s[i + 1] == '-' {
1065 cs.push(CharRange(s[i], s[i + 2]));
1066 i += 3;
1067 } else {
1068 cs.push(SingleChar(s[i]));
1069 i += 1;
1070 }
1071 }
1072 cs
1073}
1074
1075fn in_char_specifiers(specifiers: &[CharSpecifier], c: char, options: MatchOptions) -> bool {
1076 for &specifier in specifiers.iter() {
1077 match specifier {
1078 SingleChar(sc) => {
1079 if chars_eq(c, sc, options.case_sensitive) {
1080 return true;
1081 }
1082 }
1083 CharRange(start, end) => {
1084 if !options.case_sensitive && c.is_ascii() && start.is_ascii() && end.is_ascii() {
1086 if start.is_ascii_alphabetic() && end.is_ascii_alphabetic() {
1089 let start = start.to_ascii_lowercase();
1090 let end = end.to_ascii_lowercase();
1091 let c = c.to_ascii_lowercase();
1092 if (start..=end).contains(&c) {
1093 return true;
1094 }
1095 }
1096 }
1097
1098 if (start..=end).contains(&c) {
1099 return true;
1100 }
1101 }
1102 }
1103 }
1104
1105 false
1106}
1107
1108fn chars_eq(a: char, b: char, case_sensitive: bool) -> bool {
1110 if cfg!(windows) && path::is_separator(a) && path::is_separator(b) {
1111 true
1112 } else if !case_sensitive && a.is_ascii() && b.is_ascii() {
1113 a.eq_ignore_ascii_case(&b)
1115 } else {
1116 a == b
1117 }
1118}
1119
1120#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1122pub struct MatchOptions {
1123 pub case_sensitive: bool,
1128
1129 pub require_literal_separator: bool,
1133
1134 pub require_literal_leading_dot: bool,
1140
1141 pub recursive_match_hidden_dir: bool,
1144}
1145
1146impl Default for MatchOptions {
1148 fn default() -> Self {
1149 Self {
1150 case_sensitive: true,
1151 require_literal_separator: false,
1152 require_literal_leading_dot: false,
1153 recursive_match_hidden_dir: true,
1154 }
1155 }
1156}
1157
1158#[cfg(test)]
1159mod test {
1160 use crate::{Paths, PatternError, Uninterruptible};
1161
1162 use super::{MatchOptions, Pattern, glob as glob_with_signals};
1163 use std::path::Path;
1164
1165 fn glob(pattern: &str) -> Result<Paths, PatternError> {
1166 glob_with_signals(pattern, Uninterruptible)
1167 }
1168
1169 #[test]
1170 fn test_pattern_from_str() {
1171 assert!("a*b".parse::<Pattern>().unwrap().matches("a_b"));
1172 assert_eq!("a/**b".parse::<Pattern>().unwrap_err().pos, 4);
1173 }
1174
1175 #[test]
1176 fn test_wildcard_errors() {
1177 assert!(Pattern::new("a/**b").unwrap_err().pos == 4);
1178 assert!(Pattern::new("a/bc**").unwrap_err().pos == 3);
1179 assert!(Pattern::new("a/*****").unwrap_err().pos == 4);
1180 assert!(Pattern::new("a/b**c**d").unwrap_err().pos == 2);
1181 assert!(Pattern::new("a**b").unwrap_err().pos == 0);
1182 }
1183
1184 #[test]
1185 fn test_unclosed_bracket_errors() {
1186 assert!(Pattern::new("abc[def").unwrap_err().pos == 3);
1187 assert!(Pattern::new("abc[!def").unwrap_err().pos == 3);
1188 assert!(Pattern::new("abc[").unwrap_err().pos == 3);
1189 assert!(Pattern::new("abc[!").unwrap_err().pos == 3);
1190 assert!(Pattern::new("abc[d").unwrap_err().pos == 3);
1191 assert!(Pattern::new("abc[!d").unwrap_err().pos == 3);
1192 assert!(Pattern::new("abc[]").unwrap_err().pos == 3);
1193 assert!(Pattern::new("abc[!]").unwrap_err().pos == 3);
1194 }
1195
1196 #[test]
1197 fn test_glob_errors() {
1198 assert!(glob("a/**b").err().unwrap().pos == 4);
1199 assert!(glob("abc[def").err().unwrap().pos == 3);
1200 }
1201
1202 #[cfg(all(
1206 unix,
1207 not(target_os = "macos"),
1208 not(target_os = "android"),
1209 not(target_os = "ios")
1210 ))]
1211 #[test]
1212 fn test_iteration_errors() {
1213 use std::io;
1214 let mut iter = glob("/root/*").unwrap();
1215
1216 match std::fs::read_dir("/root/") {
1217 Ok(_) => {}
1219
1220 Err(err) if err.kind() == io::ErrorKind::NotFound => {
1222 assert!(iter.count() == 0);
1223 }
1224
1225 Err(_) => {
1227 let next = iter.next();
1229 assert!(next.is_some());
1230
1231 let err = next.unwrap();
1232 assert!(err.is_err());
1233
1234 let err = err.err().unwrap();
1235 assert!(err.path() == Path::new("/root"));
1236 assert!(err.error().kind() == io::ErrorKind::PermissionDenied);
1237 }
1238 }
1239 }
1240
1241 #[test]
1242 fn test_absolute_pattern() {
1243 assert!(glob("/").unwrap().next().is_some());
1244 assert!(glob("//").unwrap().next().is_some());
1245
1246 assert!(glob("/*").unwrap().next().is_some());
1248
1249 #[cfg(not(windows))]
1250 fn win() {}
1251
1252 #[cfg(windows)]
1253 fn win() {
1254 use std::env::current_dir;
1255 use std::path::Component;
1256
1257 let root_with_device = current_dir()
1259 .ok()
1260 .map(|p| match p.components().next().unwrap() {
1261 Component::Prefix(prefix_component) => {
1262 Path::new(prefix_component.as_os_str()).join("*")
1263 }
1264 _ => panic!("no prefix in this path"),
1265 })
1266 .unwrap();
1267 assert!(
1269 glob(root_with_device.as_os_str().to_str().unwrap())
1270 .unwrap()
1271 .next()
1272 .is_some()
1273 );
1274 }
1275 win()
1276 }
1277
1278 #[test]
1279 fn test_wildcards() {
1280 assert!(Pattern::new("a*b").unwrap().matches("a_b"));
1281 assert!(Pattern::new("a*b*c").unwrap().matches("abc"));
1282 assert!(!Pattern::new("a*b*c").unwrap().matches("abcd"));
1283 assert!(Pattern::new("a*b*c").unwrap().matches("a_b_c"));
1284 assert!(Pattern::new("a*b*c").unwrap().matches("a___b___c"));
1285 assert!(
1286 Pattern::new("abc*abc*abc")
1287 .unwrap()
1288 .matches("abcabcabcabcabcabcabc")
1289 );
1290 assert!(
1291 !Pattern::new("abc*abc*abc")
1292 .unwrap()
1293 .matches("abcabcabcabcabcabcabca")
1294 );
1295 assert!(
1296 Pattern::new("a*a*a*a*a*a*a*a*a")
1297 .unwrap()
1298 .matches("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
1299 );
1300 assert!(Pattern::new("a*b[xyz]c*d").unwrap().matches("abxcdbxcddd"));
1301 }
1302
1303 #[test]
1304 fn test_recursive_wildcards() {
1305 let pat = Pattern::new("some/**/needle.txt").unwrap();
1306 assert!(pat.matches("some/needle.txt"));
1307 assert!(pat.matches("some/one/needle.txt"));
1308 assert!(pat.matches("some/one/two/needle.txt"));
1309 assert!(pat.matches("some/other/needle.txt"));
1310 assert!(!pat.matches("some/other/notthis.txt"));
1311
1312 let pat = Pattern::new("**").unwrap();
1315 assert!(pat.is_recursive);
1316 assert!(pat.matches("abcde"));
1317 assert!(pat.matches(""));
1318 assert!(pat.matches(".asdf"));
1319 assert!(pat.matches("/x/.asdf"));
1320
1321 let pat = Pattern::new("some/**/**/needle.txt").unwrap();
1323 assert!(pat.matches("some/needle.txt"));
1324 assert!(pat.matches("some/one/needle.txt"));
1325 assert!(pat.matches("some/one/two/needle.txt"));
1326 assert!(pat.matches("some/other/needle.txt"));
1327 assert!(!pat.matches("some/other/notthis.txt"));
1328
1329 let pat = Pattern::new("**/test").unwrap();
1331 assert!(pat.matches("one/two/test"));
1332 assert!(pat.matches("one/test"));
1333 assert!(pat.matches("test"));
1334
1335 let pat = Pattern::new("/**/test").unwrap();
1337 assert!(pat.matches("/one/two/test"));
1338 assert!(pat.matches("/one/test"));
1339 assert!(pat.matches("/test"));
1340 assert!(!pat.matches("/one/notthis"));
1341 assert!(!pat.matches("/notthis"));
1342
1343 let pat = Pattern::new("**/.*").unwrap();
1345 assert!(pat.matches(".abc"));
1346 assert!(pat.matches("abc/.abc"));
1347 assert!(!pat.matches("ab.c"));
1348 assert!(!pat.matches("abc/ab.c"));
1349 }
1350
1351 #[test]
1352 fn test_lots_of_files() {
1353 glob("/*/*/*/*").unwrap().nth(10000);
1355 }
1356
1357 #[test]
1358 fn test_range_pattern() {
1359 let pat = Pattern::new("a[0-9]b").unwrap();
1360 for i in 0..10 {
1361 assert!(pat.matches(&format!("a{i}b")), "a{i}b =~ a[0-9]b");
1362 }
1363 assert!(!pat.matches("a_b"));
1364
1365 let pat = Pattern::new("a[!0-9]b").unwrap();
1366 for i in 0..10 {
1367 assert!(!pat.matches(&format!("a{i}b")));
1368 }
1369 assert!(pat.matches("a_b"));
1370
1371 let pats = ["[a-z123]", "[1a-z23]", "[123a-z]"];
1372 for &p in pats.iter() {
1373 let pat = Pattern::new(p).unwrap();
1374 for c in "abcdefghijklmnopqrstuvwxyz".chars() {
1375 assert!(pat.matches(&c.to_string()));
1376 }
1377 for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars() {
1378 let options = MatchOptions {
1379 case_sensitive: false,
1380 ..MatchOptions::default()
1381 };
1382 assert!(pat.matches_with(&c.to_string(), options));
1383 }
1384 assert!(pat.matches("1"));
1385 assert!(pat.matches("2"));
1386 assert!(pat.matches("3"));
1387 }
1388
1389 let pats = ["[abc-]", "[-abc]", "[a-c-]"];
1390 for &p in pats.iter() {
1391 let pat = Pattern::new(p).unwrap();
1392 assert!(pat.matches("a"));
1393 assert!(pat.matches("b"));
1394 assert!(pat.matches("c"));
1395 assert!(pat.matches("-"));
1396 assert!(!pat.matches("d"));
1397 }
1398
1399 let pat = Pattern::new("[2-1]").unwrap();
1400 assert!(!pat.matches("1"));
1401 assert!(!pat.matches("2"));
1402
1403 assert!(Pattern::new("[-]").unwrap().matches("-"));
1404 assert!(!Pattern::new("[!-]").unwrap().matches("-"));
1405 }
1406
1407 #[test]
1408 fn test_pattern_matches() {
1409 let txt_pat = Pattern::new("*hello.txt").unwrap();
1410 assert!(txt_pat.matches("hello.txt"));
1411 assert!(txt_pat.matches("gareth_says_hello.txt"));
1412 assert!(txt_pat.matches("some/path/to/hello.txt"));
1413 assert!(txt_pat.matches("some\\path\\to\\hello.txt"));
1414 assert!(txt_pat.matches("/an/absolute/path/to/hello.txt"));
1415 assert!(!txt_pat.matches("hello.txt-and-then-some"));
1416 assert!(!txt_pat.matches("goodbye.txt"));
1417
1418 let dir_pat = Pattern::new("*some/path/to/hello.txt").unwrap();
1419 assert!(dir_pat.matches("some/path/to/hello.txt"));
1420 assert!(dir_pat.matches("a/bigger/some/path/to/hello.txt"));
1421 assert!(!dir_pat.matches("some/path/to/hello.txt-and-then-some"));
1422 assert!(!dir_pat.matches("some/other/path/to/hello.txt"));
1423 }
1424
1425 #[test]
1426 fn test_pattern_escape() {
1427 let s = "_[_]_?_*_!_";
1428 assert_eq!(Pattern::escape(s), "_[[]_[]]_[?]_[*]_!_".to_string());
1429 assert!(Pattern::new(&Pattern::escape(s)).unwrap().matches(s));
1430 }
1431
1432 #[test]
1433 fn test_pattern_matches_case_insensitive() {
1434 let pat = Pattern::new("aBcDeFg").unwrap();
1435 let options = MatchOptions {
1436 case_sensitive: false,
1437 require_literal_separator: false,
1438 require_literal_leading_dot: false,
1439 recursive_match_hidden_dir: true,
1440 };
1441
1442 assert!(pat.matches_with("aBcDeFg", options));
1443 assert!(pat.matches_with("abcdefg", options));
1444 assert!(pat.matches_with("ABCDEFG", options));
1445 assert!(pat.matches_with("AbCdEfG", options));
1446 }
1447
1448 #[test]
1449 fn test_pattern_matches_case_insensitive_range() {
1450 let pat_within = Pattern::new("[a]").unwrap();
1451 let pat_except = Pattern::new("[!a]").unwrap();
1452
1453 let options_case_insensitive = MatchOptions {
1454 case_sensitive: false,
1455 require_literal_separator: false,
1456 require_literal_leading_dot: false,
1457 recursive_match_hidden_dir: false,
1458 };
1459 let options_case_sensitive = MatchOptions {
1460 case_sensitive: true,
1461 require_literal_separator: false,
1462 require_literal_leading_dot: false,
1463 recursive_match_hidden_dir: false,
1464 };
1465
1466 assert!(pat_within.matches_with("a", options_case_insensitive));
1467 assert!(pat_within.matches_with("A", options_case_insensitive));
1468 assert!(!pat_within.matches_with("A", options_case_sensitive));
1469
1470 assert!(!pat_except.matches_with("a", options_case_insensitive));
1471 assert!(!pat_except.matches_with("A", options_case_insensitive));
1472 assert!(pat_except.matches_with("A", options_case_sensitive));
1473 }
1474
1475 #[test]
1476 fn test_pattern_matches_require_literal_separator() {
1477 let options_require_literal = MatchOptions {
1478 case_sensitive: true,
1479 require_literal_separator: true,
1480 require_literal_leading_dot: false,
1481 recursive_match_hidden_dir: true,
1482 };
1483 let options_not_require_literal = MatchOptions {
1484 case_sensitive: true,
1485 require_literal_separator: false,
1486 require_literal_leading_dot: false,
1487 recursive_match_hidden_dir: true,
1488 };
1489
1490 assert!(
1491 Pattern::new("abc/def")
1492 .unwrap()
1493 .matches_with("abc/def", options_require_literal)
1494 );
1495 assert!(
1496 !Pattern::new("abc?def")
1497 .unwrap()
1498 .matches_with("abc/def", options_require_literal)
1499 );
1500 assert!(
1501 !Pattern::new("abc*def")
1502 .unwrap()
1503 .matches_with("abc/def", options_require_literal)
1504 );
1505 assert!(
1506 !Pattern::new("abc[/]def")
1507 .unwrap()
1508 .matches_with("abc/def", options_require_literal)
1509 );
1510
1511 assert!(
1512 Pattern::new("abc/def")
1513 .unwrap()
1514 .matches_with("abc/def", options_not_require_literal)
1515 );
1516 assert!(
1517 Pattern::new("abc?def")
1518 .unwrap()
1519 .matches_with("abc/def", options_not_require_literal)
1520 );
1521 assert!(
1522 Pattern::new("abc*def")
1523 .unwrap()
1524 .matches_with("abc/def", options_not_require_literal)
1525 );
1526 assert!(
1527 Pattern::new("abc[/]def")
1528 .unwrap()
1529 .matches_with("abc/def", options_not_require_literal)
1530 );
1531 }
1532
1533 #[test]
1534 fn test_pattern_matches_require_literal_leading_dot() {
1535 let options_require_literal_leading_dot = MatchOptions {
1536 case_sensitive: true,
1537 require_literal_separator: false,
1538 require_literal_leading_dot: true,
1539 recursive_match_hidden_dir: true,
1540 };
1541 let options_not_require_literal_leading_dot = MatchOptions {
1542 case_sensitive: true,
1543 require_literal_separator: false,
1544 require_literal_leading_dot: false,
1545 recursive_match_hidden_dir: true,
1546 };
1547
1548 let f = |options| {
1549 Pattern::new("*.txt")
1550 .unwrap()
1551 .matches_with(".hello.txt", options)
1552 };
1553 assert!(f(options_not_require_literal_leading_dot));
1554 assert!(!f(options_require_literal_leading_dot));
1555
1556 let f = |options| {
1557 Pattern::new(".*.*")
1558 .unwrap()
1559 .matches_with(".hello.txt", options)
1560 };
1561 assert!(f(options_not_require_literal_leading_dot));
1562 assert!(f(options_require_literal_leading_dot));
1563
1564 let f = |options| {
1565 Pattern::new("aaa/bbb/*")
1566 .unwrap()
1567 .matches_with("aaa/bbb/.ccc", options)
1568 };
1569 assert!(f(options_not_require_literal_leading_dot));
1570 assert!(!f(options_require_literal_leading_dot));
1571
1572 let f = |options| {
1573 Pattern::new("aaa/bbb/*")
1574 .unwrap()
1575 .matches_with("aaa/bbb/c.c.c.", options)
1576 };
1577 assert!(f(options_not_require_literal_leading_dot));
1578 assert!(f(options_require_literal_leading_dot));
1579
1580 let f = |options| {
1581 Pattern::new("aaa/bbb/.*")
1582 .unwrap()
1583 .matches_with("aaa/bbb/.ccc", options)
1584 };
1585 assert!(f(options_not_require_literal_leading_dot));
1586 assert!(f(options_require_literal_leading_dot));
1587
1588 let f = |options| {
1589 Pattern::new("aaa/?bbb")
1590 .unwrap()
1591 .matches_with("aaa/.bbb", options)
1592 };
1593 assert!(f(options_not_require_literal_leading_dot));
1594 assert!(!f(options_require_literal_leading_dot));
1595
1596 let f = |options| {
1597 Pattern::new("aaa/[.]bbb")
1598 .unwrap()
1599 .matches_with("aaa/.bbb", options)
1600 };
1601 assert!(f(options_not_require_literal_leading_dot));
1602 assert!(!f(options_require_literal_leading_dot));
1603
1604 let f = |options| Pattern::new("**/*").unwrap().matches_with(".bbb", options);
1605 assert!(f(options_not_require_literal_leading_dot));
1606 assert!(!f(options_require_literal_leading_dot));
1607 }
1608
1609 #[test]
1610 fn test_matches_path() {
1611 assert!(Pattern::new("a/b").unwrap().matches_path(Path::new("a/b")));
1614 }
1615
1616 #[test]
1617 fn test_path_join() {
1618 let pattern = Path::new("one").join(Path::new("**/*.rs"));
1619 assert!(Pattern::new(pattern.to_str().unwrap()).is_ok());
1620 }
1621}