winnow_regex/
bytes.rs

1use crate::{CapturesParser, Error, Regex, RegexParser};
2use core::fmt::Debug;
3use winnow::{
4    error::ParserError,
5    stream::{Offset, Stream, StreamIsPartial},
6};
7
8pub use crate::Captures;
9
10pub trait BytesRegexPattern {
11    type Error;
12    type Output;
13
14    fn try_into_regex(self) -> Result<Self::Output, Self::Error>;
15
16    /// Converts the pattern into a regex, panicking if it fails.
17    /// ## Panics
18    ///
19    /// Panics if the regex pattern fails to compile.
20    fn into_regex(self) -> Self::Output
21    where
22        Self: Sized,
23        Self::Error: Debug,
24    {
25        self.try_into_regex()
26            .unwrap_or_else(|e| panic!("failed to compile regex for bytes parser: {:?}", e))
27    }
28}
29
30impl BytesRegexPattern for &str {
31    type Error = Error;
32    type Output = regex::bytes::Regex;
33
34    #[inline(always)]
35    fn try_into_regex(self) -> Result<Self::Output, Self::Error> {
36        Ok(Self::Output::new(self)?)
37    }
38}
39
40/// A `&[u8]`-oriented version of [`winnow_regex::regex`].
41///
42/// This parser matches the beginning of a byte stream (`&[u8]`)
43/// using a regular expression compiled with [`regex::bytes::Regex`].
44/// It returns the matching slice if the regex matches at offset 0.
45///
46/// For more usage details, see [`winnow_regex::regex`].
47///
48/// # Panics
49///
50/// Panics if the regex pattern fails to compile.
51///
52/// # Example
53///
54/// ```
55/// use winnow::prelude::*;
56/// use winnow_regex::bytes::regex;
57///
58/// fn digits<'i>(input: &mut &'i [u8]) -> ModalResult<&'i [u8]> {
59///     regex(r"^\d+").parse_next(input)
60/// }
61///
62/// assert_eq!(digits.parse_peek(b"123abc"), Ok((&b"abc"[..], &b"123"[..])));
63/// ```
64#[inline(always)]
65pub fn regex<'h, Input, Re, Error>(re: Re) -> RegexParser<'h, Input, Re::Output, Error>
66where
67    Input: StreamIsPartial + Stream + Offset + Clone,
68    Re: BytesRegexPattern,
69    Re::Output: Regex<Haystack<'h> = <Input as Stream>::Slice>,
70    Re::Error: Debug,
71    Error: ParserError<Input> + 'static,
72{
73    let re = re.into_regex();
74
75    RegexParser {
76        re,
77        _marker: core::marker::PhantomData,
78    }
79}
80
81/// A `&[u8]`-oriented version of [`winnow_regex::captures`].
82///
83/// This parser matches and extracts capture groups from the beginning of a byte stream (`&[u8]`)
84/// using a regular expression compiled with [`regex::bytes::Regex`].
85/// If the regex matches at offset 0, all capture groups are returned.
86///
87/// For full semantics and error behavior, see [`winnow_regex::captures`].
88///
89/// # Panics
90///
91/// Panics if the regex pattern fails to compile.
92///
93/// # Example
94///
95/// ```
96/// use winnow::prelude::*;
97/// use winnow_regex::bytes::{captures, Captures};
98///
99/// fn coords(input: &mut &[u8]) -> ModalResult<(u32, u32)> {
100///     captures(r"^(\d+),(\d+)")
101///         .map(|c| {
102///             let x = std::str::from_utf8(&c[1]).unwrap().parse().unwrap();
103///             let y = std::str::from_utf8(&c[2]).unwrap().parse().unwrap();
104///             (x, y)
105///         })
106///         .parse_next(input)
107/// }
108///
109/// assert_eq!(coords.parse_peek(b"42,99 done"), Ok((&b" done"[..], (42, 99))));
110/// ```
111#[inline(always)]
112pub fn captures<'h, Input, Re, Error>(re: Re) -> CapturesParser<'h, Input, Re::Output, Error>
113where
114    Input: StreamIsPartial + Stream + Offset + Clone,
115    Re: BytesRegexPattern,
116    Re::Output: Regex<Haystack<'h> = <Input as Stream>::Slice>,
117    Re::Error: Debug,
118    Error: ParserError<Input> + 'static,
119{
120    let re = re.into_regex();
121
122    CapturesParser {
123        re,
124        _marker: core::marker::PhantomData,
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use winnow::error::{ContextError, ErrMode, Needed};
132    use winnow::prelude::*;
133
134    #[test]
135    fn partial() {
136        use winnow::stream::Partial;
137        fn partial<'i>(i: &mut Partial<&'i [u8]>) -> ModalResult<&'i [u8], ContextError> {
138            regex(r"^\d+").parse_next(i)
139        }
140        assert_eq!(
141            partial.parse_peek(Partial::new(&b"123abc"[..])),
142            Ok((Partial::new(&b"abc"[..]), &b"123"[..]))
143        );
144        assert_eq!(
145            partial.parse_peek(Partial::new(&b"123"[..])),
146            Err(ErrMode::Incomplete(Needed::Unknown))
147        );
148    }
149
150    #[test]
151    fn test_re() {
152        let re = regex::Regex::new(r"\d+").unwrap();
153        assert!(re.find_at("1abc123", 0).is_some());
154        assert!(re.find_at("1abc123", 1).is_some());
155        assert!(re.find("abc123").is_some());
156    }
157}