git_object/
parse.rs

1use bstr::{BStr, BString, ByteVec};
2use nom::{
3    bytes::complete::{is_not, tag, take_until, take_while_m_n},
4    combinator::{peek, recognize},
5    error::{context, ContextError, ParseError},
6    multi::many1_count,
7    sequence::{preceded, terminated, tuple},
8    IResult,
9};
10
11use crate::ByteSlice;
12
13pub(crate) const NL: &[u8] = b"\n";
14pub(crate) const SPACE: &[u8] = b" ";
15const SPACE_OR_NL: &[u8] = b" \n";
16
17pub(crate) fn any_header_field_multi_line<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
18    i: &'a [u8],
19) -> IResult<&'a [u8], (&'a [u8], BString), E> {
20    let (i, (k, o)) = context(
21        "name <multi-line-value>",
22        peek(tuple((
23            terminated(is_not(SPACE_OR_NL), tag(SPACE)),
24            recognize(tuple((
25                is_not(NL),
26                tag(NL),
27                many1_count(terminated(tuple((tag(SPACE), take_until(NL))), tag(NL))),
28            ))),
29        ))),
30    )(i)?;
31    assert!(!o.is_empty(), "we have parsed more than one value here");
32    let end = &o[o.len() - 1] as *const u8 as usize;
33    let start_input = &i[0] as *const u8 as usize;
34
35    let bytes = o[..o.len() - 1].as_bstr();
36    let mut out = BString::from(Vec::with_capacity(bytes.len()));
37    let mut lines = bytes.lines();
38    out.push_str(lines.next().expect("first line"));
39    for line in lines {
40        out.push(b'\n');
41        out.push_str(&line[1..]); // cut leading space
42    }
43    Ok((&i[end - start_input + 1..], (k, out)))
44}
45
46pub(crate) fn header_field<'a, T, E: ParseError<&'a [u8]>>(
47    i: &'a [u8],
48    name: &'static [u8],
49    parse_value: impl Fn(&'a [u8]) -> IResult<&'a [u8], T, E>,
50) -> IResult<&'a [u8], T, E> {
51    terminated(preceded(terminated(tag(name), tag(SPACE)), parse_value), tag(NL))(i)
52}
53
54pub(crate) fn any_header_field<'a, T, E: ParseError<&'a [u8]>>(
55    i: &'a [u8],
56    parse_value: impl Fn(&'a [u8]) -> IResult<&'a [u8], T, E>,
57) -> IResult<&'a [u8], (&'a [u8], T), E> {
58    terminated(
59        tuple((terminated(is_not(SPACE_OR_NL), tag(SPACE)), parse_value)),
60        tag(NL),
61    )(i)
62}
63
64fn is_hex_digit_lc(b: u8) -> bool {
65    matches!(b, b'0'..=b'9' | b'a'..=b'f')
66}
67
68pub fn hex_hash<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], &'a BStr, E> {
69    take_while_m_n(
70        git_hash::Kind::shortest().len_in_hex(),
71        git_hash::Kind::longest().len_in_hex(),
72        is_hex_digit_lc,
73    )(i)
74    .map(|(i, hex)| (i, hex.as_bstr()))
75}
76
77pub(crate) fn signature<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
78    i: &'a [u8],
79) -> IResult<&'a [u8], git_actor::SignatureRef<'a>, E> {
80    git_actor::signature::decode(i)
81}