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 fn match_one(&self, pattern: &Self::Pattern) -> Option<Self::Out>;
26
27 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
114pub 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 pat.next_if(|_| cur == dash);
174
175 first = *cur;
176 }
177
178 false
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn basic_pattern() {
187 let datas = [
188 ("a", 'a'),
189 ("ab", 'a'),
190 ("ba", 'a'),
191 ("bac", 'a'),
192 ("bca", 'a'),
193 ("a-e", 'c'),
194 ("a-c", 'c'),
195 ("a-bc", 'c'),
196 ("a-bc", 'a'),
197 ("a-bc", 'b'),
198 ("你好", '你'),
199 ("你好", '好'),
200 ("a-", 'a'),
201 ("a-", '-'),
202 ("-a", '-'),
203 ("-", '-'),
204 ];
205
206 for (pat, val) in datas {
207 assert!(match_impl(pat.chars(), &val), "{pat:?}, {val:?}");
208 }
209 }
210
211 #[test]
212 fn basic_not_pattern() {
213 let datas = [
214 ("", 'a'),
215 ("a-b", '-'),
216 ("a-", 'c'),
217 ("a-", 'b'),
218 ];
219
220 for (pat, val) in datas {
221 assert!(! match_impl(pat.chars(), &val), "{pat:?}, {val:?}");
222 }
223 }
224
225 #[test]
226 fn first_pattern() {
227 let datas = [
228 ("a", "a"),
229 ("你", "你好"),
230 ("a-c", "a"),
231 ("a-c", "b"),
232 ];
233
234 for (pat, val) in datas {
235 assert!(any(pat, val));
236 }
237 }
238
239 #[test]
240 fn first_not_pattern_rest() {
241 let datas = [
242 ("a", "da"),
243 ("好", "你好"),
244 ("a-c", "da"),
245 ("a-c", "db"),
246 ("ab", "db"),
247 ];
248
249 for (pat, val) in datas {
250 assert!(! any(pat, val));
251 }
252 }
253
254 #[test]
255 fn first_not_pattern_empty() {
256 let datas = [
257 ("a", ""),
258 ("a-c", ""),
259 ];
260
261 for (pat, val) in datas {
262 assert!(! any(pat, val));
263 }
264 }
265
266 #[test]
267 fn dash_range() {
268 let datas = [
269 ("+--", "+"),
270 ("+--", ","),
271 ("+--", "-"),
272 ("+--a", "+"),
273 ("+--a", ","),
274 ("+--a", "-"),
275 ("+--a", "a"),
276 ("--0", "-"),
277 ("--0", "."),
278 ("--0", "/"),
279 ("--0", "0"),
280 ("--0a", "-"),
281 ("--0a", "."),
282 ("--0a", "/"),
283 ("--0a", "0"),
284 ("--0a", "a"),
285 ];
286
287 for (pat, val) in datas {
288 assert!(any(pat, val));
289 }
290 }
291
292 #[test]
293 fn dash_range_not_pat() {
294 let datas = [
295 ("+--a", "0"),
296 ("---a", "0"),
297 ];
298
299 for (pat, val) in datas {
300 assert!(! any(pat, val));
301 }
302 }
303}