1use std::cmp::Ordering;
2use std::fmt;
3use std::str::FromStr;
4
5use super::Patterns;
6
7#[derive(Clone, Debug, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum IntersectionError {
11 TooManyPatterns,
13}
14
15impl fmt::Display for IntersectionError {
16 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17 match self {
18 Self::TooManyPatterns => write!(f, "pattern intersection exceeds the complexity limit"),
19 }
20 }
21}
22
23impl std::error::Error for IntersectionError {}
24
25#[derive(Clone, Debug, PartialEq, Eq)]
27#[non_exhaustive]
28pub enum InvalidPattern {
29 EmptySegment,
31 InvalidSegment(String),
35 MultipleGlobstars,
37 TooManySegments,
39}
40
41impl fmt::Display for InvalidPattern {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 match self {
44 Self::EmptySegment => write!(f, "empty path segment"),
45 Self::InvalidSegment(segment) => write!(f, "invalid pattern segment: {segment:?}"),
46 Self::MultipleGlobstars => write!(f, "more than one ** segment"),
47 Self::TooManySegments => write!(f, "more than {} segments", Pattern::MAX_SEGMENTS),
48 }
49 }
50}
51
52impl std::error::Error for InvalidPattern {}
53
54#[derive(Clone, Debug, PartialEq, Eq, Hash)]
56#[non_exhaustive]
57pub enum Segment {
58 Literal(String),
60 Wildcard,
62 Partial {
65 prefix: String,
67 suffix: String,
69 },
70 Globstar,
72}
73
74impl Segment {
75 fn parse(text: &str) -> Result<Self, InvalidPattern> {
77 match text {
78 "" => Err(InvalidPattern::EmptySegment),
79 "*" => Ok(Self::Wildcard),
80 "**" => Ok(Self::Globstar),
81 _ if text.contains('/') => Err(InvalidPattern::InvalidSegment(text.to_string())),
82 _ => match text.split_once('*') {
83 None => Ok(Self::Literal(text.to_string())),
84 Some((prefix, suffix)) if !suffix.contains('*') => Ok(Self::Partial {
85 prefix: prefix.to_string(),
86 suffix: suffix.to_string(),
87 }),
88 Some(_) => Err(InvalidPattern::InvalidSegment(text.to_string())),
90 },
91 }
92 }
93
94 fn covers(&self, other: &Self) -> bool {
98 match (self, other) {
99 (Self::Wildcard, Self::Literal(_) | Self::Partial { .. } | Self::Wildcard) => true,
100 (Self::Literal(a), Self::Literal(b)) => a == b,
101 (Self::Partial { .. }, Self::Literal(literal)) => self.matches(literal),
102 (
105 Self::Partial { prefix, suffix },
106 Self::Partial {
107 prefix: other_prefix,
108 suffix: other_suffix,
109 },
110 ) => other_prefix.starts_with(prefix.as_str()) && other_suffix.ends_with(suffix.as_str()),
111 _ => false,
112 }
113 }
114
115 fn compatible(&self, other: &Self) -> bool {
117 match (self, other) {
118 (
121 Self::Partial { prefix, suffix },
122 Self::Partial {
123 prefix: other_prefix,
124 suffix: other_suffix,
125 },
126 ) => {
127 (prefix.starts_with(other_prefix.as_str()) || other_prefix.starts_with(prefix.as_str()))
128 && (suffix.ends_with(other_suffix.as_str()) || other_suffix.ends_with(suffix.as_str()))
129 }
130 _ => self.covers(other) || other.covers(self),
131 }
132 }
133
134 fn matches(&self, part: &str) -> bool {
136 match self {
137 Self::Literal(literal) => literal == part,
138 Self::Wildcard => true,
139 Self::Partial { prefix, suffix } => {
140 part.len() >= prefix.len() + suffix.len()
141 && part.starts_with(prefix.as_str())
142 && part.ends_with(suffix.as_str())
143 }
144 Self::Globstar => false,
145 }
146 }
147
148 fn intersect(&self, other: &Self) -> Vec<Self> {
151 match (self, other) {
152 (Self::Globstar, _) | (_, Self::Globstar) => Vec::new(),
153 (Self::Wildcard, other) => vec![other.clone()],
154 (this, Self::Wildcard) => vec![this.clone()],
155 (Self::Literal(a), Self::Literal(b)) => (a == b).then(|| self.clone()).into_iter().collect(),
156 (Self::Literal(literal), partial @ Self::Partial { .. })
157 | (partial @ Self::Partial { .. }, Self::Literal(literal)) => partial
158 .matches(literal)
159 .then(|| Self::Literal(literal.clone()))
160 .into_iter()
161 .collect(),
162 (
163 Self::Partial { prefix, suffix },
164 Self::Partial {
165 prefix: other_prefix,
166 suffix: other_suffix,
167 },
168 ) => {
169 if !self.compatible(other) {
170 return Vec::new();
171 }
172 let prefix = if prefix.len() >= other_prefix.len() {
176 prefix
177 } else {
178 other_prefix
179 };
180 let suffix = if suffix.len() >= other_suffix.len() {
181 suffix
182 } else {
183 other_suffix
184 };
185 let mut out = vec![Self::Partial {
186 prefix: prefix.clone(),
187 suffix: suffix.clone(),
188 }];
189 for overlap in 1..=prefix.len().min(suffix.len()) {
190 if !prefix.is_char_boundary(prefix.len() - overlap) || !suffix.is_char_boundary(overlap) {
191 continue;
192 }
193 if prefix[prefix.len() - overlap..] != suffix[..overlap] {
194 continue;
195 }
196 let part = format!("{prefix}{}", &suffix[overlap..]);
197 if self.matches(&part) && other.matches(&part) {
198 out.push(Self::Literal(part));
199 }
200 }
201 out
202 }
203 }
204 }
205}
206
207fn intersect_run(a: &[Segment], b: &[Segment], limit: usize) -> Result<Vec<Vec<Segment>>, IntersectionError> {
211 debug_assert_eq!(a.len(), b.len());
212 let mut out: Vec<Vec<Segment>> = vec![Vec::with_capacity(a.len())];
213 for (a, b) in a.iter().zip(b) {
214 let choices = a.intersect(b);
215 if choices.is_empty() {
216 return Ok(Vec::new());
217 }
218 if out.len().checked_mul(choices.len()).is_none_or(|size| size > limit) {
219 return Err(IntersectionError::TooManyPatterns);
220 }
221 out = out
222 .iter()
223 .flat_map(|prefix| {
224 choices.iter().map(move |choice| {
225 let mut next = prefix.clone();
226 next.push(choice.clone());
227 next
228 })
229 })
230 .collect();
231 }
232 Ok(out)
233}
234
235fn insert_intersection(
236 out: &mut Patterns,
237 remaining: &mut usize,
238 segments: Vec<Segment>,
239) -> Result<(), IntersectionError> {
240 if *remaining == 0 {
241 return Err(IntersectionError::TooManyPatterns);
242 }
243 *remaining -= 1;
244 if let Ok(pattern) = Pattern::new(segments) {
245 out.insert(pattern);
246 }
247 Ok(())
248}
249
250fn intersect_into(
251 a: &[Segment],
252 b: &[Segment],
253 out: &mut Patterns,
254 remaining: &mut usize,
255) -> Result<(), IntersectionError> {
256 for run in intersect_run(a, b, *remaining)? {
257 insert_intersection(out, remaining, run)?;
258 }
259 Ok(())
260}
261
262fn expand(head: &[Segment], tail: &[Segment], len: usize) -> Vec<Segment> {
264 let mut out = head.to_vec();
265 out.extend(std::iter::repeat_n(Segment::Wildcard, len - head.len() - tail.len()));
266 out.extend_from_slice(tail);
267 out
268}
269
270impl fmt::Display for Segment {
271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272 match self {
273 Self::Literal(literal) => f.write_str(literal),
274 Self::Wildcard => f.write_str("*"),
275 Self::Partial { prefix, suffix } => write!(f, "{prefix}*{suffix}"),
276 Self::Globstar => f.write_str("**"),
277 }
278 }
279}
280
281#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
292pub struct Specificity {
293 literals: usize,
294 exact: bool,
295 partials: usize,
296 wildcards: usize,
297 pinned: usize,
298 head: usize,
299}
300
301#[derive(Clone, PartialEq, Eq, Hash)]
311pub struct Pattern {
312 text: String,
313 segments: Vec<Segment>,
314 globstar: Option<usize>,
316 head: usize,
318}
319
320impl Pattern {
321 pub const MAX_SEGMENTS: usize = 32;
323 pub const MAX_INTERSECTIONS: usize = 1024;
325
326 pub fn new(segments: impl IntoIterator<Item = Segment>) -> Result<Self, InvalidPattern> {
328 let mut segments: Vec<Segment> = segments.into_iter().collect();
329 if segments.len() > Self::MAX_SEGMENTS {
330 return Err(InvalidPattern::TooManySegments);
331 }
332
333 let mut globstar = None;
334 for (i, segment) in segments.iter().enumerate() {
335 match segment {
336 Segment::Literal(literal) if literal.is_empty() => return Err(InvalidPattern::EmptySegment),
337 Segment::Literal(literal) if literal.contains(['*', '/']) => {
338 return Err(InvalidPattern::InvalidSegment(literal.clone()));
339 }
340 Segment::Partial { prefix, suffix }
341 if (prefix.is_empty() && suffix.is_empty())
342 || prefix.contains(['*', '/'])
343 || suffix.contains(['*', '/']) =>
344 {
345 return Err(InvalidPattern::InvalidSegment(format!("{prefix}*{suffix}")));
346 }
347 Segment::Globstar if globstar.is_some() => return Err(InvalidPattern::MultipleGlobstars),
348 Segment::Globstar => globstar = Some(i),
349 _ => {}
350 }
351 }
352
353 if let Some(mut index) = globstar {
355 while index > 0 && segments[index - 1] == Segment::Wildcard {
356 segments.swap(index - 1, index);
357 index -= 1;
358 }
359 globstar = Some(index);
360 }
361
362 let mut text = String::new();
363 let mut head = 0;
364 let mut in_head = true;
365 for (i, segment) in segments.iter().enumerate() {
366 if i > 0 {
367 text.push('/');
368 }
369 match segment {
370 Segment::Literal(literal) => text.push_str(literal),
371 other => {
372 in_head = false;
373 text.push_str(&other.to_string());
374 }
375 }
376 if in_head {
377 head = text.len();
378 }
379 }
380
381 Ok(Self {
382 text,
383 segments,
384 globstar,
385 head,
386 })
387 }
388
389 pub fn all() -> Self {
391 Self::new([Segment::Globstar]).expect("** is valid")
392 }
393
394 pub fn literal(path: &str) -> Result<Self, InvalidPattern> {
400 Self::new(literal_segments(path))
401 }
402
403 pub fn subtree(path: &str) -> Result<Self, InvalidPattern> {
409 let mut segments: Vec<Segment> = literal_segments(path).collect();
410 if segments.len() < Self::MAX_SEGMENTS {
411 segments.push(Segment::Globstar);
412 }
413 Self::new(segments)
414 }
415
416 pub fn as_str(&self) -> &str {
418 &self.text
419 }
420
421 pub fn segments(&self) -> &[Segment] {
423 &self.segments
424 }
425
426 pub fn head(&self) -> &str {
431 &self.text[..self.head]
432 }
433
434 pub fn is_literal(&self) -> bool {
436 self.head == self.text.len()
437 }
438
439 pub fn as_prefix(&self) -> Option<&str> {
444 match self.segments.split_last() {
445 Some((Segment::Globstar, head)) if head.iter().all(|s| matches!(s, Segment::Literal(_))) => {
446 Some(self.head())
447 }
448 _ => None,
449 }
450 }
451
452 pub fn has_globstar(&self) -> bool {
454 self.globstar.is_some()
455 }
456
457 pub fn matches(&self, path: &str) -> bool {
461 let parts: Vec<&str> = split_path(path).collect();
462 match self.globstar {
463 None => {
464 parts.len() == self.segments.len()
465 && self
466 .segments
467 .iter()
468 .zip(&parts)
469 .all(|(segment, part)| segment.matches(part))
470 }
471 Some(_) => {
472 let (head, tail) = self.split();
473 parts.len() >= head.len() + tail.len()
474 && head.iter().zip(&parts).all(|(segment, part)| segment.matches(part))
475 && tail
476 .iter()
477 .rev()
478 .zip(parts.iter().rev())
479 .all(|(segment, part)| segment.matches(part))
480 }
481 }
482 }
483
484 pub fn contains(&self, other: &Self) -> bool {
489 match (self.globstar, other.globstar) {
490 (None, None) => {
491 self.segments.len() == other.segments.len()
492 && self.segments.iter().zip(&other.segments).all(|(a, b)| a.covers(b))
493 }
494 (None, Some(_)) => false,
496 (Some(_), None) => {
497 let (head, tail) = self.split();
498 other.segments.len() >= head.len() + tail.len()
499 && head.iter().zip(&other.segments).all(|(a, b)| a.covers(b))
500 && tail
501 .iter()
502 .rev()
503 .zip(other.segments.iter().rev())
504 .all(|(a, b)| a.covers(b))
505 }
506 (Some(_), Some(_)) => {
507 let (head, tail) = self.split();
508 let (other_head, other_tail) = other.split();
509
510 let covers_run = |ours: &[Segment], theirs: &[Segment]| {
514 ours.iter().enumerate().all(|(i, a)| match theirs.get(i) {
515 Some(b) => a.covers(b),
516 None => *a == Segment::Wildcard,
517 })
518 };
519 let reversed = |run: &[Segment]| run.iter().rev().cloned().collect::<Vec<_>>();
520
521 head.len() + tail.len() <= other_head.len() + other_tail.len()
522 && covers_run(head, other_head)
523 && covers_run(&reversed(tail), &reversed(other_tail))
524 }
525 }
526 }
527
528 pub fn overlaps(&self, other: &Self) -> bool {
530 let compatible_run = |a: &[Segment], b: &[Segment]| a.iter().zip(b).all(|(a, b)| a.compatible(b));
531 let compatible_tail =
532 |a: &[Segment], b: &[Segment]| a.iter().rev().zip(b.iter().rev()).all(|(a, b)| a.compatible(b));
533
534 match (self.globstar, other.globstar) {
535 (None, None) => {
536 self.segments.len() == other.segments.len() && compatible_run(&self.segments, &other.segments)
537 }
538 (None, Some(_)) => other.overlaps(self),
539 (Some(_), None) => {
540 let (head, tail) = self.split();
541 other.segments.len() >= head.len() + tail.len()
542 && compatible_run(head, &other.segments)
543 && compatible_tail(tail, &other.segments)
544 }
545 (Some(_), Some(_)) => {
546 let (head, tail) = self.split();
549 let (other_head, other_tail) = other.split();
550 compatible_run(head, other_head) && compatible_tail(tail, other_tail)
551 }
552 }
553 }
554
555 pub fn specificity(&self) -> Specificity {
557 let count = |wanted: fn(&Segment) -> bool| self.segments.iter().filter(|s| wanted(s)).count();
558 Specificity {
559 literals: count(|s| matches!(s, Segment::Literal(_))),
560 exact: self.globstar.is_none(),
561 partials: count(|s| matches!(s, Segment::Partial { .. })),
562 wildcards: count(|s| matches!(s, Segment::Wildcard)),
563 pinned: self
564 .segments
565 .iter()
566 .map(|s| match s {
567 Segment::Partial { prefix, suffix } => prefix.len() + suffix.len(),
568 _ => 0,
569 })
570 .sum(),
571 head: self
572 .segments
573 .iter()
574 .take_while(|s| matches!(s, Segment::Literal(_)))
575 .count(),
576 }
577 }
578
579 pub fn rebase(&self, root: &str) -> Patterns {
588 let root: Vec<&str> = split_path(root).collect();
589 let mut out = Patterns::new();
590
591 let matches_run = |segments: &[Segment], parts: &[&str]| segments.iter().zip(parts).all(|(s, p)| s.matches(p));
592 let build = |segments: &[Segment]| Pattern::new(segments.to_vec()).expect("a rebased pattern is valid");
595
596 match self.globstar {
597 None => {
598 if root.len() <= self.segments.len() && matches_run(&self.segments, &root) {
599 out.insert(build(&self.segments[root.len()..]));
600 }
601 }
602 Some(index) => {
603 let (head, tail) = self.split();
604 if root.len() <= head.len() {
605 if matches_run(head, &root) {
606 out.insert(build(&self.segments[root.len()..]));
607 }
608 return out;
609 }
610 if !matches_run(head, &root) {
611 return out;
612 }
613
614 let rest = &root[head.len()..];
618 out.insert(build(&self.segments[index..]));
619 for consumed in 1..=tail.len().min(rest.len()) {
620 if matches_run(&tail[..consumed], &rest[rest.len() - consumed..]) {
621 out.insert(build(&tail[consumed..]));
622 }
623 }
624 }
625 }
626
627 out
628 }
629
630 pub fn intersect(&self, other: &Self) -> Result<Patterns, IntersectionError> {
638 if self.contains(other) {
641 return Ok(Patterns::from(other.clone()));
642 }
643 if other.contains(self) {
644 return Ok(Patterns::from(self.clone()));
645 }
646
647 let mut out = Patterns::new();
648 let mut remaining = Self::MAX_INTERSECTIONS;
649
650 match (self.globstar, other.globstar) {
651 (None, None) => {
652 if self.segments.len() == other.segments.len() {
653 intersect_into(&self.segments, &other.segments, &mut out, &mut remaining)?;
654 }
655 }
656 (Some(_), None) => {
657 let (head, tail) = self.split();
658 if other.segments.len() >= head.len() + tail.len() {
659 let stretched = expand(head, tail, other.segments.len());
660 intersect_into(&stretched, &other.segments, &mut out, &mut remaining)?;
661 }
662 }
663 (None, Some(_)) => return other.intersect(self),
664 (Some(_), Some(_)) => {
665 let (head, tail) = self.split();
666 let (other_head, other_tail) = other.split();
667 let heads = head.len().max(other_head.len());
668 let tails = tail.len().max(other_tail.len());
669 let shortest = (head.len() + tail.len()).max(other_head.len() + other_tail.len());
670
671 let long = heads + tails;
675 let open = long < Self::MAX_SEGMENTS;
676 let cap = if open { long } else { Self::MAX_SEGMENTS + 1 };
677 for len in shortest..cap {
678 let a = expand(head, tail, len);
679 let b = expand(other_head, other_tail, len);
680 intersect_into(&a, &b, &mut out, &mut remaining)?;
681 }
682
683 if open {
686 let pad = |run: &[Segment], len: usize, front: bool| -> Vec<Segment> {
687 let fill = std::iter::repeat_n(Segment::Wildcard, len - run.len());
688 if front {
689 run.iter().cloned().chain(fill).collect()
690 } else {
691 fill.chain(run.iter().cloned()).collect()
692 }
693 };
694 let fronts = intersect_run(&pad(head, heads, true), &pad(other_head, heads, true), remaining)?;
695 let backs = intersect_run(&pad(tail, tails, false), &pad(other_tail, tails, false), remaining)?;
696 if fronts
697 .len()
698 .checked_mul(backs.len())
699 .is_none_or(|size| size > remaining)
700 {
701 return Err(IntersectionError::TooManyPatterns);
702 }
703 for front in &fronts {
704 for back in &backs {
705 let mut segments = front.clone();
706 segments.push(Segment::Globstar);
707 segments.extend_from_slice(back);
708 insert_intersection(&mut out, &mut remaining, segments)?;
709 }
710 }
711 }
712 }
713 }
714
715 Ok(out)
716 }
717
718 pub fn captures(&self, matched: &Self) -> Option<Vec<Self>> {
730 if !self.contains(matched) {
731 return None;
732 }
733 let build = |segments: &[Segment]| Pattern::new(segments.to_vec()).expect("a capture is valid");
736 let mut out = Vec::new();
737
738 if self.globstar.is_none() {
739 for (segment, theirs) in self.segments.iter().zip(&matched.segments) {
740 if !matches!(segment, Segment::Literal(_)) {
741 out.push(build(std::slice::from_ref(theirs)));
742 }
743 }
744 return Some(out);
745 }
746
747 let (head, tail) = self.split();
748 let middle = matched.segments.len() - tail.len();
749 let free = matched.globstar;
753 let pinned = |at: usize, from_front: bool| match free {
754 Some(free) if from_front => at < free,
755 Some(free) => at > free,
756 None => true,
757 };
758
759 for (i, segment) in head.iter().enumerate() {
760 if !matches!(segment, Segment::Literal(_)) {
761 let capture = if pinned(i, true) { &matched.segments[i] } else { segment };
762 out.push(build(std::slice::from_ref(capture)));
763 }
764 }
765 if free.is_none_or(|free| free >= head.len() && free < middle) {
766 out.push(build(&matched.segments[head.len()..middle]));
767 } else {
768 out.push(Pattern::all());
769 }
770 for (j, segment) in tail.iter().enumerate() {
771 if !matches!(segment, Segment::Literal(_)) {
772 let at = middle + j;
773 let capture = if pinned(at, false) {
774 &matched.segments[at]
775 } else {
776 segment
777 };
778 out.push(build(std::slice::from_ref(capture)));
779 }
780 }
781 Some(out)
782 }
783
784 pub fn rooted(&self, root: &str) -> Result<Self, InvalidPattern> {
790 Self::new(literal_segments(root).chain(self.segments.iter().cloned()))
791 }
792
793 fn split(&self) -> (&[Segment], &[Segment]) {
795 match self.globstar {
796 Some(index) => (&self.segments[..index], &self.segments[index + 1..]),
797 None => (&self.segments, &[]),
798 }
799 }
800}
801
802fn split_path(path: &str) -> impl Iterator<Item = &str> {
805 path.split('/').filter(|part| !part.is_empty())
806}
807
808fn literal_segments(path: &str) -> impl Iterator<Item = Segment> + '_ {
810 split_path(path).map(|part| Segment::Literal(part.to_string()))
813}
814
815impl FromStr for Pattern {
816 type Err = InvalidPattern;
817
818 fn from_str(text: &str) -> Result<Self, InvalidPattern> {
821 if text.is_empty() {
822 return Self::new([]);
823 }
824 text.split('/')
825 .map(Segment::parse)
826 .collect::<Result<Vec<_>, _>>()
827 .and_then(Self::new)
828 }
829}
830
831impl TryFrom<&str> for Pattern {
832 type Error = InvalidPattern;
833
834 fn try_from(text: &str) -> Result<Self, InvalidPattern> {
835 text.parse()
836 }
837}
838
839impl TryFrom<String> for Pattern {
840 type Error = InvalidPattern;
841
842 fn try_from(text: String) -> Result<Self, InvalidPattern> {
843 text.parse()
844 }
845}
846
847impl Default for Pattern {
848 fn default() -> Self {
850 Self::new([]).expect("the empty pattern is valid")
851 }
852}
853
854impl fmt::Display for Pattern {
855 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
856 f.write_str(&self.text)
857 }
858}
859
860impl fmt::Debug for Pattern {
861 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
862 write!(f, "Pattern({:?})", self.text)
863 }
864}
865
866impl PartialOrd for Pattern {
867 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
868 Some(self.cmp(other))
869 }
870}
871
872impl Ord for Pattern {
873 fn cmp(&self, other: &Self) -> Ordering {
875 self.text.cmp(&other.text)
876 }
877}
878
879#[cfg(feature = "serde")]
880impl serde::Serialize for Pattern {
881 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
882 serializer.serialize_str(self.as_str())
883 }
884}
885
886#[cfg(feature = "serde")]
887impl<'de> serde::Deserialize<'de> for Pattern {
888 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
890 let text = <std::borrow::Cow<'de, str>>::deserialize(deserializer)?;
891 text.parse().map_err(serde::de::Error::custom)
892 }
893}
894
895impl AsRef<str> for Pattern {
896 fn as_ref(&self) -> &str {
897 &self.text
898 }
899}
900
901#[cfg(test)]
902mod tests {
903 use super::*;
904
905 fn pattern(text: &str) -> Pattern {
906 text.parse().unwrap_or_else(|err| panic!("{text:?}: {err}"))
907 }
908
909 #[test]
910 fn parses_and_prints_canonically() {
911 for text in [
912 "",
913 "a",
914 "a/b",
915 "*",
916 "**",
917 "a/*/b",
918 "**/transcode.pro",
919 "a/**/b/*",
920 "**/*",
921 "**/*.hang",
922 "foo*",
923 "foo.*.hang",
924 ] {
925 assert_eq!(pattern(text).to_string(), text);
926 }
927 assert_eq!(
928 pattern("a/*/**/b").segments(),
929 &[
930 Segment::Literal("a".into()),
931 Segment::Globstar,
932 Segment::Wildcard,
933 Segment::Literal("b".into()),
934 ]
935 );
936 }
937
938 #[test]
939 fn rejects_bad_syntax() {
940 assert_eq!("/a".parse::<Pattern>(), Err(InvalidPattern::EmptySegment));
941 assert_eq!("a/".parse::<Pattern>(), Err(InvalidPattern::EmptySegment));
942 assert_eq!("a//b".parse::<Pattern>(), Err(InvalidPattern::EmptySegment));
943 assert_eq!("/".parse::<Pattern>(), Err(InvalidPattern::EmptySegment));
944 assert_eq!("**/**".parse::<Pattern>(), Err(InvalidPattern::MultipleGlobstars));
945 assert_eq!(
946 "***".parse::<Pattern>(),
947 Err(InvalidPattern::InvalidSegment("***".into()))
948 );
949
950 assert_eq!(
951 "a*b*c".parse::<Pattern>(),
952 Err(InvalidPattern::InvalidSegment("a*b*c".into()))
953 );
954 assert_eq!(
955 "*a*".parse::<Pattern>(),
956 Err(InvalidPattern::InvalidSegment("*a*".into()))
957 );
958 assert_eq!(
959 "*.hang".parse::<Pattern>().unwrap().segments(),
960 &[Segment::Partial {
961 prefix: String::new(),
962 suffix: ".hang".into()
963 }]
964 );
965 assert_eq!(
966 Pattern::new([Segment::Partial {
967 prefix: String::new(),
968 suffix: String::new()
969 }]),
970 Err(InvalidPattern::InvalidSegment("*".into()))
971 );
972 assert_eq!(
973 Pattern::new([Segment::Partial {
974 prefix: "a/".into(),
975 suffix: String::new()
976 }]),
977 Err(InvalidPattern::InvalidSegment("a/*".into()))
978 );
979
980 let deep = ["a"; Pattern::MAX_SEGMENTS + 1].join("/");
981 assert_eq!(deep.parse::<Pattern>(), Err(InvalidPattern::TooManySegments));
982 let max = ["a"; Pattern::MAX_SEGMENTS].join("/");
983 assert!(max.parse::<Pattern>().is_ok());
984
985 assert_eq!(
986 Pattern::new([Segment::Literal("a/b".into())]),
987 Err(InvalidPattern::InvalidSegment("a/b".into()))
988 );
989 assert_eq!(
990 Pattern::new([Segment::Literal(String::new())]),
991 Err(InvalidPattern::EmptySegment)
992 );
993 }
994
995 #[test]
996 fn literal_and_subtree_normalize_paths() {
997 assert_eq!(Pattern::literal("/foo//bar/").unwrap(), pattern("foo/bar"));
998 assert_eq!(Pattern::literal("").unwrap(), Pattern::default());
999 assert_eq!(Pattern::subtree("foo").unwrap(), pattern("foo/**"));
1000 assert_eq!(Pattern::subtree("/").unwrap(), Pattern::all());
1001 let max = ["a"; Pattern::MAX_SEGMENTS].join("/");
1002 assert_eq!(Pattern::subtree(&max).unwrap(), pattern(&max));
1003 let deep = ["a"; Pattern::MAX_SEGMENTS + 1].join("/");
1004 assert_eq!(Pattern::subtree(&deep), Err(InvalidPattern::TooManySegments));
1005 assert_eq!(Pattern::literal("a/*"), Err(InvalidPattern::InvalidSegment("*".into())));
1006 assert_eq!(Pattern::literal("**"), Err(InvalidPattern::InvalidSegment("**".into())));
1007 }
1008
1009 #[test]
1010 fn as_prefix_accepts_literals_then_globstar() {
1011 assert_eq!(Pattern::all().as_prefix(), Some(""));
1012 assert_eq!(pattern("foo/**").as_prefix(), Some("foo"));
1013 assert_eq!(pattern("foo/bar/**").as_prefix(), Some("foo/bar"));
1014 assert_eq!(pattern("foo").as_prefix(), None);
1015 assert_eq!(Pattern::default().as_prefix(), None);
1016 assert_eq!(pattern("foo/*").as_prefix(), None);
1017 assert_eq!(pattern("*/foo/**").as_prefix(), None);
1018 assert_eq!(pattern("foo/**/bar").as_prefix(), None);
1019 assert_eq!(pattern("foo*/**").as_prefix(), None);
1020 }
1021
1022 #[test]
1023 fn matches_whole_segments() {
1024 let cases = [
1025 ("", "", true),
1026 ("", "a", false),
1027 ("a", "a", true),
1028 ("a", "a/b", false),
1029 ("a", "ab", false),
1030 ("*", "a", true),
1031 ("*", "", false),
1032 ("*", "a/b", false),
1033 ("**", "", true),
1034 ("**", "a/b/c", true),
1035 ("a/**", "a", true),
1036 ("a/**", "a/b/c", true),
1037 ("a/**", "b", false),
1038 ("**/c", "c", true),
1039 ("**/c", "a/b/c", true),
1040 ("**/c", "a/c/b", false),
1041 ("a/**/c", "a/c", true),
1042 ("a/**/c", "a/x/y/c", true),
1043 ("a/**/c", "a", false),
1044 ("a/*/c", "a/x/c", true),
1045 ("a/*/c", "a/c", false),
1046 ("a/*/**", "a", false),
1047 ("a/*/**", "a/b", true),
1048 ("**/transcode.pro", "pid/foo.hang/transcode.pro", true),
1049 ("**/transcode.pro", "pid/foo.transcode.pro", false),
1050 ("**/*.hang", "pid/cam.hang", true),
1051 ("**/*.hang", ".hang", true),
1052 ("**/*.hang", "pid/cam.hang/x", false),
1053 ("foo*", "foo", true),
1054 ("foo*", "foobar", true),
1055 ("foo*", "fo", false),
1056 ("foo.*.hang", "foo..hang", true),
1057 ("foo.*.hang", "foo.1.hang", true),
1058 ("foo.*.hang", "foo.hang", false),
1059 ("a*a", "a", false),
1060 ("a*a", "aa", true),
1061 ];
1062 for (text, path, expected) in cases {
1063 assert_eq!(pattern(text).matches(path), expected, "{text} vs {path}");
1064 }
1065 assert!(pattern("a/b").matches("/a//b/"));
1067 }
1068
1069 #[test]
1070 fn contains_is_containment() {
1071 let cases = [
1072 ("**", "**", true),
1073 ("**", "", true),
1074 ("**", "a/*/b", true),
1075 ("", "**", false),
1076 ("*", "a", true),
1077 ("a", "*", false),
1078 ("a/**", "a", true),
1079 ("a/**", "a/b/**", true),
1080 ("a/**", "**", false),
1081 ("a/**", "**/a", false),
1082 ("**/a", "a", true),
1083 ("**/a", "**/b/a", true),
1084 ("**/a", "a/**", false),
1085 ("*/**", "**", false),
1086 ("*/**", "a/**", true),
1087 ("*/*/**", "a/**", false),
1088 ("*/*/**", "a/b/**", true),
1089 ("a/**/c", "a/c", true),
1090 ("a/**/c", "a/x/c", true),
1091 ("a/**/c", "a/**/x/c", true),
1092 ("a/*/**/*", "a/**/b", false),
1093 ("a/*/c", "a/b/c", true),
1094 ("a/*/c", "a/**/c", false),
1095 ("*", "*.hang", true),
1096 ("*.hang", "*", false),
1097 ("*.hang", "cam.hang", true),
1098 ("*.hang", "cam.hang2", false),
1099 ("*.hang", "*.hang", true),
1100 ("*.hang", "cam*.hang", true),
1101 ("*.hang", "cam*hang", false),
1102 ("foo*", "foo.*.hang", true),
1103 ("foo.*", "foo*", false),
1104 ("**/*.hang", "pid/*/cam.hang", true),
1105 ];
1106 for (outer, inner, expected) in cases {
1107 assert_eq!(
1108 pattern(outer).contains(&pattern(inner)),
1109 expected,
1110 "{outer} contains {inner}"
1111 );
1112 }
1113 }
1114
1115 #[test]
1116 fn overlaps_is_symmetric_intersection() {
1117 let cases = [
1118 ("a", "a", true),
1119 ("a", "b", false),
1120 ("a", "*", true),
1121 ("a", "a/*", false),
1122 ("a/**", "**/b", true),
1123 ("a/**", "b/**", false),
1124 ("a/*", "*/b", true),
1125 ("a/*", "b/*", false),
1126 ("*/*", "a/**", true),
1127 ("*", "a/**", true),
1128 ("*", "a/*/**", false),
1129 ("**", "", true),
1130 ("a/**/b", "**/c", false),
1131 ("a/**/b", "**/*", true),
1132 ("*.hang", "cam*", true),
1133 ("*.hang", "cam.msf", false),
1134 ("*.hang", "*.msf", false),
1135 ("foo*", "foo.bar*", true),
1136 ("foo*", "fob*", false),
1137 ("a*b", "ab", true),
1138 ("ab*", "*ab", true),
1139 ("a/**/b", "x/**", false),
1140 ("a/**/b", "**/x", false),
1141 ];
1142 for (a, b, expected) in cases {
1143 assert_eq!(pattern(a).overlaps(&pattern(b)), expected, "{a} overlaps {b}");
1144 assert_eq!(pattern(b).overlaps(&pattern(a)), expected, "{b} overlaps {a}");
1145 }
1146 }
1147
1148 #[test]
1149 fn specificity_ranks_by_what_is_pinned_down() {
1150 let ranked = ["a/b/c", "a/b", "a/*.hang", "a/*", "a/**", "*.hang", "*", "**"];
1152 for pair in ranked.windows(2) {
1153 assert!(
1154 pattern(pair[0]).specificity() > pattern(pair[1]).specificity(),
1155 "{} should outrank {}",
1156 pair[0],
1157 pair[1]
1158 );
1159 }
1160 assert!(pattern("a/**").specificity() > pattern("**/a").specificity());
1162 assert!(pattern("cam*.hang").specificity() > pattern("*.hang").specificity());
1164 assert!(pattern("*.hang").specificity() > pattern("*").specificity());
1165 assert_eq!(pattern("*/a").specificity(), pattern("*/b").specificity());
1167 assert_eq!(pattern("*/a/**").specificity(), pattern("*/**/a").specificity());
1168 }
1169
1170 #[test]
1171 fn rebase_is_set_valued() {
1172 let cases: &[(&str, &str, &[&str])] = &[
1173 ("**", "a", &["**"]),
1174 ("**/a", "a", &["", "**/a"]),
1175 ("a/**", "a", &["**"]),
1176 ("a/**", "a/b", &["**"]),
1177 ("a/**", "b", &[]),
1178 ("a/b", "a", &["b"]),
1179 ("a/b", "a/b", &[""]),
1180 ("a/b", "a/b/c", &[]),
1181 ("*/b", "a", &["b"]),
1182 ("a/*/c", "a/x", &["c"]),
1183 ("a/**/b/c", "a/b", &["**/b/c", "c"]),
1184 ("a/**/b", "a/b/b", &["**/b", ""]),
1185 ("**/b/c", "b", &["**/b/c", "c"]),
1186 ("", "", &[""]),
1187 ("", "a", &[]),
1188 ("**", "", &["**"]),
1189 ("*.hang/**", "cam.hang", &["**"]),
1190 ("*.hang/**", "cam.msf", &[]),
1191 ("**/*.hang", "a.hang", &["", "**/*.hang"]),
1192 ];
1193 for (text, root, expected) in cases {
1194 let got = pattern(text).rebase(root);
1195 let expected: Patterns = expected.iter().map(|e| pattern(e)).collect();
1196 assert_eq!(got, expected, "{text} rebased at {root}");
1197 }
1198 }
1199
1200 #[test]
1201 fn rooted_inverts_rebase() {
1202 assert_eq!(pattern("**").rooted("a/b").unwrap(), pattern("a/b/**"));
1203 assert_eq!(pattern("").rooted("a").unwrap(), pattern("a"));
1204 assert_eq!(pattern("*/c").rooted("").unwrap(), pattern("*/c"));
1205 assert_eq!(
1206 pattern("a").rooted("*"),
1207 Err(InvalidPattern::InvalidSegment("*".into()))
1208 );
1209
1210 let deep = ["a"; Pattern::MAX_SEGMENTS].join("/");
1211 assert_eq!(pattern("b").rooted(&deep), Err(InvalidPattern::TooManySegments));
1212 }
1213
1214 #[test]
1215 fn head_is_the_literal_prefix() {
1216 assert_eq!(pattern("a/b/*/c").head(), "a/b");
1217 assert_eq!(pattern("**/a").head(), "");
1218 assert_eq!(pattern("a/b").head(), "a/b");
1219 assert_eq!(pattern("").head(), "");
1220 assert_eq!(pattern("a/b*/c").head(), "a");
1221 assert!(!pattern("a/b*").is_literal());
1222 assert!(pattern("a/b").is_literal());
1223 assert!(!pattern("a/*").is_literal());
1224 assert!(pattern("a/**").has_globstar());
1225 assert!(!pattern("a/*").has_globstar());
1226 }
1227
1228 #[cfg(feature = "serde")]
1229 #[test]
1230 fn serde_round_trips_as_text() {
1231 let p = pattern("a/*/**");
1232 let json = serde_json::to_string(&p).unwrap();
1233 assert_eq!(json, "\"a/**/*\"");
1234 assert_eq!(serde_json::from_str::<Pattern>(&json).unwrap(), p);
1235 assert!(serde_json::from_str::<Pattern>("\"a//b\"").is_err());
1236 }
1237
1238 #[test]
1239 fn ordering_is_by_text() {
1240 let mut list = [pattern("b"), pattern("**"), pattern("a/*"), pattern("a")];
1241 list.sort();
1242 let texts: Vec<_> = list.iter().map(ToString::to_string).collect();
1243 assert_eq!(texts, ["**", "a", "a/*", "b"]);
1244 }
1245}