Skip to main content

simple_string_patterns/
matches.rs

1use crate::ci_engine;
2use alphanumeric::CharType;
3
4/// Regex-free matcher methods for common use cases
5/// There are no plain and _cs-suffixed variants because the standard
6/// starts_with(pat: &str), contains(pat: &str) and ends_with(pat: &str) methods meet those needs
7pub trait SimpleMatch {
8    /// Matches the whole string in case-insensitive mode
9    fn equals_ci(&self, pattern: &str) -> bool;
10
11    /// Matches the the plain Latin letters [a-z] and numerals [0=9] in the string in case-insensitive mode
12    fn equals_ci_alphanum(&self, pattern: &str) -> bool;
13
14    /// Starts with a pattern in case-insensitive mode
15    fn starts_with_ci(&self, pattern: &str) -> bool;
16
17    /// Starts with a case-insensitive alphanumeric sequence, ignoring any other characters in the pattern or sample string
18    fn starts_with_ci_alphanum(&self, pattern: &str) -> bool;
19
20    /// Ends with a pattern in case-insensitive mode
21    fn ends_with_ci(&self, pattern: &str) -> bool;
22
23    /// Ends with a case-insensitive alphanumeric sequence, ignoring any other characters in the pattern or sample string
24    fn ends_with_ci_alphanum(&self, pattern: &str) -> bool;
25
26    /// Contains a pattern in case-insensitive mode
27    fn contains_ci(&self, pattern: &str) -> bool;
28
29    /// Contains a case-insensitive alphanumeric sequence, ignoring any other characters in the pattern or sample string
30    fn contains_ci_alphanum(&self, pattern: &str) -> bool;
31}
32
33impl<T: AsRef<str>> SimpleMatch for T {
34    fn equals_ci(&self, pattern: &str) -> bool {
35        ci_engine::ci_eq(self.as_ref(), pattern)
36    }
37
38    fn equals_ci_alphanum(&self, pattern: &str) -> bool {
39        ci_engine::ci_alphanum_eq(self.as_ref(), pattern)
40    }
41
42    fn starts_with_ci(&self, pattern: &str) -> bool {
43        ci_engine::ci_starts_with(self.as_ref(), pattern)
44    }
45
46    fn starts_with_ci_alphanum(&self, pattern: &str) -> bool {
47        ci_engine::ci_alphanum_starts_with(self.as_ref(), pattern)
48    }
49
50    fn ends_with_ci(&self, pattern: &str) -> bool {
51        ci_engine::ci_ends_with(self.as_ref(), pattern)
52    }
53
54    fn ends_with_ci_alphanum(&self, pattern: &str) -> bool {
55        ci_engine::ci_alphanum_ends_with(self.as_ref(), pattern)
56    }
57
58    fn contains_ci(&self, pattern: &str) -> bool {
59        ci_engine::ci_contains(self.as_ref(), pattern)
60    }
61
62    fn contains_ci_alphanum(&self, pattern: &str) -> bool {
63        ci_engine::ci_alphanum_contains(self.as_ref(), pattern)
64    }
65}
66
67/// Return the indices of all ocurrences of a string
68pub trait MatchOccurrences {
69    /// Return the indices only of all matches of a given string pattern (not a regular expression)
70    /// Builds on match_indices in the Rust standard library
71    fn find_matched_indices(&self, pat: &str) -> Vec<usize>;
72
73    /// Match occurrences of a single character
74    fn find_char_indices(&self, pat: char) -> Vec<usize>;
75}
76
77impl<T: AsRef<str>> MatchOccurrences for T {
78    fn find_matched_indices(&self, pat: &str) -> Vec<usize> {
79        self.as_ref()
80            .match_indices(pat)
81            .into_iter()
82            .map(|pair| pair.0)
83            .collect::<Vec<usize>>()
84    }
85
86    fn find_char_indices(&self, pat: char) -> Vec<usize> {
87        self.as_ref()
88            .match_indices(pat)
89            .into_iter()
90            .map(|pair| pair.0)
91            .collect::<Vec<usize>>()
92    }
93}
94
95/// Test if character set (CharType) is in the string
96pub trait SimplContainsType
97where
98    Self: SimpleMatch,
99{
100    /// contains characters in the specified set
101    fn contains_type(&self, char_type: CharType) -> bool;
102
103    /// contains characters in the specified sets
104    fn contains_types(&self, char_types: &[CharType]) -> bool;
105
106    /// starts with one or more characters in the specified set
107    fn starts_with_type(&self, char_type: CharType) -> bool;
108
109    /// starts with one or more characters in the specified set
110    fn starts_with_types(&self, char_types: &[CharType]) -> bool;
111
112    /// ends with one or more characters in the specified sets
113    fn ends_with_type(&self, char_type: CharType) -> bool;
114
115    /// ends with one or more characters in the specified sets
116    fn ends_with_types(&self, char_types: &[CharType]) -> bool;
117}
118
119impl<T: AsRef<str>> SimplContainsType for T {
120    fn contains_type(&self, char_type: CharType) -> bool {
121        self.as_ref().chars().any(|ch| char_type.is_in_range(&ch))
122    }
123
124    fn contains_types(&self, char_types: &[CharType]) -> bool {
125        self.as_ref()
126            .chars()
127            .any(|ch| char_types.into_iter().any(|ct| ct.is_in_range(&ch)))
128    }
129
130    fn starts_with_type(&self, char_type: CharType) -> bool {
131        if let Some(first) = self.as_ref().chars().nth(0) {
132            char_type.is_in_range(&first)
133        } else {
134            false
135        }
136    }
137
138    fn starts_with_types(&self, char_types: &[CharType]) -> bool {
139        if let Some(first) = self.as_ref().chars().nth(0) {
140            char_types.into_iter().any(|ct| ct.is_in_range(&first))
141        } else {
142            false
143        }
144    }
145
146    fn ends_with_type(&self, char_type: CharType) -> bool {
147        if let Some(first) = self.as_ref().chars().last() {
148            char_type.is_in_range(&first)
149        } else {
150            false
151        }
152    }
153
154    fn ends_with_types(&self, char_types: &[CharType]) -> bool {
155        if let Some(first) = self.as_ref().chars().last() {
156            char_types.into_iter().any(|ct| ct.is_in_range(&first))
157        } else {
158            false
159        }
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn test_equals_ci() {
169        assert!("Hello World".equals_ci("hello world"));
170        assert!("RUST".equals_ci("rust"));
171        assert!(!"Hello".equals_ci("World"));
172        assert!("".equals_ci(""));
173    }
174
175    #[test]
176    fn test_equals_ci_alphanum() {
177        assert!("Start-Up".equals_ci_alphanum("startup"));
178        assert!("hello---world".equals_ci_alphanum("HELLOWORLD"));
179        assert!(!"abc".equals_ci_alphanum("xyz"));
180    }
181
182    #[test]
183    fn test_starts_with_ci() {
184        assert!("Hello World".starts_with_ci("hello"));
185        assert!("Hello World".starts_with_ci("HELLO"));
186        assert!(!"Hello World".starts_with_ci("world"));
187        assert!("Hello".starts_with_ci(""));
188    }
189
190    #[test]
191    fn test_starts_with_ci_alphanum() {
192        assert!("_Cat-image".starts_with_ci_alphanum("cat"));
193        assert!("---Hello!!!".starts_with_ci_alphanum("hello"));
194        assert!(!"Dog-photo".starts_with_ci_alphanum("cat"));
195    }
196
197    #[test]
198    fn test_ends_with_ci() {
199        assert!("Hello World".ends_with_ci("WORLD"));
200        assert!("photo.JPG".ends_with_ci(".jpg"));
201        assert!(!"photo.png".ends_with_ci(".jpg"));
202        assert!("Hello".ends_with_ci(""));
203    }
204
205    #[test]
206    fn test_ends_with_ci_alphanum() {
207        assert!("photo-file.JPG!!!".ends_with_ci_alphanum("jpg"));
208        assert!("data---CSV".ends_with_ci_alphanum("csv"));
209        assert!(!"photo.png".ends_with_ci_alphanum("jpg"));
210    }
211
212    #[test]
213    fn test_contains_ci() {
214        assert!("Hello World".contains_ci("LO WO"));
215        assert!("ABCDEF".contains_ci("cde"));
216        assert!(!"Hello".contains_ci("xyz"));
217        assert!("Hello".contains_ci(""));
218    }
219
220    #[test]
221    fn test_contains_ci_alphanum() {
222        assert!("He--llo Wo!!rld".contains_ci_alphanum("lloworld"));
223        assert!("a-b-c-d".contains_ci_alphanum("ABCD"));
224        assert!(!"Hello".contains_ci_alphanum("xyz"));
225    }
226
227    #[test]
228    fn test_find_matched_indices() {
229        assert_eq!("abcabc".find_matched_indices("bc"), vec![1, 4]);
230        assert_eq!("hello".find_matched_indices("ll"), vec![2]);
231        assert_eq!("hello".find_matched_indices("xyz"), vec![]);
232        assert_eq!("aaa".find_matched_indices("a"), vec![0, 1, 2]);
233    }
234
235    #[test]
236    fn test_find_char_indices() {
237        assert_eq!("a.b.c".find_char_indices('.'), vec![1, 3]);
238        assert_eq!("hello".find_char_indices('l'), vec![2, 3]);
239        assert_eq!("hello".find_char_indices('z'), vec![]);
240    }
241
242    #[test]
243    fn test_contains_type() {
244        assert!("abc123".contains_type(CharType::DecDigit));
245        assert!(!"abcdef".contains_type(CharType::DecDigit));
246        assert!("Hello World".contains_type(CharType::Spaces));
247        assert!(!"HelloWorld".contains_type(CharType::Spaces));
248        assert!("hello!".contains_type(CharType::Punctuation));
249    }
250
251    #[test]
252    fn test_contains_types() {
253        assert!("abc".contains_types(&[CharType::DecDigit, CharType::Alpha]));
254        assert!(!"123".contains_types(&[CharType::Alpha, CharType::Spaces]));
255        assert!("hello world".contains_types(&[CharType::Spaces, CharType::Upper]));
256    }
257
258    #[test]
259    fn test_starts_with_type() {
260        assert!("123abc".starts_with_type(CharType::DecDigit));
261        assert!(!"abc123".starts_with_type(CharType::DecDigit));
262        assert!("Hello".starts_with_type(CharType::Upper));
263        assert!(!"hello".starts_with_type(CharType::Upper));
264        assert!(!("".starts_with_type(CharType::Alpha)));
265    }
266
267    #[test]
268    fn test_starts_with_types() {
269        assert!("Hello".starts_with_types(&[CharType::Upper, CharType::DecDigit]));
270        assert!("9lives".starts_with_types(&[CharType::Upper, CharType::DecDigit]));
271        assert!(!"hello".starts_with_types(&[CharType::Upper, CharType::DecDigit]));
272        assert!(!("".starts_with_types(&[CharType::Alpha])));
273    }
274
275    #[test]
276    fn test_ends_with_type() {
277        assert!("abc9".ends_with_type(CharType::DecDigit));
278        assert!(!"abc".ends_with_type(CharType::DecDigit));
279        assert!("hellO".ends_with_type(CharType::Upper));
280        assert!(!("".ends_with_type(CharType::Alpha)));
281    }
282
283    #[test]
284    fn test_ends_with_types() {
285        assert!("hello!".ends_with_types(&[CharType::Punctuation, CharType::DecDigit]));
286        assert!("test9".ends_with_types(&[CharType::Punctuation, CharType::DecDigit]));
287        assert!(!"hello".ends_with_types(&[CharType::Punctuation, CharType::DecDigit]));
288        assert!(!("".ends_with_types(&[CharType::Alpha])));
289    }
290}