char_classes/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3
4#[doc = include_str!("../README.md")]
5pub trait MatchOne: FirstElem {
6    type Pattern: ?Sized;
7
8    /// Match one element or first element
9    ///
10    /// Match `'-'` Please write at the beginning or end, e.g `"a-z-"`
11    ///
12    /// # Examples
13    ///
14    /// ```
15    /// # use char_classes::MatchOne;
16    /// assert_eq!(Some('a'), 'a'.match_one("a-c"));
17    /// assert_eq!(Some('b'), 'b'.match_one("a-c"));
18    /// assert_eq!(None,      '-'.match_one("a-c"));
19    /// assert_eq!(Some('-'), '-'.match_one("a-"));
20    ///
21    /// assert_eq!(Some('a'), "ab".match_one("a"));
22    /// assert_eq!(None,      "ab".match_one("b"));
23    /// ```
24    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out>;
25
26    /// Call [`.match_one(pattern).is_some()`](#tymethod.match_one)
27    fn is_match_one(&self, pattern: &Self::Pattern) -> bool {
28        self.match_one(pattern).is_some()
29    }
30}
31
32impl<T: MatchOne> MatchOne for Option<T> {
33    type Pattern = T::Pattern;
34
35    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
36        self.as_ref().and_then(|val| val.match_one(pattern))
37    }
38}
39
40impl<T: MatchOne + ?Sized> MatchOne for &T {
41    type Pattern = T::Pattern;
42
43    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
44        (**self).match_one(pattern)
45    }
46}
47
48impl<T: MatchOne + ?Sized> MatchOne for &mut T {
49    type Pattern = T::Pattern;
50
51    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
52        (**self).match_one(pattern)
53    }
54}
55
56impl MatchOne for char {
57    type Pattern = str;
58
59    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
60        match_impl(pattern.chars(), self).then_some(*self)
61    }
62}
63
64impl MatchOne for u8 {
65    type Pattern = [u8];
66
67    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
68        match_impl(pattern.iter().copied(), self).then_some(*self)
69    }
70}
71
72impl MatchOne for str {
73    type Pattern = str;
74
75    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
76        self.chars().next().match_one(pattern)
77    }
78}
79
80impl MatchOne for () {
81    type Pattern = ();
82
83    fn match_one(&self, _pattern: &Self::Pattern) -> Option<Self::Out> {
84        None
85    }
86}
87
88impl<T: MatchOne> MatchOne for [T] {
89    type Pattern = T::Pattern;
90
91    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
92        self.first().match_one(pattern)
93    }
94}
95
96impl<T: MatchOne, const N: usize> MatchOne for [T; N] {
97    type Pattern = T::Pattern;
98
99    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
100        self.first().match_one(pattern)
101    }
102}
103
104macro_rules! impl_first_elem_trivial {
105    ($($ty:ty),+ $(,)?) => {
106        $(
107            impl FirstElem for $ty {
108                type Out = $ty;
109
110                fn first_elem(&self) -> Option<Self::Out> {
111                    Some(*self)
112                }
113            }
114        )+
115    };
116}
117
118/// Get the first element
119pub trait FirstElem {
120    type Out;
121
122    fn first_elem(&self) -> Option<Self::Out>;
123}
124
125impl_first_elem_trivial! {
126    u8,
127    u16,
128    u32,
129    u64,
130    u128,
131    i8,
132    i16,
133    i32,
134    i64,
135    i128,
136    char,
137    f32,
138    f64,
139}
140
141impl FirstElem for () {
142    type Out = core::convert::Infallible;
143
144    fn first_elem(&self) -> Option<Self::Out> {
145        None
146    }
147}
148
149impl<T: FirstElem + ?Sized> FirstElem for &T {
150    type Out = T::Out;
151
152    fn first_elem(&self) -> Option<Self::Out> {
153        (**self).first_elem()
154    }
155}
156
157impl<T: FirstElem + ?Sized> FirstElem for &mut T {
158    type Out = T::Out;
159
160    fn first_elem(&self) -> Option<Self::Out> {
161        (**self).first_elem()
162    }
163}
164
165impl<T: FirstElem> FirstElem for Option<T> {
166    type Out = T::Out;
167
168    fn first_elem(&self) -> Option<Self::Out> {
169        self.as_ref().and_then(T::first_elem)
170    }
171}
172
173impl FirstElem for str {
174    type Out = char;
175
176    fn first_elem(&self) -> Option<Self::Out> {
177        self.chars().next()
178    }
179}
180
181impl<T: FirstElem> FirstElem for [T] {
182    type Out = T::Out;
183
184    fn first_elem(&self) -> Option<Self::Out> {
185        self.first().and_then(T::first_elem)
186    }
187}
188
189impl<T: FirstElem, const N: usize> FirstElem for [T; N] {
190    type Out = T::Out;
191
192    fn first_elem(&self) -> Option<Self::Out> {
193        self.first().and_then(T::first_elem)
194    }
195}
196
197/// Call [`MatchOne::is_match_one`]
198///
199/// Match `'-'` Please write at the beginning or end, e.g `"a-z-"`
200///
201/// # Examples
202///
203/// ```
204/// # use char_classes::any;
205/// assert!(any("ab",       'a'));
206/// assert!(any("ab",       'b'));
207/// assert!(any("a-c",      'a'));
208/// assert!(any("a-c",      'b'));
209/// assert!(any("a-ce-f",   'e'));
210/// assert!(any("a-ce-",    '-'));
211/// assert!(any("a-ce-",    'e'));
212/// assert!(any("a-c",      Some('b')));
213/// assert!(any("a-c",      ['b', 'd']));
214/// assert!(any("a-c",      "bd"));
215///
216/// assert!(! any("a-c",    '-'));
217/// assert!(! any("a-ce-",  'f'));
218///
219/// assert!(! any("a-c",    None::<char>));
220/// assert!(! any("a-c",    ['d', 'b']));
221/// assert!(! any("a-c",    "db"));
222/// ```
223pub fn any<T>(pattern: impl AsRef<T::Pattern>, val: T) -> bool
224where T: MatchOne,
225{
226    val.is_match_one(pattern.as_ref())
227}
228
229fn match_impl<I>(pattern: I, val: &I::Item) -> bool
230where I: IntoIterator,
231      I::Item: PartialOrd + From<u8> + Copy,
232{
233    let pat = &mut pattern.into_iter().peekable();
234    let dash = &I::Item::from(b'-');
235
236    let Some(mut first) = pat.next() else {
237        return false;
238    };
239
240    if first == *val {
241        return true;
242    }
243
244    while let Some(ref cur) = pat.next() {
245        let peek = pat.peek();
246
247        if cur == dash && peek
248            .is_some_and(|peek| {
249                (first..=*peek).contains(val)
250            })
251        || cur == val && peek.is_none_or(|_| cur != dash)
252        {
253            return true;
254        }
255
256        pat.next_if(|_| cur == dash);
257
258        first = *cur;
259    }
260
261    false
262}
263
264#[cfg(test)]
265mod tests {
266    use super::*;
267
268    #[test]
269    fn basic_pattern() {
270        let datas = [
271            ("a", 'a'),
272            ("ab", 'a'),
273            ("ba", 'a'),
274            ("bac", 'a'),
275            ("bca", 'a'),
276            ("a-e", 'c'),
277            ("a-c", 'c'),
278            ("a-bc", 'c'),
279            ("a-bc", 'a'),
280            ("a-bc", 'b'),
281            ("你好", '你'),
282            ("你好", '好'),
283            ("a-", 'a'),
284            ("a-", '-'),
285            ("-a", '-'),
286            ("-", '-'),
287        ];
288
289        for (pat, val) in datas {
290            assert!(match_impl(pat.chars(), &val), "{pat:?}, {val:?}");
291        }
292    }
293
294    #[test]
295    fn basic_not_pattern() {
296        let datas = [
297            ("", 'a'),
298            ("a-b", '-'),
299            ("a-", 'c'),
300            ("a-", 'b'),
301        ];
302
303        for (pat, val) in datas {
304            assert!(! match_impl(pat.chars(), &val), "{pat:?}, {val:?}");
305        }
306    }
307
308    #[test]
309    fn first_pattern() {
310        let datas = [
311            ("a", "a"),
312            ("你", "你好"),
313            ("a-c", "a"),
314            ("a-c", "b"),
315        ];
316
317        for (pat, val) in datas {
318            assert!(any(pat, val));
319        }
320    }
321
322    #[test]
323    fn first_not_pattern_rest() {
324        let datas = [
325            ("a", "da"),
326            ("好", "你好"),
327            ("a-c", "da"),
328            ("a-c", "db"),
329            ("ab", "db"),
330        ];
331
332        for (pat, val) in datas {
333            assert!(! any(pat, val));
334        }
335    }
336
337    #[test]
338    fn first_not_pattern_empty() {
339        let datas = [
340            ("a", ""),
341            ("a-c", ""),
342        ];
343
344        for (pat, val) in datas {
345            assert!(! any(pat, val));
346        }
347    }
348
349    #[test]
350    fn dash_range() {
351        let datas = [
352            ("+--", "+"),
353            ("+--", ","),
354            ("+--", "-"),
355            ("+--a", "+"),
356            ("+--a", ","),
357            ("+--a", "-"),
358            ("+--a", "a"),
359            ("--0", "-"),
360            ("--0", "."),
361            ("--0", "/"),
362            ("--0", "0"),
363            ("--0a", "-"),
364            ("--0a", "."),
365            ("--0a", "/"),
366            ("--0a", "0"),
367            ("--0a", "a"),
368        ];
369
370        for (pat, val) in datas {
371            assert!(any(pat, val));
372        }
373    }
374
375    #[test]
376    fn dash_range_not_pat() {
377        let datas = [
378            ("+--a", "0"),
379            ("---a", "0"),
380        ];
381
382        for (pat, val) in datas {
383            assert!(! any(pat, val));
384        }
385    }
386}