char_classes/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3
4#[doc = include_str!("../README.md")]
5pub trait MatchOne {
6    type Out;
7    type Pattern: ?Sized;
8
9    /// Match one element or first element
10    ///
11    /// Match `'-'` Please write at the beginning or end, e.g `"a-z-"`
12    ///
13    /// # Examples
14    ///
15    /// ```
16    /// # use char_classes::MatchOne;
17    /// assert_eq!(Some('a'), 'a'.match_one("a-c"));
18    /// assert_eq!(Some('b'), 'b'.match_one("a-c"));
19    /// assert_eq!(None,      '-'.match_one("a-c"));
20    /// assert_eq!(Some('-'), '-'.match_one("a-"));
21    ///
22    /// assert_eq!(Some('a'), "ab".match_one("a"));
23    /// assert_eq!(None,      "ab".match_one("b"));
24    /// ```
25    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out>;
26
27    /// Call [`.match_one(pattern).is_some()`](#method.match_one)
28    fn is_match_one(&self, pattern: &Self::Pattern) -> bool {
29        self.match_one(pattern).is_some()
30    }
31}
32
33impl<T: MatchOne> MatchOne for Option<T> {
34    type Out = T::Out;
35    type Pattern = T::Pattern;
36
37    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
38        self.as_ref().and_then(|val| val.match_one(pattern))
39    }
40}
41
42impl<T: MatchOne + ?Sized> MatchOne for &T {
43    type Out = T::Out;
44    type Pattern = T::Pattern;
45
46    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
47        (**self).match_one(pattern)
48    }
49}
50
51impl<T: MatchOne + ?Sized> MatchOne for &mut T {
52    type Out = T::Out;
53    type Pattern = T::Pattern;
54
55    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
56        (**self).match_one(pattern)
57    }
58}
59
60impl MatchOne for char {
61    type Out = char;
62    type Pattern = str;
63
64    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
65        match_impl(pattern.chars(), self).then_some(*self)
66    }
67}
68
69impl MatchOne for u8 {
70    type Out = u8;
71    type Pattern = [u8];
72
73    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
74        match_impl(pattern.iter().copied(), self).then_some(*self)
75    }
76}
77
78impl MatchOne for str {
79    type Out = char;
80    type Pattern = str;
81
82    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
83        self.chars().next().match_one(pattern)
84    }
85}
86
87impl MatchOne for () {
88    type Out = core::convert::Infallible;
89    type Pattern = ();
90
91    fn match_one(&self, _pattern: &Self::Pattern) -> Option<Self::Out> {
92        None
93    }
94}
95
96impl<T: MatchOne> MatchOne for [T] {
97    type Out = T::Out;
98    type Pattern = T::Pattern;
99
100    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
101        self.first().match_one(pattern)
102    }
103}
104
105impl<T: MatchOne, const N: usize> MatchOne for [T; N] {
106    type Out = T::Out;
107    type Pattern = T::Pattern;
108
109    fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out> {
110        self.first().match_one(pattern)
111    }
112}
113
114/// Call [`MatchOne::is_match_one`]
115///
116/// Match `'-'` Please write at the beginning or end, e.g `"a-z-"`
117///
118/// # Examples
119///
120/// ```
121/// # use char_classes::any;
122/// assert!(any("ab",       'a'));
123/// assert!(any("ab",       'b'));
124/// assert!(any("a-c",      'a'));
125/// assert!(any("a-c",      'b'));
126/// assert!(any("a-ce-f",   'e'));
127/// assert!(any("a-ce-",    '-'));
128/// assert!(any("a-ce-",    'e'));
129/// assert!(any("a-c",      Some('b')));
130/// assert!(any("a-c",      ['b', 'd']));
131/// assert!(any("a-c",      "bd"));
132///
133/// assert!(! any("a-c",    '-'));
134/// assert!(! any("a-ce-",  'f'));
135///
136/// assert!(! any("a-c",    None::<char>));
137/// assert!(! any("a-c",    ['d', 'b']));
138/// assert!(! any("a-c",    "db"));
139/// ```
140pub fn any<T>(pattern: impl AsRef<T::Pattern>, val: T) -> bool
141where T: MatchOne,
142{
143    val.is_match_one(pattern.as_ref())
144}
145
146fn match_impl<I>(pattern: I, val: &I::Item) -> bool
147where I: IntoIterator,
148      I::Item: PartialOrd + From<u8> + Copy,
149{
150    let pat = &mut pattern.into_iter().peekable();
151    let dash = &I::Item::from(b'-');
152
153    let Some(mut first) = pat.next() else {
154        return false;
155    };
156
157    if first == *val {
158        return true;
159    }
160
161    while let Some(ref cur) = pat.next() {
162        let peek = pat.peek();
163
164        if cur == dash && peek
165            .is_some_and(|peek| {
166                (first..=*peek).contains(val)
167            })
168        || cur == val && peek.is_none_or(|_| cur != dash)
169        {
170            return true;
171        }
172
173        first = *cur;
174    }
175
176    false
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn basic_pattern() {
185        let datas = [
186            ("a", 'a'),
187            ("ab", 'a'),
188            ("ba", 'a'),
189            ("bac", 'a'),
190            ("bca", 'a'),
191            ("a-e", 'c'),
192            ("a-c", 'c'),
193            ("a-bc", 'c'),
194            ("a-bc", 'a'),
195            ("a-bc", 'b'),
196            ("a-", 'a'),
197            ("a-", '-'),
198            ("-a", '-'),
199            ("-", '-'),
200        ];
201
202        for (pat, val) in datas {
203            assert!(match_impl(pat.chars(), &val), "{pat:?}, {val:?}");
204        }
205    }
206
207    #[test]
208    fn basic_not_pattern() {
209        let datas = [
210            ("", 'a'),
211            ("a-b", '-'),
212            ("a-", 'c'),
213            ("a-", 'b'),
214        ];
215
216        for (pat, val) in datas {
217            assert!(! match_impl(pat.chars(), &val), "{pat:?}, {val:?}");
218        }
219    }
220
221    #[test]
222    fn first_pattern() {
223        let datas = [
224            ("a", "a"),
225            ("a-c", "a"),
226            ("a-c", "b"),
227        ];
228
229        for (pat, val) in datas {
230            assert!(any(pat, val));
231        }
232    }
233
234    #[test]
235    fn first_not_pattern_rest() {
236        let datas = [
237            ("a", "da"),
238            ("a-c", "da"),
239            ("a-c", "db"),
240            ("ab", "db"),
241        ];
242
243        for (pat, val) in datas {
244            assert!(! any(pat, val));
245        }
246    }
247
248    #[test]
249    fn first_not_pattern_empty() {
250        let datas = [
251            ("a", ""),
252            ("a-c", ""),
253        ];
254
255        for (pat, val) in datas {
256            assert!(! any(pat, val));
257        }
258    }
259}