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