Skip to main content

git_async/object/
header.rs

1use crate::{parsing::ParseResult, subslice_range::SubsliceRange};
2use accessory::Accessors;
3use alloc::vec::Vec;
4use core::iter::FusedIterator;
5use core::ops::Range;
6use nom::{
7    Parser,
8    bytes::complete::take_till,
9    character::complete::{char, newline},
10    combinator::{not, peek},
11    multi::many0,
12    sequence::{delimited, terminated},
13};
14
15/// An arbitrary object header, with a name and a value
16#[derive(Clone, PartialEq, Eq, Accessors)]
17pub struct ObjectHeader<'a> {
18    /// The name of the header
19    #[access(get(cp))]
20    name: &'a [u8],
21    /// The value of the header
22    ///
23    /// Multi-line values (using wrapped lines) are not decoded.
24    #[access(get(cp))]
25    value: &'a [u8],
26}
27
28#[derive(Clone)]
29pub(crate) struct RangeObjectHeader {
30    pub(crate) name: Range<usize>,
31    pub(crate) value: Range<usize>,
32}
33
34impl RangeObjectHeader {
35    pub(crate) fn parser(input: &[u8]) -> ParseResult<&[u8], Vec<Self>> {
36        let header = (
37            delimited(peek(not(newline)), take_till(|c| c == b' '), char(' ')),
38            continued_line,
39        );
40        let mut p = terminated(many0(header), newline);
41        let (rest, raw_headers) = p.parse(input)?;
42        Ok((
43            rest,
44            raw_headers
45                .into_iter()
46                .map(|(name, value)| RangeObjectHeader {
47                    name: input.subslice_range_stable(name).unwrap(),
48                    value: input.subslice_range_stable(value).unwrap(),
49                })
50                .collect(),
51        ))
52    }
53}
54
55/// An iterator over additional headers in an object
56pub struct ObjectHeaderIter<'a> {
57    body: &'a [u8],
58    headers: &'a [RangeObjectHeader],
59    pos: usize,
60}
61
62impl<'a> ObjectHeaderIter<'a> {
63    pub(crate) fn new(body: &'a [u8], headers: &'a [RangeObjectHeader]) -> Self {
64        Self {
65            body,
66            headers,
67            pos: 0,
68        }
69    }
70}
71
72impl<'a> Iterator for ObjectHeaderIter<'a> {
73    type Item = ObjectHeader<'a>;
74
75    fn next(&mut self) -> Option<Self::Item> {
76        let header = self.headers.get(self.pos)?;
77        self.pos += 1;
78        Some(ObjectHeader {
79            name: &self.body[header.name.clone()],
80            value: &self.body[header.value.clone()],
81        })
82    }
83
84    fn size_hint(&self) -> (usize, Option<usize>) {
85        (
86            self.headers.len() - self.pos,
87            Some(self.headers.len() - self.pos),
88        )
89    }
90}
91
92impl FusedIterator for ObjectHeaderIter<'_> {}
93impl ExactSizeIterator for ObjectHeaderIter<'_> {}
94
95#[allow(clippy::unnecessary_wraps)]
96fn continued_line(input: &[u8]) -> ParseResult<&[u8], &[u8]> {
97    let mut slice_pos = input.len();
98    for (pos, window) in input.windows(2).enumerate() {
99        if window[0] == b'\n' && window[1] != b' ' {
100            slice_pos = pos;
101            break;
102        }
103    }
104    Ok((&input[(slice_pos + 1)..], &input[..slice_pos]))
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn parse_continued_line() {
113        let data = b"some stuff\n  continuation\nnext line";
114        let (rest, parsed) = continued_line.parse(data).unwrap();
115        assert_eq!(parsed, b"some stuff\n  continuation");
116        assert_eq!(rest, b"next line");
117    }
118}