winnow/ascii/caseless.rs
1use crate::error::ParserError;
2use crate::stream::{Compare, CompareResult};
3use crate::stream::{SliceLen, Stream, StreamIsPartial};
4use crate::Parser;
5use crate::Result;
6
7/// Mark a value as case-insensitive for ASCII characters
8///
9/// # Example
10/// ```rust
11/// # use winnow::prelude::*;
12/// # use winnow::ascii::Caseless;
13///
14/// fn parser<'s>(s: &mut &'s str) -> ModalResult<&'s str> {
15/// Caseless("hello").parse_next(s)
16/// }
17///
18/// assert_eq!(parser.parse_peek("Hello, World!"), Ok((", World!", "Hello")));
19/// assert_eq!(parser.parse_peek("hello, World!"), Ok((", World!", "hello")));
20/// assert_eq!(parser.parse_peek("HeLlo, World!"), Ok((", World!", "HeLlo")));
21/// assert!(parser.parse_peek("Some").is_err());
22/// assert!(parser.parse_peek("").is_err());
23/// ```
24#[derive(Copy, Clone, Debug)]
25pub struct Caseless<T>(pub T);
26
27impl Caseless<&str> {
28 /// Get the byte-representation of this case-insensitive value
29 #[inline(always)]
30 pub fn as_bytes(&self) -> Caseless<&[u8]> {
31 Caseless(self.0.as_bytes())
32 }
33}
34
35impl<S: SliceLen> SliceLen for Caseless<S> {
36 #[inline(always)]
37 fn slice_len(&self) -> usize {
38 self.0.slice_len()
39 }
40}
41
42impl<'b> Compare<Caseless<&'b [u8]>> for &[u8] {
43 #[inline]
44 fn compare(&self, t: Caseless<&'b [u8]>) -> CompareResult {
45 if t.0
46 .iter()
47 .zip(*self)
48 .any(|(a, b)| !a.eq_ignore_ascii_case(b))
49 {
50 CompareResult::Error
51 } else if self.len() < t.slice_len() {
52 CompareResult::Incomplete
53 } else {
54 CompareResult::Ok(t.slice_len())
55 }
56 }
57}
58
59impl<const LEN: usize> Compare<Caseless<[u8; LEN]>> for &[u8] {
60 #[inline(always)]
61 fn compare(&self, t: Caseless<[u8; LEN]>) -> CompareResult {
62 self.compare(Caseless(&t.0[..]))
63 }
64}
65
66impl<'b, const LEN: usize> Compare<Caseless<&'b [u8; LEN]>> for &[u8] {
67 #[inline(always)]
68 fn compare(&self, t: Caseless<&'b [u8; LEN]>) -> CompareResult {
69 self.compare(Caseless(&t.0[..]))
70 }
71}
72
73impl<'b> Compare<Caseless<&'b str>> for &[u8] {
74 #[inline(always)]
75 fn compare(&self, t: Caseless<&'b str>) -> CompareResult {
76 self.compare(Caseless(t.0.as_bytes()))
77 }
78}
79
80impl Compare<Caseless<u8>> for &[u8] {
81 #[inline]
82 fn compare(&self, t: Caseless<u8>) -> CompareResult {
83 match self.first() {
84 Some(c) if t.0.eq_ignore_ascii_case(c) => CompareResult::Ok(t.slice_len()),
85 Some(_) => CompareResult::Error,
86 None => CompareResult::Incomplete,
87 }
88 }
89}
90
91impl Compare<Caseless<char>> for &[u8] {
92 #[inline(always)]
93 fn compare(&self, t: Caseless<char>) -> CompareResult {
94 self.compare(Caseless(t.0.encode_utf8(&mut [0; 4]).as_bytes()))
95 }
96}
97
98impl<'b> Compare<Caseless<&'b str>> for &str {
99 #[inline(always)]
100 fn compare(&self, t: Caseless<&'b str>) -> CompareResult {
101 self.as_bytes().compare(t.as_bytes())
102 }
103}
104
105impl Compare<Caseless<char>> for &str {
106 #[inline(always)]
107 fn compare(&self, t: Caseless<char>) -> CompareResult {
108 self.as_bytes().compare(t)
109 }
110}
111
112/// This is a shortcut for [`literal`][crate::token::literal].
113///
114/// # Example
115/// ```rust
116/// # use winnow::prelude::*;
117/// # use winnow::{error::ErrMode, error::ContextError, error::Needed};
118/// # use winnow::combinator::alt;
119/// # use winnow::token::take;
120/// use winnow::ascii::Caseless;
121///
122/// fn parser<'s>(s: &mut &'s [u8]) -> ModalResult<&'s [u8]> {
123/// alt((Caseless(&"hello"[..]), take(5usize))).parse_next(s)
124/// }
125///
126/// assert_eq!(parser.parse_peek(&b"Hello, World!"[..]), Ok((&b", World!"[..], &b"Hello"[..])));
127/// assert_eq!(parser.parse_peek(&b"hello, World!"[..]), Ok((&b", World!"[..], &b"hello"[..])));
128/// assert_eq!(parser.parse_peek(&b"HeLlo, World!"[..]), Ok((&b", World!"[..], &b"HeLlo"[..])));
129/// assert_eq!(parser.parse_peek(&b"Something"[..]), Ok((&b"hing"[..], &b"Somet"[..])));
130/// assert!(parser.parse_peek(&b"Some"[..]).is_err());
131/// assert!(parser.parse_peek(&b""[..]).is_err());
132/// ```
133impl<'s, I, E: ParserError<I>> Parser<I, <I as Stream>::Slice, E> for Caseless<&'s [u8]>
134where
135 I: Compare<Caseless<&'s [u8]>> + StreamIsPartial,
136 I: Stream,
137{
138 #[inline(always)]
139 fn parse_next(&mut self, i: &mut I) -> Result<<I as Stream>::Slice, E> {
140 crate::token::literal(*self).parse_next(i)
141 }
142}
143
144/// This is a shortcut for [`literal`][crate::token::literal].
145///
146/// # Example
147/// ```rust
148/// # use winnow::prelude::*;
149/// # use winnow::{error::ErrMode, error::ContextError, error::Needed};
150/// # use winnow::combinator::alt;
151/// # use winnow::token::take;
152/// use winnow::ascii::Caseless;
153///
154/// fn parser<'s>(s: &mut &'s [u8]) -> ModalResult<&'s [u8]> {
155/// alt((Caseless(b"hello"), take(5usize))).parse_next(s)
156/// }
157///
158/// assert_eq!(parser.parse_peek(&b"Hello, World!"[..]), Ok((&b", World!"[..], &b"Hello"[..])));
159/// assert_eq!(parser.parse_peek(&b"hello, World!"[..]), Ok((&b", World!"[..], &b"hello"[..])));
160/// assert_eq!(parser.parse_peek(&b"HeLlo, World!"[..]), Ok((&b", World!"[..], &b"HeLlo"[..])));
161/// assert_eq!(parser.parse_peek(&b"Something"[..]), Ok((&b"hing"[..], &b"Somet"[..])));
162/// assert!(parser.parse_peek(&b"Some"[..]).is_err());
163/// assert!(parser.parse_peek(&b""[..]).is_err());
164/// ```
165impl<'s, I, E: ParserError<I>, const N: usize> Parser<I, <I as Stream>::Slice, E>
166 for Caseless<&'s [u8; N]>
167where
168 I: Compare<Caseless<&'s [u8; N]>> + StreamIsPartial,
169 I: Stream,
170{
171 #[inline(always)]
172 fn parse_next(&mut self, i: &mut I) -> Result<<I as Stream>::Slice, E> {
173 crate::token::literal(*self).parse_next(i)
174 }
175}
176
177/// This is a shortcut for [`literal`][crate::token::literal].
178///
179/// # Example
180/// ```rust
181/// # use winnow::prelude::*;
182/// # use winnow::{error::ErrMode, error::ContextError};
183/// # use winnow::combinator::alt;
184/// # use winnow::token::take;
185/// # use winnow::ascii::Caseless;
186///
187/// fn parser<'s>(s: &mut &'s str) -> ModalResult<&'s str> {
188/// alt((Caseless("hello"), take(5usize))).parse_next(s)
189/// }
190///
191/// assert_eq!(parser.parse_peek("Hello, World!"), Ok((", World!", "Hello")));
192/// assert_eq!(parser.parse_peek("hello, World!"), Ok((", World!", "hello")));
193/// assert_eq!(parser.parse_peek("HeLlo, World!"), Ok((", World!", "HeLlo")));
194/// assert_eq!(parser.parse_peek("Something"), Ok(("hing", "Somet")));
195/// assert!(parser.parse_peek("Some").is_err());
196/// assert!(parser.parse_peek("").is_err());
197/// ```
198impl<'s, I, E: ParserError<I>> Parser<I, <I as Stream>::Slice, E> for Caseless<&'s str>
199where
200 I: Compare<Caseless<&'s str>> + StreamIsPartial,
201 I: Stream,
202{
203 #[inline(always)]
204 fn parse_next(&mut self, i: &mut I) -> Result<<I as Stream>::Slice, E> {
205 crate::token::literal(*self).parse_next(i)
206 }
207}