1 | //! A type that represents the union of a set of regular expressions. |
2 | |
3 | use regex::RegexSet as RxSet; |
4 | use std::cell::Cell; |
5 | |
6 | /// A dynamic set of regular expressions. |
7 | #[derive (Clone, Debug, Default)] |
8 | pub struct RegexSet { |
9 | items: Vec<String>, |
10 | /// Whether any of the items in the set was ever matched. The length of this |
11 | /// vector is exactly the length of `items`. |
12 | matched: Vec<Cell<bool>>, |
13 | set: Option<RxSet>, |
14 | /// Whether we should record matching items in the `matched` vector or not. |
15 | record_matches: bool, |
16 | } |
17 | |
18 | impl RegexSet { |
19 | /// Create a new RegexSet |
20 | pub fn new() -> RegexSet { |
21 | RegexSet { |
22 | ..Default::default() |
23 | } |
24 | } |
25 | |
26 | /// Is this set empty? |
27 | pub fn is_empty(&self) -> bool { |
28 | self.items.is_empty() |
29 | } |
30 | |
31 | /// Insert a new regex into this set. |
32 | pub fn insert<S>(&mut self, string: S) |
33 | where |
34 | S: AsRef<str>, |
35 | { |
36 | let string = string.as_ref().to_owned(); |
37 | if string == "*" { |
38 | warn!("using wildcard patterns (`*`) is no longer considered valid. Use `.*` instead" ); |
39 | } |
40 | self.items.push(string); |
41 | self.matched.push(Cell::new(false)); |
42 | self.set = None; |
43 | } |
44 | |
45 | /// Returns slice of String from its field 'items' |
46 | pub fn get_items(&self) -> &[String] { |
47 | &self.items[..] |
48 | } |
49 | |
50 | /// Returns an iterator over regexes in the set which didn't match any |
51 | /// strings yet. |
52 | pub fn unmatched_items(&self) -> impl Iterator<Item = &String> { |
53 | self.items.iter().enumerate().filter_map(move |(i, item)| { |
54 | if !self.record_matches || self.matched[i].get() { |
55 | return None; |
56 | } |
57 | |
58 | Some(item) |
59 | }) |
60 | } |
61 | |
62 | /// Construct a RegexSet from the set of entries we've accumulated. |
63 | /// |
64 | /// Must be called before calling `matches()`, or it will always return |
65 | /// false. |
66 | pub fn build(&mut self, record_matches: bool) { |
67 | let items = self.items.iter().map(|item| format!("^( {})$" , item)); |
68 | self.record_matches = record_matches; |
69 | self.set = match RxSet::new(items) { |
70 | Ok(x) => Some(x), |
71 | Err(e) => { |
72 | warn!("Invalid regex in {:?}: {:?}" , self.items, e); |
73 | None |
74 | } |
75 | } |
76 | } |
77 | |
78 | /// Does the given `string` match any of the regexes in this set? |
79 | pub fn matches<S>(&self, string: S) -> bool |
80 | where |
81 | S: AsRef<str>, |
82 | { |
83 | let s = string.as_ref(); |
84 | let set = match self.set { |
85 | Some(ref set) => set, |
86 | None => return false, |
87 | }; |
88 | |
89 | if !self.record_matches { |
90 | return set.is_match(s); |
91 | } |
92 | |
93 | let matches = set.matches(s); |
94 | if !matches.matched_any() { |
95 | return false; |
96 | } |
97 | for i in matches.iter() { |
98 | self.matched[i].set(true); |
99 | } |
100 | |
101 | true |
102 | } |
103 | } |
104 | |