string_patterns/
pattern_capture.rs1use regex::{Captures, Match};
2
3use crate::pattern_cache::with_cached_regex;
4use crate::utils::build_whole_word_pattern;
5
6pub trait PatternCapture<'a> {
8
9 fn pattern_captures(&'a self, pattern: &str, case_insensitive: bool) -> Option<Captures<'a>>;
11
12 fn pattern_matches_as_vec(&'a self, pattern: &str, case_insensitive: bool, outer: bool) -> Vec<Match<'a>>;
15
16 fn pattern_matches_vec(&'a self, pattern: &str, case_insensitive: bool) -> Vec<Match<'a>> {
19 self.pattern_matches_as_vec(pattern, case_insensitive, false)
20 }
21
22 fn pattern_matches_outer(&'a self, pattern: &str, case_insensitive: bool) -> Vec<Match<'a>> {
25 self.pattern_matches_as_vec(pattern, case_insensitive, true)
26 }
27
28 fn pattern_first_match(&'a self, pattern: &str, case_insensitive: bool) -> Option<Match<'a>>;
31
32 fn pattern_last_match(&'a self, pattern: &str, case_insensitive: bool) -> Option<Match<'a>> {
34 let matched_segments = self.pattern_matches_vec(pattern, case_insensitive);
35 matched_segments.last().map(|m| *m)
36 }
37
38 fn pattern_first_last_matches(&'a self, pattern: &str, case_insensitive: bool) -> Option<(Match<'a>, Match<'a>)> {
41 let matched_segments = self.pattern_matches_vec(pattern, case_insensitive);
42 if let Some(first) = matched_segments.get(0) {
43 if let Some(last) = matched_segments.last() {
44 return Some((*first, *last));
45 }
46 }
47 None
48 }
49
50 fn pattern_first_index(&'a self, pattern: &str, case_insensitive: bool) -> Option<usize> {
53 if let Some(first) = self.pattern_first_match(pattern, case_insensitive) {
54 Some(first.start())
55 } else {
56 None
57 }
58 }
59
60 fn pattern_first_end_index(&'a self, pattern: &str, case_insensitive: bool) -> Option<usize> {
63 if let Some(first) = self.pattern_first_match(pattern, case_insensitive) {
64 Some(first.end())
65 } else {
66 None
67 }
68 }
69
70 fn pattern_last_start_index(&'a self, pattern: &str, case_insensitive: bool) -> Option<usize> {
73 if let Some(first) = self.pattern_first_match(pattern, case_insensitive) {
74 Some(first.start())
75 } else {
76 None
77 }
78 }
79
80 fn pattern_last_index(&'a self, pattern: &str, case_insensitive: bool) -> Option<usize> {
83 if let Some(first) = self.pattern_first_match(pattern, case_insensitive) {
84 Some(first.end())
85 } else {
86 None
87 }
88 }
89
90 fn count_pattern(&'a self, pattern: &'a str, case_insensitive: bool) -> usize {
92 self.pattern_matches_vec(pattern, case_insensitive).len()
93 }
94
95 fn count_word(&'a self, word: &'a str, case_insensitive: bool) -> usize {
97 let pattern = build_whole_word_pattern(word);
98 self.pattern_matches_vec(&pattern, case_insensitive).len()
99 }
100}
101
102
103pub fn find_matches_within_haystack<'a>(haystack: &'a str, pattern: &str, case_insensitive: bool, outer: bool) -> Vec<Match<'a>> {
108 with_cached_regex(pattern, case_insensitive, |re| {
109 let mut matched_items: Vec<Match<'a>> = Vec::new();
110 let mut item_keys: Vec<(&str, usize, usize)> = Vec::new();
111 for inner_captures in re.captures_iter(haystack) {
112 for capture_opt in inner_captures.iter() {
113 if let Some(matched_item) = capture_opt {
114 let item_str = matched_item.as_str();
115
116 let item_key = (item_str, matched_item.start(), matched_item.end());
117 let is_matched = if outer {
118 true
119 } else {
120 item_keys.contains(&item_key) == false
121 };
122 if is_matched {
123 matched_items.push(matched_item.to_owned());
124 if !outer {
125 item_keys.push(item_key);
126 }
127 }
128 if outer {
130 break;
131 }
132 }
133 }
134 }
135 matched_items
136 })
137 .unwrap_or_default()
138}
139
140impl<'a, T: AsRef<str>> PatternCapture<'a> for T {
142
143 fn pattern_captures(&'a self, pattern: &str, case_insensitive: bool) -> Option<Captures<'a>> {
145 with_cached_regex(pattern, case_insensitive, |re| re.captures(self.as_ref()))
146 .ok()
147 .flatten()
148 }
149
150 fn pattern_matches_as_vec(&'a self, pattern: &str, case_insensitive: bool, outer: bool) -> Vec<Match<'a>> {
152 find_matches_within_haystack(self.as_ref(), pattern, case_insensitive, outer)
153 }
154
155 fn pattern_first_match(&'a self, pattern: &str, case_insensitive: bool) -> Option<Match<'a>> {
159 with_cached_regex(pattern, case_insensitive, |re| re.find(self.as_ref()))
160 .ok()
161 .flatten()
162 }
163
164}