1use crate::parsing::{MatchPower, ParseScopeError, Scope, ScopeStack};
4use serde_derive::{Deserialize, Serialize};
5use std::str::FromStr;
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
37 .excludes
38 .iter()
39 .any(|sel| sel.is_empty() || sel.does_match(stack).is_some())
40 {
41 return None;
42 }
43 if self.path.is_empty() {
44 Some(MatchPower(0o1u64 as f64))
46 } else {
47 self.path.does_match(stack)
48 }
49 }
50
51 pub fn extract_single_scope(&self) -> Option<Scope> {
53 if self.path.len() > 1 || !self.excludes.is_empty() || self.path.is_empty() {
54 return None;
55 }
56 Some(self.path.as_slice()[0])
57 }
58
59 pub fn extract_scopes(&self) -> Vec<Scope> {
61 self.path.scopes.clone()
62 }
63}
64
65impl FromStr for ScopeSelector {
66 type Err = ParseScopeError;
67
68 fn from_str(s: &str) -> Result<ScopeSelector, ParseScopeError> {
70 let mut excludes = Vec::new();
71 let mut path_str: &str = "";
72 for (i, selector) in s.split(" -").enumerate() {
73 if i == 0 {
74 path_str = selector;
75 } else {
76 excludes.push(ScopeStack::from_str(selector)?);
77 }
78 }
79 Ok(ScopeSelector {
80 path: ScopeStack::from_str(path_str)?,
81 excludes,
82 })
83 }
84}
85
86impl ScopeSelectors {
87 pub fn does_match(&self, stack: &[Scope]) -> Option<MatchPower> {
103 self.selectors
104 .iter()
105 .filter_map(|sel| sel.does_match(stack))
106 .max()
107 }
108}
109
110impl FromStr for ScopeSelectors {
111 type Err = ParseScopeError;
112
113 fn from_str(s: &str) -> Result<ScopeSelectors, ParseScopeError> {
115 let mut selectors = Vec::new();
116 for selector in s.split(&[',', '|'][..]) {
117 selectors.push(ScopeSelector::from_str(selector)?)
118 }
119 Ok(ScopeSelectors { selectors })
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 #[test]
127 fn selectors_work() {
128 use std::str::FromStr;
129 let sels = ScopeSelectors::from_str(
130 "source.php meta.preprocessor - string.quoted, \
131 source string",
132 )
133 .unwrap();
134 assert_eq!(sels.selectors.len(), 2);
135 let first_sel = &sels.selectors[0];
136 assert_eq!(format!("{:?}", first_sel),
137 "ScopeSelector { path: ScopeStack { clear_stack: [], scopes: [<source.php>, <meta.preprocessor>] }, excludes: [ScopeStack { clear_stack: [], scopes: [<string.quoted>] }] }");
138
139 let sels = ScopeSelectors::from_str(
140 "source.php meta.preprocessor -string.quoted|\
141 source string",
142 )
143 .unwrap();
144 assert_eq!(sels.selectors.len(), 2);
145 let first_sel = &sels.selectors[0];
146 assert_eq!(format!("{:?}", first_sel),
147 "ScopeSelector { path: ScopeStack { clear_stack: [], scopes: [<source.php>, <meta.preprocessor>] }, excludes: [ScopeStack { clear_stack: [], scopes: [<string.quoted>] }] }");
148
149 let sels = ScopeSelectors::from_str(
150 "text.xml meta.tag.preprocessor.xml punctuation.separator.key-value.xml",
151 )
152 .unwrap();
153 assert_eq!(sels.selectors.len(), 1);
154 let first_sel = &sels.selectors[0];
155 assert_eq!(format!("{:?}", first_sel),
156 "ScopeSelector { path: ScopeStack { clear_stack: [], scopes: [<text.xml>, <meta.tag.preprocessor.xml>, <punctuation.separator.key-value.xml>] }, excludes: [] }");
157
158 let sels = ScopeSelectors::from_str("text.xml meta.tag.preprocessor.xml punctuation.separator.key-value.xml - text.html - string")
159 .unwrap();
160 assert_eq!(sels.selectors.len(), 1);
161 let first_sel = &sels.selectors[0];
162 assert_eq!(format!("{:?}", first_sel),
163 "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>] }] }");
164
165 let sels = ScopeSelectors::from_str("text.xml meta.tag.preprocessor.xml punctuation.separator.key-value.xml - text.html - string, source - comment")
166 .unwrap();
167 assert_eq!(sels.selectors.len(), 2);
168 let first_sel = &sels.selectors[0];
169 assert_eq!(format!("{:?}", first_sel),
170 "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>] }] }");
171 let second_sel = &sels.selectors[1];
172 assert_eq!(format!("{:?}", second_sel),
173 "ScopeSelector { path: ScopeStack { clear_stack: [], scopes: [<source>] }, excludes: [ScopeStack { clear_stack: [], scopes: [<comment>] }] }");
174
175 let sels = ScopeSelectors::from_str(" -a.b|j.g").unwrap();
176 assert_eq!(sels.selectors.len(), 2);
177 let first_sel = &sels.selectors[0];
178 assert_eq!(format!("{:?}", first_sel),
179 "ScopeSelector { path: ScopeStack { clear_stack: [], scopes: [] }, excludes: [ScopeStack { clear_stack: [], scopes: [<a.b>] }] }");
180 let second_sel = &sels.selectors[1];
181 assert_eq!(
182 format!("{:?}", second_sel),
183 "ScopeSelector { path: ScopeStack { clear_stack: [], scopes: [<j.g>] }, excludes: [] }"
184 );
185 }
186 #[test]
187 fn matching_works() {
188 use crate::parsing::{MatchPower, ScopeStack};
189 use std::str::FromStr;
190 assert_eq!(
191 ScopeSelectors::from_str("a.b, a e, e.f")
192 .unwrap()
193 .does_match(ScopeStack::from_str("a.b e.f").unwrap().as_slice()),
194 Some(MatchPower(0o20u64 as f64))
195 );
196 assert_eq!(
197 ScopeSelectors::from_str("a.b, a e.f, e.f")
198 .unwrap()
199 .does_match(ScopeStack::from_str("a.b e.f").unwrap().as_slice()),
200 Some(MatchPower(0o21u64 as f64))
201 );
202 assert_eq!(
203 ScopeSelectors::from_str("a.b, a e.f - c j, e.f")
204 .unwrap()
205 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
206 Some(MatchPower(0o2000u64 as f64))
207 );
208 assert_eq!(
209 ScopeSelectors::from_str("a.b, a e.f - c j, e.f - a.b")
210 .unwrap()
211 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
212 Some(MatchPower(0o2u64 as f64))
213 );
214 assert_eq!(
215 ScopeSelectors::from_str("a.b, a e.f - c k, e.f - a.b")
216 .unwrap()
217 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
218 Some(MatchPower(0o2001u64 as f64))
219 );
220 assert_eq!(
221 ScopeSelectors::from_str("a.b|a e.f -d, e.f -a.b")
222 .unwrap()
223 .does_match(ScopeStack::from_str("a.b c.d e.f").unwrap().as_slice()),
224 Some(MatchPower(0o201u64 as f64))
225 );
226 }
227
228 #[test]
229 fn empty_stack_matching_works() {
230 use crate::parsing::{MatchPower, ScopeStack};
231 use std::str::FromStr;
232 assert_eq!(
233 ScopeSelector::from_str(" - a.b")
234 .unwrap()
235 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
236 None
237 );
238 assert_eq!(
239 ScopeSelector::from_str("")
240 .unwrap()
241 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
242 Some(MatchPower(0o1u64 as f64))
243 );
244 assert_eq!(
245 ScopeSelector::from_str("")
246 .unwrap()
247 .does_match(ScopeStack::from_str("").unwrap().as_slice()),
248 Some(MatchPower(0o1u64 as f64))
249 );
250 assert_eq!(
251 ScopeSelector::from_str(" - a.b")
252 .unwrap()
253 .does_match(ScopeStack::from_str("").unwrap().as_slice()),
254 Some(MatchPower(0o1u64 as f64))
255 );
256 assert_eq!(
257 ScopeSelector::from_str("a.b - ")
258 .unwrap()
259 .does_match(ScopeStack::from_str("").unwrap().as_slice()),
260 None
261 );
262 assert_eq!(
263 ScopeSelector::from_str("a.b - ")
264 .unwrap()
265 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
266 None
267 );
268 assert_eq!(
269 ScopeSelector::from_str(" - ")
270 .unwrap()
271 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
272 None
273 );
274 assert_eq!(
275 ScopeSelector::from_str(" - a.b")
276 .unwrap()
277 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
278 None
279 );
280 assert_eq!(
281 ScopeSelector::from_str(" - g.h")
282 .unwrap()
283 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
284 Some(MatchPower(0o1u64 as f64))
285 );
286
287 assert_eq!(
288 ScopeSelector::from_str(" -a.b")
289 .unwrap()
290 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
291 None
292 );
293 assert_eq!(
294 ScopeSelector::from_str("")
295 .unwrap()
296 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
297 Some(MatchPower(0o1u64 as f64))
298 );
299 assert_eq!(
300 ScopeSelector::from_str(" -a.b")
301 .unwrap()
302 .does_match(ScopeStack::from_str("").unwrap().as_slice()),
303 Some(MatchPower(0o1u64 as f64))
304 );
305 assert_eq!(
306 ScopeSelector::from_str("a.b -")
307 .unwrap()
308 .does_match(ScopeStack::from_str("").unwrap().as_slice()),
309 None
310 );
311 assert_eq!(
312 ScopeSelector::from_str("a.b -")
313 .unwrap()
314 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
315 None
316 );
317 assert_eq!(
318 ScopeSelector::from_str(" -")
319 .unwrap()
320 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
321 None
322 );
323 assert_eq!(
324 ScopeSelector::from_str(" -a.b")
325 .unwrap()
326 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
327 None
328 );
329 assert_eq!(
330 ScopeSelector::from_str(" -g.h")
331 .unwrap()
332 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
333 Some(MatchPower(0o1u64 as f64))
334 );
335 }
336
337 #[test]
338 fn multiple_excludes_matching_works() {
339 use crate::parsing::{MatchPower, ScopeStack};
340 use std::str::FromStr;
341 assert_eq!(
342 ScopeSelector::from_str(" - a.b - c.d")
343 .unwrap()
344 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
345 None
346 );
347 assert_eq!(
348 ScopeSelector::from_str(" - a.b - c.d")
349 .unwrap()
350 .does_match(ScopeStack::from_str("").unwrap().as_slice()),
351 Some(MatchPower(0o1u64 as f64))
352 );
353 assert_eq!(
354 ScopeSelector::from_str("a.b - c.d -e.f")
355 .unwrap()
356 .does_match(ScopeStack::from_str("").unwrap().as_slice()),
357 None
358 );
359 assert_eq!(
360 ScopeSelector::from_str("a.b - c.d -")
361 .unwrap()
362 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
363 None
364 );
365 assert_eq!(
366 ScopeSelector::from_str(" -g.h - h.i")
367 .unwrap()
368 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
369 Some(MatchPower(0o1u64 as f64))
370 );
371 assert_eq!(
372 ScopeSelector::from_str("a.b")
373 .unwrap()
374 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
375 Some(MatchPower(0o2u64 as f64))
376 );
377 assert_eq!(
378 ScopeSelector::from_str("a.b -g.h - h.i")
379 .unwrap()
380 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
381 Some(MatchPower(0o2u64 as f64))
382 );
383 assert_eq!(
384 ScopeSelector::from_str("c.d")
385 .unwrap()
386 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
387 Some(MatchPower(0o20u64 as f64))
388 );
389 assert_eq!(
390 ScopeSelector::from_str("c.d - j.g - h.i")
391 .unwrap()
392 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
393 Some(MatchPower(0o20u64 as f64))
394 );
395 assert_eq!(
396 ScopeSelectors::from_str("j.g| -a.b")
397 .unwrap()
398 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
399 None
400 );
401 assert_eq!(
402 ScopeSelectors::from_str(" -a.b|j.g")
403 .unwrap()
404 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
405 None
406 );
407 assert_eq!(
408 ScopeSelectors::from_str(" -a.b,c.d - j.g - h.i")
409 .unwrap()
410 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
411 Some(MatchPower(0o20u64 as f64))
412 );
413 assert_eq!(
414 ScopeSelectors::from_str(" -a.b, -d.c -f.e")
415 .unwrap()
416 .does_match(ScopeStack::from_str("a.b c.d j e.f").unwrap().as_slice()),
417 Some(MatchPower(0o01u64 as f64))
418 );
419 }
420}