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 fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out>;
25
26 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
118pub 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
197pub 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}