1pub mod ast;
40mod char_iter;
41
42use self::ast::Ast;
43pub use self::char_iter::*;
44use regex::Regex;
45use regex::RegexBuilder;
46use std::ops::Range;
47use thiserror::Error;
48
49#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
51#[non_exhaustive]
52pub struct Config {
53 pub anchor_begin: bool,
58
59 pub anchor_end: bool,
64
65 pub literal_period: bool,
75
76 pub shortest_match: bool,
81
82 pub case_insensitive: bool,
89}
90
91#[derive(Clone, Debug, Error, PartialEq)]
93#[non_exhaustive]
94pub enum Error {
95 #[error("empty bracket expression")]
101 EmptyBracket,
102
103 #[error("empty collating symbol")]
105 EmptyCollatingSymbol,
106
107 #[error("undefined character class [:{0}:]")]
113 UndefinedCharClass(String),
114
115 #[error("character class [:{0}:] used as range bound")]
121 CharClassInRange(String),
122
123 #[error(transparent)]
125 RegexError(#[from] regex::Error),
126}
127
128#[derive(Clone, Debug)]
130enum Body {
131 Literal(String),
133 Regex {
135 regex: Regex,
136 starts_with_literal_dot: bool,
137 },
138}
139
140#[derive(Clone, Debug)]
142#[must_use = "creating a pattern without doing pattern matching is nonsense"]
143pub struct Pattern {
144 body: Body,
145 config: Config,
146}
147
148impl Pattern {
149 #[inline]
151 pub fn parse<I>(pattern: I) -> Result<Self, Error>
152 where
153 I: IntoIterator<Item = PatternChar>,
154 <I as IntoIterator>::IntoIter: Clone,
155 {
156 Self::from_ast(&Ast::new(pattern))
157 }
158
159 pub fn parse_with_config<I>(pattern: I, config: Config) -> Result<Self, Error>
161 where
162 I: IntoIterator<Item = PatternChar>,
163 <I as IntoIterator>::IntoIter: Clone,
164 {
165 Self::from_ast_and_config(&Ast::new(pattern), config)
166 }
167
168 pub fn from_ast(ast: &Ast) -> Result<Self, Error> {
170 Self::from_ast_and_config(ast, Config::default())
171 }
172
173 pub fn from_ast_and_config(ast: &Ast, config: Config) -> Result<Self, Error> {
175 let body = if let Some(literal) = ast.to_literal() {
176 Body::Literal(literal)
177 } else {
178 Body::Regex {
179 regex: RegexBuilder::new(&ast.to_regex(&config)?)
180 .case_insensitive(config.case_insensitive)
181 .dot_matches_new_line(true)
182 .swap_greed(config.shortest_match)
183 .build()?,
184 starts_with_literal_dot: ast.starts_with_literal_dot(),
185 }
186 };
187 Ok(Pattern { body, config })
188 }
189
190 #[inline]
192 #[must_use]
193 pub fn config(&self) -> &Config {
194 &self.config
195 }
196
197 #[must_use]
203 pub fn as_literal(&self) -> Option<&str> {
204 match &self.body {
205 Body::Literal(s) => Some(s),
206 Body::Regex { .. } => None,
207 }
208 }
209
210 pub fn into_literal(self) -> Result<String, Self> {
216 match self.body {
217 Body::Literal(s) => Ok(s),
218 Body::Regex { .. } => Err(self),
219 }
220 }
221
222 #[must_use]
224 pub fn is_match(&self, text: &str) -> bool {
225 match &self.body {
226 Body::Literal(s) => match (self.config.anchor_begin, self.config.anchor_end) {
227 (false, false) => text.contains(s),
228 (true, false) => text.starts_with(s),
229 (false, true) => text.ends_with(s),
230 (true, true) => text == s,
231 },
232 Body::Regex {
233 regex,
234 starts_with_literal_dot,
235 } => {
236 let reject_initial_dot =
237 self.config.literal_period && !starts_with_literal_dot && text.starts_with('.');
238 #[allow(clippy::bool_to_int_with_if)]
239 let at_index = if reject_initial_dot { 1 } else { 0 };
240 regex.is_match_at(text, at_index)
241 }
242 }
243 }
244
245 #[must_use]
250 pub fn find(&self, text: &str) -> Option<Range<usize>> {
251 match &self.body {
252 Body::Literal(s) => match (self.config.anchor_begin, self.config.anchor_end) {
253 (false, false) => text.find(s).map(|pos| pos..pos + s.len()),
254 (true, false) => text.starts_with(s).then(|| 0..s.len()),
255 (false, true) => text.ends_with(s).then(|| text.len() - s.len()..text.len()),
256 (true, true) => (text == s).then(|| 0..s.len()),
257 },
258 Body::Regex {
259 regex,
260 starts_with_literal_dot,
261 } => {
262 let reject_initial_dot =
263 self.config.literal_period && !starts_with_literal_dot && text.starts_with('.');
264 #[allow(clippy::bool_to_int_with_if)]
265 let at_index = if reject_initial_dot { 1 } else { 0 };
266 regex.find_at(text, at_index).map(|m| m.range())
267 }
268 }
269 }
270
271 #[must_use]
276 pub fn rfind(&self, text: &str) -> Option<Range<usize>> {
277 match &self.body {
278 Body::Literal(s) => match (self.config.anchor_begin, self.config.anchor_end) {
279 (false, false) => text.rfind(s).map(|pos| pos..pos + s.len()),
280 (true, false) => text.starts_with(s).then(|| 0..s.len()),
281 (false, true) => text.ends_with(s).then(|| text.len() - s.len()..text.len()),
282 (true, true) => (text == s).then(|| 0..s.len()),
283 },
284
285 Body::Regex {
286 regex,
287 starts_with_literal_dot: _,
288 } => {
289 let mut range = self.find(text)?;
290
291 while let Some(next_range) = (range.start + 1..=text.len())
292 .find(|&index| text.is_char_boundary(index))
293 .and_then(|index| regex.find_at(text, index).map(|m| m.range()))
294 {
295 range = next_range;
296 }
297
298 Some(range)
299 }
300 }
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use assert_matches::assert_matches;
308
309 #[test]
310 fn empty_pattern() {
311 let p = Pattern::parse(without_escape("")).unwrap();
312 assert_eq!(p.as_literal(), Some(""));
313
314 assert!(p.is_match(""));
315 assert!(p.is_match("a"));
316 assert!(p.is_match("."));
317 assert!(p.is_match("*"));
318
319 assert_eq!(p.find(""), Some(0..0));
320 assert_eq!(p.find("a"), Some(0..0));
321 assert_eq!(p.find("."), Some(0..0));
322 assert_eq!(p.find("*"), Some(0..0));
323
324 assert_eq!(p.rfind(""), Some(0..0));
325 assert_eq!(p.rfind("a"), Some(1..1));
326 assert_eq!(p.rfind("."), Some(1..1));
327 assert_eq!(p.rfind("*"), Some(1..1));
328 }
329
330 #[test]
331 fn single_character_pattern() {
332 let p = Pattern::parse(without_escape("a")).unwrap();
333 assert_eq!(p.as_literal(), Some("a"));
334
335 assert!(!p.is_match(""));
336 assert!(p.is_match("a"));
337 assert!(p.is_match("aa"));
338 assert!(!p.is_match("b"));
339 assert!(p.is_match("ab"));
340 assert!(p.is_match("ba"));
341
342 assert_eq!(p.find(""), None);
343 assert_eq!(p.find("a"), Some(0..1));
344 assert_eq!(p.find("aa"), Some(0..1));
345 assert_eq!(p.find("b"), None);
346 assert_eq!(p.find("ab"), Some(0..1));
347 assert_eq!(p.find("ba"), Some(1..2));
348
349 assert_eq!(p.rfind(""), None);
350 assert_eq!(p.rfind("a"), Some(0..1));
351 assert_eq!(p.rfind("aa"), Some(1..2));
352 assert_eq!(p.rfind("b"), None);
353 assert_eq!(p.rfind("ab"), Some(0..1));
354 assert_eq!(p.rfind("ba"), Some(1..2));
355 }
356
357 #[test]
358 fn double_character_pattern() {
359 let p = Pattern::parse(without_escape("in")).unwrap();
360 assert_eq!(p.as_literal(), Some("in"));
361
362 assert!(!p.is_match(""));
363 assert!(!p.is_match("i"));
364 assert!(!p.is_match("n"));
365 assert!(p.is_match("bin"));
366 assert!(p.is_match("inn"));
367 assert!(!p.is_match("nit"));
368
369 assert_eq!(p.find(""), None);
370 assert_eq!(p.find("i"), None);
371 assert_eq!(p.find("n"), None);
372 assert_eq!(p.find("bin"), Some(1..3));
373 assert_eq!(p.find("inn"), Some(0..2));
374 assert_eq!(p.find("nit"), None);
375
376 assert_eq!(p.rfind(""), None);
377 assert_eq!(p.rfind("i"), None);
378 assert_eq!(p.rfind("n"), None);
379 assert_eq!(p.rfind("bin"), Some(1..3));
380 assert_eq!(p.rfind("inn"), Some(0..2));
381 assert_eq!(p.rfind("nit"), None);
382 }
383
384 #[test]
385 fn characters_that_needs_escaping() {
386 let p = Pattern::parse(without_escape(r".\+()][{}^$-?")).unwrap();
387 assert_eq!(p.as_literal(), None);
388
389 assert_eq!(p.find(r".\+()][{}^$-X"), Some(0..13));
390 assert_eq!(p.find(r".\+()][{}^$-Y"), Some(0..13));
391 assert_eq!(p.find(r".\+()][{}^$-"), None);
392
393 assert_eq!(p.rfind(r".\+()][{}^$-X"), Some(0..13));
394 assert_eq!(p.rfind(r".\+()][{}^$-Y"), Some(0..13));
395 assert_eq!(p.rfind(r".\+()][{}^$-"), None);
396 }
397
398 #[test]
399 fn matching_newline() {
400 let p = Pattern::parse(without_escape("a\nb")).unwrap();
401 assert_eq!(p.as_literal(), Some("a\nb"));
402 assert_eq!(p.find("a\nb"), Some(0..3));
403 assert_eq!(p.rfind("a\nb"), Some(0..3));
404
405 let p = Pattern::parse(without_escape("a?b")).unwrap();
406 assert_eq!(p.as_literal(), None);
407 assert_eq!(p.find("a\nb"), Some(0..3));
408 assert_eq!(p.rfind("a\nb"), Some(0..3));
409 }
410
411 #[test]
412 fn any_single_character_pattern() {
413 let p = Pattern::parse(without_escape("?")).unwrap();
414 assert_eq!(p.as_literal(), None);
415
416 assert_eq!(p.find(""), None);
417 assert_eq!(p.find("i"), Some(0..1));
418 assert_eq!(p.find("yes"), Some(0..1));
419
420 assert_eq!(p.rfind(""), None);
421 assert_eq!(p.rfind("i"), Some(0..1));
422 assert_eq!(p.rfind("yes"), Some(2..3));
423 }
424
425 #[test]
426 fn any_single_character_pattern_combined() {
427 let p = Pattern::parse(without_escape("a?c")).unwrap();
428 assert_eq!(p.as_literal(), None);
429
430 assert_eq!(p.find(""), None);
431 assert_eq!(p.find("ab"), None);
432 assert_eq!(p.find("ac"), None);
433 assert_eq!(p.find("bc"), None);
434 assert_eq!(p.find("abc"), Some(0..3));
435
436 assert_eq!(p.rfind(""), None);
437 assert_eq!(p.rfind("ab"), None);
438 assert_eq!(p.rfind("ac"), None);
439 assert_eq!(p.rfind("bc"), None);
440 assert_eq!(p.rfind("abc"), Some(0..3));
441 }
442
443 #[test]
444 fn any_multi_character_pattern() {
445 let p = Pattern::parse(without_escape("*")).unwrap();
446 assert_eq!(p.as_literal(), None);
447
448 assert_eq!(p.find(""), Some(0..0));
449 assert_eq!(p.find("i"), Some(0..1));
450 assert_eq!(p.find("yes"), Some(0..3));
451
452 assert_eq!(p.rfind(""), Some(0..0));
453 assert_eq!(p.rfind("i"), Some(1..1));
454 assert_eq!(p.rfind("yes"), Some(3..3));
455 }
456
457 #[test]
458 fn any_multi_character_pattern_combined() {
459 let p = Pattern::parse(without_escape("a*b")).unwrap();
460 assert_eq!(p.as_literal(), None);
461
462 assert_eq!(p.find(""), None);
463 assert_eq!(p.find("a"), None);
464 assert_eq!(p.find("ab"), Some(0..2));
465 assert_eq!(p.find("aabb"), Some(0..4));
466 assert_eq!(p.find("lambda"), Some(1..4));
467
468 assert_eq!(p.rfind(""), None);
469 assert_eq!(p.rfind("a"), None);
470 assert_eq!(p.rfind("ab"), Some(0..2));
471 assert_eq!(p.rfind("aabb"), Some(1..4));
472 assert_eq!(p.rfind("lambda"), Some(1..4));
473 }
474
475 #[test]
476 fn unmatched_bracket_1() {
477 let p = Pattern::parse(without_escape("[a")).unwrap();
478 assert_eq!(p.as_literal(), Some("[a"));
479
480 assert_eq!(p.find(""), None);
481 assert_eq!(p.find("a"), None);
482 assert_eq!(p.find("[a]"), Some(0..2));
483 }
484
485 #[test]
486 fn unmatched_bracket_2() {
487 let p = Pattern::parse(without_escape("a]")).unwrap();
488 assert_eq!(p.as_literal(), Some("a]"));
489
490 assert_eq!(p.find(""), None);
491 assert_eq!(p.find("a"), None);
492 assert_eq!(p.find("[a]"), Some(1..3));
493 }
494
495 #[test]
496 fn unmatched_bracket_3() {
497 let p = Pattern::parse(without_escape("][")).unwrap();
498 assert_eq!(p.as_literal(), Some("]["));
499
500 assert_eq!(p.find(""), None);
501 assert_eq!(p.find("]["), Some(0..2));
502 assert_eq!(p.find("[][]"), Some(1..3));
503 }
504
505 #[test]
506 fn unmatched_bracket_4() {
507 let p = Pattern::parse(without_escape("[]")).unwrap();
508 assert_eq!(p.as_literal(), Some("[]"));
509
510 assert_eq!(p.find(""), None);
511 assert_eq!(p.find("[]"), Some(0..2));
512 assert_eq!(p.find("][]["), Some(1..3));
513 }
514
515 #[test]
516 fn unmatched_bracket_after_another_bracket() {
517 let p = Pattern::parse(without_escape("[a][")).unwrap();
518 assert_eq!(p.as_literal(), None);
519
520 assert_eq!(p.find(""), None);
521 assert_eq!(p.find("a"), None);
522 assert_eq!(p.find("["), None);
523 assert_eq!(p.find("a["), Some(0..2));
524 assert_eq!(p.find("]]a[[a][]"), Some(2..4));
525 }
526
527 #[test]
528 fn single_character_bracket_expression_pattern() {
529 let p = Pattern::parse(without_escape("[a]")).unwrap();
530 assert_eq!(p.as_literal(), None);
531
532 assert_eq!(p.find(""), None);
533 assert_eq!(p.find("a"), Some(0..1));
534 assert_eq!(p.find("[a]"), Some(1..2));
535 }
536
537 #[test]
538 fn multi_character_bracket_expression_pattern() {
539 let p = Pattern::parse(without_escape("[abc]")).unwrap();
540 assert_eq!(p.as_literal(), None);
541
542 assert_eq!(p.find(""), None);
543 assert_eq!(p.find("a"), Some(0..1));
544 assert_eq!(p.find("b"), Some(0..1));
545 assert_eq!(p.find("c"), Some(0..1));
546 assert_eq!(p.find("d"), None);
547 }
548
549 #[test]
550 fn ampersand_in_bracket_expression() {
551 let p = Pattern::parse(without_escape("[a&&b]")).unwrap();
552 assert_eq!(p.as_literal(), None);
553
554 assert_eq!(p.find(""), None);
555 assert_eq!(p.find("a"), Some(0..1));
556 assert_eq!(p.find("b"), Some(0..1));
557 assert_eq!(p.find("&"), Some(0..1));
558 }
559
560 #[test]
561 fn tilde_in_bracket_expression() {
562 let p = Pattern::parse(without_escape("[a~~b]")).unwrap();
563 assert_eq!(p.as_literal(), None);
564
565 assert_eq!(p.find(""), None);
566 assert_eq!(p.find("a"), Some(0..1));
567 assert_eq!(p.find("b"), Some(0..1));
568 assert_eq!(p.find("~"), Some(0..1));
569 }
570
571 #[test]
572 fn characters_that_needs_escaping_in_bracket_expression() {
573 let p = Pattern::parse(without_escape("[.?*-][&|~^][][]")).unwrap();
574 assert_eq!(p.as_literal(), None);
575
576 assert_eq!(p.find(".&["), Some(0..3));
577 assert_eq!(p.find("?|["), Some(0..3));
578 assert_eq!(p.find("*~]"), Some(0..3));
579 assert_eq!(p.find("-^]"), Some(0..3));
580 assert_eq!(p.find("?&]"), Some(0..3));
581 }
582
583 #[test]
584 fn brackets_in_bracket_expression() {
585 let p = Pattern::parse(without_escape("[]a[]")).unwrap();
586 assert_eq!(p.as_literal(), None);
587
588 assert_eq!(p.find(""), None);
589 assert_eq!(p.find("a"), Some(0..1));
590 assert_eq!(p.find("["), Some(0..1));
591 assert_eq!(p.find("]"), Some(0..1));
592 }
593
594 #[test]
595 fn character_range() {
596 let p = Pattern::parse(without_escape("[3-5]")).unwrap();
597 assert_eq!(p.as_literal(), None);
598
599 assert_eq!(p.find(""), None);
600 assert_eq!(p.find("2"), None);
601 assert_eq!(p.find("3"), Some(0..1));
602 assert_eq!(p.find("4"), Some(0..1));
603 assert_eq!(p.find("5"), Some(0..1));
604 assert_eq!(p.find("6"), None);
605 assert_eq!(p.find("02468"), Some(2..3));
606 }
607
608 #[test]
609 fn dash_at_start_of_bracket_expression() {
610 let p = Pattern::parse(without_escape("[-0]")).unwrap();
612 assert_eq!(p.as_literal(), None);
613
614 assert_eq!(p.find(""), None);
615 assert_eq!(p.find("-"), Some(0..1));
616 assert_eq!(p.find("."), None);
617 assert_eq!(p.find("0"), Some(0..1));
618 assert_eq!(p.find("1"), None);
619 }
620
621 #[test]
622 fn dash_at_end_of_bracket_expression() {
623 let p = Pattern::parse(without_escape("[+-]")).unwrap();
625 assert_eq!(p.as_literal(), None);
626
627 assert_eq!(p.find(""), None);
628 assert_eq!(p.find("+"), Some(0..1));
629 assert_eq!(p.find(","), None);
630 assert_eq!(p.find("-"), Some(0..1));
631 assert_eq!(p.find("."), None);
632 }
633
634 #[test]
635 fn ambiguous_character_range() {
636 let p = Pattern::parse(without_escape("[2-4-6]")).unwrap();
637 assert_eq!(p.as_literal(), None);
638
639 assert_eq!(p.find("1"), None);
642 assert_eq!(p.find("2"), Some(0..1));
643 assert_eq!(p.find("3"), Some(0..1));
644 assert_eq!(p.find("4"), Some(0..1));
645 assert_eq!(p.find("5"), None);
646 assert_eq!(p.find("6"), Some(0..1));
647 assert_eq!(p.find("7"), None);
648 }
649
650 #[test]
651 fn double_dash_in_bracket_expression() {
652 let p = Pattern::parse(without_escape("[+--.]")).unwrap();
655 assert_eq!(p.as_literal(), None);
656
657 assert_eq!(p.find(""), None);
658 assert_eq!(p.find("+"), Some(0..1));
659 assert_eq!(p.find(","), Some(0..1));
660 assert_eq!(p.find("-"), Some(0..1));
661 assert_eq!(p.find("."), Some(0..1));
662 }
663
664 #[test]
665 fn double_dash_at_start_of_bracket_expression() {
666 let p = Pattern::parse(without_escape("[--0]")).unwrap();
669 assert_eq!(p.as_literal(), None);
670
671 assert_eq!(p.find(""), None);
672 assert_eq!(p.find("-"), Some(0..1));
673 assert_eq!(p.find("."), Some(0..1));
674 assert_eq!(p.find("0"), Some(0..1));
675 assert_eq!(p.find("1"), None);
676 }
677
678 #[test]
679 fn double_dash_at_end_of_bracket_expression() {
680 let p = Pattern::parse(without_escape("[+--]")).unwrap();
683 assert_eq!(p.as_literal(), None);
684
685 assert_eq!(p.find(""), None);
686 assert_eq!(p.find("+"), Some(0..1));
687 assert_eq!(p.find(","), Some(0..1));
688 assert_eq!(p.find("-"), Some(0..1));
689 assert_eq!(p.find("."), None);
690 }
691
692 #[test]
693 fn bracket_expression_complement() {
694 let p = Pattern::parse(without_escape("[!ab]")).unwrap();
695 assert_eq!(p.as_literal(), None);
696
697 assert_eq!(p.find(""), None);
698 assert_eq!(p.find("a"), None);
699 assert_eq!(p.find("b"), None);
700 assert_eq!(p.find("c"), Some(0..1));
701 assert_eq!(p.find("!"), Some(0..1));
702 assert_eq!(p.find("abcd"), Some(2..3));
703
704 assert_eq!(p.rfind(""), None);
705 assert_eq!(p.rfind("a"), None);
706 assert_eq!(p.rfind("b"), None);
707 assert_eq!(p.rfind("c"), Some(0..1));
708 assert_eq!(p.rfind("!"), Some(0..1));
709 assert_eq!(p.rfind("abcd"), Some(3..4));
710 }
711
712 #[test]
713 fn exclamation_in_bracket_expression() {
714 let p = Pattern::parse(without_escape("[ab!]")).unwrap();
715 assert_eq!(p.as_literal(), None);
716
717 assert_eq!(p.find(""), None);
718 assert_eq!(p.find("a"), Some(0..1));
719 assert_eq!(p.find("b"), Some(0..1));
720 assert_eq!(p.find("c"), None);
721 assert_eq!(p.find("!"), Some(0..1));
722 }
723
724 #[test]
725 fn exclamation_in_bracket_expression_complement() {
726 let p = Pattern::parse(without_escape("[!!]")).unwrap();
727 assert_eq!(p.as_literal(), None);
728
729 assert_eq!(p.find(""), None);
730 assert_eq!(p.find("!"), None);
731 assert_eq!(p.find("a"), Some(0..1));
732 assert_eq!(p.find("!x!"), Some(1..2));
733 }
734
735 #[test]
736 fn bracket_in_bracket_expression_complement() {
737 let p = Pattern::parse(without_escape("[!]a]")).unwrap();
738 assert_eq!(p.as_literal(), None);
739
740 assert_eq!(p.find(""), None);
741 assert_eq!(p.find("!"), Some(0..1));
742 assert_eq!(p.find("]"), None);
743 assert_eq!(p.find("a"), None);
744 assert_eq!(p.find("b"), Some(0..1));
745 assert_eq!(p.find("abc"), Some(1..2));
746 }
747
748 #[test]
749 fn caret_in_bracket_expression() {
750 let p = Pattern::parse(without_escape("[^]a]")).unwrap();
751 assert_eq!(p.as_literal(), None);
752
753 assert_eq!(p.find(""), None);
754 assert_eq!(p.find("^"), Some(0..1));
755 assert_eq!(p.find("]"), None);
756 assert_eq!(p.find("a"), None);
757 assert_eq!(p.find("b"), Some(0..1));
758 assert_eq!(p.find("abc"), Some(1..2));
759
760 let p = Pattern::parse(without_escape("[^^]")).unwrap();
761 assert_eq!(p.as_literal(), None);
762 assert!(!p.is_match("^"));
763 }
764
765 #[test]
766 fn single_character_collating_symbol() {
767 let p = Pattern::parse(without_escape("[[.a.]]")).unwrap();
768 assert_eq!(p.as_literal(), None);
769
770 assert_eq!(p.find(""), None);
771 assert_eq!(p.find("a"), Some(0..1));
772 assert_eq!(p.find("x"), None);
773 assert_eq!(p.find("."), None);
774 assert_eq!(p.find("[a]"), Some(1..2));
775 }
776
777 #[test]
778 fn multi_character_collating_symbol() {
779 let p = Pattern::parse(without_escape("[[.ch.]]")).unwrap();
780 assert_eq!(p.as_literal(), None);
781
782 assert_eq!(p.find(""), None);
783 assert_eq!(p.find("c"), None);
784 assert_eq!(p.find("h"), None);
785 assert_eq!(p.find("."), None);
786 assert_eq!(p.find("ch"), Some(0..2));
787 assert_eq!(p.find("[ch]"), Some(1..3));
788 }
789
790 #[test]
791 fn single_character_equivalence_class() {
792 let p = Pattern::parse(without_escape("[[=a=]]")).unwrap();
793 assert_eq!(p.as_literal(), None);
794
795 assert_eq!(p.find(""), None);
796 assert_eq!(p.find("a"), Some(0..1));
797 assert_eq!(p.find("x"), None);
798 assert_eq!(p.find("="), None);
799 assert_eq!(p.find("[a]"), Some(1..2));
800 }
801
802 #[test]
803 fn multi_character_equivalence_class() {
804 let p = Pattern::parse(without_escape("[[=ij=]]")).unwrap();
805 assert_eq!(p.as_literal(), None);
806
807 assert_eq!(p.find(""), None);
808 assert_eq!(p.find("i"), None);
809 assert_eq!(p.find("j"), None);
810 assert_eq!(p.find("."), None);
811 assert_eq!(p.find("ij"), Some(0..2));
812 assert_eq!(p.find("[ij]"), Some(1..3));
813 }
814
815 #[test]
816 fn character_class_alnum() {
817 let p = Pattern::parse(without_escape("[[:alnum:]]")).unwrap();
818 assert_eq!(p.as_literal(), None);
819
820 assert_eq!(p.find(""), None);
821 assert_eq!(p.find("a"), Some(0..1));
822 assert_eq!(p.find("x"), Some(0..1));
823 assert_eq!(p.find("7"), Some(0..1));
824 assert_eq!(p.find("="), None);
825 assert_eq!(p.find(":"), None);
826 assert_eq!(p.find("[A]"), Some(1..2));
827 }
828
829 #[test]
830 fn character_class_alpha() {
831 let p = Pattern::parse(without_escape("[[:alpha:]]")).unwrap();
832 assert_eq!(p.as_literal(), None);
833
834 assert_eq!(p.find(""), None);
835 assert_eq!(p.find("a"), Some(0..1));
836 assert_eq!(p.find("x"), Some(0..1));
837 assert_eq!(p.find("7"), None);
838 assert_eq!(p.find("="), None);
839 assert_eq!(p.find(":"), None);
840 assert_eq!(p.find("[A]"), Some(1..2));
841 }
842
843 #[test]
844 fn character_class_blank() {
845 let p = Pattern::parse(without_escape("[[:blank:]]")).unwrap();
846 assert_eq!(p.as_literal(), None);
847
848 assert_eq!(p.find(""), None);
849 assert_eq!(p.find("a"), None);
850 assert_eq!(p.find("="), None);
851 assert_eq!(p.find(" "), Some(0..1));
852 assert_eq!(p.find("\t"), Some(0..1));
853 assert_eq!(p.find("\n"), None);
854 assert_eq!(p.find("\r"), None);
855 assert_eq!(p.find("[A]"), None);
856 }
857
858 #[test]
859 fn character_class_cntrl() {
860 let p = Pattern::parse(without_escape("[[:cntrl:]]")).unwrap();
861 assert_eq!(p.as_literal(), None);
862
863 assert_eq!(p.find(""), None);
864 assert_eq!(p.find("a"), None);
865 assert_eq!(p.find("="), None);
866 assert_eq!(p.find(" "), None);
867 assert_eq!(p.find("\t"), Some(0..1));
868 assert_eq!(p.find("\n"), Some(0..1));
869 assert_eq!(p.find("\r"), Some(0..1));
870 assert_eq!(p.find("[A]"), None);
871 }
872
873 #[test]
874 fn character_class_digit() {
875 let p = Pattern::parse(without_escape("[[:digit:]]")).unwrap();
876 assert_eq!(p.as_literal(), None);
877
878 assert_eq!(p.find(""), None);
879 assert_eq!(p.find("a"), None);
880 assert_eq!(p.find("x"), None);
881 assert_eq!(p.find("7"), Some(0..1));
882 assert_eq!(p.find("="), None);
883 assert_eq!(p.find(":"), None);
884 assert_eq!(p.find("[A]"), None);
885 }
886
887 #[test]
888 fn character_class_graph() {
889 let p = Pattern::parse(without_escape("[[:graph:]]")).unwrap();
890 assert_eq!(p.as_literal(), None);
891
892 assert_eq!(p.find(""), None);
893 assert_eq!(p.find("a"), Some(0..1));
894 assert_eq!(p.find("x"), Some(0..1));
895 assert_eq!(p.find("7"), Some(0..1));
896 assert_eq!(p.find("="), Some(0..1));
897 assert_eq!(p.find(":"), Some(0..1));
898 assert_eq!(p.find(" "), None);
899 assert_eq!(p.find("\t"), None);
900 assert_eq!(p.find("[A]"), Some(0..1));
901 }
902
903 #[test]
904 fn character_class_lower() {
905 let p = Pattern::parse(without_escape("[[:lower:]]")).unwrap();
906 assert_eq!(p.as_literal(), None);
907
908 assert_eq!(p.find(""), None);
909 assert_eq!(p.find("A"), None);
910 assert_eq!(p.find("a"), Some(0..1));
911 assert_eq!(p.find("x"), Some(0..1));
912 assert_eq!(p.find("7"), None);
913 assert_eq!(p.find("="), None);
914 assert_eq!(p.find("\t"), None);
915 assert_eq!(p.find("[a]"), Some(1..2));
916 }
917
918 #[test]
919 fn character_class_print() {
920 let p = Pattern::parse(without_escape("[[:print:]]")).unwrap();
921 assert_eq!(p.as_literal(), None);
922
923 assert_eq!(p.find(""), None);
924 assert_eq!(p.find("a"), Some(0..1));
925 assert_eq!(p.find("x"), Some(0..1));
926 assert_eq!(p.find("7"), Some(0..1));
927 assert_eq!(p.find("="), Some(0..1));
928 assert_eq!(p.find(":"), Some(0..1));
929 assert_eq!(p.find(" "), Some(0..1));
930 assert_eq!(p.find("\t"), None);
931 assert_eq!(p.find("[A]"), Some(0..1));
932 }
933
934 #[test]
935 fn character_class_punct() {
936 let p = Pattern::parse(without_escape("[[:punct:]]")).unwrap();
937 assert_eq!(p.as_literal(), None);
938
939 assert_eq!(p.find(""), None);
940 assert_eq!(p.find("a"), None);
941 assert_eq!(p.find("x"), None);
942 assert_eq!(p.find("7"), None);
943 assert_eq!(p.find("="), Some(0..1));
944 assert_eq!(p.find(":"), Some(0..1));
945 assert_eq!(p.find("[A]"), Some(0..1));
946 }
947
948 #[test]
949 fn character_class_space() {
950 let p = Pattern::parse(without_escape("[[:space:]]")).unwrap();
951 assert_eq!(p.as_literal(), None);
952
953 assert_eq!(p.find(""), None);
954 assert_eq!(p.find("a"), None);
955 assert_eq!(p.find("="), None);
956 assert_eq!(p.find(" "), Some(0..1));
957 assert_eq!(p.find("\t"), Some(0..1));
958 assert_eq!(p.find("\n"), Some(0..1));
959 assert_eq!(p.find("\r"), Some(0..1));
960 assert_eq!(p.find("[A]"), None);
961 }
962
963 #[test]
964 fn character_class_upper() {
965 let p = Pattern::parse(without_escape("[[:upper:]]")).unwrap();
966 assert_eq!(p.as_literal(), None);
967
968 assert_eq!(p.find(""), None);
969 assert_eq!(p.find("A"), Some(0..1));
970 assert_eq!(p.find("a"), None);
971 assert_eq!(p.find("X"), Some(0..1));
972 assert_eq!(p.find("7"), None);
973 assert_eq!(p.find("="), None);
974 assert_eq!(p.find("\t"), None);
975 assert_eq!(p.find("[A]"), Some(1..2));
976 }
977
978 #[test]
979 fn character_class_xdigit() {
980 let p = Pattern::parse(without_escape("[[:xdigit:]]")).unwrap();
981 assert_eq!(p.as_literal(), None);
982
983 assert_eq!(p.find(""), None);
984 assert_eq!(p.find("a"), Some(0..1));
985 assert_eq!(p.find("x"), None);
986 assert_eq!(p.find("7"), Some(0..1));
987 assert_eq!(p.find("="), None);
988 assert_eq!(p.find(":"), None);
989 assert_eq!(p.find("[A]"), Some(1..2));
990 }
991
992 #[test]
993 fn undefined_character_class() {
994 let e = Pattern::parse(without_escape("[[:foo_bar:]]")).unwrap_err();
995 assert_matches!(e, Error::UndefinedCharClass(name) if name == "foo_bar");
996 }
997
998 #[test]
999 fn combinations_of_inner_bracket_expressions() {
1000 let p = Pattern::parse(without_escape("[][.-.]-[=0=][:blank:]]")).unwrap();
1001 assert_eq!(p.as_literal(), None);
1002
1003 assert_eq!(p.find(""), None);
1004 assert_eq!(p.find("]"), Some(0..1));
1005 assert_eq!(p.find("-"), Some(0..1));
1006 assert_eq!(p.find("."), Some(0..1));
1007 assert_eq!(p.find("/"), Some(0..1));
1008 assert_eq!(p.find("0"), Some(0..1));
1009 assert_eq!(p.find("1"), None);
1010 assert_eq!(p.find(" "), Some(0..1));
1011 assert_eq!(p.find("\t"), Some(0..1));
1012 assert_eq!(p.find("["), None);
1013 }
1014
1015 #[test]
1016 fn escaped_pattern() {
1017 let p = Pattern::parse(with_escape(r"\*\?\[a]")).unwrap();
1018 assert_eq!(p.as_literal(), Some("*?[a]"));
1019
1020 assert_eq!(p.find(""), None);
1021 assert_eq!(p.find("*?[a]"), Some(0..5));
1022 assert_eq!(p.find("aaa"), None);
1023 }
1024
1025 #[test]
1026 fn literal_with_anchor_begin() {
1027 let config = Config {
1028 anchor_begin: true,
1029 ..Config::default()
1030 };
1031 let p = Pattern::parse_with_config(without_escape("a"), config).unwrap();
1032 assert_eq!(p.as_literal(), Some("a"));
1033
1034 assert!(!p.is_match(""));
1035 assert!(p.is_match("a"));
1036 assert!(!p.is_match(".a"));
1037 assert!(p.is_match("a."));
1038
1039 assert_eq!(p.find(""), None);
1040 assert_eq!(p.find("a"), Some(0..1));
1041 assert_eq!(p.find(".a"), None);
1042 assert_eq!(p.find("a."), Some(0..1));
1043
1044 assert_eq!(p.rfind(""), None);
1045 assert_eq!(p.rfind("a"), Some(0..1));
1046 assert_eq!(p.rfind(".a"), None);
1047 assert_eq!(p.rfind("a."), Some(0..1));
1048 }
1049
1050 #[test]
1051 fn non_literal_with_anchor_begin() {
1052 let config = Config {
1053 anchor_begin: true,
1054 ..Config::default()
1055 };
1056 let p = Pattern::parse_with_config(without_escape("a?"), config).unwrap();
1057 assert_eq!(p.as_literal(), None);
1058
1059 assert!(!p.is_match(""));
1060 assert!(!p.is_match("a"));
1061 assert!(p.is_match("as"));
1062 assert!(p.is_match("apple"));
1063 assert!(!p.is_match("bass"));
1064
1065 assert_eq!(p.find(""), None);
1066 assert_eq!(p.find("a"), None);
1067 assert_eq!(p.find("as"), Some(0..2));
1068 assert_eq!(p.find("apple"), Some(0..2));
1069 assert_eq!(p.find("bass"), None);
1070
1071 assert_eq!(p.rfind(""), None);
1072 assert_eq!(p.rfind("a"), None);
1073 assert_eq!(p.rfind("as"), Some(0..2));
1074 assert_eq!(p.rfind("apple"), Some(0..2));
1075 assert_eq!(p.rfind("bass"), None);
1076 }
1077
1078 #[test]
1079 fn literal_with_anchor_end() {
1080 let config = Config {
1081 anchor_end: true,
1082 ..Config::default()
1083 };
1084 let p = Pattern::parse_with_config(without_escape("a"), config).unwrap();
1085 assert_eq!(p.as_literal(), Some("a"));
1086
1087 assert!(!p.is_match(""));
1088 assert!(p.is_match("a"));
1089 assert!(p.is_match("..a"));
1090 assert!(!p.is_match("a.."));
1091
1092 assert_eq!(p.find(""), None);
1093 assert_eq!(p.find("a"), Some(0..1));
1094 assert_eq!(p.find("..a"), Some(2..3));
1095 assert_eq!(p.find("a.."), None);
1096
1097 assert_eq!(p.rfind(""), None);
1098 assert_eq!(p.rfind("a"), Some(0..1));
1099 assert_eq!(p.rfind("..a"), Some(2..3));
1100 assert_eq!(p.rfind("a.."), None);
1101 }
1102
1103 #[test]
1104 fn non_literal_with_anchor_end() {
1105 let config = Config {
1106 anchor_end: true,
1107 ..Config::default()
1108 };
1109 let p = Pattern::parse_with_config(without_escape("?n"), config).unwrap();
1110 assert_eq!(p.as_literal(), None);
1111
1112 assert!(!p.is_match(""));
1113 assert!(!p.is_match("n"));
1114 assert!(p.is_match("in"));
1115 assert!(p.is_match("begin"));
1116 assert!(!p.is_match("beginning"));
1117
1118 assert_eq!(p.find(""), None);
1119 assert_eq!(p.find("n"), None);
1120 assert_eq!(p.find("in"), Some(0..2));
1121 assert_eq!(p.find("begin"), Some(3..5));
1122 assert_eq!(p.find("beginning"), None);
1123
1124 assert_eq!(p.rfind(""), None);
1125 assert_eq!(p.rfind("n"), None);
1126 assert_eq!(p.rfind("in"), Some(0..2));
1127 assert_eq!(p.rfind("begin"), Some(3..5));
1128 assert_eq!(p.rfind("beginning"), None);
1129 }
1130
1131 #[test]
1132 fn literal_with_anchor_both() {
1133 let config = Config {
1134 anchor_begin: true,
1135 anchor_end: true,
1136 ..Config::default()
1137 };
1138 let p = Pattern::parse_with_config(without_escape("a"), config).unwrap();
1139 assert_eq!(p.as_literal(), Some("a"));
1140
1141 assert!(!p.is_match(""));
1142 assert!(p.is_match("a"));
1143 assert!(!p.is_match("..a"));
1144 assert!(!p.is_match("a.."));
1145
1146 assert_eq!(p.find(""), None);
1147 assert_eq!(p.find("a"), Some(0..1));
1148 assert_eq!(p.find("..a"), None);
1149 assert_eq!(p.find("a.."), None);
1150
1151 assert_eq!(p.rfind(""), None);
1152 assert_eq!(p.rfind("a"), Some(0..1));
1153 assert_eq!(p.rfind("..a"), None);
1154 assert_eq!(p.rfind("a.."), None);
1155 }
1156
1157 #[test]
1158 fn non_literal_with_anchor_both() {
1159 let config = Config {
1160 anchor_begin: true,
1161 anchor_end: true,
1162 ..Config::default()
1163 };
1164 let p = Pattern::parse_with_config(without_escape("???"), config).unwrap();
1165 assert_eq!(p.as_literal(), None);
1166
1167 assert!(!p.is_match("in"));
1168 assert!(p.is_match("out"));
1169 assert!(!p.is_match("from"));
1170
1171 assert_eq!(p.find("in"), None);
1172 assert_eq!(p.find("out"), Some(0..3));
1173 assert_eq!(p.find("from"), None);
1174
1175 assert_eq!(p.rfind("in"), None);
1176 assert_eq!(p.rfind("out"), Some(0..3));
1177 assert_eq!(p.rfind("from"), None);
1178 }
1179
1180 #[test]
1181 fn initial_period_with_literal_period() {
1182 let config = Config {
1183 literal_period: true,
1184 ..Config::default()
1185 };
1186
1187 let p = Pattern::parse_with_config(without_escape("?"), config).unwrap();
1188 assert!(!p.is_match("."));
1189 assert!(p.is_match(".."));
1190 assert!(p.is_match("a"));
1191 assert_eq!(p.find("."), None);
1192 assert_eq!(p.find(".."), Some(1..2));
1193 assert_eq!(p.find("a"), Some(0..1));
1194 assert_eq!(p.rfind("."), None);
1195 assert_eq!(p.rfind(".."), Some(1..2));
1196 assert_eq!(p.rfind("a"), Some(0..1));
1197
1198 let p = Pattern::parse_with_config(without_escape("*"), config).unwrap();
1199 assert!(p.is_match("."));
1200 assert!(p.is_match(".."));
1201 assert!(p.is_match("a"));
1202 assert_eq!(p.find("."), Some(1..1));
1203 assert_eq!(p.find(".."), Some(1..2));
1204 assert_eq!(p.find("a"), Some(0..1));
1205 assert_eq!(p.rfind("."), Some(1..1));
1206 assert_eq!(p.rfind(".."), Some(2..2));
1207 assert_eq!(p.rfind("a"), Some(1..1));
1208
1209 let p = Pattern::parse_with_config(without_escape("[.a]"), config).unwrap();
1210 assert!(!p.is_match("."));
1211 assert!(p.is_match(".."));
1212 assert!(p.is_match("a"));
1213 assert_eq!(p.find("."), None);
1214 assert_eq!(p.find(".."), Some(1..2));
1215 assert_eq!(p.find("a"), Some(0..1));
1216 assert_eq!(p.rfind("."), None);
1217 assert_eq!(p.rfind(".."), Some(1..2));
1218 assert_eq!(p.rfind("a"), Some(0..1));
1219
1220 let p = Pattern::parse_with_config(without_escape(".*"), config).unwrap();
1221 assert!(p.is_match("."));
1222 assert!(p.is_match(".."));
1223 assert_eq!(p.find("."), Some(0..1));
1224 assert_eq!(p.find(".."), Some(0..2));
1225 assert_eq!(p.rfind("."), Some(0..1));
1226 assert_eq!(p.rfind(".."), Some(1..2));
1227 }
1228
1229 #[test]
1230 fn initial_period_without_literal_period() {
1231 let config = Config::default();
1232
1233 let p = Pattern::parse_with_config(without_escape("?"), config).unwrap();
1234 assert!(p.is_match("."));
1235 assert!(p.is_match(".."));
1236 assert!(p.is_match("a"));
1237 assert_eq!(p.find("."), Some(0..1));
1238 assert_eq!(p.find(".."), Some(0..1));
1239 assert_eq!(p.find("a"), Some(0..1));
1240 assert_eq!(p.rfind("."), Some(0..1));
1241 assert_eq!(p.rfind(".."), Some(1..2));
1242 assert_eq!(p.rfind("a"), Some(0..1));
1243
1244 let p = Pattern::parse_with_config(without_escape("*"), config).unwrap();
1245 assert!(p.is_match("."));
1246 assert!(p.is_match(".."));
1247 assert!(p.is_match("a"));
1248 assert_eq!(p.find("."), Some(0..1));
1249 assert_eq!(p.find(".."), Some(0..2));
1250 assert_eq!(p.find("a"), Some(0..1));
1251 assert_eq!(p.rfind("."), Some(1..1));
1252 assert_eq!(p.rfind(".."), Some(2..2));
1253 assert_eq!(p.rfind("a"), Some(1..1));
1254
1255 let p = Pattern::parse_with_config(without_escape("[.a]"), config).unwrap();
1256 assert!(p.is_match("."));
1257 assert!(p.is_match(".."));
1258 assert!(p.is_match("a"));
1259 assert_eq!(p.find("."), Some(0..1));
1260 assert_eq!(p.find(".."), Some(0..1));
1261 assert_eq!(p.find("a"), Some(0..1));
1262 assert_eq!(p.rfind("."), Some(0..1));
1263 assert_eq!(p.rfind(".."), Some(1..2));
1264 assert_eq!(p.rfind("a"), Some(0..1));
1265
1266 let p = Pattern::parse_with_config(without_escape(".*"), config).unwrap();
1267 assert!(p.is_match("."));
1268 assert!(p.is_match(".."));
1269 assert_eq!(p.find("."), Some(0..1));
1270 assert_eq!(p.find(".."), Some(0..2));
1271 assert_eq!(p.rfind("."), Some(0..1));
1272 assert_eq!(p.rfind(".."), Some(1..2));
1273 }
1274
1275 #[test]
1276 fn non_literal_with_shortest_match() {
1277 let p = Pattern::parse_with_config(without_escape("1*9"), Config::default()).unwrap();
1278 assert!(p.is_match("119"));
1279 assert!(p.is_match("11999"));
1280 assert_eq!(p.find("119"), Some(0..3));
1281 assert_eq!(p.find("11999"), Some(0..5));
1282 assert_eq!(p.rfind("119"), Some(1..3));
1283 assert_eq!(p.rfind("11999"), Some(1..5));
1284
1285 let config = Config {
1286 shortest_match: true,
1287 ..Config::default()
1288 };
1289 let p = Pattern::parse_with_config(without_escape("1*9"), config).unwrap();
1290 assert!(p.is_match("119"));
1291 assert!(p.is_match("11999"));
1292 assert_eq!(p.find("119"), Some(0..3));
1293 assert_eq!(p.find("11999"), Some(0..3));
1294 assert_eq!(p.rfind("119"), Some(1..3));
1295 assert_eq!(p.rfind("11999"), Some(1..3));
1296 }
1297
1298 #[test]
1299 fn non_literal_with_case_insensitive() {
1300 let config = Config {
1301 case_insensitive: true,
1302 ..Config::default()
1303 };
1304 let p = Pattern::parse_with_config(without_escape("a?z"), config).unwrap();
1305 assert_eq!(p.as_literal(), None);
1306
1307 assert!(!p.is_match(""));
1308 assert!(p.is_match("a-z"));
1309 assert!(p.is_match("A-Z"));
1310 assert!(!p.is_match("b&b"));
1311
1312 assert_eq!(p.find(""), None);
1313 assert_eq!(p.find("a-z"), Some(0..3));
1314 assert_eq!(p.find("A-Z"), Some(0..3));
1315 assert_eq!(p.find("b&b"), None);
1316
1317 assert_eq!(p.rfind(""), None);
1318 assert_eq!(p.rfind("a-z"), Some(0..3));
1319 assert_eq!(p.rfind("A-Z"), Some(0..3));
1320 assert_eq!(p.rfind("b&b"), None);
1321 }
1322}