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