Skip to main content

arch_pkg_text/parse/
desc.rs

1use super::{ParseWithIssues, PartialParse, PartialParseResult};
2use crate::desc::{
3    FieldName, ParseRawFieldError, ParsedField, Query, QueryMut, RawField,
4    misc::{ReuseAdvice, True},
5};
6use derive_more::{Display, Error};
7use lines_inclusive::{LinesInclusive, LinesInclusiveIter};
8use pipe_trait::Pipe;
9
10macro_rules! def_struct {
11    ($(
12        $(#[$attrs:meta])*
13        $field:ident $(,)? $(;)?
14    )*) => {
15        /// Parsed data of a `desc` file text.
16        ///
17        /// Every function call in [`Query`] and [`QueryMut`] is constant time.
18        #[derive(Debug, Default, Clone, Copy)]
19        #[allow(non_snake_case, reason = "We don't access the field names directly, keep it simple.")]
20        pub struct ParsedDesc<'a> {$(
21            $(#[$attrs])*
22            $field: Option<&'a str>,
23        )*}
24
25        impl<'a> ParsedDesc<'a> {
26            /// Get a raw value from the querier.
27            fn get_raw_value(&self, field_name: FieldName) -> Option<&'a str> {
28                match field_name {$(
29                    FieldName::$field => self.$field.filter(|value| !value.is_empty()),
30                )*}
31            }
32
33            /// Add a raw value into the querier.
34            fn set_raw_value(&mut self, field_name: FieldName, raw_value: &'a str) {
35                match field_name {$(
36                    FieldName::$field => if self.$field.is_none() {
37                        self.$field = Some(raw_value);
38                    },
39                )*}
40            }
41        }
42    };
43}
44
45def_struct!(
46    FileName Name Base Version Description Groups
47    CompressedSize InstalledSize Md5Checksum Sha256Checksum
48    PgpSignature Url License Architecture BuildDate Packager
49    Dependencies CheckDependencies MakeDependencies OptionalDependencies
50    Provides Conflicts Replaces
51);
52
53/// Error type of [`ParsedDesc::parse`].
54#[derive(Debug, Display, Error, Clone, Copy)]
55pub enum DescParseError<'a> {
56    #[display("Input has no field")]
57    NoField,
58    #[display("Receive a value without field: {_0:?}")]
59    ValueWithoutField(#[error(not(source))] &'a str),
60}
61
62/// Issue that may arise during parsing.
63#[derive(Debug, Clone, Copy)]
64pub enum DescParseIssue<'a> {
65    NoField,
66    FirstLineIsNotAField(&'a str, ParseRawFieldError),
67    UnknownField(RawField<'a>),
68}
69
70impl<'a> DescParseIssue<'a> {
71    /// Return `Ok(())` if the issue was [`DescParseIssue::UnknownField`],
72    /// or return an `Err` of [`DescParseError`] otherwise.
73    ///
74    /// This function is the default issue handler for [`ParsedDesc`].
75    pub fn ignore_unknown_field(self) -> Result<(), DescParseError<'a>> {
76        Err(match self {
77            DescParseIssue::NoField => DescParseError::NoField,
78            DescParseIssue::FirstLineIsNotAField(line, _) => {
79                DescParseError::ValueWithoutField(line)
80            }
81            DescParseIssue::UnknownField(_) => return Ok(()),
82        })
83    }
84}
85
86impl<'a> ParsedDesc<'a> {
87    /// Parse a `desc` file text, [unknown fields are ignored](DescParseIssue::ignore_unknown_field).
88    pub fn parse(text: &'a str) -> Result<Self, DescParseError<'a>> {
89        ParsedDesc::partial_parse(text).try_into_complete()
90    }
91
92    /// Parse a `desc` file text with a callback that handle [parsing issues](DescParseIssue).
93    pub fn parse_with_issues<HandleIssue, Error>(
94        text: &'a str,
95        mut handle_issue: HandleIssue,
96    ) -> PartialParseResult<ParsedDesc<'a>, Error>
97    where
98        HandleIssue: FnMut(DescParseIssue<'a>) -> Result<(), Error>,
99    {
100        let mut parsed = ParsedDesc::default();
101        let mut lines = text.lines_inclusive();
102
103        macro_rules! return_or {
104            ($issue:expr, $alternative:expr) => {
105                match handle_issue($issue) {
106                    Err(error) => return PartialParseResult::new_partial(parsed, error),
107                    Ok(()) => $alternative,
108                }
109            };
110        }
111
112        // parse the first field
113        let (first_line, first_field) = loop {
114            let Some(first_line) = lines.next() else {
115                return_or!(
116                    DescParseIssue::NoField,
117                    return PartialParseResult::new_complete(parsed)
118                );
119            };
120            let first_field = match first_line.trim().pipe(RawField::parse_raw) {
121                Ok(first_field) => first_field,
122                Err(error) => {
123                    return_or!(
124                        DescParseIssue::FirstLineIsNotAField(first_line, error),
125                        continue
126                    )
127                }
128            };
129            break (first_line, first_field);
130        };
131
132        // parse the remaining values and fields.
133        let mut current_field = Some((first_field, first_line));
134        while let Some((field, field_line)) = current_field {
135            let (value_length, next_field) = ParsedDesc::parse_next(&mut lines);
136            let value_start_offset =
137                field_line.as_ptr() as usize + field_line.len() - text.as_ptr() as usize;
138            let value_end_offset = value_start_offset + value_length;
139            if let Ok(field) = field.to_parsed::<FieldName>() {
140                let value = text[value_start_offset..value_end_offset].trim();
141                parsed.set_raw_value(*field.name(), value);
142            } else {
143                return_or!(DescParseIssue::UnknownField(field), ())
144            }
145            current_field = next_field;
146        }
147
148        PartialParseResult::new_complete(parsed)
149    }
150
151    /// Parse a value until the end of input or when a [`RawField`] is found.
152    ///
153    /// This function returns a tuple of the length of the value and the next field.
154    fn parse_next(
155        remaining_lines: &mut LinesInclusiveIter<'a>,
156    ) -> (usize, Option<(RawField<'a>, &'a str)>) {
157        let mut value_length = 0;
158
159        for line in remaining_lines {
160            if let Ok(field) = line.trim().pipe(RawField::parse_raw) {
161                return (value_length, Some((field, line)));
162            }
163            value_length += line.len();
164        }
165
166        (value_length, None)
167    }
168}
169
170/// Try parsing a `desc` text, [unknown fields are ignored](DescParseIssue::ignore_unknown_field), partial success means error.
171impl<'a> TryFrom<&'a str> for ParsedDesc<'a> {
172    /// Error that occurs when parsing fails or incomplete.
173    type Error = DescParseError<'a>;
174    /// Try parsing a `desc` text, [unknown fields are ignored](DescParseIssue::ignore_unknown_field), partial success means error.
175    fn try_from(text: &'a str) -> Result<Self, Self::Error> {
176        ParsedDesc::parse(text)
177    }
178}
179
180impl<'a> PartialParse<&'a str> for ParsedDesc<'a> {
181    type Error = DescParseError<'a>;
182    fn partial_parse(input: &'a str) -> PartialParseResult<Self, Self::Error> {
183        ParsedDesc::parse_with_issues(input, DescParseIssue::ignore_unknown_field)
184    }
185}
186
187impl<'a, HandleIssue, Error> ParseWithIssues<&'a str, HandleIssue, Error> for ParsedDesc<'a>
188where
189    HandleIssue: FnMut(DescParseIssue<'a>) -> Result<(), Error>,
190{
191    fn parse_with_issues(
192        input: &'a str,
193        handle_issue: HandleIssue,
194    ) -> PartialParseResult<Self, Error> {
195        ParsedDesc::parse_with_issues(input, handle_issue)
196    }
197}
198
199impl<'a> Query<'a> for ParsedDesc<'a> {
200    fn query_raw_text(&self, field: ParsedField) -> Option<&'a str> {
201        self.get_raw_value(*field.name())
202    }
203}
204
205impl<'a> QueryMut<'a> for ParsedDesc<'a> {
206    fn query_raw_text_mut(&mut self, field: ParsedField) -> Option<&'a str> {
207        self.query_raw_text(field)
208    }
209}
210
211impl ReuseAdvice for ParsedDesc<'_> {
212    /// [`ParsedDesc`] costs O(n) time to construct (n being text length).
213    /// Performing a lookup on it costs O(1) time.
214    ///
215    /// This struct is designed to be reused.
216    type ShouldReuse = True;
217}