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}