Showing the changes to git-object/src (1 of 3 files). Show all
Unified · Split
| @@ -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 |