1 //! A type that represents the union of a set of regular expressions.
2 #![deny(clippy::missing_docs_in_private_items)]
3
4 use regex::RegexSet as RxSet;
5 use std::cell::Cell;
6
7 /// A dynamic set of regular expressions.
8 #[derive(Clone, Debug, Default)]
9 pub struct RegexSet {
10 items: Vec<Box<str>>,
11 /// Whether any of the items in the set was ever matched. The length of this
12 /// vector is exactly the length of `items`.
13 matched: Vec<Cell<bool>>,
14 set: Option<RxSet>,
15 /// Whether we should record matching items in the `matched` vector or not.
16 record_matches: bool,
17 }
18
19 impl RegexSet {
20 /// Create a new RegexSet
new() -> RegexSet21 pub fn new() -> RegexSet {
22 RegexSet::default()
23 }
24
25 /// Is this set empty?
is_empty(&self) -> bool26 pub fn is_empty(&self) -> bool {
27 self.items.is_empty()
28 }
29
30 /// Insert a new regex into this set.
insert<S>(&mut self, string: S) where S: AsRef<str>,31 pub fn insert<S>(&mut self, string: S)
32 where
33 S: AsRef<str>,
34 {
35 self.items.push(string.as_ref().to_owned().into_boxed_str());
36 self.matched.push(Cell::new(false));
37 self.set = None;
38 }
39
40 /// Returns slice of String from its field 'items'
get_items(&self) -> &[Box<str>]41 pub fn get_items(&self) -> &[Box<str>] {
42 &self.items
43 }
44
45 /// Returns an iterator over regexes in the set which didn't match any
46 /// strings yet.
unmatched_items(&self) -> impl Iterator<Item = &str>47 pub fn unmatched_items(&self) -> impl Iterator<Item = &str> {
48 self.items.iter().enumerate().filter_map(move |(i, item)| {
49 if !self.record_matches || self.matched[i].get() {
50 return None;
51 }
52
53 Some(item.as_ref())
54 })
55 }
56
57 /// Construct a RegexSet from the set of entries we've accumulated.
58 ///
59 /// Must be called before calling `matches()`, or it will always return
60 /// false.
61 #[inline]
build(&mut self, record_matches: bool)62 pub fn build(&mut self, record_matches: bool) {
63 self.build_inner(record_matches, None)
64 }
65
66 /// Construct a RegexSet from the set of entries we've accumulated and emit diagnostics if the
67 /// name of the regex set is passed to it.
68 ///
69 /// Must be called before calling `matches()`, or it will always return
70 /// false.
71 #[inline]
build_with_diagnostics( &mut self, record_matches: bool, name: Option<&'static str>, )72 pub fn build_with_diagnostics(
73 &mut self,
74 record_matches: bool,
75 name: Option<&'static str>,
76 ) {
77 self.build_inner(record_matches, name)
78 }
79
80
build_inner( &mut self, record_matches: bool, _name: Option<&'static str>, )81 fn build_inner(
82 &mut self,
83 record_matches: bool,
84 _name: Option<&'static str>,
85 ) {
86 let items = self.items.iter().map(|item| format!("^({})$", item));
87 self.record_matches = record_matches;
88 self.set = match RxSet::new(items) {
89 Ok(x) => Some(x),
90 Err(e) => {
91 warn!("Invalid regex in {:?}: {:?}", self.items, e);
92 #[cfg(feature = "experimental")]
93 if let Some(name) = _name {
94 invalid_regex_warning(self, e, name);
95 }
96 None
97 }
98 }
99 }
100
101 /// Does the given `string` match any of the regexes in this set?
matches<S>(&self, string: S) -> bool where S: AsRef<str>,102 pub fn matches<S>(&self, string: S) -> bool
103 where
104 S: AsRef<str>,
105 {
106 let s = string.as_ref();
107 let set = match self.set {
108 Some(ref set) => set,
109 None => return false,
110 };
111
112 if !self.record_matches {
113 return set.is_match(s);
114 }
115
116 let matches = set.matches(s);
117 if !matches.matched_any() {
118 return false;
119 }
120 for i in matches.iter() {
121 self.matched[i].set(true);
122 }
123
124 true
125 }
126 }
127
128 #[cfg(feature = "experimental")]
invalid_regex_warning( set: &RegexSet, err: regex::Error, name: &'static str, )129 fn invalid_regex_warning(
130 set: &RegexSet,
131 err: regex::Error,
132 name: &'static str,
133 ) {
134
135
136
137
138
139
140
141 }
142