Skip to main content

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}