157 lines · 5.8 KB · Rust
RawDownload
mod decode {    use bstr::ByteSlice;    use btoi::btoi;    use nom::{        branch::alt,        bytes::complete::{tag, take, take_until, take_while_m_n},        character::is_digit,        error::{context, ContextError, ParseError},        sequence::{terminated, tuple},        IResult,    };     use crate::{immutable::Signature, Sign, Time};     const SPACE: &[u8] = b" ";     /// Parse a signature from the bytes input `i` using `nom`.    pub fn signature<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(        i: &'a [u8],    ) -> IResult<&'a [u8], Signature<'a>, E> {        let (i, (name, email, time, tzsign, hours, minutes)) = context(            "<name> <<email>> <timestamp> <+|-><HHMM>",            tuple((                context("<name>", terminated(take_until(&b" <"[..]), take(2usize))),                context("<email>", terminated(take_until(&b"> "[..]), take(2usize))),                context("<timestamp>", |i| {                    terminated(take_until(SPACE), take(1usize))(i).and_then(|(i, v)| {                        btoi::<u32>(v)                            .map(|v| (i, v))                            .map_err(|_| nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::MapRes)))                    })                }),                context("+|-", alt((tag(b"-"), tag(b"+")))),                context("HH", |i| {                    take_while_m_n(2usize, 2, is_digit)(i).and_then(|(i, v)| {                        btoi::<i32>(v)                            .map(|v| (i, v))                            .map_err(|_| nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::MapRes)))                    })                }),                context("MM", |i| {                    take_while_m_n(2usize, 2, is_digit)(i).and_then(|(i, v)| {                        btoi::<i32>(v)                            .map(|v| (i, v))                            .map_err(|_| nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::MapRes)))                    })                }),            )),        )(i)?;         debug_assert!(tzsign[0] == b'-' || tzsign[0] == b'+', "parser assure it's +|- only");        let sign = if tzsign[0] == b'-' { Sign::Minus } else { Sign::Plus }; //        let offset = (hours * 3600 + minutes * 60) * if sign == Sign::Minus { -1 } else { 1 };         Ok((            i,            Signature {                name: name.as_bstr(),                email: email.as_bstr(),                time: Time { time, offset, sign },            },        ))    }     #[cfg(test)]    mod tests {        mod parse_signature {            use bstr::ByteSlice;            use git_testtools::to_bstr_err;            use nom::IResult;             use crate::{                immutable::{signature, Signature},                Sign, Time,            };             fn decode(i: &[u8]) -> IResult<&[u8], Signature<'_>, nom::error::VerboseError<&[u8]>> {                signature::decode(i)            }             fn signature(                name: &'static str,                email: &'static str,                time: u32,                sign: Sign,                offset: i32,            ) -> Signature<'static> {                Signature {                    name: name.as_bytes().as_bstr(),                    email: email.as_bytes().as_bstr(),                    time: Time { time, offset, sign },                }            }             #[test]            fn tz_minus() {                assert_eq!(                    decode(b"Sebastian Thiel <byronimo@gmail.com> 1528473343 -0230")                        .expect("parse to work")                        .1,                    signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Minus, -9000)                );            }             #[test]            fn tz_plus() {                assert_eq!(                    decode(b"Sebastian Thiel <byronimo@gmail.com> 1528473343 +0230")                        .expect("parse to work")                        .1,                    signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Plus, 9000)                );            }             #[test]            fn negative_offset_0000() {                assert_eq!(                    decode(b"Sebastian Thiel <byronimo@gmail.com> 1528473343 -0000")                        .expect("parse to work")                        .1,                    signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Minus, 0)                );            }             #[test]            fn empty_name_and_email() {                assert_eq!(                    decode(b" <> 12345 -1215").expect("parse to work").1,                    signature("", "", 12345, Sign::Minus, -44100)                );            }             #[test]            fn invalid_signature() {                assert_eq!(                        decode(b"hello < 12345 -1215")                            .map_err(to_bstr_err)                            .expect_err("parse fails as > is missing")                            .to_string(),                        "Parse error:\nTakeUntil at:  12345 -1215\nin section '<email>', at:  12345 -1215\nin section '<name> <<email>> <timestamp> <+|-><HHMM>', at: hello < 12345 -1215\n"                    );            }             #[test]            fn invalid_time() {                assert_eq!(                        decode(b"hello <> abc -1215")                            .map_err(to_bstr_err)                            .expect_err("parse fails as > is missing")                            .to_string(),                        "Parse error:\nMapRes at: -1215\nin section '<timestamp>', at: abc -1215\nin section '<name> <<email>> <timestamp> <+|-><HHMM>', at: hello <> abc -1215\n"                    );            }        }    }}pub use decode::signature as decode;