1#![deny(missing_docs)]
113
114use std::{
115 borrow::Cow,
116 panic::{RefUnwindSafe, UnwindSafe},
117 path::Path,
118 sync::Arc,
119};
120
121use {
122 aho_corasick::AhoCorasick,
123 bstr::{B, ByteSlice, ByteVec},
124 regex_automata::{
125 PatternSet,
126 meta::Regex,
127 util::pool::{Pool, PoolGuard},
128 },
129};
130
131use crate::{
132 glob::MatchStrategy,
133 pathutil::{file_name, file_name_ext, normalize_path},
134};
135
136pub use crate::glob::{Glob, GlobBuilder, GlobMatcher};
137
138mod fnv;
139mod glob;
140mod pathutil;
141
142#[cfg(feature = "serde1")]
143mod serde_impl;
144
145#[cfg(feature = "log")]
146macro_rules! debug {
147 ($($token:tt)*) => (::log::debug!($($token)*);)
148}
149
150#[cfg(not(feature = "log"))]
151macro_rules! debug {
152 ($($token:tt)*) => {};
153}
154
155#[derive(Clone, Debug, Eq, PartialEq)]
157pub struct Error {
158 glob: Option<String>,
160 kind: ErrorKind,
162}
163
164#[derive(Clone, Debug, Eq, PartialEq)]
166#[non_exhaustive]
167pub enum ErrorKind {
168 InvalidRecursive,
176 UnclosedClass,
178 InvalidRange(char, char),
182 UnopenedAlternates,
184 UnclosedAlternates,
186 NestedAlternates,
192 DanglingEscape,
194 Regex(String),
196}
197
198impl std::error::Error for Error {
199 fn description(&self) -> &str {
200 self.kind.description()
201 }
202}
203
204impl Error {
205 pub fn glob(&self) -> Option<&str> {
207 self.glob.as_ref().map(|s| &**s)
208 }
209
210 pub fn kind(&self) -> &ErrorKind {
212 &self.kind
213 }
214}
215
216impl ErrorKind {
217 fn description(&self) -> &str {
218 match *self {
219 ErrorKind::InvalidRecursive => {
220 "invalid use of **; must be one path component"
221 }
222 ErrorKind::UnclosedClass => {
223 "unclosed character class; missing ']'"
224 }
225 ErrorKind::InvalidRange(_, _) => "invalid character range",
226 ErrorKind::UnopenedAlternates => {
227 "unopened alternate group; missing '{' \
228 (maybe escape '}' with '[}]'?)"
229 }
230 ErrorKind::UnclosedAlternates => {
231 "unclosed alternate group; missing '}' \
232 (maybe escape '{' with '[{]'?)"
233 }
234 ErrorKind::NestedAlternates => {
235 "nested alternate groups are not allowed"
236 }
237 ErrorKind::DanglingEscape => "dangling '\\'",
238 ErrorKind::Regex(ref err) => err,
239 }
240 }
241}
242
243impl std::fmt::Display for Error {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 match self.glob {
246 None => self.kind.fmt(f),
247 Some(ref glob) => {
248 write!(f, "error parsing glob '{}': {}", glob, self.kind)
249 }
250 }
251 }
252}
253
254impl std::fmt::Display for ErrorKind {
255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 match *self {
257 ErrorKind::InvalidRecursive
258 | ErrorKind::UnclosedClass
259 | ErrorKind::UnopenedAlternates
260 | ErrorKind::UnclosedAlternates
261 | ErrorKind::NestedAlternates
262 | ErrorKind::DanglingEscape
263 | ErrorKind::Regex(_) => write!(f, "{}", self.description()),
264 ErrorKind::InvalidRange(s, e) => {
265 write!(f, "invalid range; '{}' > '{}'", s, e)
266 }
267 }
268 }
269}
270
271fn new_regex(pat: &str) -> Result<Regex, Error> {
272 let syntax = regex_automata::util::syntax::Config::new()
273 .utf8(false)
274 .dot_matches_new_line(true);
275 let config = Regex::config()
276 .utf8_empty(false)
277 .nfa_size_limit(Some(10 * (1 << 20)))
278 .hybrid_cache_capacity(10 * (1 << 20));
279 Regex::builder().syntax(syntax).configure(config).build(pat).map_err(
280 |err| Error {
281 glob: Some(pat.to_string()),
282 kind: ErrorKind::Regex(err.to_string()),
283 },
284 )
285}
286
287fn new_regex_set(pats: Vec<String>) -> Result<Regex, Error> {
288 let syntax = regex_automata::util::syntax::Config::new()
289 .utf8(false)
290 .dot_matches_new_line(true);
291 let config = Regex::config()
292 .match_kind(regex_automata::MatchKind::All)
293 .utf8_empty(false)
294 .nfa_size_limit(Some(10 * (1 << 20)))
295 .hybrid_cache_capacity(10 * (1 << 20));
296 Regex::builder()
297 .syntax(syntax)
298 .configure(config)
299 .build_many(&pats)
300 .map_err(|err| Error {
301 glob: None,
302 kind: ErrorKind::Regex(err.to_string()),
303 })
304}
305
306#[derive(Clone, Debug)]
309pub struct GlobSet {
310 len: usize,
311 strats: Vec<GlobSetMatchStrategy>,
312}
313
314impl GlobSet {
315 #[inline]
319 pub fn builder() -> GlobSetBuilder {
320 GlobSetBuilder::new()
321 }
322
323 #[inline]
325 pub const fn empty() -> GlobSet {
326 GlobSet { len: 0, strats: vec![] }
327 }
328
329 #[inline]
331 pub fn is_empty(&self) -> bool {
332 self.len == 0
333 }
334
335 #[inline]
337 pub fn len(&self) -> usize {
338 self.len
339 }
340
341 pub fn is_match<P: AsRef<Path>>(&self, path: P) -> bool {
343 self.is_match_candidate(&Candidate::new(path.as_ref()))
344 }
345
346 pub fn is_match_candidate(&self, path: &Candidate<'_>) -> bool {
351 if self.is_empty() {
352 return false;
353 }
354 for strat in &self.strats {
355 if strat.is_match(path) {
356 return true;
357 }
358 }
359 false
360 }
361
362 pub fn matches_all<P: AsRef<Path>>(&self, path: P) -> bool {
380 self.matches_all_candidate(&Candidate::new(path.as_ref()))
381 }
382
383 pub fn matches_all_candidate(&self, path: &Candidate<'_>) -> bool {
391 for strat in &self.strats {
392 if !strat.matches_all(path) {
393 return false;
394 }
395 }
396 true
397 }
398
399 pub fn matches<P: AsRef<Path>>(&self, path: P) -> Vec<usize> {
402 self.matches_candidate(&Candidate::new(path.as_ref()))
403 }
404
405 pub fn matches_candidate(&self, path: &Candidate<'_>) -> Vec<usize> {
411 let mut into = vec![];
412 if self.is_empty() {
413 return into;
414 }
415 self.matches_candidate_into(path, &mut into);
416 into
417 }
418
419 pub fn matches_into<P: AsRef<Path>>(
426 &self,
427 path: P,
428 into: &mut Vec<usize>,
429 ) {
430 self.matches_candidate_into(&Candidate::new(path.as_ref()), into);
431 }
432
433 pub fn matches_candidate_into(
443 &self,
444 path: &Candidate<'_>,
445 into: &mut Vec<usize>,
446 ) {
447 into.clear();
448 if self.is_empty() {
449 return;
450 }
451 for strat in &self.strats {
452 strat.matches_into(path, into);
453 }
454 into.sort();
455 into.dedup();
456 }
457
458 pub fn new<I, G>(globs: I) -> Result<GlobSet, Error>
462 where
463 I: IntoIterator<Item = G>,
464 G: AsRef<Glob>,
465 {
466 let mut it = globs.into_iter().peekable();
467 if it.peek().is_none() {
468 return Ok(GlobSet::empty());
469 }
470
471 let mut len = 0;
472 let mut lits = LiteralStrategy::new();
473 let mut base_lits = BasenameLiteralStrategy::new();
474 let mut exts = ExtensionStrategy::new();
475 let mut prefixes = MultiStrategyBuilder::new();
476 let mut suffixes = MultiStrategyBuilder::new();
477 let mut required_exts = RequiredExtensionStrategyBuilder::new();
478 let mut regexes = MultiStrategyBuilder::new();
479 for (i, p) in it.enumerate() {
480 len += 1;
481
482 let p = p.as_ref();
483 match MatchStrategy::new(p) {
484 MatchStrategy::Literal(lit) => {
485 lits.add(i, lit);
486 }
487 MatchStrategy::BasenameLiteral(lit) => {
488 base_lits.add(i, lit);
489 }
490 MatchStrategy::Extension(ext) => {
491 exts.add(i, ext);
492 }
493 MatchStrategy::Prefix(prefix) => {
494 prefixes.add(i, prefix);
495 }
496 MatchStrategy::Suffix { suffix, component } => {
497 if component {
498 lits.add(i, suffix[1..].to_string());
499 }
500 suffixes.add(i, suffix);
501 }
502 MatchStrategy::RequiredExtension(ext) => {
503 required_exts.add(i, ext, p.regex().to_owned());
504 }
505 MatchStrategy::Regex => {
506 debug!(
507 "glob `{:?}` converted to regex: `{:?}`",
508 p,
509 p.regex()
510 );
511 regexes.add(i, p.regex().to_owned());
512 }
513 }
514 }
515 debug!(
516 "built glob set; {} literals, {} basenames, {} extensions, \
517 {} prefixes, {} suffixes, {} required extensions, {} regexes",
518 lits.0.len(),
519 base_lits.0.len(),
520 exts.0.len(),
521 prefixes.literals.len(),
522 suffixes.literals.len(),
523 required_exts.0.len(),
524 regexes.literals.len()
525 );
526 let mut strats = Vec::with_capacity(7);
527 if !exts.0.is_empty() {
529 strats.push(GlobSetMatchStrategy::Extension(exts));
530 }
531 if !base_lits.0.is_empty() {
532 strats.push(GlobSetMatchStrategy::BasenameLiteral(base_lits));
533 }
534 if !lits.0.is_empty() {
535 strats.push(GlobSetMatchStrategy::Literal(lits));
536 }
537 if !suffixes.is_empty() {
538 strats.push(GlobSetMatchStrategy::Suffix(suffixes.suffix()));
539 }
540 if !prefixes.is_empty() {
541 strats.push(GlobSetMatchStrategy::Prefix(prefixes.prefix()));
542 }
543 if !required_exts.0.is_empty() {
544 strats.push(GlobSetMatchStrategy::RequiredExtension(
545 required_exts.build()?,
546 ));
547 }
548 if !regexes.is_empty() {
549 strats.push(GlobSetMatchStrategy::Regex(regexes.regex_set()?));
550 }
551
552 Ok(GlobSet { len, strats })
553 }
554}
555
556impl Default for GlobSet {
557 fn default() -> Self {
559 GlobSet::empty()
560 }
561}
562
563#[derive(Clone, Debug)]
566pub struct GlobSetBuilder {
567 pats: Vec<Glob>,
568}
569
570impl GlobSetBuilder {
571 pub fn new() -> GlobSetBuilder {
575 GlobSetBuilder { pats: vec![] }
576 }
577
578 pub fn build(&self) -> Result<GlobSet, Error> {
582 GlobSet::new(self.pats.iter())
583 }
584
585 pub fn add(&mut self, pat: Glob) -> &mut GlobSetBuilder {
587 self.pats.push(pat);
588 self
589 }
590}
591
592#[derive(Clone)]
599pub struct Candidate<'a> {
600 path: Cow<'a, [u8]>,
601 basename: Cow<'a, [u8]>,
602 ext: Cow<'a, [u8]>,
603}
604
605impl<'a> std::fmt::Debug for Candidate<'a> {
606 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
607 f.debug_struct("Candidate")
608 .field("path", &self.path.as_bstr())
609 .field("basename", &self.basename.as_bstr())
610 .field("ext", &self.ext.as_bstr())
611 .finish()
612 }
613}
614
615impl<'a> Candidate<'a> {
616 pub fn new<P: AsRef<Path> + ?Sized>(path: &'a P) -> Candidate<'a> {
618 Self::from_cow(Vec::from_path_lossy(path.as_ref()))
619 }
620
621 pub fn from_bytes<P: AsRef<[u8]> + ?Sized>(path: &'a P) -> Candidate<'a> {
630 Self::from_cow(Cow::Borrowed(path.as_ref()))
631 }
632
633 fn from_cow(path: Cow<'a, [u8]>) -> Candidate<'a> {
634 let path = normalize_path(path);
635 let basename = file_name(&path).unwrap_or(Cow::Borrowed(B("")));
636 let ext = file_name_ext(&basename).unwrap_or(Cow::Borrowed(B("")));
637 Candidate { path, basename, ext }
638 }
639
640 fn path_prefix(&self, max: usize) -> &[u8] {
641 if self.path.len() <= max { &*self.path } else { &self.path[..max] }
642 }
643
644 fn path_suffix(&self, max: usize) -> &[u8] {
645 if self.path.len() <= max {
646 &*self.path
647 } else {
648 &self.path[self.path.len() - max..]
649 }
650 }
651}
652
653#[derive(Clone, Debug)]
654enum GlobSetMatchStrategy {
655 Literal(LiteralStrategy),
656 BasenameLiteral(BasenameLiteralStrategy),
657 Extension(ExtensionStrategy),
658 Prefix(PrefixStrategy),
659 Suffix(SuffixStrategy),
660 RequiredExtension(RequiredExtensionStrategy),
661 Regex(RegexSetStrategy),
662}
663
664impl GlobSetMatchStrategy {
665 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
666 use self::GlobSetMatchStrategy::*;
667 match *self {
668 Literal(ref s) => s.is_match(candidate),
669 BasenameLiteral(ref s) => s.is_match(candidate),
670 Extension(ref s) => s.is_match(candidate),
671 Prefix(ref s) => s.is_match(candidate),
672 Suffix(ref s) => s.is_match(candidate),
673 RequiredExtension(ref s) => s.is_match(candidate),
674 Regex(ref s) => s.is_match(candidate),
675 }
676 }
677
678 fn matches_into(
679 &self,
680 candidate: &Candidate<'_>,
681 matches: &mut Vec<usize>,
682 ) {
683 use self::GlobSetMatchStrategy::*;
684 match *self {
685 Literal(ref s) => s.matches_into(candidate, matches),
686 BasenameLiteral(ref s) => s.matches_into(candidate, matches),
687 Extension(ref s) => s.matches_into(candidate, matches),
688 Prefix(ref s) => s.matches_into(candidate, matches),
689 Suffix(ref s) => s.matches_into(candidate, matches),
690 RequiredExtension(ref s) => s.matches_into(candidate, matches),
691 Regex(ref s) => s.matches_into(candidate, matches),
692 }
693 }
694
695 fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
696 use self::GlobSetMatchStrategy::*;
697 match *self {
698 Literal(ref s) => s.matches_all(candidate),
699 BasenameLiteral(ref s) => s.matches_all(candidate),
700 Extension(ref s) => s.matches_all(candidate),
701 Prefix(ref s) => s.matches_all(candidate),
702 Suffix(ref s) => s.matches_all(candidate),
703 RequiredExtension(ref s) => s.matches_all(candidate),
704 Regex(ref s) => s.matches_all(candidate),
705 }
706 }
707}
708
709#[derive(Clone, Debug)]
710struct LiteralStrategy(fnv::HashMap<Vec<u8>, Vec<usize>>);
711
712impl LiteralStrategy {
713 fn new() -> LiteralStrategy {
714 LiteralStrategy(fnv::HashMap::default())
715 }
716
717 fn add(&mut self, global_index: usize, lit: String) {
718 self.0.entry(lit.into_bytes()).or_insert(vec![]).push(global_index);
719 }
720
721 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
722 self.0.contains_key(candidate.path.as_bytes())
723 }
724
725 fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
726 self.0.len() == 1 && self.is_match(candidate)
727 }
728
729 #[inline(never)]
730 fn matches_into(
731 &self,
732 candidate: &Candidate<'_>,
733 matches: &mut Vec<usize>,
734 ) {
735 if let Some(hits) = self.0.get(candidate.path.as_bytes()) {
736 matches.extend(hits);
737 }
738 }
739}
740
741#[derive(Clone, Debug)]
742struct BasenameLiteralStrategy(fnv::HashMap<Vec<u8>, Vec<usize>>);
743
744impl BasenameLiteralStrategy {
745 fn new() -> BasenameLiteralStrategy {
746 BasenameLiteralStrategy(fnv::HashMap::default())
747 }
748
749 fn add(&mut self, global_index: usize, lit: String) {
750 self.0.entry(lit.into_bytes()).or_insert(vec![]).push(global_index);
751 }
752
753 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
754 if candidate.basename.is_empty() {
755 return false;
756 }
757 self.0.contains_key(candidate.basename.as_bytes())
758 }
759
760 fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
761 self.0.len() == 1 && self.is_match(candidate)
762 }
763
764 #[inline(never)]
765 fn matches_into(
766 &self,
767 candidate: &Candidate<'_>,
768 matches: &mut Vec<usize>,
769 ) {
770 if candidate.basename.is_empty() {
771 return;
772 }
773 if let Some(hits) = self.0.get(candidate.basename.as_bytes()) {
774 matches.extend(hits);
775 }
776 }
777}
778
779#[derive(Clone, Debug)]
780struct ExtensionStrategy(fnv::HashMap<Vec<u8>, Vec<usize>>);
781
782impl ExtensionStrategy {
783 fn new() -> ExtensionStrategy {
784 ExtensionStrategy(fnv::HashMap::default())
785 }
786
787 fn add(&mut self, global_index: usize, ext: String) {
788 self.0.entry(ext.into_bytes()).or_insert(vec![]).push(global_index);
789 }
790
791 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
792 if candidate.ext.is_empty() {
793 return false;
794 }
795 self.0.contains_key(candidate.ext.as_bytes())
796 }
797
798 fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
799 self.0.len() == 1 && self.is_match(candidate)
800 }
801
802 #[inline(never)]
803 fn matches_into(
804 &self,
805 candidate: &Candidate<'_>,
806 matches: &mut Vec<usize>,
807 ) {
808 if candidate.ext.is_empty() {
809 return;
810 }
811 if let Some(hits) = self.0.get(candidate.ext.as_bytes()) {
812 matches.extend(hits);
813 }
814 }
815}
816
817#[derive(Clone, Debug)]
818struct PrefixStrategy {
819 matcher: AhoCorasick,
820 map: Vec<usize>,
821 longest: usize,
822}
823
824impl PrefixStrategy {
825 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
826 let path = candidate.path_prefix(self.longest);
827 for m in self.matcher.find_overlapping_iter(path) {
828 if m.start() == 0 {
829 return true;
830 }
831 }
832 false
833 }
834
835 fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
836 let path = candidate.path_prefix(self.longest);
837 let mut count = 0;
838 for m in self.matcher.find_overlapping_iter(path) {
841 if m.start() == 0 {
842 count += 1;
843 }
844 }
845 count == self.map.len()
846 }
847
848 fn matches_into(
849 &self,
850 candidate: &Candidate<'_>,
851 matches: &mut Vec<usize>,
852 ) {
853 let path = candidate.path_prefix(self.longest);
854 for m in self.matcher.find_overlapping_iter(path) {
855 if m.start() == 0 {
856 matches.push(self.map[m.pattern()]);
857 }
858 }
859 }
860}
861
862#[derive(Clone, Debug)]
863struct SuffixStrategy {
864 matcher: AhoCorasick,
865 map: Vec<usize>,
866 longest: usize,
867}
868
869impl SuffixStrategy {
870 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
871 let path = candidate.path_suffix(self.longest);
872 for m in self.matcher.find_overlapping_iter(path) {
873 if m.end() == path.len() {
874 return true;
875 }
876 }
877 false
878 }
879
880 fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
881 let path = candidate.path_suffix(self.longest);
882 let mut count = 0;
883 for m in self.matcher.find_overlapping_iter(path) {
886 if m.end() == path.len() {
887 count += 1;
888 }
889 }
890 count == self.map.len()
891 }
892
893 fn matches_into(
894 &self,
895 candidate: &Candidate<'_>,
896 matches: &mut Vec<usize>,
897 ) {
898 let path = candidate.path_suffix(self.longest);
899 for m in self.matcher.find_overlapping_iter(path) {
900 if m.end() == path.len() {
901 matches.push(self.map[m.pattern()]);
902 }
903 }
904 }
905}
906
907#[derive(Clone, Debug)]
908struct RequiredExtensionStrategy(fnv::HashMap<Vec<u8>, Vec<(usize, Regex)>>);
909
910impl RequiredExtensionStrategy {
911 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
912 if candidate.ext.is_empty() {
913 return false;
914 }
915 match self.0.get(candidate.ext.as_bytes()) {
916 None => false,
917 Some(regexes) => {
918 for &(_, ref re) in regexes {
919 if re.is_match(candidate.path.as_bytes()) {
920 return true;
921 }
922 }
923 false
924 }
925 }
926 }
927
928 fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
929 if candidate.ext.is_empty() {
930 return false;
931 }
932 if let Some(regexes) = self.0.get(candidate.ext.as_bytes()) {
933 for &(_, ref re) in regexes {
934 if !re.is_match(candidate.path.as_bytes()) {
935 return false;
936 }
937 }
938 true
939 } else {
940 false
941 }
942 }
943
944 #[inline(never)]
945 fn matches_into(
946 &self,
947 candidate: &Candidate<'_>,
948 matches: &mut Vec<usize>,
949 ) {
950 if candidate.ext.is_empty() {
951 return;
952 }
953 if let Some(regexes) = self.0.get(candidate.ext.as_bytes()) {
954 for &(global_index, ref re) in regexes {
955 if re.is_match(candidate.path.as_bytes()) {
956 matches.push(global_index);
957 }
958 }
959 }
960 }
961}
962
963#[derive(Clone, Debug)]
964struct RegexSetStrategy {
965 matcher: Regex,
966 map: Vec<usize>,
967 patset: Arc<Pool<PatternSet, PatternSetPoolFn>>,
976}
977
978type PatternSetPoolFn =
979 Box<dyn Fn() -> PatternSet + Send + Sync + UnwindSafe + RefUnwindSafe>;
980
981impl RegexSetStrategy {
982 fn is_match(&self, candidate: &Candidate<'_>) -> bool {
983 self.matcher.is_match(candidate.path.as_bytes())
984 }
985
986 fn find_matches(
987 &self,
988 candidate: &Candidate<'_>,
989 ) -> PoolGuard<'_, PatternSet, PatternSetPoolFn> {
990 let input = regex_automata::Input::new(candidate.path.as_bytes());
991 let mut patset = self.patset.get();
992 patset.clear();
993 self.matcher.which_overlapping_matches(&input, &mut patset);
994 patset
995 }
996
997 fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
998 let patset = self.find_matches(candidate);
999 patset.is_full()
1000 }
1001
1002 fn matches_into(
1003 &self,
1004 candidate: &Candidate<'_>,
1005 matches: &mut Vec<usize>,
1006 ) {
1007 let patset = self.find_matches(candidate);
1008 for i in patset.iter() {
1009 matches.push(self.map[i]);
1010 }
1011 PoolGuard::put(patset);
1012 }
1013}
1014
1015#[derive(Clone, Debug)]
1016struct MultiStrategyBuilder {
1017 literals: Vec<String>,
1018 map: Vec<usize>,
1019 longest: usize,
1020}
1021
1022impl MultiStrategyBuilder {
1023 fn new() -> MultiStrategyBuilder {
1024 MultiStrategyBuilder { literals: vec![], map: vec![], longest: 0 }
1025 }
1026
1027 fn add(&mut self, global_index: usize, literal: String) {
1028 if literal.len() > self.longest {
1029 self.longest = literal.len();
1030 }
1031 self.map.push(global_index);
1032 self.literals.push(literal);
1033 }
1034
1035 fn prefix(self) -> PrefixStrategy {
1036 PrefixStrategy {
1037 matcher: AhoCorasick::new(&self.literals).unwrap(),
1038 map: self.map,
1039 longest: self.longest,
1040 }
1041 }
1042
1043 fn suffix(self) -> SuffixStrategy {
1044 SuffixStrategy {
1045 matcher: AhoCorasick::new(&self.literals).unwrap(),
1046 map: self.map,
1047 longest: self.longest,
1048 }
1049 }
1050
1051 fn regex_set(self) -> Result<RegexSetStrategy, Error> {
1052 let matcher = new_regex_set(self.literals)?;
1053 let pattern_len = matcher.pattern_len();
1054 let create: PatternSetPoolFn =
1055 Box::new(move || PatternSet::new(pattern_len));
1056 Ok(RegexSetStrategy {
1057 matcher,
1058 map: self.map,
1059 patset: Arc::new(Pool::new(create)),
1060 })
1061 }
1062
1063 fn is_empty(&self) -> bool {
1064 self.literals.is_empty()
1065 }
1066}
1067
1068#[derive(Clone, Debug)]
1069struct RequiredExtensionStrategyBuilder(
1070 fnv::HashMap<Vec<u8>, Vec<(usize, String)>>,
1071);
1072
1073impl RequiredExtensionStrategyBuilder {
1074 fn new() -> RequiredExtensionStrategyBuilder {
1075 RequiredExtensionStrategyBuilder(fnv::HashMap::default())
1076 }
1077
1078 fn add(&mut self, global_index: usize, ext: String, regex: String) {
1079 self.0
1080 .entry(ext.into_bytes())
1081 .or_insert(vec![])
1082 .push((global_index, regex));
1083 }
1084
1085 fn build(self) -> Result<RequiredExtensionStrategy, Error> {
1086 let mut exts = fnv::HashMap::default();
1087 for (ext, regexes) in self.0.into_iter() {
1088 exts.insert(ext.clone(), vec![]);
1089 for (global_index, regex) in regexes {
1090 let compiled = new_regex(®ex)?;
1091 exts.get_mut(&ext).unwrap().push((global_index, compiled));
1092 }
1093 }
1094 Ok(RequiredExtensionStrategy(exts))
1095 }
1096}
1097
1098pub fn escape(s: &str) -> String {
1116 let mut escaped = String::with_capacity(s.len());
1117 for c in s.chars() {
1118 match c {
1119 '?' | '*' | '[' | ']' | '{' | '}' => {
1122 escaped.push('[');
1123 escaped.push(c);
1124 escaped.push(']');
1125 }
1126 c => {
1127 escaped.push(c);
1128 }
1129 }
1130 }
1131 escaped
1132}
1133
1134#[cfg(test)]
1135mod tests {
1136 use crate::glob::{Glob, GlobBuilder};
1137
1138 use super::{GlobSet, GlobSetBuilder};
1139
1140 fn build_glob_set(globs: &[&str]) -> GlobSet {
1141 let mut builder = GlobSetBuilder::new();
1142 for glob in globs {
1143 builder.add(Glob::new(glob).unwrap());
1144 }
1145 builder.build().unwrap()
1146 }
1147
1148 #[test]
1149 fn set_works() {
1150 let set = build_glob_set(&["src/**/*.rs", "*.c", "src/lib.rs"]);
1151
1152 assert!(set.is_match("foo.c"));
1153 assert!(set.is_match("src/foo.c"));
1154 assert!(!set.is_match("foo.rs"));
1155 assert!(!set.is_match("tests/foo.rs"));
1156 assert!(set.is_match("src/foo.rs"));
1157 assert!(set.is_match("src/grep/src/main.rs"));
1158
1159 assert!(!set.matches_all("src/lib.rs"));
1160 assert!(!set.matches_all("src/a/b.c"));
1161
1162 let matches = set.matches("src/lib.rs");
1163 assert_eq!(2, matches.len());
1164 assert_eq!(0, matches[0]);
1165 assert_eq!(2, matches[1]);
1166 }
1167
1168 #[test]
1169 fn empty_set_works() {
1170 let set = GlobSetBuilder::new().build().unwrap();
1171 assert!(!set.is_match(""));
1172 assert!(!set.is_match("a"));
1173 assert!(set.matches_all("a"));
1174 }
1175
1176 #[test]
1177 fn matches_all_literals() {
1178 let set = build_glob_set(&["abc", "def"]);
1179
1180 assert!(!set.matches_all("abc"));
1181 assert!(!set.matches_all("def"));
1182
1183 let set = build_glob_set(&["abc"]);
1184
1185 assert!(set.matches_all("abc"));
1186 }
1187
1188 #[test]
1189 fn matches_all_basename_literals() {
1190 let set = build_glob_set(&["**/abc", "**/a.c"]);
1191
1192 assert!(!set.matches_all("foo/abc"));
1193 assert!(!set.matches_all("foo/a.c"));
1194
1195 let set = build_glob_set(&["**/a.c"]);
1196 assert!(set.matches_all("foo/a.c"));
1197 }
1198
1199 #[test]
1200 fn matches_all_extensions() {
1201 let set = build_glob_set(&["**/*.rs", "**/*.c"]);
1202
1203 assert!(!set.matches_all("foo/a.rs"));
1204 assert!(!set.matches_all("foo/a.c"));
1205
1206 let set = build_glob_set(&["**/*.rs", "*.rs"]);
1207 assert!(set.matches_all("foo/a.rs"));
1208 }
1209
1210 #[test]
1211 fn matches_all_required_extensions() {
1212 let set = build_glob_set(&["*.rs", "**/m*.rs", "a/*.rs"]);
1213
1214 assert!(set.matches_all("a/main.rs"));
1215
1216 assert!(!set.matches_all("main.rs"));
1217 assert!(!set.matches_all("a/lib.rs"));
1218 }
1219
1220 #[test]
1221 fn matches_all_prefix() {
1222 let set = build_glob_set(&["a*", "ab*", "ab/c*"]);
1223
1224 assert!(set.matches_all("ab/c/def"));
1225 assert!(set.matches_all("ab/cd"));
1226
1227 assert!(!set.matches_all("abc"));
1228 assert!(!set.matches_all("ab/x"));
1229 assert!(!set.matches_all("a"));
1230 }
1231
1232 #[test]
1233 fn matches_all_suffix() {
1234 let set = build_glob_set(&["*.rs", "*s", "*/main.rs"]);
1235
1236 assert!(set.matches_all("a/b/c/main.rs"));
1237
1238 assert!(!set.matches_all("foo.rs"));
1239 assert!(!set.matches_all("as"));
1240 }
1241
1242 #[test]
1243 fn matches_all_regex() {
1244 let set = GlobSet::new(&[
1245 GlobBuilder::new("c*").case_insensitive(true).build().unwrap(),
1246 Glob::new("*{rs,c}").unwrap(),
1247 ])
1248 .unwrap();
1249
1250 assert!(set.matches_all("c/main.rs"));
1251 assert!(set.matches_all("C/main.c"));
1252
1253 assert!(!set.matches_all("Ca"));
1254 assert!(!set.matches_all("foo.c"));
1255 }
1256
1257 #[test]
1258 fn default_set_is_empty_works() {
1259 let set: GlobSet = Default::default();
1260 assert!(!set.is_match(""));
1261 assert!(!set.is_match("a"));
1262 }
1263
1264 #[test]
1265 fn escape() {
1266 use super::escape;
1267 assert_eq!("foo", escape("foo"));
1268 assert_eq!("foo[*]", escape("foo*"));
1269 assert_eq!("[[][]]", escape("[]"));
1270 assert_eq!("[*][?]", escape("*?"));
1271 assert_eq!("src/[*][*]/[*].rs", escape("src/**/*.rs"));
1272 assert_eq!("bar[[]ab[]]baz", escape("bar[ab]baz"));
1273 assert_eq!("bar[[]!![]]!baz", escape("bar[!!]!baz"));
1274 }
1275
1276 #[test]
1280 fn set_does_not_remember() {
1281 let mut builder = GlobSetBuilder::new();
1282 builder.add(Glob::new("*foo*").unwrap());
1283 builder.add(Glob::new("*bar*").unwrap());
1284 builder.add(Glob::new("*quux*").unwrap());
1285 let set = builder.build().unwrap();
1286
1287 let matches = set.matches("ZfooZquuxZ");
1288 assert_eq!(2, matches.len());
1289 assert_eq!(0, matches[0]);
1290 assert_eq!(2, matches[1]);
1291
1292 let matches = set.matches("nada");
1293 assert_eq!(0, matches.len());
1294 }
1295
1296 #[test]
1297 fn debug() {
1298 let mut builder = GlobSetBuilder::new();
1299 builder.add(Glob::new("*foo*").unwrap());
1300 builder.add(Glob::new("*bar*").unwrap());
1301 builder.add(Glob::new("*quux*").unwrap());
1302 assert_eq!(
1303 format!("{builder:?}"),
1304 "GlobSetBuilder { pats: [Glob(\"*foo*\"), Glob(\"*bar*\"), Glob(\"*quux*\")] }",
1305 );
1306 }
1307}