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 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}