1use std::borrow::Borrow;
18use std::borrow::Cow;
19use std::collections::BTreeMap;
20use std::fmt;
21use std::fmt::Debug;
22use std::iter;
23use std::ops::Deref;
24
25use bstr::ByteSlice as _;
26use either::Either;
27use globset::Glob;
28use globset::GlobBuilder;
29use thiserror::Error;
30
31#[derive(Debug, Error)]
33pub enum StringPatternParseError {
34 #[error("Invalid string pattern kind `{0}:`")]
36 InvalidKind(String),
37 #[error(transparent)]
39 GlobPattern(globset::Error),
40 #[error(transparent)]
42 Regex(regex::Error),
43}
44
45#[derive(Clone)]
47pub struct GlobPattern {
48 glob: Glob,
49}
50
51impl GlobPattern {
52 pub fn as_str(&self) -> &str {
54 self.glob.glob()
55 }
56
57 pub fn to_regex(&self) -> regex::bytes::Regex {
59 regex::bytes::RegexBuilder::new(self.glob.regex())
62 .dot_matches_new_line(true)
63 .build()
64 .expect("glob regex should be valid")
65 }
66}
67
68impl Debug for GlobPattern {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 f.debug_tuple("GlobPattern").field(&self.as_str()).finish()
71 }
72}
73
74fn parse_glob(src: &str, icase: bool) -> Result<GlobPattern, StringPatternParseError> {
75 let glob = GlobBuilder::new(src)
76 .case_insensitive(icase)
77 .backslash_escape(true)
81 .build()
82 .map_err(StringPatternParseError::GlobPattern)?;
83 Ok(GlobPattern { glob })
84}
85
86fn is_glob_char(c: char) -> bool {
87 matches!(c, '?' | '*' | '[' | ']' | '{' | '}' | '\\')
90}
91
92#[derive(Clone, Debug)]
95pub enum StringPattern {
96 Exact(String),
98 ExactI(String),
100 Substring(String),
102 SubstringI(String),
104 Glob(Box<GlobPattern>),
106 GlobI(Box<GlobPattern>),
108 Regex(regex::bytes::Regex),
110 RegexI(regex::bytes::Regex),
112}
113
114impl StringPattern {
115 pub const fn all() -> Self {
117 Self::Substring(String::new())
118 }
119
120 pub fn exact(src: impl Into<String>) -> Self {
122 Self::Exact(src.into())
123 }
124
125 pub fn exact_i(src: impl Into<String>) -> Self {
127 Self::ExactI(src.into())
128 }
129
130 pub fn substring(src: impl Into<String>) -> Self {
132 Self::Substring(src.into())
133 }
134
135 pub fn substring_i(src: impl Into<String>) -> Self {
137 Self::SubstringI(src.into())
138 }
139
140 pub fn glob(src: &str) -> Result<Self, StringPatternParseError> {
142 if !src.contains(is_glob_char) {
143 return Ok(Self::exact(src));
144 }
145 Ok(Self::Glob(Box::new(parse_glob(src, false)?)))
146 }
147
148 pub fn glob_i(src: &str) -> Result<Self, StringPatternParseError> {
150 Ok(Self::GlobI(Box::new(parse_glob(src, true)?)))
154 }
155
156 pub fn regex(src: &str) -> Result<Self, StringPatternParseError> {
158 let pattern = regex::bytes::Regex::new(src).map_err(StringPatternParseError::Regex)?;
159 Ok(Self::Regex(pattern))
160 }
161
162 pub fn regex_i(src: &str) -> Result<Self, StringPatternParseError> {
164 let pattern = regex::bytes::RegexBuilder::new(src)
165 .case_insensitive(true)
166 .build()
167 .map_err(StringPatternParseError::Regex)?;
168 Ok(Self::RegexI(pattern))
169 }
170
171 pub fn from_str_kind(src: &str, kind: &str) -> Result<Self, StringPatternParseError> {
173 match kind {
174 "exact" => Ok(Self::exact(src)),
175 "exact-i" => Ok(Self::exact_i(src)),
176 "substring" => Ok(Self::substring(src)),
177 "substring-i" => Ok(Self::substring_i(src)),
178 "glob" => Self::glob(src),
179 "glob-i" => Self::glob_i(src),
180 "regex" => Self::regex(src),
181 "regex-i" => Self::regex_i(src),
182 _ => Err(StringPatternParseError::InvalidKind(kind.to_owned())),
183 }
184 }
185
186 pub fn is_all(&self) -> bool {
188 match self {
189 Self::Exact(_) | Self::ExactI(_) => false,
190 Self::Substring(needle) | Self::SubstringI(needle) => needle.is_empty(),
191 Self::Glob(pattern) | Self::GlobI(pattern) => pattern.as_str() == "*",
192 Self::Regex(pattern) | Self::RegexI(pattern) => pattern.as_str().is_empty(),
193 }
194 }
195
196 pub fn is_exact(&self) -> bool {
198 self.as_exact().is_some()
199 }
200
201 pub fn as_exact(&self) -> Option<&str> {
205 match self {
209 Self::Exact(literal) => Some(literal),
210 _ => None,
211 }
212 }
213
214 pub fn as_str(&self) -> &str {
216 match self {
217 Self::Exact(literal) => literal,
218 Self::ExactI(literal) => literal,
219 Self::Substring(needle) => needle,
220 Self::SubstringI(needle) => needle,
221 Self::Glob(pattern) => pattern.as_str(),
222 Self::GlobI(pattern) => pattern.as_str(),
223 Self::Regex(pattern) => pattern.as_str(),
224 Self::RegexI(pattern) => pattern.as_str(),
225 }
226 }
227
228 pub fn to_glob(&self) -> Option<Cow<'_, str>> {
231 match self {
235 Self::Exact(literal) => Some(globset::escape(literal).into()),
236 Self::Substring(needle) => {
237 if needle.is_empty() {
238 Some("*".into())
239 } else {
240 Some(format!("*{}*", globset::escape(needle)).into())
241 }
242 }
243 Self::Glob(pattern) => Some(pattern.as_str().into()),
244 Self::ExactI(_) => None,
245 Self::SubstringI(_) => None,
246 Self::GlobI(_) => None,
247 Self::Regex(_) => None,
248 Self::RegexI(_) => None,
249 }
250 }
251
252 fn to_match_fn(&self) -> Box<DynMatchFn> {
253 match self {
271 Self::Exact(literal) => {
272 let literal = literal.clone();
273 Box::new(move |haystack| haystack == literal.as_bytes())
274 }
275 Self::ExactI(literal) => {
276 let literal = literal.clone();
277 Box::new(move |haystack| haystack.eq_ignore_ascii_case(literal.as_bytes()))
278 }
279 Self::Substring(needle) => {
280 let needle = needle.clone();
281 Box::new(move |haystack| haystack.contains_str(&needle))
282 }
283 Self::SubstringI(needle) => {
284 let needle = needle.to_ascii_lowercase();
285 Box::new(move |haystack| haystack.to_ascii_lowercase().contains_str(&needle))
286 }
287 Self::Glob(pattern) | Self::GlobI(pattern) => {
291 let pattern = pattern.to_regex();
292 Box::new(move |haystack| pattern.is_match(haystack))
293 }
294 Self::Regex(pattern) | Self::RegexI(pattern) => {
295 let pattern = pattern.clone();
296 Box::new(move |haystack| pattern.is_match(haystack))
297 }
298 }
299 }
300
301 pub fn to_matcher(&self) -> StringMatcher {
303 if self.is_all() {
304 StringMatcher::All
305 } else if let Some(literal) = self.as_exact() {
306 StringMatcher::Exact(literal.to_owned())
307 } else {
308 StringMatcher::Fn(self.to_match_fn())
309 }
310 }
311
312 pub fn to_regex(&self) -> regex::bytes::Regex {
314 match self {
315 Self::Exact(literal) => {
316 regex::bytes::RegexBuilder::new(&format!("^{}$", regex::escape(literal)))
317 .build()
318 .expect("impossible to fail to compile regex of literal")
319 }
320 Self::ExactI(literal) => {
321 regex::bytes::RegexBuilder::new(&format!("^{}$", regex::escape(literal)))
322 .case_insensitive(true)
323 .build()
324 .expect("impossible to fail to compile regex of literal")
325 }
326 Self::Substring(literal) => regex::bytes::RegexBuilder::new(®ex::escape(literal))
327 .build()
328 .expect("impossible to fail to compile regex of literal"),
329 Self::SubstringI(literal) => regex::bytes::RegexBuilder::new(®ex::escape(literal))
330 .case_insensitive(true)
331 .build()
332 .expect("impossible to fail to compile regex of literal"),
333 Self::Glob(glob_pattern) => glob_pattern.to_regex(),
334 Self::GlobI(glob_pattern) => glob_pattern.to_regex(),
336 Self::Regex(regex) => regex.clone(),
337 Self::RegexI(regex) => regex.clone(),
338 }
339 }
340}
341
342#[derive(Clone, Debug)]
344pub enum StringExpression {
345 Pattern(Box<StringPattern>),
348 NotIn(Box<Self>),
350 Union(Box<Self>, Box<Self>),
352 Intersection(Box<Self>, Box<Self>),
354}
355
356impl StringExpression {
357 pub fn none() -> Self {
359 Self::all().negated()
360 }
361
362 pub fn all() -> Self {
364 Self::pattern(StringPattern::all())
365 }
366
367 pub fn pattern(pattern: StringPattern) -> Self {
369 Self::Pattern(Box::new(pattern))
370 }
371
372 pub fn exact(src: impl Into<String>) -> Self {
374 Self::pattern(StringPattern::exact(src))
375 }
376
377 pub fn substring(src: impl Into<String>) -> Self {
379 Self::pattern(StringPattern::substring(src))
380 }
381
382 pub fn negated(self) -> Self {
384 Self::NotIn(Box::new(self))
385 }
386
387 pub fn union(self, other: Self) -> Self {
389 Self::Union(Box::new(self), Box::new(other))
390 }
391
392 pub fn union_all(expressions: Vec<Self>) -> Self {
394 to_binary_expression(expressions, &Self::none, &Self::union)
395 }
396
397 pub fn intersection(self, other: Self) -> Self {
399 Self::Intersection(Box::new(self), Box::new(other))
400 }
401
402 fn dfs_pre(&self) -> impl Iterator<Item = &Self> {
403 let mut stack: Vec<&Self> = vec![self];
404 iter::from_fn(move || {
405 let expr = stack.pop()?;
406 match expr {
407 Self::Pattern(_) => {}
408 Self::NotIn(expr) => stack.push(expr),
409 Self::Union(expr1, expr2) | Self::Intersection(expr1, expr2) => {
410 stack.push(expr2);
411 stack.push(expr1);
412 }
413 }
414 Some(expr)
415 })
416 }
417
418 pub fn exact_strings(&self) -> impl Iterator<Item = &str> {
423 self.dfs_pre().filter_map(|expr| match expr {
426 Self::Pattern(pattern) => pattern.as_exact(),
427 _ => None,
428 })
429 }
430
431 pub fn to_matcher(&self) -> StringMatcher {
433 match self {
434 Self::Pattern(pattern) => pattern.to_matcher(),
435 Self::NotIn(expr) => {
436 let p = expr.to_matcher().into_match_fn();
437 StringMatcher::Fn(Box::new(move |haystack| !p(haystack)))
438 }
439 Self::Union(expr1, expr2) => {
440 let p1 = expr1.to_matcher().into_match_fn();
441 let p2 = expr2.to_matcher().into_match_fn();
442 StringMatcher::Fn(Box::new(move |haystack| p1(haystack) || p2(haystack)))
443 }
444 Self::Intersection(expr1, expr2) => {
445 let p1 = expr1.to_matcher().into_match_fn();
446 let p2 = expr2.to_matcher().into_match_fn();
447 StringMatcher::Fn(Box::new(move |haystack| p1(haystack) && p2(haystack)))
448 }
449 }
450 }
451}
452
453fn to_binary_expression<T>(
456 expressions: Vec<T>,
457 unit: &impl Fn() -> T,
458 binary: &impl Fn(T, T) -> T,
459) -> T {
460 match expressions.len() {
461 0 => unit(),
462 1 => expressions.into_iter().next().unwrap(),
463 _ => {
464 let mut left = expressions;
466 let right = left.split_off(left.len() / 2);
467 binary(
468 to_binary_expression(left, unit, binary),
469 to_binary_expression(right, unit, binary),
470 )
471 }
472 }
473}
474
475type DynMatchFn = dyn Fn(&[u8]) -> bool + Send + Sync;
476
477pub enum StringMatcher {
479 All,
481 Exact(String),
483 Fn(Box<DynMatchFn>),
485}
486
487impl StringMatcher {
488 pub const fn all() -> Self {
490 Self::All
491 }
492
493 pub fn exact(src: impl Into<String>) -> Self {
495 Self::Exact(src.into())
496 }
497
498 pub fn is_match(&self, haystack: &str) -> bool {
500 self.is_match_bytes(haystack.as_bytes())
501 }
502
503 pub fn is_match_bytes(&self, haystack: &[u8]) -> bool {
505 match self {
506 Self::All => true,
507 Self::Exact(needle) => haystack == needle.as_bytes(),
508 Self::Fn(predicate) => predicate(haystack),
509 }
510 }
511
512 pub fn match_lines<'a>(&self, text: &'a [u8]) -> impl Iterator<Item = &'a [u8]> {
514 text.split_inclusive(|b| *b == b'\n').filter(|line| {
517 let line = line.strip_suffix(b"\n").unwrap_or(line);
518 self.is_match_bytes(line)
519 })
520 }
521
522 fn into_match_fn(self) -> Box<DynMatchFn> {
523 match self {
524 Self::All => Box::new(|_haystack| true),
525 Self::Exact(needle) => Box::new(move |haystack| haystack == needle.as_bytes()),
526 Self::Fn(predicate) => predicate,
527 }
528 }
529
530 pub fn filter_btree_map<'a, K: Borrow<str> + Ord, V>(
532 &self,
533 map: &'a BTreeMap<K, V>,
534 ) -> impl Iterator<Item = (&'a K, &'a V)> {
535 self.filter_btree_map_with(map, |key| key, |key| key)
536 }
537
538 pub fn filter_btree_map_as_deref<'a, K, V>(
543 &self,
544 map: &'a BTreeMap<K, V>,
545 ) -> impl Iterator<Item = (&'a K, &'a V)>
546 where
547 K: Borrow<K::Target> + Deref + Ord,
548 K::Target: AsRef<str> + Ord,
549 str: AsRef<K::Target>,
550 {
551 self.filter_btree_map_with(map, AsRef::as_ref, AsRef::as_ref)
552 }
553
554 fn filter_btree_map_with<'a, K, Q, V>(
555 &self,
556 map: &'a BTreeMap<K, V>,
557 from_key: impl Fn(&Q) -> &str,
558 to_key: impl Fn(&str) -> &Q,
559 ) -> impl Iterator<Item = (&'a K, &'a V)>
560 where
561 K: Borrow<Q> + Ord,
562 Q: Ord + ?Sized,
563 {
564 match self {
565 Self::All => Either::Left(map.iter()),
566 Self::Exact(key) => {
567 Either::Right(Either::Left(map.get_key_value(to_key(key)).into_iter()))
568 }
569 Self::Fn(predicate) => {
570 Either::Right(Either::Right(map.iter().filter(move |&(key, _)| {
571 predicate(from_key(key.borrow()).as_bytes())
572 })))
573 }
574 }
575 }
576}
577
578impl Debug for StringMatcher {
579 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580 match self {
581 Self::All => write!(f, "All"),
582 Self::Exact(needle) => f.debug_tuple("Exact").field(needle).finish(),
583 Self::Fn(_) => f.debug_tuple("Fn").finish_non_exhaustive(),
584 }
585 }
586}
587
588#[cfg(test)]
589mod tests {
590 use std::assert_matches;
591
592 use itertools::Itertools as _;
593 use maplit::btreemap;
594
595 use super::*;
596 use crate::tests::TestResult;
597
598 fn insta_settings() -> insta::Settings {
599 let mut settings = insta::Settings::clone_current();
600 for _ in 0..4 {
603 settings.add_filter(
604 r"(?x)
605 \b([A-Z]\w*)\(\n
606 \s*(.{1,60}),\n
607 \s*\)",
608 "$1($2)",
609 );
610 }
611 settings
612 }
613
614 #[test]
615 fn test_string_pattern_to_glob() -> TestResult {
616 assert_eq!(StringPattern::all().to_glob(), Some("*".into()));
617 assert_eq!(StringPattern::exact("a").to_glob(), Some("a".into()));
618 assert_eq!(StringPattern::exact("*").to_glob(), Some("[*]".into()));
619 assert_eq!(StringPattern::glob("*")?.to_glob(), Some("*".into()));
620 assert_eq!(
621 StringPattern::Substring("a".into()).to_glob(),
622 Some("*a*".into())
623 );
624 assert_eq!(
625 StringPattern::Substring("*".into()).to_glob(),
626 Some("*[*]*".into())
627 );
628 Ok(())
629 }
630
631 #[test]
632 fn test_parse() {
633 assert_matches!(
635 StringPattern::from_str_kind("foo", "exact"),
636 Ok(StringPattern::Exact(s)) if s == "foo"
637 );
638 assert_matches!(
639 StringPattern::from_str_kind("foo*", "glob"),
640 Ok(StringPattern::Glob(p)) if p.as_str() == "foo*"
641 );
642 assert_matches!(
643 StringPattern::from_str_kind("foo", "substring"),
644 Ok(StringPattern::Substring(s)) if s == "foo"
645 );
646 assert_matches!(
647 StringPattern::from_str_kind("foo", "substring-i"),
648 Ok(StringPattern::SubstringI(s)) if s == "foo"
649 );
650 assert_matches!(
651 StringPattern::from_str_kind("foo", "regex"),
652 Ok(StringPattern::Regex(p)) if p.as_str() == "foo"
653 );
654 assert_matches!(
655 StringPattern::from_str_kind("foo", "regex-i"),
656 Ok(StringPattern::RegexI(p)) if p.as_str() == "foo"
657 );
658 }
659
660 #[test]
661 fn test_glob_is_match() {
662 let glob = |src: &str| StringPattern::glob(src).unwrap().to_matcher();
663 let glob_i = |src: &str| StringPattern::glob_i(src).unwrap().to_matcher();
664
665 assert!(glob("foo").is_match("foo"));
666 assert!(!glob("foo").is_match("foobar"));
667
668 assert!(glob("*").is_match(".foo"));
670
671 assert!(glob("*").is_match("foo/bar"));
673 assert!(glob(r"*/*").is_match("foo/bar"));
674 assert!(!glob(r"*/*").is_match(r"foo\bar"));
675
676 assert!(!glob(r"*\*").is_match("foo/bar"));
678 assert!(glob(r"*\*").is_match("foo*"));
679 assert!(glob(r"\\").is_match(r"\"));
680
681 assert!(glob(r"*").is_match("foo\nbar"));
683
684 assert!(!glob("f?O").is_match("Foo"));
685 assert!(glob_i("f?O").is_match("Foo"));
686 }
687
688 #[test]
689 fn test_regex_is_match() {
690 let regex = |src: &str| StringPattern::regex(src).unwrap().to_matcher();
691 assert!(regex(r"^\w$").is_match("\u{c0}"));
693 assert!(regex(r"^.$").is_match("\u{c0}"));
694 assert!(regex(r"^(?-u)\w$").is_match("a"));
696 assert!(!regex(r"^(?-u)\w$").is_match("\u{c0}"));
697 assert!(regex(r"^(?-u).{2}$").is_match("\u{c0}"));
698 }
699
700 #[test]
701 fn test_string_pattern_to_regex() {
702 let check = |pattern: StringPattern, match_to: &str| {
703 let regex = pattern.to_regex();
704 regex.is_match(match_to.as_bytes())
705 };
706 assert!(check(StringPattern::exact("$a"), "$a"));
707 assert!(!check(StringPattern::exact("$a"), "$A"));
708 assert!(!check(StringPattern::exact("a"), "aa"));
709 assert!(!check(StringPattern::exact("a"), "aa"));
710 assert!(check(StringPattern::exact_i("a"), "A"));
711 assert!(check(StringPattern::substring("$a"), "$abc"));
712 assert!(!check(StringPattern::substring("$a"), "$Abc"));
713 assert!(check(StringPattern::substring_i("$a"), "$Abc"));
714 assert!(!check(StringPattern::glob("a").unwrap(), "A"));
715 assert!(check(StringPattern::glob_i("a").unwrap(), "A"));
716 assert!(check(StringPattern::regex("^a{1,3}").unwrap(), "abcde"));
717 assert!(!check(StringPattern::regex("^a{1,3}").unwrap(), "Abcde"));
718 assert!(check(StringPattern::regex_i("^a{1,3}").unwrap(), "Abcde"));
719 }
720
721 #[test]
722 fn test_exact_pattern_to_matcher() {
723 assert_matches!(
724 StringPattern::exact("").to_matcher(),
725 StringMatcher::Exact(needle) if needle.is_empty()
726 );
727 assert_matches!(
728 StringPattern::exact("x").to_matcher(),
729 StringMatcher::Exact(needle) if needle == "x"
730 );
731
732 assert_matches!(
733 StringPattern::exact_i("").to_matcher(),
734 StringMatcher::Fn(_) );
736 assert_matches!(
737 StringPattern::exact_i("x").to_matcher(),
738 StringMatcher::Fn(_)
739 );
740 }
741
742 #[test]
743 fn test_substring_pattern_to_matcher() {
744 assert_matches!(
745 StringPattern::substring("").to_matcher(),
746 StringMatcher::All
747 );
748 assert_matches!(
749 StringPattern::substring("x").to_matcher(),
750 StringMatcher::Fn(_)
751 );
752
753 assert_matches!(
754 StringPattern::substring_i("").to_matcher(),
755 StringMatcher::All
756 );
757 assert_matches!(
758 StringPattern::substring_i("x").to_matcher(),
759 StringMatcher::Fn(_)
760 );
761 }
762
763 #[test]
764 fn test_glob_pattern_to_matcher() -> TestResult {
765 assert_matches!(
766 StringPattern::glob("")?.to_matcher(),
767 StringMatcher::Exact(_)
768 );
769 assert_matches!(
770 StringPattern::glob("x")?.to_matcher(),
771 StringMatcher::Exact(_)
772 );
773 assert_matches!(
774 StringPattern::glob("x?")?.to_matcher(),
775 StringMatcher::Fn(_)
776 );
777 assert_matches!(StringPattern::glob("*")?.to_matcher(), StringMatcher::All);
778 assert_matches!(
779 StringPattern::glob(r"\\")?.to_matcher(),
780 StringMatcher::Fn(_) );
782
783 assert_matches!(
784 StringPattern::glob_i("")?.to_matcher(),
785 StringMatcher::Fn(_) );
787 assert_matches!(
788 StringPattern::glob_i("x")?.to_matcher(),
789 StringMatcher::Fn(_)
790 );
791 assert_matches!(
792 StringPattern::glob_i("x?")?.to_matcher(),
793 StringMatcher::Fn(_)
794 );
795 assert_matches!(StringPattern::glob_i("*")?.to_matcher(), StringMatcher::All);
796 Ok(())
797 }
798
799 #[test]
800 fn test_regex_pattern_to_matcher() -> TestResult {
801 assert_matches!(StringPattern::regex("")?.to_matcher(), StringMatcher::All);
802 assert_matches!(
803 StringPattern::regex("x")?.to_matcher(),
804 StringMatcher::Fn(_)
805 );
806 assert_matches!(
807 StringPattern::regex(".")?.to_matcher(),
808 StringMatcher::Fn(_)
809 );
810
811 assert_matches!(StringPattern::regex_i("")?.to_matcher(), StringMatcher::All);
812 assert_matches!(
813 StringPattern::regex_i("x")?.to_matcher(),
814 StringMatcher::Fn(_)
815 );
816 assert_matches!(
817 StringPattern::regex_i(".")?.to_matcher(),
818 StringMatcher::Fn(_)
819 );
820 Ok(())
821 }
822
823 #[test]
824 fn test_union_all_expressions() {
825 let settings = insta_settings();
826 let _guard = settings.bind_to_scope();
827
828 insta::assert_debug_snapshot!(
829 StringExpression::union_all(vec![]),
830 @r#"NotIn(Pattern(Substring("")))"#);
831 insta::assert_debug_snapshot!(
832 StringExpression::union_all(vec![StringExpression::exact("a")]),
833 @r#"Pattern(Exact("a"))"#);
834 insta::assert_debug_snapshot!(
835 StringExpression::union_all(vec![
836 StringExpression::exact("a"),
837 StringExpression::exact("b"),
838 ]),
839 @r#"
840 Union(
841 Pattern(Exact("a")),
842 Pattern(Exact("b")),
843 )
844 "#);
845 insta::assert_debug_snapshot!(
846 StringExpression::union_all(vec![
847 StringExpression::exact("a"),
848 StringExpression::exact("b"),
849 StringExpression::exact("c"),
850 ]),
851 @r#"
852 Union(
853 Pattern(Exact("a")),
854 Union(
855 Pattern(Exact("b")),
856 Pattern(Exact("c")),
857 ),
858 )
859 "#);
860 insta::assert_debug_snapshot!(
861 StringExpression::union_all(vec![
862 StringExpression::exact("a"),
863 StringExpression::exact("b"),
864 StringExpression::exact("c"),
865 StringExpression::exact("d"),
866 ]),
867 @r#"
868 Union(
869 Union(
870 Pattern(Exact("a")),
871 Pattern(Exact("b")),
872 ),
873 Union(
874 Pattern(Exact("c")),
875 Pattern(Exact("d")),
876 ),
877 )
878 "#);
879 }
880
881 #[test]
882 fn test_exact_strings_in_expression() {
883 assert_eq!(
884 StringExpression::all().exact_strings().collect_vec(),
885 [""; 0]
886 );
887 assert_eq!(
888 StringExpression::union_all(vec![
889 StringExpression::exact("a"),
890 StringExpression::substring("b"),
891 StringExpression::intersection(
892 StringExpression::exact("c"),
893 StringExpression::exact("d").negated(),
894 ),
895 ])
896 .exact_strings()
897 .collect_vec(),
898 ["a", "c", "d"]
899 );
900 }
901
902 #[test]
903 fn test_trivial_expression_to_matcher() {
904 assert_matches!(StringExpression::all().to_matcher(), StringMatcher::All);
905 assert_matches!(
906 StringExpression::exact("x").to_matcher(),
907 StringMatcher::Exact(needle) if needle == "x"
908 );
909 }
910
911 #[test]
912 fn test_compound_expression_to_matcher() {
913 let matcher = StringExpression::exact("foo").negated().to_matcher();
914 assert!(!matcher.is_match("foo"));
915 assert!(matcher.is_match("bar"));
916
917 let matcher = StringExpression::union(
918 StringExpression::exact("foo"),
919 StringExpression::exact("bar"),
920 )
921 .to_matcher();
922 assert!(matcher.is_match("foo"));
923 assert!(matcher.is_match("bar"));
924 assert!(!matcher.is_match("baz"));
925
926 let matcher = StringExpression::intersection(
927 StringExpression::substring("a"),
928 StringExpression::substring("r"),
929 )
930 .to_matcher();
931 assert!(!matcher.is_match("foo"));
932 assert!(matcher.is_match("bar"));
933 assert!(!matcher.is_match("baz"));
934 }
935
936 #[test]
937 fn test_matcher_is_match() {
938 assert!(StringMatcher::all().is_match(""));
939 assert!(StringMatcher::all().is_match("foo"));
940 assert!(!StringMatcher::exact("o").is_match(""));
941 assert!(!StringMatcher::exact("o").is_match("foo"));
942 assert!(StringMatcher::exact("foo").is_match("foo"));
943 assert!(StringPattern::substring("o").to_matcher().is_match("foo"));
944 }
945
946 #[test]
947 fn test_matcher_match_lines() {
948 assert_eq!(
949 StringMatcher::all().match_lines(b"").collect_vec(),
950 Vec::<&[u8]>::new()
951 );
952 assert_eq!(
953 StringMatcher::all().match_lines(b"\n").collect_vec(),
954 vec![b"\n"]
955 );
956 assert_eq!(
957 StringMatcher::all().match_lines(b"foo").collect_vec(),
958 vec![b"foo"]
959 );
960 assert_eq!(
961 StringMatcher::all().match_lines(b"foo\n").collect_vec(),
962 vec![b"foo\n"]
963 );
964 assert_eq!(
965 StringMatcher::exact("foo")
966 .match_lines(b"foo\nbar\n")
967 .collect_vec(),
968 vec![b"foo\n"]
969 );
970 assert_eq!(
971 StringMatcher::exact("foo\n")
972 .match_lines(b"foo\nbar\n")
973 .collect_vec(),
974 Vec::<&[u8]>::new()
975 );
976 }
977
978 #[test]
979 fn test_matcher_filter_btree_map() {
980 let data = btreemap! {
981 "bar" => (),
982 "baz" => (),
983 "foo" => (),
984 };
985 let filter = |matcher: &StringMatcher| {
986 matcher
987 .filter_btree_map(&data)
988 .map(|(&key, ())| key)
989 .collect_vec()
990 };
991 assert_eq!(filter(&StringMatcher::all()), vec!["bar", "baz", "foo"]);
992 assert_eq!(filter(&StringMatcher::exact("o")), vec![""; 0]);
993 assert_eq!(filter(&StringMatcher::exact("foo")), vec!["foo"]);
994 assert_eq!(
995 filter(&StringPattern::substring("o").to_matcher()),
996 vec!["foo"]
997 );
998 assert_eq!(
999 filter(&StringPattern::substring("a").to_matcher()),
1000 vec!["bar", "baz"]
1001 );
1002 }
1003}