1use crate::parsing::{Scope, ScopeStack, MatchPower, ParseScopeError};
4use std::str::FromStr;
5use serde_derive::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
14pub struct ScopeSelector {
15 pub path: ScopeStack,
16 pub excludes: Vec<ScopeStack>,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
23pub struct ScopeSelectors {
24 pub selectors: Vec<ScopeSelector>,
26}
27
28impl ScopeSelector {
29 pub fn does_match(&self, stack: &[Scope]) -> Option<MatchPower> {
35 if self.excludes.iter().any(|sel| sel.is_empty() || sel.does_match(stack).is_some()) {
37 return None;
38 }
39 if self.path.is_empty() {
40 Some(MatchPower(0o1u64 as f64))
42 } else {
43 self.path.does_match(stack)
44 }
45 }
46
47 pub fn extract_single_scope(&self) -> Option<Scope> {
49 if self.path.len() > 1 || !self.excludes.is_empty() || self.path.is_empty() {
50 return None;
51 }
52 Some(self.path.as_slice()[0])
53 }
54
55 pub fn extract_scopes(&self) -> Vec<Scope> {
57 self.path.scopes.clone()
58 }
59}
60
61impl FromStr for ScopeSelector {
62 type Err = ParseScopeError;
63
64 fn from_str(s: &str) -> Result<ScopeSelector, ParseScopeError> {
66 let mut excludes = Vec::new();
67 let mut path_str: &str = "";
68 for (i, selector) in s.split(" -").enumerate() {
69 if i == 0 {
70 path_str = selector;
71 } else {
72 excludes.push(ScopeStack::from_str(selector)?);
73 }
74 }
75 Ok(ScopeSelector {
76 path: ScopeStack::from_str(path_str)?,
77 excludes,
78 })
79 }
80}
81
82impl ScopeSelectors {
83 pub fn does_match(&self, stack: &[Scope]) -> Option<MatchPower> {
99 self.selectors.iter().filter_map(|sel| sel.does_match(stack)).max()
100 }
101}
102
103impl FromStr for ScopeSelectors {
104 type Err = ParseScopeError;
105
106 fn from_str(s: &str) -> Result<ScopeSelectors, ParseScopeError> {
108 let mut selectors = Vec::new();
109 for selector in s.split(&[',', '|'][..]) {
110 selectors.push(ScopeSelector::from_str(selector)?)
111 }
112 Ok(ScopeSelectors { selectors })
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 #[test]
120 fn selectors_work() {
121 use std::str::FromStr;
122 let sels = ScopeSelectors::from_str("source.php meta.preprocessor - string.quoted, \
123 source string")
124 .unwrap();
125 assert_eq!(sels.selectors.len(), 2);
126 let first_sel = &sels.selectors[0];
127 assert_eq!(format!("{:?}", first_sel),
128 "ScopeSelector { path: ScopeStack { clear_stack: [], scopes: [<source.php>, <meta.preprocessor>] }, excludes: [ScopeStack { clear_stack: [], scopes: [<string.quoted>] }] }");
129
130 let sels = ScopeSelectors::from_str("source.php meta.preprocessor -string.quoted|\
131 source string")
132 .unwrap();
133 assert_eq!(sels.selectors.len(), 2);
134 let first_sel = &sels.selectors[0];
135 assert_eq!(format!("{:?}", first_sel),
136 "ScopeSelector { path: ScopeStack { clear_stack: [], scopes: [<source.php>, <meta.preprocessor>] }, excludes: [ScopeStack { clear_stack: [], scopes: [<string.quoted>] }] }");
137
138 let sels = ScopeSelectors::from_str("text.xml meta.tag.preprocessor.xml punctuation.separator.key-value.xml")
139 .unwrap();
140 assert_eq!(sels.selectors.len(), 1);
141 let first_sel = &sels.selectors[0];
142 assert_eq!(format!("{:?}", first_sel),
143 "ScopeSelector { path: ScopeStack { clear_stack: [], scopes: [<text.xml>, <meta.tag.preprocessor.xml>, <punctuation.separator.key-value.xml>] }, excludes: [] }");
144
145 let sels = ScopeSelectors::from_str("text.xml meta.tag.preprocessor.xml punctuation.separator.key-value.xml - text.html - string")
146 .unwrap();
147 assert_eq!(sels.selectors.len(), 1);
148 let first_sel = &sels.selectors[0];
149 assert_eq!(format!("{:?}", first_sel),
150 "ScopeSelector { path: ScopeStack { clear_stack: [], scopes: [<text.xml>, <meta.tag.preprocessor.xml>, <punctuation.separator.key-value.xml>] }, excludes: [ScopeStack { clear_stack: [], scopes: [<text.html>] }, ScopeStack { clear_stack: [], scopes: [<string>] }] }");
151
152 let sels = ScopeSelectors::from_str("text.xml meta.tag.preprocessor.xml punctuation.separator.key-value.xml - text.html - string, source - comment")
153 .unwrap();
154 assert_eq!(sels.selectors.len(), 2);
155 let first_sel = &sels.selectors[0];
156 assert_eq!(format!("{:?}", first_sel),
157 "ScopeSelector { path: ScopeStack { clear_stack: [], scopes: [<text.xml>, <meta.tag.preprocessor.xml>, <punctuation.separator.key-value.xml>] }, excludes: [ScopeStack { clear_stack: [], scopes: [<text.html>] }, ScopeStack { clear_stack: [], scopes: [<string>] }] }");
158 let second_sel = &sels.selectors[1];
159 assert_eq!(format!("{:?}", second_sel),
160 "ScopeSelector { path: ScopeStack { clear_stack: [], scopes: [<source>] }, excludes: [ScopeStack { clear_stack: [], scopes: [<comment>] }] }");
161
162 let sels = ScopeSelectors::from_str(" -a.b|j.g")
163 .unwrap();
164 assert_eq!(sels.selectors.len(), 2);
165 let first_sel = &sels.selectors[0];
166 assert_eq!(format!("{:?}", first_sel),
167 "ScopeSelector { path: ScopeStack { clear_stack: [], scopes: [] }, excludes: [ScopeStack { clear_stack: [], scopes: [<a.b>] }] }");
168 let second_sel = &sels.selectors[1];
169 assert_eq!(format!("{:?}", second_sel),
170 "ScopeSelector { path: ScopeStack { clear_stack: [], scopes: [<j.g>] }, excludes: [] }");
171 }
172 #[test]
173 fn matching_works() {
174 use crate::parsing::{ScopeStack, MatchPower};
175 use std::str::FromStr;
176 assert_eq!(ScopeSelectors::from_str("a.b, a e, e.f")
177 .unwrap()
178 .does_match(ScopeStack::from_str("a.b e.f").unwrap().as_slice()),
179 Some(MatchPower(0o20u64 as f64)));
180 assert_eq!(ScopeSelectors::from_str("a.b, a e.f, e.f")
181 .unwrap()
182 .does_match(ScopeStack::from_str("a.b e.f").unwrap().as_slice()),
183 Some(MatchPower(0o21u64 as f64)));
184 assert_eq!(ScopeSelectors::from_str("a.b, a e.f - c j, e.f")
185 .unwrap()
186 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
187 Some(MatchPower(0o2000u64 as f64)));
188 assert_eq!(ScopeSelectors::from_str("a.b, a e.f - c j, e.f - a.b")
189 .unwrap()
190 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
191 Some(MatchPower(0o2u64 as f64)));
192 assert_eq!(ScopeSelectors::from_str("a.b, a e.f - c k, e.f - a.b")
193 .unwrap()
194 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
195 Some(MatchPower(0o2001u64 as f64)));
196 assert_eq!(ScopeSelectors::from_str("a.b|a e.f -d, e.f -a.b")
197 .unwrap()
198 .does_match(ScopeStack::from_str("a.b c.d e.f").unwrap().as_slice()),
199 Some(MatchPower(0o201u64 as f64)));
200 }
201
202 #[test]
203 fn empty_stack_matching_works() {
204 use crate::parsing::{ScopeStack, MatchPower};
205 use std::str::FromStr;
206 assert_eq!(ScopeSelector::from_str(" - a.b")
207 .unwrap()
208 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
209 None);
210 assert_eq!(ScopeSelector::from_str("")
211 .unwrap()
212 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
213 Some(MatchPower(0o1u64 as f64)));
214 assert_eq!(ScopeSelector::from_str("")
215 .unwrap()
216 .does_match(ScopeStack::from_str("").unwrap().as_slice()),
217 Some(MatchPower(0o1u64 as f64)));
218 assert_eq!(ScopeSelector::from_str(" - a.b")
219 .unwrap()
220 .does_match(ScopeStack::from_str("").unwrap().as_slice()),
221 Some(MatchPower(0o1u64 as f64)));
222 assert_eq!(ScopeSelector::from_str("a.b - ")
223 .unwrap()
224 .does_match(ScopeStack::from_str("").unwrap().as_slice()),
225 None);
226 assert_eq!(ScopeSelector::from_str("a.b - ")
227 .unwrap()
228 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
229 None);
230 assert_eq!(ScopeSelector::from_str(" - ")
231 .unwrap()
232 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
233 None);
234 assert_eq!(ScopeSelector::from_str(" - a.b")
235 .unwrap()
236 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
237 None);
238 assert_eq!(ScopeSelector::from_str(" - g.h")
239 .unwrap()
240 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
241 Some(MatchPower(0o1u64 as f64)));
242
243 assert_eq!(ScopeSelector::from_str(" -a.b")
244 .unwrap()
245 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
246 None);
247 assert_eq!(ScopeSelector::from_str("")
248 .unwrap()
249 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
250 Some(MatchPower(0o1u64 as f64)));
251 assert_eq!(ScopeSelector::from_str(" -a.b")
252 .unwrap()
253 .does_match(ScopeStack::from_str("").unwrap().as_slice()),
254 Some(MatchPower(0o1u64 as f64)));
255 assert_eq!(ScopeSelector::from_str("a.b -")
256 .unwrap()
257 .does_match(ScopeStack::from_str("").unwrap().as_slice()),
258 None);
259 assert_eq!(ScopeSelector::from_str("a.b -")
260 .unwrap()
261 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
262 None);
263 assert_eq!(ScopeSelector::from_str(" -")
264 .unwrap()
265 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
266 None);
267 assert_eq!(ScopeSelector::from_str(" -a.b")
268 .unwrap()
269 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
270 None);
271 assert_eq!(ScopeSelector::from_str(" -g.h")
272 .unwrap()
273 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
274 Some(MatchPower(0o1u64 as f64)));
275 }
276
277 #[test]
278 fn multiple_excludes_matching_works() {
279 use crate::parsing::{ScopeStack, MatchPower};
280 use std::str::FromStr;
281 assert_eq!(ScopeSelector::from_str(" - a.b - c.d")
282 .unwrap()
283 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
284 None);
285 assert_eq!(ScopeSelector::from_str(" - a.b - c.d")
286 .unwrap()
287 .does_match(ScopeStack::from_str("").unwrap().as_slice()),
288 Some(MatchPower(0o1u64 as f64)));
289 assert_eq!(ScopeSelector::from_str("a.b - c.d -e.f")
290 .unwrap()
291 .does_match(ScopeStack::from_str("").unwrap().as_slice()),
292 None);
293 assert_eq!(ScopeSelector::from_str("a.b - c.d -")
294 .unwrap()
295 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
296 None);
297 assert_eq!(ScopeSelector::from_str(" -g.h - h.i")
298 .unwrap()
299 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
300 Some(MatchPower(0o1u64 as f64)));
301 assert_eq!(ScopeSelector::from_str("a.b")
302 .unwrap()
303 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
304 Some(MatchPower(0o2u64 as f64)));
305 assert_eq!(ScopeSelector::from_str("a.b -g.h - h.i")
306 .unwrap()
307 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
308 Some(MatchPower(0o2u64 as f64)));
309 assert_eq!(ScopeSelector::from_str("c.d")
310 .unwrap()
311 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
312 Some(MatchPower(0o20u64 as f64)));
313 assert_eq!(ScopeSelector::from_str("c.d - j.g - h.i")
314 .unwrap()
315 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
316 Some(MatchPower(0o20u64 as f64)));
317 assert_eq!(ScopeSelectors::from_str("j.g| -a.b")
318 .unwrap()
319 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
320 None);
321 assert_eq!(ScopeSelectors::from_str(" -a.b|j.g")
322 .unwrap()
323 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
324 None);
325 assert_eq!(ScopeSelectors::from_str(" -a.b,c.d - j.g - h.i")
326 .unwrap()
327 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
328 Some(MatchPower(0o20u64 as f64)));
329 assert_eq!(ScopeSelectors::from_str(" -a.b, -d.c -f.e")
330 .unwrap()
331 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
332 Some(MatchPower(0o01u64 as f64)));
333 }
334}