sauce/
filter.rs

1use glob::Pattern;
2
3pub type MatchOption<'a> = (Option<&'a str>, &'a str);
4
5#[derive(Debug, Clone, Default)]
6pub struct FilterOptions<'a> {
7    pub target: Option<&'a str>,
8    pub as_: Option<Vec<String>>,
9
10    pub globs: &'a [MatchOption<'a>],
11
12    pub filters: &'a [MatchOption<'a>],
13    pub filter_exclusions: &'a [MatchOption<'a>],
14}
15
16impl<'a> FilterOptions<'a> {
17    pub fn glob_match(&self, kinds: &[&str], value: &str) -> bool {
18        check_matches(
19            self.globs,
20            kinds,
21            value,
22            |g, v| {
23                if let Ok(pattern) = Pattern::new(g) {
24                    if pattern.matches(v) {
25                        return true;
26                    }
27                } else {
28                    eprintln!("Invalid pattern {}", g);
29                }
30                false
31            },
32            true,
33        )
34    }
35
36    pub fn filter_match(&self, kinds: &[&str], value: &str) -> bool {
37        check_matches(self.filters, kinds, value, |f, v| f == v, true)
38    }
39
40    pub fn filter_exclude(&self, kinds: &[&str], value: &str) -> bool {
41        check_matches(self.filter_exclusions, kinds, value, |f, v| f == v, false)
42    }
43}
44
45fn check_matches<F>(
46    globs: &[MatchOption],
47    kinds: &[&str],
48    value: &str,
49    matcher: F,
50    match_returns: bool,
51) -> bool
52where
53    F: Fn(&str, &str) -> bool,
54{
55    if globs.is_empty() {
56        return true;
57    }
58
59    globs.iter().any(|(tag, glob)| {
60        if let Some(tag) = tag {
61            if !kinds.iter().any(|k| k == tag) {
62                return false;
63            }
64        }
65
66        matcher(glob, value)
67    }) == match_returns
68}
69
70pub fn parse_match_option(value: Option<&str>) -> Vec<MatchOption> {
71    if let Some(value) = value {
72        value
73            .split(',')
74            .map(|raw| {
75                let mut iter = raw.splitn(2, ':');
76                let first = iter.next();
77                let second = iter.next();
78                match second {
79                    Some(second) => (first, second),
80                    None => (None, raw),
81                }
82            })
83            .collect()
84    } else {
85        Vec::new()
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    mod parse_match_options {
92        use super::super::*;
93        use pretty_assertions::assert_eq;
94
95        #[test]
96        fn it_no_ops_with_none_value() {
97            let result = parse_match_option(None);
98            assert_eq!(result, vec![])
99        }
100
101        #[test]
102        fn it_splits_matches_on_commas() {
103            let result = parse_match_option(Some("foo,bar"));
104            assert_eq!(result, vec![(None, "foo"), (None, "bar")])
105        }
106
107        #[test]
108        fn it_splits_target_and_term() {
109            let result = parse_match_option(Some("env:bar"));
110            assert_eq!(result, vec![(Some("env"), "bar")])
111        }
112
113        #[test]
114        fn it_multiple() {
115            let result = parse_match_option(Some("foo,alias:wat"));
116            assert_eq!(result, vec![(None, "foo"), (Some("alias"), "wat")])
117        }
118    }
119
120    mod filter_match {
121        use super::super::*;
122        use pretty_assertions::assert_eq;
123
124        #[test]
125        fn it_includes_all_values_when_empty() {
126            let filter_options = FilterOptions::default();
127            let result = filter_options.filter_match(&["env"], "foo");
128            assert_eq!(result, true)
129        }
130
131        #[test]
132        fn it_includes_untagged_match() {
133            let mut filter_options = FilterOptions::default();
134            filter_options.filters = &[(None, "foo")];
135            let result = filter_options.filter_match(&["env"], "foo");
136            assert_eq!(result, true)
137        }
138
139        #[test]
140        fn it_includes_tagged_matches() {
141            let mut filter_options = FilterOptions::default();
142            filter_options.filters = &[(Some("env"), "foo")];
143            let result = filter_options.filter_match(&["env"], "foo");
144            assert_eq!(result, true)
145        }
146
147        #[test]
148        fn it_excludes_non_matching_tagged_non_match() {
149            let mut filter_options = FilterOptions::default();
150            filter_options.filters = &[(Some("not-env"), "foo")];
151            let result = filter_options.filter_match(&["env"], "foo");
152            assert_eq!(result, false)
153        }
154
155        #[test]
156        fn it_excludes_untagged_non_match() {
157            let mut filter_options = FilterOptions::default();
158            filter_options.filters = &[(None, "bar")];
159            let result = filter_options.filter_match(&["env"], "foo");
160            assert_eq!(result, false)
161        }
162    }
163
164    mod filter_exclude {
165        use super::super::*;
166        use pretty_assertions::assert_eq;
167
168        #[test]
169        fn it_includes_all_values_when_empty() {
170            let filter_options = FilterOptions::default();
171            let result = filter_options.filter_exclude(&["env"], "foo");
172            assert_eq!(result, true)
173        }
174
175        #[test]
176        fn it_excludes_untagged_match() {
177            let mut filter_options = FilterOptions::default();
178            filter_options.filter_exclusions = &[(None, "foo")];
179            let result = filter_options.filter_exclude(&["env"], "foo");
180            assert_eq!(result, false)
181        }
182
183        #[test]
184        fn it_excludes_tagged_matches() {
185            let mut filter_options = FilterOptions::default();
186            filter_options.filter_exclusions = &[(Some("env"), "foo")];
187            let result = filter_options.filter_exclude(&["env"], "foo");
188            assert_eq!(result, false)
189        }
190
191        #[test]
192        fn it_includes_non_matching_tag() {
193            let mut filter_options = FilterOptions::default();
194            filter_options.filter_exclusions = &[(Some("not-env"), "foo")];
195            let result = filter_options.filter_exclude(&["env"], "foo");
196            assert_eq!(result, true)
197        }
198
199        #[test]
200        fn it_includes_untagged_non_match() {
201            let mut filter_options = FilterOptions::default();
202            filter_options.filter_exclusions = &[(None, "bar")];
203            let result = filter_options.filter_exclude(&["env"], "foo");
204            assert_eq!(result, true)
205        }
206    }
207}