1#![doc(
60 html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
61 html_favicon_url = "https://www.rust-lang.org/favicon.ico",
62 html_root_url = "https://docs.rs/glob-sl/0.4.2"
63)]
64#![deny(missing_docs)]
65
66#[cfg(test)]
67#[macro_use]
68extern crate doc_comment;
69
70#[cfg(test)]
71doctest!("../README.md");
72
73use std::cmp;
74use std::error::Error;
75use std::fmt;
76use std::fs;
77use std::io;
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<(PathBuf, usize), GlobError>>,
101 scope: Option<PathBuf>,
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)) => p.kind().is_verbatim(),
183 _ => false,
184 }
185 }
186 #[cfg(not(windows))]
187 fn check_windows_verbatim(_: &Path) -> bool {
188 false
189 }
190
191 #[cfg(windows)]
192 fn to_scope(p: &Path) -> PathBuf {
193 p.to_path_buf()
195 }
196 #[cfg(not(windows))]
197 fn to_scope(p: &Path) -> PathBuf {
198 p.to_path_buf()
199 }
200
201 if let Err(err) = Pattern::new(pattern) {
203 return Err(err);
204 }
205
206 let mut components = Path::new(pattern).components().peekable();
207 loop {
208 match components.peek() {
209 Some(&Component::Prefix(..)) | Some(&Component::RootDir) => {
210 components.next();
211 }
212 _ => break,
213 }
214 }
215 let rest = components.map(|s| s.as_os_str()).collect::<PathBuf>();
216 let normalized_pattern = Path::new(pattern).iter().collect::<PathBuf>();
217 let root_len = normalized_pattern.to_str().unwrap().len() - rest.to_str().unwrap().len();
218 let root = if root_len > 0 {
219 Some(Path::new(&pattern[..root_len]))
220 } else {
221 None
222 };
223
224 if root_len > 0 && check_windows_verbatim(root.unwrap()) {
225 return Ok(Paths {
229 dir_patterns: Vec::new(),
230 require_dir: false,
231 options,
232 todo: Vec::new(),
233 scope: None,
234 });
235 }
236
237 let scope = root.map_or_else(|| PathBuf::from("."), to_scope);
238
239 let mut dir_patterns = Vec::new();
240 let components =
241 pattern[cmp::min(root_len, pattern.len())..].split_terminator(path::is_separator);
242
243 for component in components {
244 dir_patterns.push(Pattern::new(component)?);
245 }
246
247 if root_len == pattern.len() {
248 dir_patterns.push(Pattern {
249 original: "".to_string(),
250 tokens: Vec::new(),
251 is_recursive: false,
252 });
253 }
254
255 let last_is_separator = pattern.chars().next_back().map(path::is_separator);
256 let require_dir = last_is_separator == Some(true);
257 let todo = Vec::new();
258
259 Ok(Paths {
260 dir_patterns,
261 require_dir,
262 options,
263 todo,
264 scope: Some(scope),
265 })
266}
267
268#[derive(Debug)]
274pub struct GlobError {
275 path: PathBuf,
276 error: io::Error,
277}
278
279impl GlobError {
280 pub fn path(&self) -> &Path {
282 &self.path
283 }
284
285 pub fn error(&self) -> &io::Error {
287 &self.error
288 }
289
290 pub fn into_error(self) -> io::Error {
292 self.error
293 }
294}
295
296impl Error for GlobError {
297 #[allow(unknown_lints, bare_trait_objects)]
298 fn cause(&self) -> Option<&Error> {
299 Some(&self.error)
300 }
301}
302
303impl fmt::Display for GlobError {
304 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
305 write!(
306 f,
307 "attempting to read `{}` resulted in an error: {}",
308 self.path.display(),
309 self.error
310 )
311 }
312}
313
314fn is_dir(p: &Path, follow_links: bool) -> bool {
315 let metadata = if follow_links {
316 fs::metadata(p)
317 } else {
318 fs::symlink_metadata(p)
319 };
320 metadata.map(|m| m.is_dir()).unwrap_or(false)
321}
322
323pub type GlobResult = Result<PathBuf, GlobError>;
328
329impl Iterator for Paths {
330 type Item = GlobResult;
331
332 fn next(&mut self) -> Option<GlobResult> {
333 if let Some(scope) = self.scope.take() {
338 if !self.dir_patterns.is_empty() {
339 assert!(self.dir_patterns.len() < !0 as usize);
341
342 fill_todo(&mut self.todo, &self.dir_patterns, 0, &scope, self.options);
343 }
344 }
345
346 loop {
347 if self.dir_patterns.is_empty() || self.todo.is_empty() {
348 return None;
349 }
350
351 let (path, mut idx) = match self.todo.pop().unwrap() {
352 Ok(pair) => pair,
353 Err(e) => return Some(Err(e)),
354 };
355
356 if idx == !0 as usize {
359 if self.require_dir && !is_dir(&path, self.options.follow_links) {
360 continue;
361 }
362 return Some(Ok(path));
363 }
364
365 if self.dir_patterns[idx].is_recursive {
366 let mut next = idx;
367
368 while (next + 1) < self.dir_patterns.len()
370 && self.dir_patterns[next + 1].is_recursive
371 {
372 next += 1;
373 }
374
375 if is_dir(&path, self.options.follow_links) {
376 fill_todo(
380 &mut self.todo,
381 &self.dir_patterns,
382 next,
383 &path,
384 self.options,
385 );
386
387 if next == self.dir_patterns.len() - 1 {
388 return Some(Ok(path));
391 } else {
392 idx = next + 1;
394 }
395 } else if next == self.dir_patterns.len() - 1 {
396 continue;
399 } else {
400 idx = next + 1;
402 }
403 }
404
405 if self.dir_patterns[idx].matches_with(
407 {
408 match path.file_name().and_then(|s| s.to_str()) {
409 None => continue,
413 Some(x) => x,
414 }
415 },
416 self.options,
417 ) {
418 if idx == self.dir_patterns.len() - 1 {
419 if !self.require_dir || is_dir(&path, self.options.follow_links) {
424 return Some(Ok(path));
425 }
426 } else {
427 fill_todo(
428 &mut self.todo,
429 &self.dir_patterns,
430 idx + 1,
431 &path,
432 self.options,
433 );
434 }
435 }
436 }
437 }
438}
439
440#[derive(Debug)]
442#[allow(missing_copy_implementations)]
443pub struct PatternError {
444 pub pos: usize,
446
447 pub msg: &'static str,
449}
450
451impl Error for PatternError {
452 fn description(&self) -> &str {
453 self.msg
454 }
455}
456
457impl fmt::Display for PatternError {
458 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
459 write!(
460 f,
461 "Pattern syntax error near position {}: {}",
462 self.pos, self.msg
463 )
464 }
465}
466
467#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
493pub struct Pattern {
494 original: String,
495 tokens: Vec<PatternToken>,
496 is_recursive: bool,
497}
498
499impl fmt::Display for Pattern {
501 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
502 self.original.fmt(f)
503 }
504}
505
506impl FromStr for Pattern {
507 type Err = PatternError;
508
509 fn from_str(s: &str) -> Result<Self, PatternError> {
510 Self::new(s)
511 }
512}
513
514#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
515enum PatternToken {
516 Char(char),
517 AnyChar,
518 AnySequence,
519 AnyRecursiveSequence,
520 AnyWithin(Vec<CharSpecifier>),
521 AnyExcept(Vec<CharSpecifier>),
522}
523
524#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
525enum CharSpecifier {
526 SingleChar(char),
527 CharRange(char, char),
528}
529
530#[derive(Copy, Clone, PartialEq)]
531enum MatchResult {
532 Match,
533 SubPatternDoesntMatch,
534 EntirePatternDoesntMatch,
535}
536
537const ERROR_WILDCARDS: &str = "wildcards are either regular `*` or recursive `**`";
538const ERROR_RECURSIVE_WILDCARDS: &str = "recursive wildcards must form a single path \
539 component";
540const ERROR_INVALID_RANGE: &str = "invalid range pattern";
541
542impl Pattern {
543 pub fn new(pattern: &str) -> Result<Self, PatternError> {
547 let chars = pattern.chars().collect::<Vec<_>>();
548 let mut tokens = Vec::new();
549 let mut is_recursive = false;
550 let mut i = 0;
551
552 while i < chars.len() {
553 match chars[i] {
554 '?' => {
555 tokens.push(AnyChar);
556 i += 1;
557 }
558 '*' => {
559 let old = i;
560
561 while i < chars.len() && chars[i] == '*' {
562 i += 1;
563 }
564
565 let count = i - old;
566
567 if count > 2 {
568 return Err(PatternError {
569 pos: old + 2,
570 msg: ERROR_WILDCARDS,
571 });
572 } else if count == 2 {
573 let is_valid = if i == 2 || path::is_separator(chars[i - count - 1]) {
577 if i < chars.len() && path::is_separator(chars[i]) {
579 i += 1;
580 true
581 } else if i == chars.len() {
584 true
585 } else {
587 return Err(PatternError {
588 pos: i,
589 msg: ERROR_RECURSIVE_WILDCARDS,
590 });
591 }
592 } else {
594 return Err(PatternError {
595 pos: old - 1,
596 msg: ERROR_RECURSIVE_WILDCARDS,
597 });
598 };
599
600 if is_valid {
601 let tokens_len = tokens.len();
605
606 if !(tokens_len > 1 && tokens[tokens_len - 1] == AnyRecursiveSequence) {
607 is_recursive = true;
608 tokens.push(AnyRecursiveSequence);
609 }
610 }
611 } else {
612 tokens.push(AnySequence);
613 }
614 }
615 '[' => {
616 if i + 4 <= chars.len() && chars[i + 1] == '!' {
617 match chars[i + 3..].iter().position(|x| *x == ']') {
618 None => (),
619 Some(j) => {
620 let chars = &chars[i + 2..i + 3 + j];
621 let cs = parse_char_specifiers(chars);
622 tokens.push(AnyExcept(cs));
623 i += j + 4;
624 continue;
625 }
626 }
627 } else if i + 3 <= chars.len() && chars[i + 1] != '!' {
628 match chars[i + 2..].iter().position(|x| *x == ']') {
629 None => (),
630 Some(j) => {
631 let cs = parse_char_specifiers(&chars[i + 1..i + 2 + j]);
632 tokens.push(AnyWithin(cs));
633 i += j + 3;
634 continue;
635 }
636 }
637 }
638
639 return Err(PatternError {
641 pos: i,
642 msg: ERROR_INVALID_RANGE,
643 });
644 }
645 c => {
646 tokens.push(Char(c));
647 i += 1;
648 }
649 }
650 }
651
652 Ok(Self {
653 tokens,
654 original: pattern.to_string(),
655 is_recursive,
656 })
657 }
658
659 pub fn escape(s: &str) -> String {
663 let mut escaped = String::new();
664 for c in s.chars() {
665 match c {
666 '?' | '*' | '[' | ']' => {
669 escaped.push('[');
670 escaped.push(c);
671 escaped.push(']');
672 }
673 c => {
674 escaped.push(c);
675 }
676 }
677 }
678 escaped
679 }
680
681 pub fn matches(&self, str: &str) -> bool {
694 self.matches_with(str, MatchOptions::new())
695 }
696
697 pub fn matches_path(&self, path: &Path) -> bool {
700 path.to_str().map_or(false, |s| self.matches(s))
702 }
703
704 pub fn matches_with(&self, str: &str, options: MatchOptions) -> bool {
707 self.matches_from(true, str.chars(), 0, options) == Match
708 }
709
710 pub fn matches_path_with(&self, path: &Path, options: MatchOptions) -> bool {
713 path.to_str()
715 .map_or(false, |s| self.matches_with(s, options))
716 }
717
718 pub fn as_str(&self) -> &str {
720 &self.original
721 }
722
723 fn matches_from(
724 &self,
725 mut follows_separator: bool,
726 mut file: std::str::Chars,
727 i: usize,
728 options: MatchOptions,
729 ) -> MatchResult {
730 for (ti, token) in self.tokens[i..].iter().enumerate() {
731 match *token {
732 AnySequence | AnyRecursiveSequence => {
733 debug_assert!(match *token {
735 AnyRecursiveSequence => follows_separator,
736 _ => true,
737 });
738
739 match self.matches_from(follows_separator, file.clone(), i + ti + 1, options) {
741 SubPatternDoesntMatch => (), m => return m,
743 };
744
745 while let Some(c) = file.next() {
746 if follows_separator && options.require_literal_leading_dot && c == '.' {
747 return SubPatternDoesntMatch;
748 }
749 follows_separator = path::is_separator(c);
750 match *token {
751 AnyRecursiveSequence if !follows_separator => continue,
752 AnySequence
753 if options.require_literal_separator && follows_separator =>
754 {
755 return SubPatternDoesntMatch
756 }
757 _ => (),
758 }
759 match self.matches_from(
760 follows_separator,
761 file.clone(),
762 i + ti + 1,
763 options,
764 ) {
765 SubPatternDoesntMatch => (), m => return m,
767 }
768 }
769 }
770 _ => {
771 let c = match file.next() {
772 Some(c) => c,
773 None => return EntirePatternDoesntMatch,
774 };
775
776 let is_sep = path::is_separator(c);
777
778 if !match *token {
779 AnyChar | AnyWithin(..) | AnyExcept(..)
780 if (options.require_literal_separator && is_sep)
781 || (follows_separator
782 && options.require_literal_leading_dot
783 && c == '.') =>
784 {
785 false
786 }
787 AnyChar => true,
788 AnyWithin(ref specifiers) => in_char_specifiers(&specifiers, c, options),
789 AnyExcept(ref specifiers) => !in_char_specifiers(&specifiers, c, options),
790 Char(c2) => chars_eq(c, c2, options.case_sensitive),
791 AnySequence | AnyRecursiveSequence => unreachable!(),
792 } {
793 return SubPatternDoesntMatch;
794 }
795 follows_separator = is_sep;
796 }
797 }
798 }
799
800 if file.next().is_none() {
802 Match
803 } else {
804 SubPatternDoesntMatch
805 }
806 }
807}
808
809fn fill_todo(
813 todo: &mut Vec<Result<(PathBuf, usize), GlobError>>,
814 patterns: &[Pattern],
815 idx: usize,
816 path: &Path,
817 options: MatchOptions,
818) {
819 fn pattern_as_str(pattern: &Pattern) -> Option<String> {
821 let mut s = String::new();
822 for token in &pattern.tokens {
823 match *token {
824 Char(c) => s.push(c),
825 _ => return None,
826 }
827 }
828
829 Some(s)
830 }
831
832 let add = |todo: &mut Vec<_>, next_path: PathBuf| {
833 if idx + 1 == patterns.len() {
834 todo.push(Ok((next_path, !0 as usize)));
838 } else {
839 fill_todo(todo, patterns, idx + 1, &next_path, options);
840 }
841 };
842
843 let pattern = &patterns[idx];
844 let is_dir = is_dir(path, options.follow_links);
845 let curdir = path == Path::new(".");
846 match pattern_as_str(pattern) {
847 Some(s) => {
848 let special = "." == s || ".." == s;
854 let next_path = if curdir {
855 PathBuf::from(s)
856 } else {
857 path.join(&s)
858 };
859 if (special && is_dir) || (!special && fs::metadata(&next_path).is_ok()) {
860 add(todo, next_path);
861 }
862 }
863 None if is_dir => {
864 let dirs = fs::read_dir(path).and_then(|d| {
865 d.map(|e| {
866 e.map(|e| {
867 if curdir {
868 PathBuf::from(e.path().file_name().unwrap())
869 } else {
870 e.path()
871 }
872 })
873 })
874 .collect::<Result<Vec<_>, _>>()
875 });
876 match dirs {
877 Ok(mut children) => {
878 if options.require_literal_leading_dot {
879 children.retain(|x| !x.file_name().unwrap().to_str().unwrap().starts_with("."));
880 }
881 children.sort_by(|p1, p2| p2.file_name().cmp(&p1.file_name()));
882 todo.extend(children.into_iter().map(|x| Ok((x, idx))));
883
884 if !pattern.tokens.is_empty() && pattern.tokens[0] == Char('.') {
890 for &special in &[".", ".."] {
891 if pattern.matches_with(special, options) {
892 add(todo, path.join(special));
893 }
894 }
895 }
896 }
897 Err(e) => {
898 todo.push(Err(GlobError {
899 path: path.to_path_buf(),
900 error: e,
901 }));
902 }
903 }
904 }
905 None => {
906 }
908 }
909}
910
911fn parse_char_specifiers(s: &[char]) -> Vec<CharSpecifier> {
912 let mut cs = Vec::new();
913 let mut i = 0;
914 while i < s.len() {
915 if i + 3 <= s.len() && s[i + 1] == '-' {
916 cs.push(CharRange(s[i], s[i + 2]));
917 i += 3;
918 } else {
919 cs.push(SingleChar(s[i]));
920 i += 1;
921 }
922 }
923 cs
924}
925
926fn in_char_specifiers(specifiers: &[CharSpecifier], c: char, options: MatchOptions) -> bool {
927 for &specifier in specifiers.iter() {
928 match specifier {
929 SingleChar(sc) => {
930 if chars_eq(c, sc, options.case_sensitive) {
931 return true;
932 }
933 }
934 CharRange(start, end) => {
935 if !options.case_sensitive && c.is_ascii() && start.is_ascii() && end.is_ascii() {
937 let start = start.to_ascii_lowercase();
938 let end = end.to_ascii_lowercase();
939
940 let start_up = start.to_uppercase().next().unwrap();
941 let end_up = end.to_uppercase().next().unwrap();
942
943 if start != start_up && end != end_up {
946 let c = c.to_ascii_lowercase();
947 if c >= start && c <= end {
948 return true;
949 }
950 }
951 }
952
953 if c >= start && c <= end {
954 return true;
955 }
956 }
957 }
958 }
959
960 false
961}
962
963fn chars_eq(a: char, b: char, case_sensitive: bool) -> bool {
965 if cfg!(windows) && path::is_separator(a) && path::is_separator(b) {
966 true
967 } else if !case_sensitive && a.is_ascii() && b.is_ascii() {
968 a.to_ascii_lowercase() == b.to_ascii_lowercase()
970 } else {
971 a == b
972 }
973}
974
975#[allow(missing_copy_implementations)]
977#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
978pub struct MatchOptions {
979 pub case_sensitive: bool,
984
985 pub require_literal_separator: bool,
989
990 pub require_literal_leading_dot: bool,
996
997 pub follow_links: bool,
1000}
1001
1002impl MatchOptions {
1003 pub fn new() -> Self {
1018 Self {
1019 case_sensitive: true,
1020 require_literal_separator: false,
1021 require_literal_leading_dot: false,
1022 follow_links: false,
1023 }
1024 }
1025}
1026
1027#[cfg(test)]
1028mod test {
1029 use super::{glob, MatchOptions, Pattern};
1030 use std::path::Path;
1031
1032 #[test]
1033 fn test_pattern_from_str() {
1034 assert!("a*b".parse::<Pattern>().unwrap().matches("a_b"));
1035 assert!("a/**b".parse::<Pattern>().unwrap_err().pos == 4);
1036 }
1037
1038 #[test]
1039 fn test_wildcard_errors() {
1040 assert!(Pattern::new("a/**b").unwrap_err().pos == 4);
1041 assert!(Pattern::new("a/bc**").unwrap_err().pos == 3);
1042 assert!(Pattern::new("a/*****").unwrap_err().pos == 4);
1043 assert!(Pattern::new("a/b**c**d").unwrap_err().pos == 2);
1044 assert!(Pattern::new("a**b").unwrap_err().pos == 0);
1045 }
1046
1047 #[test]
1048 fn test_unclosed_bracket_errors() {
1049 assert!(Pattern::new("abc[def").unwrap_err().pos == 3);
1050 assert!(Pattern::new("abc[!def").unwrap_err().pos == 3);
1051 assert!(Pattern::new("abc[").unwrap_err().pos == 3);
1052 assert!(Pattern::new("abc[!").unwrap_err().pos == 3);
1053 assert!(Pattern::new("abc[d").unwrap_err().pos == 3);
1054 assert!(Pattern::new("abc[!d").unwrap_err().pos == 3);
1055 assert!(Pattern::new("abc[]").unwrap_err().pos == 3);
1056 assert!(Pattern::new("abc[!]").unwrap_err().pos == 3);
1057 }
1058
1059 #[test]
1060 fn test_glob_errors() {
1061 assert!(glob("a/**b").err().unwrap().pos == 4);
1062 assert!(glob("abc[def").err().unwrap().pos == 3);
1063 }
1064
1065 #[cfg(all(unix, not(target_os = "macos")))]
1069 #[test]
1070 fn test_iteration_errors() {
1071 use std::io;
1072 let mut iter = glob("/root/*").unwrap();
1073
1074 let next = iter.next();
1076 assert!(next.is_some());
1077
1078 let err = next.unwrap();
1079 assert!(err.is_err());
1080
1081 let err = err.err().unwrap();
1082 assert!(err.path() == Path::new("/root"));
1083 assert!(err.error().kind() == io::ErrorKind::PermissionDenied);
1084 }
1085
1086 #[test]
1087 fn test_absolute_pattern() {
1088 assert!(glob("/").unwrap().next().is_some());
1089 assert!(glob("//").unwrap().next().is_some());
1090
1091 assert!(glob("/*").unwrap().next().is_some());
1093
1094 #[cfg(not(windows))]
1095 fn win() {}
1096
1097 #[cfg(windows)]
1098 fn win() {
1099 use std::env::current_dir;
1100 use std::path::Component;
1101
1102 let root_with_device = current_dir()
1104 .ok()
1105 .and_then(|p| match p.components().next().unwrap() {
1106 Component::Prefix(prefix_component) => {
1107 let path = Path::new(prefix_component.as_os_str());
1108 path.join("*");
1109 Some(path.to_path_buf())
1110 }
1111 _ => panic!("no prefix in this path"),
1112 })
1113 .unwrap();
1114 assert!(glob(root_with_device.as_os_str().to_str().unwrap())
1116 .unwrap()
1117 .next()
1118 .is_some());
1119 }
1120 win()
1121 }
1122
1123 #[test]
1124 fn test_wildcards() {
1125 assert!(Pattern::new("a*b").unwrap().matches("a_b"));
1126 assert!(Pattern::new("a*b*c").unwrap().matches("abc"));
1127 assert!(!Pattern::new("a*b*c").unwrap().matches("abcd"));
1128 assert!(Pattern::new("a*b*c").unwrap().matches("a_b_c"));
1129 assert!(Pattern::new("a*b*c").unwrap().matches("a___b___c"));
1130 assert!(Pattern::new("abc*abc*abc")
1131 .unwrap()
1132 .matches("abcabcabcabcabcabcabc"));
1133 assert!(!Pattern::new("abc*abc*abc")
1134 .unwrap()
1135 .matches("abcabcabcabcabcabcabca"));
1136 assert!(Pattern::new("a*a*a*a*a*a*a*a*a")
1137 .unwrap()
1138 .matches("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
1139 assert!(Pattern::new("a*b[xyz]c*d").unwrap().matches("abxcdbxcddd"));
1140 }
1141
1142 #[test]
1143 fn test_recursive_wildcards() {
1144 let pat = Pattern::new("some/**/needle.txt").unwrap();
1145 assert!(pat.matches("some/needle.txt"));
1146 assert!(pat.matches("some/one/needle.txt"));
1147 assert!(pat.matches("some/one/two/needle.txt"));
1148 assert!(pat.matches("some/other/needle.txt"));
1149 assert!(!pat.matches("some/other/notthis.txt"));
1150
1151 let pat = Pattern::new("**").unwrap();
1154 assert!(pat.is_recursive);
1155 assert!(pat.matches("abcde"));
1156 assert!(pat.matches(""));
1157 assert!(pat.matches(".asdf"));
1158 assert!(pat.matches("/x/.asdf"));
1159
1160 let pat = Pattern::new("some/**/**/needle.txt").unwrap();
1162 assert!(pat.matches("some/needle.txt"));
1163 assert!(pat.matches("some/one/needle.txt"));
1164 assert!(pat.matches("some/one/two/needle.txt"));
1165 assert!(pat.matches("some/other/needle.txt"));
1166 assert!(!pat.matches("some/other/notthis.txt"));
1167
1168 let pat = Pattern::new("**/test").unwrap();
1170 assert!(pat.matches("one/two/test"));
1171 assert!(pat.matches("one/test"));
1172 assert!(pat.matches("test"));
1173
1174 let pat = Pattern::new("/**/test").unwrap();
1176 assert!(pat.matches("/one/two/test"));
1177 assert!(pat.matches("/one/test"));
1178 assert!(pat.matches("/test"));
1179 assert!(!pat.matches("/one/notthis"));
1180 assert!(!pat.matches("/notthis"));
1181
1182 let pat = Pattern::new("**/.*").unwrap();
1184 assert!(pat.matches(".abc"));
1185 assert!(pat.matches("abc/.abc"));
1186 assert!(!pat.matches("ab.c"));
1187 assert!(!pat.matches("abc/ab.c"));
1188 }
1189
1190 #[test]
1191 fn test_lots_of_files() {
1192 glob("/*/*/*/*").unwrap().skip(10000).next();
1194 }
1195
1196 #[test]
1197 fn test_range_pattern() {
1198 let pat = Pattern::new("a[0-9]b").unwrap();
1199 for i in 0..10 {
1200 assert!(pat.matches(&format!("a{}b", i)));
1201 }
1202 assert!(!pat.matches("a_b"));
1203
1204 let pat = Pattern::new("a[!0-9]b").unwrap();
1205 for i in 0..10 {
1206 assert!(!pat.matches(&format!("a{}b", i)));
1207 }
1208 assert!(pat.matches("a_b"));
1209
1210 let pats = ["[a-z123]", "[1a-z23]", "[123a-z]"];
1211 for &p in pats.iter() {
1212 let pat = Pattern::new(p).unwrap();
1213 for c in "abcdefghijklmnopqrstuvwxyz".chars() {
1214 assert!(pat.matches(&c.to_string()));
1215 }
1216 for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars() {
1217 let options = MatchOptions {
1218 case_sensitive: false,
1219 ..MatchOptions::new()
1220 };
1221 assert!(pat.matches_with(&c.to_string(), options));
1222 }
1223 assert!(pat.matches("1"));
1224 assert!(pat.matches("2"));
1225 assert!(pat.matches("3"));
1226 }
1227
1228 let pats = ["[abc-]", "[-abc]", "[a-c-]"];
1229 for &p in pats.iter() {
1230 let pat = Pattern::new(p).unwrap();
1231 assert!(pat.matches("a"));
1232 assert!(pat.matches("b"));
1233 assert!(pat.matches("c"));
1234 assert!(pat.matches("-"));
1235 assert!(!pat.matches("d"));
1236 }
1237
1238 let pat = Pattern::new("[2-1]").unwrap();
1239 assert!(!pat.matches("1"));
1240 assert!(!pat.matches("2"));
1241
1242 assert!(Pattern::new("[-]").unwrap().matches("-"));
1243 assert!(!Pattern::new("[!-]").unwrap().matches("-"));
1244 }
1245
1246 #[test]
1247 fn test_pattern_matches() {
1248 let txt_pat = Pattern::new("*hello.txt").unwrap();
1249 assert!(txt_pat.matches("hello.txt"));
1250 assert!(txt_pat.matches("gareth_says_hello.txt"));
1251 assert!(txt_pat.matches("some/path/to/hello.txt"));
1252 assert!(txt_pat.matches("some\\path\\to\\hello.txt"));
1253 assert!(txt_pat.matches("/an/absolute/path/to/hello.txt"));
1254 assert!(!txt_pat.matches("hello.txt-and-then-some"));
1255 assert!(!txt_pat.matches("goodbye.txt"));
1256
1257 let dir_pat = Pattern::new("*some/path/to/hello.txt").unwrap();
1258 assert!(dir_pat.matches("some/path/to/hello.txt"));
1259 assert!(dir_pat.matches("a/bigger/some/path/to/hello.txt"));
1260 assert!(!dir_pat.matches("some/path/to/hello.txt-and-then-some"));
1261 assert!(!dir_pat.matches("some/other/path/to/hello.txt"));
1262 }
1263
1264 #[test]
1265 fn test_pattern_escape() {
1266 let s = "_[_]_?_*_!_";
1267 assert_eq!(Pattern::escape(s), "_[[]_[]]_[?]_[*]_!_".to_string());
1268 assert!(Pattern::new(&Pattern::escape(s)).unwrap().matches(s));
1269 }
1270
1271 #[test]
1272 fn test_pattern_matches_case_insensitive() {
1273 let pat = Pattern::new("aBcDeFg").unwrap();
1274 let options = MatchOptions {
1275 case_sensitive: false,
1276 require_literal_separator: false,
1277 require_literal_leading_dot: false,
1278 follow_links: false,
1279 };
1280
1281 assert!(pat.matches_with("aBcDeFg", options));
1282 assert!(pat.matches_with("abcdefg", options));
1283 assert!(pat.matches_with("ABCDEFG", options));
1284 assert!(pat.matches_with("AbCdEfG", options));
1285 }
1286
1287 #[test]
1288 fn test_pattern_matches_case_insensitive_range() {
1289 let pat_within = Pattern::new("[a]").unwrap();
1290 let pat_except = Pattern::new("[!a]").unwrap();
1291
1292 let options_case_insensitive = MatchOptions {
1293 case_sensitive: false,
1294 require_literal_separator: false,
1295 require_literal_leading_dot: false,
1296 follow_links: false,
1297 };
1298 let options_case_sensitive = MatchOptions {
1299 case_sensitive: true,
1300 require_literal_separator: false,
1301 require_literal_leading_dot: false,
1302 follow_links: false,
1303 };
1304
1305 assert!(pat_within.matches_with("a", options_case_insensitive));
1306 assert!(pat_within.matches_with("A", options_case_insensitive));
1307 assert!(!pat_within.matches_with("A", options_case_sensitive));
1308
1309 assert!(!pat_except.matches_with("a", options_case_insensitive));
1310 assert!(!pat_except.matches_with("A", options_case_insensitive));
1311 assert!(pat_except.matches_with("A", options_case_sensitive));
1312 }
1313
1314 #[test]
1315 fn test_pattern_matches_require_literal_separator() {
1316 let options_require_literal = MatchOptions {
1317 case_sensitive: true,
1318 require_literal_separator: true,
1319 require_literal_leading_dot: false,
1320 follow_links: false,
1321 };
1322 let options_not_require_literal = MatchOptions {
1323 case_sensitive: true,
1324 require_literal_separator: false,
1325 require_literal_leading_dot: false,
1326 follow_links: false,
1327 };
1328
1329 assert!(Pattern::new("abc/def")
1330 .unwrap()
1331 .matches_with("abc/def", options_require_literal));
1332 assert!(!Pattern::new("abc?def")
1333 .unwrap()
1334 .matches_with("abc/def", options_require_literal));
1335 assert!(!Pattern::new("abc*def")
1336 .unwrap()
1337 .matches_with("abc/def", options_require_literal));
1338 assert!(!Pattern::new("abc[/]def")
1339 .unwrap()
1340 .matches_with("abc/def", options_require_literal));
1341
1342 assert!(Pattern::new("abc/def")
1343 .unwrap()
1344 .matches_with("abc/def", options_not_require_literal));
1345 assert!(Pattern::new("abc?def")
1346 .unwrap()
1347 .matches_with("abc/def", options_not_require_literal));
1348 assert!(Pattern::new("abc*def")
1349 .unwrap()
1350 .matches_with("abc/def", options_not_require_literal));
1351 assert!(Pattern::new("abc[/]def")
1352 .unwrap()
1353 .matches_with("abc/def", options_not_require_literal));
1354 }
1355
1356 #[test]
1357 fn test_pattern_matches_require_literal_leading_dot() {
1358 let options_require_literal_leading_dot = MatchOptions {
1359 case_sensitive: true,
1360 require_literal_separator: false,
1361 require_literal_leading_dot: true,
1362 follow_links: false,
1363 };
1364 let options_not_require_literal_leading_dot = MatchOptions {
1365 case_sensitive: true,
1366 require_literal_separator: false,
1367 require_literal_leading_dot: false,
1368 follow_links: false,
1369 };
1370
1371 let f = |options| {
1372 Pattern::new("*.txt")
1373 .unwrap()
1374 .matches_with(".hello.txt", options)
1375 };
1376 assert!(f(options_not_require_literal_leading_dot));
1377 assert!(!f(options_require_literal_leading_dot));
1378
1379 let f = |options| {
1380 Pattern::new(".*.*")
1381 .unwrap()
1382 .matches_with(".hello.txt", options)
1383 };
1384 assert!(f(options_not_require_literal_leading_dot));
1385 assert!(f(options_require_literal_leading_dot));
1386
1387 let f = |options| {
1388 Pattern::new("aaa/bbb/*")
1389 .unwrap()
1390 .matches_with("aaa/bbb/.ccc", options)
1391 };
1392 assert!(f(options_not_require_literal_leading_dot));
1393 assert!(!f(options_require_literal_leading_dot));
1394
1395 let f = |options| {
1396 Pattern::new("aaa/bbb/*")
1397 .unwrap()
1398 .matches_with("aaa/bbb/c.c.c.", options)
1399 };
1400 assert!(f(options_not_require_literal_leading_dot));
1401 assert!(f(options_require_literal_leading_dot));
1402
1403 let f = |options| {
1404 Pattern::new("aaa/bbb/.*")
1405 .unwrap()
1406 .matches_with("aaa/bbb/.ccc", options)
1407 };
1408 assert!(f(options_not_require_literal_leading_dot));
1409 assert!(f(options_require_literal_leading_dot));
1410
1411 let f = |options| {
1412 Pattern::new("aaa/?bbb")
1413 .unwrap()
1414 .matches_with("aaa/.bbb", options)
1415 };
1416 assert!(f(options_not_require_literal_leading_dot));
1417 assert!(!f(options_require_literal_leading_dot));
1418
1419 let f = |options| {
1420 Pattern::new("aaa/[.]bbb")
1421 .unwrap()
1422 .matches_with("aaa/.bbb", options)
1423 };
1424 assert!(f(options_not_require_literal_leading_dot));
1425 assert!(!f(options_require_literal_leading_dot));
1426
1427 let f = |options| Pattern::new("**/*").unwrap().matches_with(".bbb", options);
1428 assert!(f(options_not_require_literal_leading_dot));
1429 assert!(!f(options_require_literal_leading_dot));
1430 }
1431
1432 #[test]
1433 fn test_matches_path() {
1434 assert!(Pattern::new("a/b").unwrap().matches_path(&Path::new("a/b")));
1437 }
1438
1439 #[test]
1440 fn test_path_join() {
1441 let pattern = Path::new("one").join(&Path::new("**/*.rs"));
1442 assert!(Pattern::new(pattern.to_str().unwrap()).is_ok());
1443 }
1444}