1#![doc(
59 html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
60 html_favicon_url = "https://www.rust-lang.org/favicon.ico",
61 html_root_url = "https://docs.rs/glob/0.3.1"
62)]
63#![deny(missing_docs)]
64#![allow(clippy::while_let_loop)]
65
66#[cfg(test)]
67doc_comment::doctest!("../README.md");
68
69use std::cmp;
70use std::cmp::Ordering;
71use std::error::Error;
72use std::ffi::OsString;
73use std::fmt;
74use std::fs;
75use std::fs::DirEntry;
76use std::io;
77use std::ops::Deref;
78use std::path::{self, Component, Path, PathBuf};
79use std::str::FromStr;
80
81use CharSpecifier::{CharRange, SingleChar};
82use MatchResult::{EntirePatternDoesntMatch, Match, SubPatternDoesntMatch};
83use PatternToken::AnyExcept;
84use PatternToken::{AnyChar, AnyRecursiveSequence, AnySequence, AnyWithin, Char};
85
86#[derive(Debug)]
96pub struct Paths {
97 dir_patterns: Vec<Pattern>,
98 require_dir: bool,
99 options: MatchOptions,
100 todo: Vec<Result<(PathWrapper, usize), GlobError>>,
101 scope: Option<PathWrapper>,
102}
103
104pub fn glob(pattern: &str) -> Result<Paths, PatternError> {
162 glob_with(pattern, MatchOptions::new())
163}
164
165pub fn glob_with(pattern: &str, options: MatchOptions) -> Result<Paths, PatternError> {
179 #[cfg(windows)]
180 fn check_windows_verbatim(p: &Path) -> bool {
181 match p.components().next() {
182 Some(Component::Prefix(ref p)) => {
183 p.kind().is_verbatim()
185 && if let std::path::Prefix::VerbatimDisk(_) = p.kind() {
186 false
187 } else {
188 true
189 }
190 }
191 _ => false,
192 }
193 }
194 #[cfg(not(windows))]
195 fn check_windows_verbatim(_: &Path) -> bool {
196 false
197 }
198
199 #[cfg(windows)]
200 fn to_scope(p: &Path) -> PathBuf {
201 p.to_path_buf()
203 }
204 #[cfg(not(windows))]
205 fn to_scope(p: &Path) -> PathBuf {
206 p.to_path_buf()
207 }
208
209 let _ = Pattern::new(pattern)?;
211
212 let mut components = Path::new(pattern).components().peekable();
213 loop {
214 match components.peek() {
215 Some(&Component::Prefix(..)) | Some(&Component::RootDir) => {
216 components.next();
217 }
218 _ => break,
219 }
220 }
221 let rest = components.map(|s| s.as_os_str()).collect::<PathBuf>();
222 let normalized_pattern = Path::new(pattern).iter().collect::<PathBuf>();
223 let root_len = normalized_pattern.to_str().unwrap().len() - rest.to_str().unwrap().len();
224 let root = if root_len > 0 {
225 Some(Path::new(&pattern[..root_len]))
226 } else {
227 None
228 };
229
230 if root_len > 0 && check_windows_verbatim(root.unwrap()) {
231 return Ok(Paths {
235 dir_patterns: Vec::new(),
236 require_dir: false,
237 options,
238 todo: Vec::new(),
239 scope: None,
240 });
241 }
242
243 let scope = root.map_or_else(|| PathBuf::from("."), to_scope);
244 let scope = PathWrapper::from_path(scope);
245
246 let mut dir_patterns = Vec::new();
247 let components =
248 pattern[cmp::min(root_len, pattern.len())..].split_terminator(path::is_separator);
249
250 for component in components {
251 dir_patterns.push(Pattern::new(component)?);
252 }
253
254 if root_len == pattern.len() {
255 dir_patterns.push(Pattern {
256 original: "".to_string(),
257 tokens: Vec::new(),
258 is_recursive: false,
259 has_metachars: false,
260 });
261 }
262
263 let last_is_separator = pattern.chars().next_back().map(path::is_separator);
264 let require_dir = last_is_separator == Some(true);
265 let todo = Vec::new();
266
267 Ok(Paths {
268 dir_patterns,
269 require_dir,
270 options,
271 todo,
272 scope: Some(scope),
273 })
274}
275
276#[derive(Debug)]
282pub struct GlobError {
283 path: PathBuf,
284 error: io::Error,
285}
286
287impl GlobError {
288 pub fn path(&self) -> &Path {
290 &self.path
291 }
292
293 pub fn error(&self) -> &io::Error {
295 &self.error
296 }
297
298 #[deprecated(note = "use `.into` instead")]
300 pub fn into_error(self) -> io::Error {
301 self.error
302 }
303}
304
305impl From<GlobError> for io::Error {
306 fn from(value: GlobError) -> Self {
307 value.error
308 }
309}
310
311impl Error for GlobError {
312 #[allow(deprecated)]
313 fn description(&self) -> &str {
314 self.error.description()
315 }
316
317 #[allow(unknown_lints, bare_trait_objects)]
318 fn cause(&self) -> Option<&dyn Error> {
319 Some(&self.error)
320 }
321}
322
323impl fmt::Display for GlobError {
324 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
325 write!(
326 f,
327 "attempting to read `{}` resulted in an error: {}",
328 self.path.display(),
329 self.error
330 )
331 }
332}
333
334#[derive(Debug)]
335struct PathWrapper {
336 path: PathBuf,
337 is_directory: bool,
338}
339
340impl PathWrapper {
341 fn from_dir_entry(path: PathBuf, e: DirEntry) -> Self {
342 let is_directory = e
343 .file_type()
344 .ok()
345 .and_then(|file_type| {
346 if file_type.is_symlink() {
349 None
350 } else {
351 Some(file_type.is_dir())
352 }
353 })
354 .or_else(|| fs::metadata(&path).map(|m| m.is_dir()).ok())
355 .unwrap_or(false);
356 Self { path, is_directory }
357 }
358 fn from_path(path: PathBuf) -> Self {
359 let is_directory = fs::metadata(&path).map(|m| m.is_dir()).unwrap_or(false);
360 Self { path, is_directory }
361 }
362
363 fn into_path(self) -> PathBuf {
364 self.path
365 }
366}
367
368impl Deref for PathWrapper {
369 type Target = Path;
370
371 fn deref(&self) -> &Self::Target {
372 self.path.deref()
373 }
374}
375
376impl AsRef<Path> for PathWrapper {
377 fn as_ref(&self) -> &Path {
378 self.path.as_ref()
379 }
380}
381
382pub type GlobResult = Result<PathBuf, GlobError>;
387
388impl Iterator for Paths {
389 type Item = GlobResult;
390
391 fn next(&mut self) -> Option<GlobResult> {
392 if let Some(scope) = self.scope.take() {
397 if !self.dir_patterns.is_empty() {
398 assert!(self.dir_patterns.len() < usize::MAX);
400
401 fill_todo(&mut self.todo, &self.dir_patterns, 0, &scope, self.options);
402 }
403 }
404
405 loop {
406 if self.dir_patterns.is_empty() || self.todo.is_empty() {
407 return None;
408 }
409
410 let (path, mut idx) = match self.todo.pop().unwrap() {
411 Ok(pair) => pair,
412 Err(e) => return Some(Err(e)),
413 };
414
415 if idx == usize::MAX {
418 if self.require_dir && !path.is_directory {
419 continue;
420 }
421 return Some(Ok(path.into_path()));
422 }
423
424 if self.dir_patterns[idx].is_recursive {
425 let mut next = idx;
426
427 while (next + 1) < self.dir_patterns.len()
429 && self.dir_patterns[next + 1].is_recursive
430 {
431 next += 1;
432 }
433
434 if path.is_directory {
435 fill_todo(
439 &mut self.todo,
440 &self.dir_patterns,
441 next,
442 &path,
443 self.options,
444 );
445
446 if next == self.dir_patterns.len() - 1 {
447 return Some(Ok(path.into_path()));
450 } else {
451 idx = next + 1;
453 }
454 } else if next == self.dir_patterns.len() - 1 {
455 continue;
458 } else {
459 idx = next + 1;
461 }
462 }
463
464 if self.dir_patterns[idx].matches_with(
466 {
467 match path.file_name().and_then(|s| s.to_str()) {
468 None => continue,
472 Some(x) => x,
473 }
474 },
475 self.options,
476 ) {
477 if idx == self.dir_patterns.len() - 1 {
478 if !self.require_dir || path.is_directory {
483 return Some(Ok(path.into_path()));
484 }
485 } else {
486 fill_todo(
487 &mut self.todo,
488 &self.dir_patterns,
489 idx + 1,
490 &path,
491 self.options,
492 );
493 }
494 }
495 }
496 }
497}
498
499#[derive(Debug)]
501#[allow(missing_copy_implementations)]
502pub struct PatternError {
503 pub pos: usize,
505
506 pub msg: &'static str,
508}
509
510impl Error for PatternError {
511 fn description(&self) -> &str {
512 self.msg
513 }
514}
515
516impl fmt::Display for PatternError {
517 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
518 write!(
519 f,
520 "Pattern syntax error near position {}: {}",
521 self.pos, self.msg
522 )
523 }
524}
525
526#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
556pub struct Pattern {
557 original: String,
558 tokens: Vec<PatternToken>,
559 is_recursive: bool,
560 has_metachars: bool,
563}
564
565impl fmt::Display for Pattern {
567 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
568 self.original.fmt(f)
569 }
570}
571
572impl FromStr for Pattern {
573 type Err = PatternError;
574
575 fn from_str(s: &str) -> Result<Self, PatternError> {
576 Self::new(s)
577 }
578}
579
580#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
581enum PatternToken {
582 Char(char),
583 AnyChar,
584 AnySequence,
585 AnyRecursiveSequence,
586 AnyWithin(Vec<CharSpecifier>),
587 AnyExcept(Vec<CharSpecifier>),
588}
589
590#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
591enum CharSpecifier {
592 SingleChar(char),
593 CharRange(char, char),
594}
595
596#[derive(Copy, Clone, PartialEq)]
597enum MatchResult {
598 Match,
599 SubPatternDoesntMatch,
600 EntirePatternDoesntMatch,
601}
602
603const ERROR_WILDCARDS: &str = "wildcards are either regular `*` or recursive `**`";
604const ERROR_RECURSIVE_WILDCARDS: &str = "recursive wildcards must form a single path \
605 component";
606const ERROR_INVALID_RANGE: &str = "invalid range pattern";
607
608impl Pattern {
609 pub fn new(pattern: &str) -> Result<Self, PatternError> {
613 let chars = pattern.chars().collect::<Vec<_>>();
614 let mut tokens = Vec::new();
615 let mut is_recursive = false;
616 let mut has_metachars = false;
617 let mut i = 0;
618
619 while i < chars.len() {
620 match chars[i] {
621 '?' => {
622 has_metachars = true;
623 tokens.push(AnyChar);
624 i += 1;
625 }
626 '*' => {
627 has_metachars = true;
628
629 let old = i;
630
631 while i < chars.len() && chars[i] == '*' {
632 i += 1;
633 }
634
635 let count = i - old;
636
637 match count.cmp(&2) {
638 Ordering::Greater => {
639 return Err(PatternError {
640 pos: old + 2,
641 msg: ERROR_WILDCARDS,
642 })
643 }
644 Ordering::Equal => {
645 let is_valid = if i == 2 || path::is_separator(chars[i - count - 1]) {
649 if i < chars.len() && path::is_separator(chars[i]) {
651 i += 1;
652 true
653 } else if i == chars.len() {
656 true
657 } else {
659 return Err(PatternError {
660 pos: i,
661 msg: ERROR_RECURSIVE_WILDCARDS,
662 });
663 }
664 } else {
666 return Err(PatternError {
667 pos: old - 1,
668 msg: ERROR_RECURSIVE_WILDCARDS,
669 });
670 };
671
672 if is_valid {
673 let tokens_len = tokens.len();
677
678 if !(tokens_len > 1
679 && tokens[tokens_len - 1] == AnyRecursiveSequence)
680 {
681 is_recursive = true;
682 tokens.push(AnyRecursiveSequence);
683 }
684 }
685 }
686 Ordering::Less => tokens.push(AnySequence),
687 }
688 }
689 '[' => {
690 has_metachars = true;
691
692 if i + 4 <= chars.len() && chars[i + 1] == '!' {
693 match chars[i + 3..].iter().position(|x| *x == ']') {
694 None => (),
695 Some(j) => {
696 let chars = &chars[i + 2..i + 3 + j];
697 let cs = parse_char_specifiers(chars);
698 tokens.push(AnyExcept(cs));
699 i += j + 4;
700 continue;
701 }
702 }
703 } else if i + 3 <= chars.len() && chars[i + 1] != '!' {
704 match chars[i + 2..].iter().position(|x| *x == ']') {
705 None => (),
706 Some(j) => {
707 let cs = parse_char_specifiers(&chars[i + 1..i + 2 + j]);
708 tokens.push(AnyWithin(cs));
709 i += j + 3;
710 continue;
711 }
712 }
713 }
714
715 return Err(PatternError {
717 pos: i,
718 msg: ERROR_INVALID_RANGE,
719 });
720 }
721 c => {
722 tokens.push(Char(c));
723 i += 1;
724 }
725 }
726 }
727
728 Ok(Self {
729 tokens,
730 original: pattern.to_string(),
731 is_recursive,
732 has_metachars,
733 })
734 }
735
736 pub fn escape(s: &str) -> String {
740 let mut escaped = String::new();
741 for c in s.chars() {
742 match c {
743 '?' | '*' | '[' | ']' => {
746 escaped.push('[');
747 escaped.push(c);
748 escaped.push(']');
749 }
750 c => {
751 escaped.push(c);
752 }
753 }
754 }
755 escaped
756 }
757
758 pub fn matches(&self, str: &str) -> bool {
771 self.matches_with(str, MatchOptions::new())
772 }
773
774 pub fn matches_path(&self, path: &Path) -> bool {
777 path.to_str().map_or(false, |s| self.matches(s))
779 }
780
781 pub fn matches_with(&self, str: &str, options: MatchOptions) -> bool {
784 self.matches_from(true, str.chars(), 0, options) == Match
785 }
786
787 pub fn matches_path_with(&self, path: &Path, options: MatchOptions) -> bool {
790 path.to_str()
792 .map_or(false, |s| self.matches_with(s, options))
793 }
794
795 pub fn as_str(&self) -> &str {
797 &self.original
798 }
799
800 fn matches_from(
801 &self,
802 mut follows_separator: bool,
803 mut file: std::str::Chars,
804 i: usize,
805 options: MatchOptions,
806 ) -> MatchResult {
807 for (ti, token) in self.tokens[i..].iter().enumerate() {
808 match *token {
809 AnySequence | AnyRecursiveSequence => {
810 debug_assert!(match *token {
812 AnyRecursiveSequence => follows_separator,
813 _ => true,
814 });
815
816 match self.matches_from(follows_separator, file.clone(), i + ti + 1, options) {
818 SubPatternDoesntMatch => (), m => return m,
820 };
821
822 while let Some(c) = file.next() {
823 if follows_separator && options.require_literal_leading_dot && c == '.' {
824 return SubPatternDoesntMatch;
825 }
826 follows_separator = path::is_separator(c);
827 match *token {
828 AnyRecursiveSequence if !follows_separator => continue,
829 AnySequence
830 if options.require_literal_separator && follows_separator =>
831 {
832 return SubPatternDoesntMatch
833 }
834 _ => (),
835 }
836 match self.matches_from(
837 follows_separator,
838 file.clone(),
839 i + ti + 1,
840 options,
841 ) {
842 SubPatternDoesntMatch => (), m => return m,
844 }
845 }
846 }
847 _ => {
848 let c = match file.next() {
849 Some(c) => c,
850 None => return EntirePatternDoesntMatch,
851 };
852
853 let is_sep = path::is_separator(c);
854
855 if !match *token {
856 AnyChar | AnyWithin(..) | AnyExcept(..)
857 if (options.require_literal_separator && is_sep)
858 || (follows_separator
859 && options.require_literal_leading_dot
860 && c == '.') =>
861 {
862 false
863 }
864 AnyChar => true,
865 AnyWithin(ref specifiers) => in_char_specifiers(specifiers, c, options),
866 AnyExcept(ref specifiers) => !in_char_specifiers(specifiers, c, options),
867 Char(c2) => chars_eq(c, c2, options.case_sensitive),
868 AnySequence | AnyRecursiveSequence => unreachable!(),
869 } {
870 return SubPatternDoesntMatch;
871 }
872 follows_separator = is_sep;
873 }
874 }
875 }
876
877 if file.next().is_none() {
879 Match
880 } else {
881 SubPatternDoesntMatch
882 }
883 }
884}
885
886fn fill_todo(
890 todo: &mut Vec<Result<(PathWrapper, usize), GlobError>>,
891 patterns: &[Pattern],
892 idx: usize,
893 path: &PathWrapper,
894 options: MatchOptions,
895) {
896 let add = |todo: &mut Vec<_>, next_path: PathWrapper| {
897 if idx + 1 == patterns.len() {
898 todo.push(Ok((next_path, usize::MAX)));
902 } else {
903 fill_todo(todo, patterns, idx + 1, &next_path, options);
904 }
905 };
906
907 let pattern = &patterns[idx];
908 let is_dir = path.is_directory;
909 let curdir = path.as_ref() == Path::new(".");
910 match (pattern.has_metachars, is_dir) {
911 (false, _) => {
912 debug_assert!(
913 pattern
914 .tokens
915 .iter()
916 .all(|tok| matches!(tok, PatternToken::Char(_))),
917 "broken invariant: pattern has metachars but shouldn't"
918 );
919 let s = pattern.as_str();
920
921 let special = "." == s || ".." == s;
927 let next_path = if curdir {
928 PathBuf::from(s)
929 } else {
930 path.join(s)
931 };
932 let next_path = PathWrapper::from_path(next_path);
933 if (special && is_dir)
934 || (!special
935 && (fs::metadata(&next_path).is_ok()
936 || fs::symlink_metadata(&next_path).is_ok()))
937 {
938 add(todo, next_path);
939 }
940 }
941 (true, true) => {
942 let dirs = fs::read_dir(path).and_then(|d| {
943 d.map(|e| {
944 e.map(|e| {
945 let (path, filename) = if curdir {
952 (PathBuf::from(e.path().file_name().unwrap()), e.file_name())
953 } else {
954 (e.path(), e.file_name())
955 };
956 (PathWrapper::from_dir_entry(path, e), filename)
957 })
958 })
959 .collect::<Result<Vec<(PathWrapper, OsString)>, _>>()
960 });
961 match dirs {
962 Ok(mut children) => {
963 if options.require_literal_leading_dot {
964 children.retain(|x| !x.1.to_str().unwrap().starts_with('.'));
965 }
966 children.sort_by(|p1, p2| p2.1.cmp(&p1.1));
967 todo.extend(children.into_iter().map(|x| Ok((x.0, idx))));
968
969 if !pattern.tokens.is_empty() && pattern.tokens[0] == Char('.') {
975 for &special in &[".", ".."] {
976 if pattern.matches_with(special, options) {
977 add(todo, PathWrapper::from_path(path.join(special)));
978 }
979 }
980 }
981 }
982 Err(e) => {
983 todo.push(Err(GlobError {
984 path: path.to_path_buf(),
985 error: e,
986 }));
987 }
988 }
989 }
990 (true, false) => {
991 }
993 }
994}
995
996fn parse_char_specifiers(s: &[char]) -> Vec<CharSpecifier> {
997 let mut cs = Vec::new();
998 let mut i = 0;
999 while i < s.len() {
1000 if i + 3 <= s.len() && s[i + 1] == '-' {
1001 cs.push(CharRange(s[i], s[i + 2]));
1002 i += 3;
1003 } else {
1004 cs.push(SingleChar(s[i]));
1005 i += 1;
1006 }
1007 }
1008 cs
1009}
1010
1011fn in_char_specifiers(specifiers: &[CharSpecifier], c: char, options: MatchOptions) -> bool {
1012 for &specifier in specifiers.iter() {
1013 match specifier {
1014 SingleChar(sc) => {
1015 if chars_eq(c, sc, options.case_sensitive) {
1016 return true;
1017 }
1018 }
1019 CharRange(start, end) => {
1020 if !options.case_sensitive && c.is_ascii() && start.is_ascii() && end.is_ascii() {
1022 let start = start.to_ascii_lowercase();
1023 let end = end.to_ascii_lowercase();
1024
1025 let start_up = start.to_uppercase().next().unwrap();
1026 let end_up = end.to_uppercase().next().unwrap();
1027
1028 if start != start_up && end != end_up {
1031 let c = c.to_ascii_lowercase();
1032 if c >= start && c <= end {
1033 return true;
1034 }
1035 }
1036 }
1037
1038 if c >= start && c <= end {
1039 return true;
1040 }
1041 }
1042 }
1043 }
1044
1045 false
1046}
1047
1048fn chars_eq(a: char, b: char, case_sensitive: bool) -> bool {
1050 if cfg!(windows) && path::is_separator(a) && path::is_separator(b) {
1051 true
1052 } else if !case_sensitive && a.is_ascii() && b.is_ascii() {
1053 a.eq_ignore_ascii_case(&b)
1055 } else {
1056 a == b
1057 }
1058}
1059
1060#[allow(missing_copy_implementations)]
1062#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1063pub struct MatchOptions {
1064 pub case_sensitive: bool,
1069
1070 pub require_literal_separator: bool,
1074
1075 pub require_literal_leading_dot: bool,
1081}
1082
1083impl MatchOptions {
1084 pub fn new() -> Self {
1103 Self {
1104 case_sensitive: true,
1105 require_literal_separator: false,
1106 require_literal_leading_dot: false,
1107 }
1108 }
1109}
1110
1111#[cfg(test)]
1112mod test {
1113 use super::{glob, MatchOptions, Pattern};
1114 use std::path::Path;
1115
1116 #[test]
1117 fn test_pattern_from_str() {
1118 assert!("a*b".parse::<Pattern>().unwrap().matches("a_b"));
1119 assert!("a/**b".parse::<Pattern>().unwrap_err().pos == 4);
1120 }
1121
1122 #[test]
1123 fn test_wildcard_errors() {
1124 assert!(Pattern::new("a/**b").unwrap_err().pos == 4);
1125 assert!(Pattern::new("a/bc**").unwrap_err().pos == 3);
1126 assert!(Pattern::new("a/*****").unwrap_err().pos == 4);
1127 assert!(Pattern::new("a/b**c**d").unwrap_err().pos == 2);
1128 assert!(Pattern::new("a**b").unwrap_err().pos == 0);
1129 }
1130
1131 #[test]
1132 fn test_unclosed_bracket_errors() {
1133 assert!(Pattern::new("abc[def").unwrap_err().pos == 3);
1134 assert!(Pattern::new("abc[!def").unwrap_err().pos == 3);
1135 assert!(Pattern::new("abc[").unwrap_err().pos == 3);
1136 assert!(Pattern::new("abc[!").unwrap_err().pos == 3);
1137 assert!(Pattern::new("abc[d").unwrap_err().pos == 3);
1138 assert!(Pattern::new("abc[!d").unwrap_err().pos == 3);
1139 assert!(Pattern::new("abc[]").unwrap_err().pos == 3);
1140 assert!(Pattern::new("abc[!]").unwrap_err().pos == 3);
1141 }
1142
1143 #[test]
1144 fn test_glob_errors() {
1145 assert!(glob("a/**b").err().unwrap().pos == 4);
1146 assert!(glob("abc[def").err().unwrap().pos == 3);
1147 }
1148
1149 #[cfg(all(unix, not(target_os = "macos")))]
1153 #[test]
1154 fn test_iteration_errors() {
1155 use std::io;
1156 let mut iter = glob("/root/*").unwrap();
1157
1158 let next = iter.next();
1160 assert!(next.is_some());
1161
1162 let err = next.unwrap();
1163 assert!(err.is_err());
1164
1165 let err = err.err().unwrap();
1166 assert!(err.path() == Path::new("/root"));
1167 assert!(err.error().kind() == io::ErrorKind::PermissionDenied);
1168 }
1169
1170 #[test]
1171 fn test_absolute_pattern() {
1172 assert!(glob("/").unwrap().next().is_some());
1173 assert!(glob("//").unwrap().next().is_some());
1174
1175 assert!(glob("/*").unwrap().next().is_some());
1177
1178 #[cfg(not(windows))]
1179 fn win() {}
1180
1181 #[cfg(windows)]
1182 fn win() {
1183 use std::env::current_dir;
1184 use std::path::Component;
1185
1186 let root_with_device = current_dir()
1188 .ok()
1189 .and_then(|p| match p.components().next().unwrap() {
1190 Component::Prefix(prefix_component) => {
1191 let path = Path::new(prefix_component.as_os_str()).join("*");
1192 Some(path.to_path_buf())
1193 }
1194 _ => panic!("no prefix in this path"),
1195 })
1196 .unwrap();
1197 assert!(glob(root_with_device.as_os_str().to_str().unwrap())
1199 .unwrap()
1200 .next()
1201 .is_some());
1202 }
1203 win()
1204 }
1205
1206 #[test]
1207 fn test_wildcards() {
1208 assert!(Pattern::new("a*b").unwrap().matches("a_b"));
1209 assert!(Pattern::new("a*b*c").unwrap().matches("abc"));
1210 assert!(!Pattern::new("a*b*c").unwrap().matches("abcd"));
1211 assert!(Pattern::new("a*b*c").unwrap().matches("a_b_c"));
1212 assert!(Pattern::new("a*b*c").unwrap().matches("a___b___c"));
1213 assert!(Pattern::new("abc*abc*abc")
1214 .unwrap()
1215 .matches("abcabcabcabcabcabcabc"));
1216 assert!(!Pattern::new("abc*abc*abc")
1217 .unwrap()
1218 .matches("abcabcabcabcabcabcabca"));
1219 assert!(Pattern::new("a*a*a*a*a*a*a*a*a")
1220 .unwrap()
1221 .matches("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
1222 assert!(Pattern::new("a*b[xyz]c*d").unwrap().matches("abxcdbxcddd"));
1223 }
1224
1225 #[test]
1226 fn test_recursive_wildcards() {
1227 let pat = Pattern::new("some/**/needle.txt").unwrap();
1228 assert!(pat.matches("some/needle.txt"));
1229 assert!(pat.matches("some/one/needle.txt"));
1230 assert!(pat.matches("some/one/two/needle.txt"));
1231 assert!(pat.matches("some/other/needle.txt"));
1232 assert!(!pat.matches("some/other/notthis.txt"));
1233
1234 let pat = Pattern::new("**").unwrap();
1237 assert!(pat.is_recursive);
1238 assert!(pat.matches("abcde"));
1239 assert!(pat.matches(""));
1240 assert!(pat.matches(".asdf"));
1241 assert!(pat.matches("/x/.asdf"));
1242
1243 let pat = Pattern::new("some/**/**/needle.txt").unwrap();
1245 assert!(pat.matches("some/needle.txt"));
1246 assert!(pat.matches("some/one/needle.txt"));
1247 assert!(pat.matches("some/one/two/needle.txt"));
1248 assert!(pat.matches("some/other/needle.txt"));
1249 assert!(!pat.matches("some/other/notthis.txt"));
1250
1251 let pat = Pattern::new("**/test").unwrap();
1253 assert!(pat.matches("one/two/test"));
1254 assert!(pat.matches("one/test"));
1255 assert!(pat.matches("test"));
1256
1257 let pat = Pattern::new("/**/test").unwrap();
1259 assert!(pat.matches("/one/two/test"));
1260 assert!(pat.matches("/one/test"));
1261 assert!(pat.matches("/test"));
1262 assert!(!pat.matches("/one/notthis"));
1263 assert!(!pat.matches("/notthis"));
1264
1265 let pat = Pattern::new("**/.*").unwrap();
1267 assert!(pat.matches(".abc"));
1268 assert!(pat.matches("abc/.abc"));
1269 assert!(!pat.matches("ab.c"));
1270 assert!(!pat.matches("abc/ab.c"));
1271 }
1272
1273 #[test]
1274 fn test_lots_of_files() {
1275 glob("/*/*/*/*").unwrap().skip(10000).next();
1277 }
1278
1279 #[test]
1280 fn test_range_pattern() {
1281 let pat = Pattern::new("a[0-9]b").unwrap();
1282 for i in 0..10 {
1283 assert!(pat.matches(&format!("a{}b", i)));
1284 }
1285 assert!(!pat.matches("a_b"));
1286
1287 let pat = Pattern::new("a[!0-9]b").unwrap();
1288 for i in 0..10 {
1289 assert!(!pat.matches(&format!("a{}b", i)));
1290 }
1291 assert!(pat.matches("a_b"));
1292
1293 let pats = ["[a-z123]", "[1a-z23]", "[123a-z]"];
1294 for &p in pats.iter() {
1295 let pat = Pattern::new(p).unwrap();
1296 for c in "abcdefghijklmnopqrstuvwxyz".chars() {
1297 assert!(pat.matches(&c.to_string()));
1298 }
1299 for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars() {
1300 let options = MatchOptions {
1301 case_sensitive: false,
1302 ..MatchOptions::new()
1303 };
1304 assert!(pat.matches_with(&c.to_string(), options));
1305 }
1306 assert!(pat.matches("1"));
1307 assert!(pat.matches("2"));
1308 assert!(pat.matches("3"));
1309 }
1310
1311 let pats = ["[abc-]", "[-abc]", "[a-c-]"];
1312 for &p in pats.iter() {
1313 let pat = Pattern::new(p).unwrap();
1314 assert!(pat.matches("a"));
1315 assert!(pat.matches("b"));
1316 assert!(pat.matches("c"));
1317 assert!(pat.matches("-"));
1318 assert!(!pat.matches("d"));
1319 }
1320
1321 let pat = Pattern::new("[2-1]").unwrap();
1322 assert!(!pat.matches("1"));
1323 assert!(!pat.matches("2"));
1324
1325 assert!(Pattern::new("[-]").unwrap().matches("-"));
1326 assert!(!Pattern::new("[!-]").unwrap().matches("-"));
1327 }
1328
1329 #[test]
1330 fn test_pattern_matches() {
1331 let txt_pat = Pattern::new("*hello.txt").unwrap();
1332 assert!(txt_pat.matches("hello.txt"));
1333 assert!(txt_pat.matches("gareth_says_hello.txt"));
1334 assert!(txt_pat.matches("some/path/to/hello.txt"));
1335 assert!(txt_pat.matches("some\\path\\to\\hello.txt"));
1336 assert!(txt_pat.matches("/an/absolute/path/to/hello.txt"));
1337 assert!(!txt_pat.matches("hello.txt-and-then-some"));
1338 assert!(!txt_pat.matches("goodbye.txt"));
1339
1340 let dir_pat = Pattern::new("*some/path/to/hello.txt").unwrap();
1341 assert!(dir_pat.matches("some/path/to/hello.txt"));
1342 assert!(dir_pat.matches("a/bigger/some/path/to/hello.txt"));
1343 assert!(!dir_pat.matches("some/path/to/hello.txt-and-then-some"));
1344 assert!(!dir_pat.matches("some/other/path/to/hello.txt"));
1345 }
1346
1347 #[test]
1348 fn test_pattern_escape() {
1349 let s = "_[_]_?_*_!_";
1350 assert_eq!(Pattern::escape(s), "_[[]_[]]_[?]_[*]_!_".to_string());
1351 assert!(Pattern::new(&Pattern::escape(s)).unwrap().matches(s));
1352 }
1353
1354 #[test]
1355 fn test_pattern_matches_case_insensitive() {
1356 let pat = Pattern::new("aBcDeFg").unwrap();
1357 let options = MatchOptions {
1358 case_sensitive: false,
1359 require_literal_separator: false,
1360 require_literal_leading_dot: false,
1361 };
1362
1363 assert!(pat.matches_with("aBcDeFg", options));
1364 assert!(pat.matches_with("abcdefg", options));
1365 assert!(pat.matches_with("ABCDEFG", options));
1366 assert!(pat.matches_with("AbCdEfG", options));
1367 }
1368
1369 #[test]
1370 fn test_pattern_matches_case_insensitive_range() {
1371 let pat_within = Pattern::new("[a]").unwrap();
1372 let pat_except = Pattern::new("[!a]").unwrap();
1373
1374 let options_case_insensitive = MatchOptions {
1375 case_sensitive: false,
1376 require_literal_separator: false,
1377 require_literal_leading_dot: false,
1378 };
1379 let options_case_sensitive = MatchOptions {
1380 case_sensitive: true,
1381 require_literal_separator: false,
1382 require_literal_leading_dot: false,
1383 };
1384
1385 assert!(pat_within.matches_with("a", options_case_insensitive));
1386 assert!(pat_within.matches_with("A", options_case_insensitive));
1387 assert!(!pat_within.matches_with("A", options_case_sensitive));
1388
1389 assert!(!pat_except.matches_with("a", options_case_insensitive));
1390 assert!(!pat_except.matches_with("A", options_case_insensitive));
1391 assert!(pat_except.matches_with("A", options_case_sensitive));
1392 }
1393
1394 #[test]
1395 fn test_pattern_matches_require_literal_separator() {
1396 let options_require_literal = MatchOptions {
1397 case_sensitive: true,
1398 require_literal_separator: true,
1399 require_literal_leading_dot: false,
1400 };
1401 let options_not_require_literal = MatchOptions {
1402 case_sensitive: true,
1403 require_literal_separator: false,
1404 require_literal_leading_dot: false,
1405 };
1406
1407 assert!(Pattern::new("abc/def")
1408 .unwrap()
1409 .matches_with("abc/def", options_require_literal));
1410 assert!(!Pattern::new("abc?def")
1411 .unwrap()
1412 .matches_with("abc/def", options_require_literal));
1413 assert!(!Pattern::new("abc*def")
1414 .unwrap()
1415 .matches_with("abc/def", options_require_literal));
1416 assert!(!Pattern::new("abc[/]def")
1417 .unwrap()
1418 .matches_with("abc/def", options_require_literal));
1419
1420 assert!(Pattern::new("abc/def")
1421 .unwrap()
1422 .matches_with("abc/def", options_not_require_literal));
1423 assert!(Pattern::new("abc?def")
1424 .unwrap()
1425 .matches_with("abc/def", options_not_require_literal));
1426 assert!(Pattern::new("abc*def")
1427 .unwrap()
1428 .matches_with("abc/def", options_not_require_literal));
1429 assert!(Pattern::new("abc[/]def")
1430 .unwrap()
1431 .matches_with("abc/def", options_not_require_literal));
1432 }
1433
1434 #[test]
1435 fn test_pattern_matches_require_literal_leading_dot() {
1436 let options_require_literal_leading_dot = MatchOptions {
1437 case_sensitive: true,
1438 require_literal_separator: false,
1439 require_literal_leading_dot: true,
1440 };
1441 let options_not_require_literal_leading_dot = MatchOptions {
1442 case_sensitive: true,
1443 require_literal_separator: false,
1444 require_literal_leading_dot: false,
1445 };
1446
1447 let f = |options| {
1448 Pattern::new("*.txt")
1449 .unwrap()
1450 .matches_with(".hello.txt", options)
1451 };
1452 assert!(f(options_not_require_literal_leading_dot));
1453 assert!(!f(options_require_literal_leading_dot));
1454
1455 let f = |options| {
1456 Pattern::new(".*.*")
1457 .unwrap()
1458 .matches_with(".hello.txt", options)
1459 };
1460 assert!(f(options_not_require_literal_leading_dot));
1461 assert!(f(options_require_literal_leading_dot));
1462
1463 let f = |options| {
1464 Pattern::new("aaa/bbb/*")
1465 .unwrap()
1466 .matches_with("aaa/bbb/.ccc", options)
1467 };
1468 assert!(f(options_not_require_literal_leading_dot));
1469 assert!(!f(options_require_literal_leading_dot));
1470
1471 let f = |options| {
1472 Pattern::new("aaa/bbb/*")
1473 .unwrap()
1474 .matches_with("aaa/bbb/c.c.c.", options)
1475 };
1476 assert!(f(options_not_require_literal_leading_dot));
1477 assert!(f(options_require_literal_leading_dot));
1478
1479 let f = |options| {
1480 Pattern::new("aaa/bbb/.*")
1481 .unwrap()
1482 .matches_with("aaa/bbb/.ccc", options)
1483 };
1484 assert!(f(options_not_require_literal_leading_dot));
1485 assert!(f(options_require_literal_leading_dot));
1486
1487 let f = |options| {
1488 Pattern::new("aaa/?bbb")
1489 .unwrap()
1490 .matches_with("aaa/.bbb", options)
1491 };
1492 assert!(f(options_not_require_literal_leading_dot));
1493 assert!(!f(options_require_literal_leading_dot));
1494
1495 let f = |options| {
1496 Pattern::new("aaa/[.]bbb")
1497 .unwrap()
1498 .matches_with("aaa/.bbb", options)
1499 };
1500 assert!(f(options_not_require_literal_leading_dot));
1501 assert!(!f(options_require_literal_leading_dot));
1502
1503 let f = |options| Pattern::new("**/*").unwrap().matches_with(".bbb", options);
1504 assert!(f(options_not_require_literal_leading_dot));
1505 assert!(!f(options_require_literal_leading_dot));
1506 }
1507
1508 #[test]
1509 fn test_matches_path() {
1510 assert!(Pattern::new("a/b").unwrap().matches_path(Path::new("a/b")));
1513 }
1514
1515 #[test]
1516 fn test_path_join() {
1517 let pattern = Path::new("one").join(Path::new("**/*.rs"));
1518 assert!(Pattern::new(pattern.to_str().unwrap()).is_ok());
1519 }
1520}