| @@ -7,22 +7,31 @@ use crate::{ · 6 unmodified lines |
| 7 | | 7 | |
| 8 | /// | 8 | /// |
| 9 | pub mod body { | 9 | pub mod body { |
| | 10 | use nom::{ |
| | 11 | bytes::complete::{tag, take_until1}, |
| | 12 | combinator::all_consuming, |
| | 13 | error::ParseError, |
| | 14 | sequence::terminated, |
| | 15 | IResult, |
| | 16 | }; |
| | 17 | |
| 10 | use crate::{ | 18 | use crate::{ |
| 11 | bstr::{BStr, ByteSlice}, | 19 | bstr::{BStr, ByteSlice}, |
| 12 | commit::message::BodyRef, | 20 | commit::message::BodyRef, |
| 13 | }; | 21 | }; |
| | 22 | use nom::error::ErrorKind; |
| 14 | | 23 | |
| 15 | /// An iterator over trailers as parsed from a commit message body. | 24 | /// An iterator over trailers as parsed from a commit message body. |
| 16 | /// | 25 | /// |
| 17 | /// lines with parsing failures will be skipped | 26 | /// lines with parsing failures will be skipped |
| 18 | pub struct Trailers<'a> { | 27 | pub struct Trailers<'a> { |
| 19 | cursor: &'a [u8], | 28 | pub(crate) cursor: &'a [u8], |
| 20 | } | 29 | } |
| 21 | | 30 | |
| 22 | /// A trailer as parsed from the commit message body. | 31 | /// A trailer as parsed from the commit message body. |
| 23 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] | 32 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] |
| 24 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] | 33 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] |
| 25 | pub struct Trailer<'a> { | 34 | pub struct TrailerRef<'a> { |
| 26 | /// The name of the trailer, like "Signed-off-by", up to the separator ": " | 35 | /// The name of the trailer, like "Signed-off-by", up to the separator ": " |
| 27 | #[cfg_attr(feature = "serde1", serde(borrow))] | 36 | #[cfg_attr(feature = "serde1", serde(borrow))] |
| 28 | pub token: &'a BStr, | 37 | pub token: &'a BStr, |
| @@ -31,13 +40,74 @@ pub mod body { · 2 unmodified lines |
| 31 | pub value: &'a BStr, | 40 | pub value: &'a BStr, |
| 32 | } | 41 | } |
| 33 | | 42 | |
| | 43 | fn parse_single_line_trailer<'a, E: ParseError<&'a [u8]>>( |
| | 44 | i: &'a [u8], |
| | 45 | ) -> IResult<&'a [u8], (&'a BStr, &'a BStr), E> { |
| | 46 | let (value, token) = terminated(take_until1(b":".as_ref()), tag(b": "))(i.trim_end())?; |
| | 47 | if token.trim_end().len() != token.len() { |
| | 48 | Err(nom::Err::Failure(E::from_error_kind(i, ErrorKind::Fail))) |
| | 49 | } else { |
| | 50 | Ok((&[], (token.as_bstr(), value.as_bstr()))) |
| | 51 | } |
| | 52 | } |
| | 53 | |
| | 54 | #[cfg(test)] |
| | 55 | mod test_parse_trailer { |
| | 56 | use super::*; |
| | 57 | |
| | 58 | fn parse(input: &str) -> (&BStr, &BStr) { |
| | 59 | parse_single_line_trailer::<()>(input.as_bytes()).unwrap().1 |
| | 60 | } |
| | 61 | |
| | 62 | #[test] |
| | 63 | fn simple_newline() { |
| | 64 | assert_eq!(parse("foo: bar\n"), ("foo".into(), "bar".into())); |
| | 65 | } |
| | 66 | |
| | 67 | #[test] |
| | 68 | fn simple_non_ascii_no_newline() { |
| | 69 | assert_eq!(parse("🤗: 🎉"), ("🤗".into(), "🎉".into())); |
| | 70 | } |
| | 71 | |
| | 72 | #[test] |
| | 73 | fn with_lots_of_whitespace_newline() { |
| | 74 | assert_eq!( |
| | 75 | parse("hello foo: bar there \n"), |
| | 76 | ("hello foo".into(), "bar there".into()) |
| | 77 | ); |
| | 78 | } |
| | 79 | |
| | 80 | #[test] |
| | 81 | fn whitespace_before_token_is_error() { |
| | 82 | assert!(parse_single_line_trailer::<()>(b"foo : bar").is_err()) |
| | 83 | } |
| | 84 | |
| | 85 | #[test] |
| | 86 | fn simple_newline_windows() { |
| | 87 | assert_eq!(parse("foo: bar\r\n"), ("foo".into(), "bar".into())); |
| | 88 | } |
| | 89 | } |
| | 90 | |
| 34 | impl<'a> Iterator for Trailers<'a> { | 91 | impl<'a> Iterator for Trailers<'a> { |
| 35 | type Item = Trailer<'a>; | 92 | type Item = TrailerRef<'a>; |
| 36 | | 93 | |
| 37 | fn next(&mut self) -> Option<Self::Item> { | 94 | fn next(&mut self) -> Option<Self::Item> { |
| 38 | if self.cursor.is_empty() { | 95 | if self.cursor.is_empty() { |
| 39 | return None; | 96 | return None; |
| 40 | } | 97 | } |
| | 98 | for line in self.cursor.lines_with_terminator() { |
| | 99 | self.cursor = &self.cursor[line.len()..]; |
| | 100 | if let Some(trailer) = |
| | 101 | all_consuming(parse_single_line_trailer::<()>)(line) |
| | 102 | .ok() |
| | 103 | .map(|(_, (token, value))| TrailerRef { |
| | 104 | token: token.trim().as_bstr(), |
| | 105 | value: value.trim().as_bstr(), |
| | 106 | }) |
| | 107 | { |
| | 108 | return Some(trailer); |
| | 109 | } |
| | 110 | } |
| 41 | None | 111 | None |
| 42 | } | 112 | } |
| 43 | } | 113 | } |
| @@ -75,7 +145,7 @@ mod decode { · 31 unmodified lines |
| 75 | | 145 | |
| 76 | use crate::bstr::{BStr, ByteSlice}; | 146 | use crate::bstr::{BStr, ByteSlice}; |
| 77 | | 147 | |
| 78 | fn newline<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], &'a [u8], E> { | 148 | pub(crate) fn newline<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], &'a [u8], E> { |
| 79 | alt((tag(b"\r\n"), tag(b"\n")))(i) | 149 | alt((tag(b"\r\n"), tag(b"\n")))(i) |
| 80 | } | 150 | } |
| 81 | | 151 | |
| @@ -149,10 +219,7 @@ impl<'a> MessageRef<'a> { · 67 unmodified lines |
| 149 | | 219 | |
| 150 | /// Further parse the body into into non-trailer and trailers, which can be iterated from the returned [`BodyRef`]. | 220 | /// Further parse the body into into non-trailer and trailers, which can be iterated from the returned [`BodyRef`]. |
| 151 | pub fn body(&self) -> Option<BodyRef<'a>> { | 221 | pub fn body(&self) -> Option<BodyRef<'a>> { |
| 152 | self.body.map(|b| BodyRef { | 222 | self.body.map(|b| BodyRef::from_bytes(b)) |
| 153 | body_without_trailer: b, | | |
| 154 | start_of_trailer: &[], | | |
| 155 | }) | | |
| 156 | } | 223 | } |
| 157 | } | 224 | } |
| 158 | | 225 | |
| @@ -204,6 +271,15 @@ pub struct BodyRef<'a> { · 45 unmodified lines |
| 204 | start_of_trailer: &'a [u8], | 271 | start_of_trailer: &'a [u8], |
| 205 | } | 272 | } |
| 206 | | 273 | |
| | 274 | impl<'a> BodyRef<'a> { |
| | 275 | /// Return an iterator over the trailers parsed from the last paragraph of the body. May be empty. |
| | 276 | pub fn trailers(&self) -> body::Trailers<'a> { |
| | 277 | body::Trailers { |
| | 278 | cursor: self.start_of_trailer, |
| | 279 | } |
| | 280 | } |
| | 281 | } |
| | 282 | |
| 207 | impl<'a> AsRef<BStr> for BodyRef<'a> { | 283 | impl<'a> AsRef<BStr> for BodyRef<'a> { |
| 208 | fn as_ref(&self) -> &BStr { | 284 | fn as_ref(&self) -> &BStr { |
| 209 | self.body_without_trailer | 285 | self.body_without_trailer |