Skip to main content

alpm_srcinfo/source_info/
parser.rs

1//! The parser for SRCINFO data.
2//!
3//! It returns a rather raw line-based, but already typed representation of the contents.
4//! The representation is not useful for end-users as it provides data that is not yet validated.
5use std::str::FromStr;
6
7use alpm_parsers::{iter_str_context, prelude::*};
8use alpm_types::{
9    Architecture,
10    Backup,
11    Changelog,
12    Epoch,
13    Group,
14    Install,
15    License,
16    MakepkgOption,
17    Name,
18    OpenPGPIdentifier,
19    OptionalDependency,
20    PackageDescription,
21    PackageRelation,
22    PackageRelease,
23    PackageVersion,
24    RelationOrSoname,
25    RelativeFilePath,
26    SkippableChecksum,
27    Source,
28    Url,
29    digests::{Blake2b512, Crc32Cksum, Md5, Sha1, Sha224, Sha256, Sha384, Sha512},
30};
31use strum::{EnumString, VariantNames};
32use winnow::{
33    ascii::{alpha1, alphanumeric1, line_ending, multispace0, newline, space0, till_line_ending},
34    combinator::{alt, cut_err, eof, fail, opt, peek, preceded, repeat, repeat_till, terminated},
35    error::{ErrMode, ParserError},
36    token::take_until,
37};
38
39/// Recognizes the ` = ` delimiter between keywords.
40fn delimiter<'a>(input: &mut Input<'a>) -> PResult<'a, &'a str> {
41    cut_err(" = ")
42        .description("Expected an equal sign surrounded by spaces: ' = '.")
43        .layer("delimiter")
44        .parse_next(input)
45}
46
47/// Recognizes all content until the end of line.
48///
49/// # Note
50///
51/// This function is called after a ` = ` has been recognized using [`delimiter`].
52/// It extends upon winnow's [`till_line_ending`] by also consuming the newline character.
53/// [`till_line_ending`]: <https://docs.rs/winnow/latest/winnow/ascii/fn.till_line_ending.html>
54fn till_line_end<'a>(input: &mut Input<'a>) -> PResult<'a, &'a str> {
55    // Get the content til the end of line.
56    let out = till_line_ending.parse_next(input)?;
57
58    // Consume the newline. This is `opt` in case we hit `eof`, which is also handled by winnow's
59    // `till_line_ending`
60    opt(line_ending).parse_next(input)?;
61
62    Ok(out)
63}
64
65/// An arbitrarily typed attribute that is specific to an [alpm-architecture].
66///
67/// This type is designed to wrap **any** type that is architecture specific.
68/// For example, all checksums may be architecture specific.
69///
70/// # Example
71///
72/// ```text
73/// # Without architecture
74/// sha256 = 0db1b39fd70097c6733cdcce56b1559ece5521ec1aad9ee1d520dda73eff03d0
75///
76/// # With architecture
77/// sha256_x86_64 = 0db1b39fd70097c6733cdcce56b1559ece5521ec1aad9ee1d520dda73eff03d0
78/// ```
79///
80/// The above would be reflected by the following code.
81/// ```
82/// use std::str::FromStr;
83///
84/// use alpm_srcinfo::source_info::parser::ArchProperty;
85/// use alpm_types::{Sha256Checksum, SystemArchitecture};
86///
87/// # fn main() -> Result<(), alpm_srcinfo::Error> {
88/// let without_architecture = ArchProperty {
89///     architecture: None,
90///     value: Sha256Checksum::from_str(
91///         "0db1b39fd70097c6733cdcce56b1559ece5521ec1aad9ee1d520dda73eff03d0",
92///     )?,
93/// };
94///
95/// let with_architecture = ArchProperty {
96///     architecture: Some(SystemArchitecture::X86_64.into()),
97///     value: Sha256Checksum::from_str(
98///         "0db1b39fd70097c6733cdcce56b1559ece5521ec1aad9ee1d520dda73eff03d0",
99///     )?,
100/// };
101///
102/// # Ok(())
103/// # }
104/// ```
105///
106/// [alpm-architecture]: <https://alpm.archlinux.page/specifications/alpm-architecture.7.html>
107#[derive(Debug)]
108pub struct ArchProperty<T> {
109    /// The optional [alpm-architecture] of the `value`.
110    ///
111    /// If `architecture` is [`None`] it is considered to be `"any"`.
112    /// [alpm-architecture]: <https://alpm.archlinux.page/specifications/alpm-architecture.7.html>
113    pub architecture: Option<Architecture>,
114    /// The architecture specific type.
115    pub value: T,
116}
117
118/// Recognizes and returns the architecture suffix of a keyword, if it exists.
119///
120/// Returns [`None`] if no architecture suffix is found.
121///
122/// ## Examples
123/// ```txt
124/// sha256sums_i386 = 0db1b39fd70097c6733cdcce56b1559ece5521ec1aad9ee1d520dda73eff03d0
125///           ^^^^^
126///         This is the suffix with `i386` being the architecture.
127/// ```
128pub fn architecture_suffix<'a>(input: &mut Input<'a>) -> PResult<'a, Option<Architecture>> {
129    // First up, check if there's an underscore.
130    // If there's none, there's no suffix and we can return early.
131    //
132    // If there is one, we **expect** an architecture to be there and we have to fail hard if that
133    // doesn't work. As such, we expect the Architecture parser to succeed and be followed by
134    // the `delimiter`.
135    opt(preceded('_', cut_err(Architecture::parser_until(" "))))
136        .layer("alpm-architecture keyword suffix")
137        .parse_next(input)
138}
139
140/// Track empty/comment lines
141#[derive(Debug)]
142pub enum Ignored {
143    /// An empty line
144    EmptyLine,
145
146    /// A commented line.
147    Comment(String),
148}
149
150/// A representation of all high-level components of parsed SRCINFO data.
151#[derive(Debug)]
152pub struct SourceInfoContent {
153    /// Empty or comment lines that occur outside of `pkgbase` or `pkgname` sections.
154    pub preceding_lines: Vec<Ignored>,
155    /// The raw package base data.
156    pub package_base: RawPackageBase,
157    /// The list of raw package data.
158    pub packages: Vec<RawPackage>,
159}
160
161impl SourceInfoContent {
162    /// Parses the start of the file in case it contains one or more empty lines or comment lines.
163    ///
164    /// This consumes the first few lines until the `pkgbase` section is hit.
165    /// Further comments and newlines are handled in the scope of the respective `pkgbase`/`pkgname`
166    /// sections.
167    fn preceding_lines_parser<'a>(input: &mut Input<'a>) -> PResult<'a, Ignored> {
168        alt((
169            terminated(("#", take_until(0.., "\n")).take(), line_ending)
170                .map(|s: &str| Ignored::Comment(s.to_string())),
171            terminated(space0, line_ending).map(|_s: &str| Ignored::EmptyLine),
172        ))
173        .parse_next(input)
174    }
175
176    /// Recognizes a complete SRCINFO file from a string slice.
177    ///
178    /// ```rust
179    /// use alpm_parsers::prelude::*;
180    /// use alpm_srcinfo::source_info::parser::SourceInfoContent;
181    /// use winnow::Parser;
182    ///
183    /// # fn main() -> Result<(), alpm_srcinfo::Error> {
184    /// let source_info_data = r#"
185    /// pkgbase = example
186    ///     pkgver = 1.0.0
187    ///     epoch = 1
188    ///     pkgrel = 1
189    ///     pkgdesc = A project that does something
190    ///     url = https://example.org/
191    ///     arch = x86_64
192    ///     depends = glibc
193    ///     optdepends = python: for special-python-script.py
194    ///     makedepends = cmake
195    ///     checkdepends = extra-test-tool
196    ///
197    /// pkgname = example
198    ///     depends = glibc
199    ///     depends = gcc-libs
200    /// "#;
201    ///
202    /// // Parse the given srcinfo content.
203    /// let parsed = SourceInfoContent::parser.parse(Input::new(source_info_data))?;
204    /// # Ok(())
205    /// # }
206    /// ```
207    pub fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, SourceInfoContent> {
208        // Handle any comments or empty lines at the start of the line..
209        let preceding_lines: Vec<Ignored> =
210            repeat(0.., Self::preceding_lines_parser).parse_next(input)?;
211
212        // At the first part of any SRCINFO file, a `pkgbase` section is expected which sets the
213        // base metadata and the default values for all packages to come.
214        let package_base = RawPackageBase::parser
215            .layer("pkgbase section")
216            .parse_next(input)?;
217
218        // Trim newlines or spaces between the pkgbase section and the following pkgname section.
219        let _ = multispace0.parse_next(input)?;
220
221        // Afterwards one or more `pkgname` declarations are to follow.
222        //
223        // `RawPackage::parser` expects all newlines and leading whitespaces to be trimmed.
224        // This is explicitly done once at the start (see above) and implicitly via `terminated` in
225        // between the repeats.
226        multispace0.parse_next(input)?;
227        let (packages, _eof): (Vec<RawPackage>, _) = repeat_till(
228            0..,
229            terminated(RawPackage::parser.layer("pkgname section"), multispace0),
230            eof,
231        )
232        .parse_next(input)?;
233
234        // Fail with a special error if there's no package section.
235        if packages.is_empty() {
236            fail.description("There seems to be no pkgname section.")
237                .parse_next(input)?;
238        }
239
240        Ok(SourceInfoContent {
241            preceding_lines,
242            package_base,
243            packages,
244        })
245    }
246}
247
248/// The parsed contents of a `pkgbase` section in SRCINFO data.
249#[derive(Debug)]
250pub struct RawPackageBase {
251    /// The name of the `pkgbase` section.
252    pub name: Name,
253    /// The properties of the `pkbase` section.
254    pub properties: Vec<PackageBaseProperty>,
255}
256
257impl RawPackageBase {
258    /// Recognizes the entire `pkgbase` section in SRCINFO data.
259    fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, RawPackageBase> {
260        cut_err("pkgbase")
261            .expected_string("pkgbase")
262            .layer("pkgbase section header")
263            .parse_next(input)?;
264
265        delimiter.parse_next(input)?;
266
267        // Get the name of the base package.
268        // Don't use `till_line_ending`, as we want the name to have a length of at least one.
269        let name = cut_err(Name::parser_until_line_ending_inclusive)
270            .expected_string("the name of the base package")
271            .layer("package base name")
272            .parse_next(input)?;
273
274        // Go through the lines after the initial `pkgbase` statement.
275        //
276        // We explicitly use `repeat` to allow backtracking from the inside.
277        // The reason for this is that SRCINFO is no structured data format per se and we have no
278        // clear indicator that a `pkgbase` section just stopped and a `pkgname` section started.
279        //
280        // The only way to detect this is to look for the `pkgname` keyword while parsing lines in
281        // `package_base_line`. If that keyword is detected, we trigger a backtracking error that
282        // results in this `repeat` call to wrap up and return successfully.
283        let properties: Vec<PackageBaseProperty> =
284            repeat(0.., PackageBaseProperty::parser).parse_next(input)?;
285
286        Ok(RawPackageBase { name, properties })
287    }
288}
289
290/// The parsed contents of a `pkgname` section in SRCINFO data.
291#[derive(Debug)]
292pub struct RawPackage {
293    /// The name of the `pkgname` section.
294    pub name: Name,
295    /// The properties of the `pkgname` section.
296    pub properties: Vec<PackageProperty>,
297}
298
299impl RawPackage {
300    /// Recognizes an entire single `pkgname` section in SRCINFO data.
301    ///
302    /// # Note
303    ///
304    /// This parser expects the cursor to directly start at the `pkgname` keyword.
305    /// This means that the caller must trim any leading newlines or whitespaces.
306    fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, RawPackage> {
307        cut_err("pkgname")
308            .expected_string("pkgname")
309            .layer("pkgname section header")
310            .parse_next(input)?;
311
312        cut_err(delimiter).parse_next(input)?;
313
314        // Get the name of the base package.
315        let name = cut_err(Name::parser_until_line_ending_inclusive).parse_next(input)?;
316
317        // Trim any leading whitespaces before the first pass of the `PackageProperty::parser`.
318        space0.parse_next(input)?;
319
320        // Go through the lines after the initial `pkgname` statement.
321        //
322        // # Usage of Backtracking
323        //
324        // We explicitly use `repeat` to allow backtracking from the inside.
325        // The reason for this is that SRCINFO is no structured data format per se and we have no
326        // clear indicator that the current `pkgname` section just stopped and a new `pkgname`
327        // section started.
328        //
329        // The only way to detect this is to look for the `pkgname` keyword while parsing lines in
330        // `package_line`. If that keyword is detected, we trigger a backtracking error that
331        // results in this `repeat` call to wrap up and return successfully.
332        //
333        // # Whitespace handling
334        //
335        // `PackageProperty::parser` expects leading whitespaces of a line to be trimmed.
336        // This is explicitly done once at the start (see above) and implicitly done via
337        // `terminated` in between the repeats.
338        let properties: Vec<PackageProperty> =
339            repeat(0.., terminated(PackageProperty::parser, space0)).parse_next(input)?;
340
341        Ok(RawPackage { name, properties })
342    }
343}
344
345/// Keywords that are exclusive to the `pkgbase` section in SRCINFO data.
346#[derive(Clone, Copy, Debug, EnumString, VariantNames)]
347#[strum(serialize_all = "lowercase")]
348pub(crate) enum ExclusivePackageBaseKeyword {
349    /// Test dependencies.
350    CheckDepends,
351    /// Build dependencies.
352    MakeDepends,
353    /// An alpm-pkgver.
354    PkgVer,
355    /// An alpm-pkgrel.
356    PkgRel,
357    /// An alpm-epoch
358    Epoch,
359    /// Valid Openpgp keys.
360    ValidPGPKeys,
361}
362
363impl ExclusivePackageBaseKeyword {
364    /// Recognizes a [`PackageBaseKeyword`] in an input string slice.
365    pub fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, ExclusivePackageBaseKeyword> {
366        // Read until we hit something non alphabetical.
367        // This could be either a space or a `_` in case there's an architecture specifier.
368        alpha1
369            .try_map(ExclusivePackageBaseKeyword::from_str)
370            .parse_next(input)
371    }
372}
373
374/// A keyword that may appear in a package base section.
375enum PackageBaseKeyword {
376    Source(SourceKeyword),
377    SharedMeta(SharedMetaKeyword),
378    Relation(RelationKeyword),
379    Exclusive(ExclusivePackageBaseKeyword),
380}
381
382impl AlpmParser for PackageBaseKeyword {
383    /// Recognizes a `PackageBaseKeyword` in an `Input`.
384    ///
385    /// # Errors
386    ///
387    /// Returns an error, if none of the `PackageBaseKeyword` variants are contained in `input`.
388    fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, Self> {
389        alt((
390            SourceKeyword::parser.map(PackageBaseKeyword::Source),
391            SharedMetaKeyword::parser.map(PackageBaseKeyword::SharedMeta),
392            RelationKeyword::parser.map(PackageBaseKeyword::Relation),
393            cut_err(ExclusivePackageBaseKeyword::parser.map(PackageBaseKeyword::Exclusive)),
394        ))
395        .context_with(iter_str_context!([
396            ExclusivePackageBaseKeyword::VARIANTS,
397            RelationKeyword::VARIANTS,
398            SharedMetaKeyword::VARIANTS,
399            SourceKeyword::VARIANTS,
400        ]))
401        .layer("package base property keyword")
402        .parse_next(input)
403    }
404}
405
406/// All possible properties of a `pkgbase` section in SRCINFO data.
407///
408/// The ordering of the variants represents the order in which keywords would appear in a SRCINFO
409/// file. This is important as the file format represents stateful data which needs normalization.
410///
411/// The SRCINFO format allows comments and empty lines anywhere in the file.
412/// To produce meaningful error messages for the consumer during data normalization, the line number
413/// on which an error occurred is encoded in the parsed data.
414#[derive(Debug)]
415pub enum PackageBaseProperty {
416    /// An empty line.
417    EmptyLine,
418    /// A commented line.
419    Comment(String),
420    /// A [`SharedMetaProperty`].
421    Meta(SharedMetaProperty),
422    /// A [`PackageVersion`].
423    PackageVersion(PackageVersion),
424    /// A [`PackageRelease`].
425    PackageRelease(PackageRelease),
426    /// An [`Epoch`].
427    PackageEpoch(Epoch),
428    /// An [`OpenPGPIdentifier`].
429    ValidPgpKeys(OpenPGPIdentifier),
430    /// A [`RelationProperty`]
431    Relation(RelationProperty),
432    /// Build-time specific check dependencies.
433    CheckDependency(ArchProperty<PackageRelation>),
434    /// Build-time specific make dependencies.
435    MakeDependency(ArchProperty<PackageRelation>),
436    /// Source file properties
437    Source(SourceProperty),
438}
439
440impl PackageBaseProperty {
441    /// Recognizes any line in the `pkgbase` section of SRCINFO data.
442    ///
443    /// This is a wrapper to separate the logic between comments/empty lines and actual `pkgbase`
444    /// properties.
445    fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, PackageBaseProperty> {
446        // Trim any leading spaces, which are allowed per spec.
447        let _ = multispace0.parse_next(input)?;
448
449        // Look for the `pkgbase` exit condition, which is the start of a `pkgname` section or the
450        // EOL if the pkgname section is missing.
451        // Read the docs above where this function is called for more info.
452        let pkgname = peek(opt(alt(("pkgname", eof)))).parse_next(input)?;
453        if pkgname.is_some() {
454            // If we find a `pkgname` keyword, we know that the current `pkgbase` section finished.
455            // Return a backtrack so the calling parser may wrap up and we can continue with
456            // `pkgname` parsing.
457            return Err(ErrMode::Backtrack(ParserError::from_input(input)));
458        }
459
460        alt((
461            // First of handle any empty lines or comments.
462            preceded(("#", take_until(0.., "\n")), line_ending)
463                .map(|s: &str| PackageBaseProperty::Comment(s.to_string())),
464            preceded(space0, line_ending).map(|_| PackageBaseProperty::EmptyLine),
465            // In case we got text, start parsing properties
466            Self::property_parser,
467        ))
468        .layer("package base property")
469        .parse_next(input)
470    }
471
472    /// Recognizes keyword assignments in the `pkgbase` section in SRCINFO data.
473    ///
474    /// Since there're a lot of keywords and many of them are shared between the `pkgbase` and
475    /// `pkgname` section, the keywords are bundled into somewhat logical groups.
476    ///
477    /// - [`SourceProperty`] are keywords that are related to the `source` keyword, such as
478    ///   checksums.
479    /// - [`SharedMetaProperty`] are keywords that are related to general meta properties of the
480    ///   package.
481    /// - [`RelationProperty`] are keywords that describe the relation of the package to other
482    ///   packages. [`RawPackageBase`] has two special relations that are explicitly handled in
483    ///   [`Self::exclusive_property_parser`].
484    /// - Other fields that're unique to the [`RawPackageBase`] are handled in
485    ///   [`Self::exclusive_property_parser`].
486    fn property_parser<'a>(input: &mut Input<'a>) -> PResult<'a, PackageBaseProperty> {
487        let keyword = PackageBaseKeyword::parser.parse_next(input)?;
488
489        match keyword {
490            PackageBaseKeyword::Source(keyword) => SourceProperty::parser(keyword)
491                .map(PackageBaseProperty::Source)
492                .parse_next(input),
493            PackageBaseKeyword::SharedMeta(keyword) => SharedMetaProperty::parser(keyword)
494                .map(PackageBaseProperty::Meta)
495                .parse_next(input),
496            PackageBaseKeyword::Relation(keyword) => RelationProperty::parser(keyword)
497                .map(PackageBaseProperty::Relation)
498                .parse_next(input),
499            PackageBaseKeyword::Exclusive(keyword) => {
500                PackageBaseProperty::exclusive_property_parser(keyword).parse_next(input)
501            }
502        }
503    }
504
505    /// Recognizes keyword assignments exclusive to the `pkgbase` section in SRCINFO data.
506    ///
507    /// # Note
508    ///
509    /// This function relies on the keyword already having been parsed and expects it as parameter.
510    /// The cursor should be positioned right after the keyword.
511    fn exclusive_property_parser<'a>(
512        keyword: ExclusivePackageBaseKeyword,
513    ) -> impl Parser<Input<'a>, Self, ErrMode<ParseStack<'a>>> {
514        let parser = move |input: &mut Input<'a>| -> PResult<'a, Self> {
515            // Parse a possible architecture suffix for architecture specific fields.
516            let architecture = match keyword {
517                ExclusivePackageBaseKeyword::MakeDepends
518                | ExclusivePackageBaseKeyword::CheckDepends => {
519                    architecture_suffix.parse_next(input)?
520                }
521                _ => None,
522            };
523
524            // Expect the ` = ` separator between the key-value pair
525            let _ = delimiter.parse_next(input)?;
526
527            let property = match keyword {
528                ExclusivePackageBaseKeyword::PkgVer => {
529                    PackageVersion::parser_until_line_ending_inclusive
530                        .map(PackageBaseProperty::PackageVersion)
531                        .parse_next(input)?
532                }
533                ExclusivePackageBaseKeyword::PkgRel => {
534                    PackageRelease::parser_until_line_ending_inclusive
535                        .map(PackageBaseProperty::PackageRelease)
536                        .parse_next(input)?
537                }
538
539                ExclusivePackageBaseKeyword::Epoch => Epoch::parser_until_line_ending_inclusive
540                    .map(PackageBaseProperty::PackageEpoch)
541                    .parse_next(input)?,
542                ExclusivePackageBaseKeyword::ValidPGPKeys => till_line_end
543                    .try_map(OpenPGPIdentifier::from_str)
544                    .map(PackageBaseProperty::ValidPgpKeys)
545                    .layer("OpenPGP identifier")
546                    .parse_next(input)?,
547
548                // Handle `pkgbase` specific package relations.
549                ExclusivePackageBaseKeyword::MakeDepends
550                | ExclusivePackageBaseKeyword::CheckDepends => {
551                    // Read and parse the generic architecture specific PackageRelation.
552                    let value = PackageRelation::parser_until_line_ending.parse_next(input)?;
553                    let arch_property = ArchProperty {
554                        architecture,
555                        value,
556                    };
557
558                    // Now map the generic relation to the specific relation type.
559                    match keyword {
560                        ExclusivePackageBaseKeyword::CheckDepends => {
561                            PackageBaseProperty::CheckDependency(arch_property)
562                        }
563                        ExclusivePackageBaseKeyword::MakeDepends => {
564                            PackageBaseProperty::MakeDependency(arch_property)
565                        }
566                        _ => unreachable!(),
567                    }
568                }
569            };
570
571            Ok(property)
572        };
573
574        cut_err(parser)
575    }
576}
577
578enum PackageKeyword {
579    SharedMeta(SharedMetaKeyword),
580    Relation(RelationKeyword),
581}
582
583impl AlpmParser for PackageKeyword {
584    fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, Self> {
585        alt((
586            RelationKeyword::parser.map(PackageKeyword::Relation),
587            cut_err(SharedMetaKeyword::parser.map(PackageKeyword::SharedMeta)),
588        ))
589        .context_with(iter_str_context!([
590            RelationKeyword::VARIANTS,
591            SharedMetaKeyword::VARIANTS,
592        ]))
593        .layer("package property keyword")
594        .parse_next(input)
595    }
596}
597
598/// All possible properties of a `pkgname` section in SRCINFO data.
599///
600/// It's very similar to [`RawPackageBase`], but with less fields and the possibility to explicitly
601/// set some fields to "empty".
602#[derive(Debug)]
603pub enum PackageProperty {
604    /// An empty line.
605    EmptyLine,
606    /// A commented line.
607    Comment(String),
608    /// A [`SharedMetaProperty`].
609    Meta(SharedMetaProperty),
610    /// A [`RelationProperty`].
611    Relation(RelationProperty),
612    /// A [`ClearableProperty`].
613    Clear(ClearableProperty),
614}
615
616impl PackageProperty {
617    /// Handles any line in a `pkgname` package section.
618    ///
619    /// This is a wrapper to separate the logic between comments/empty lines and actual package
620    /// properties.
621    fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, PackageProperty> {
622        // Look for one of the `pkgname` exit conditions, which is the start of a new `pkgname`
623        // section. Read the docs above where this function is called for more info.
624        let pkgname = peek(opt("pkgname")).parse_next(input)?;
625        if pkgname.is_some() {
626            // If we find a `pkgname` keyword, we know that the current `pkgname` section finished.
627            // Return a backtrack so the calling parser may wrap up.
628            return Err(ErrMode::Backtrack(ParserError::from_input(input)));
629        }
630
631        // Check if we're at the end of the file.
632        // If so, throw a backtrack error.
633        let eof_found = opt(eof).parse_next(input)?;
634        if eof_found.is_some() {
635            return Err(ErrMode::Backtrack(ParserError::from_input(input)));
636        }
637
638        alt((
639            // First of handle any empty lines or comments, which might also occur at the
640            // end of the file.
641            preceded("#", till_line_end).map(|s: &str| PackageProperty::Comment(s.to_string())),
642            line_ending.map(|_| PackageProperty::EmptyLine),
643            // In case we got text, start parsing properties
644            Self::property_parser,
645        ))
646        .layer("package property")
647        .parse_next(input)
648    }
649
650    /// Recognizes keyword assignments in a `pkgname` section in SRCINFO data.
651    ///
652    /// Since there're a lot of keywords and many of them are shared between the `pkgbase` and
653    /// `pkgname` section, the keywords are bundled into somewhat logical groups.
654    ///
655    /// - [`SourceProperty`] are keywords that are related to the `source` keyword, such as
656    ///   checksums.
657    /// - [`SharedMetaProperty`] are keywords that are related to general meta properties of the
658    ///   package.
659    /// - [`RelationProperty`] are keywords that describe the relation of the package to other
660    ///   packages. [`RawPackageBase`] has two special relations that are explicitly handled in that
661    ///   enum.
662    fn property_parser<'a>(input: &mut Input<'a>) -> PResult<'a, PackageProperty> {
663        let keyword = PackageKeyword::parser.parse_next(input)?;
664
665        // The way we handle `ClearableProperty` is a bit imperformant.
666        // Since clearable properties are only allowed to occur in `pkgname` sections, I decided to
667        // not handle clearable properties in the respective property parsers to keep the
668        // code as reusable between `pkgbase` and `pkgname` as possible.
669        //
670        // Hence, we do a check for any clearable properties at the very start. If none is detected,
671        // the actual property setters will be checked afterwards.
672        // This means that every property is preceded by `clearable_property` pass.
673        //
674        // I don't expect that this will result in any significant performance issues, but **if**
675        // this were to ever become an issue, it would be a good start to duplicate all
676        // `*_property` parser functions, where one of them explicitly handles clearable properties.
677        match keyword {
678            PackageKeyword::Relation(keyword) => alt((
679                ClearableProperty::relation_parser(keyword).map(PackageProperty::Clear),
680                RelationProperty::parser(keyword).map(PackageProperty::Relation),
681            ))
682            .parse_next(input),
683            PackageKeyword::SharedMeta(keyword) => alt((
684                ClearableProperty::shared_meta_parser(keyword).map(PackageProperty::Clear),
685                SharedMetaProperty::parser(keyword).map(PackageProperty::Meta),
686            ))
687            .parse_next(input),
688        }
689    }
690}
691
692/// Keywords that may exist both in `pkgbase` and `pkgname` sections in SRCINFO data.
693#[derive(Clone, Copy, Debug, EnumString, VariantNames)]
694#[strum(serialize_all = "lowercase")]
695pub enum SharedMetaKeyword {
696    /// The description of a package.
697    PkgDesc,
698    /// The upstream URL of a package.
699    Url,
700    /// The license of a package.
701    License,
702    /// The alpm-architecture of a package.
703    Arch,
704    /// The path to a changelog file of a package.
705    Changelog,
706    /// The path to an alpm-install-scriptlet of a package.
707    Install,
708    /// The alpm-package-groups a package is part of.
709    Groups,
710    /// The build tool options used when building a package.
711    Options,
712    /// The path of a file in a package that should be backed up.
713    Backup,
714}
715
716impl SharedMetaKeyword {
717    /// Recognizes a [`SharedMetaKeyword`] in a string slice.
718    pub fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, SharedMetaKeyword> {
719        // Read until we hit something non alphabetical.
720        // This could be either a space or a `_` in case there's an architecture specifier.
721        alpha1
722            .try_map(SharedMetaKeyword::from_str)
723            .parse_next(input)
724    }
725}
726
727/// Metadata properties that may be shared between `pkgbase` and `pkgname` sections in SRCINFO data.
728#[derive(Debug)]
729pub enum SharedMetaProperty {
730    /// A [`PackageDescription`].
731    Description(PackageDescription),
732    /// A [`Url`].
733    Url(Url),
734    /// A [`License`].
735    License(License),
736    /// An [`Architecture`].
737    Architecture(Architecture),
738    /// A [`RelativeFilePath`] for a changelog of a package.
739    Changelog(RelativeFilePath),
740    /// A [`RelativeFilePath`] for an alpm-install-scriptlet of a package.
741    Install(RelativeFilePath),
742    /// An alpm-package-group of a package.
743    Group(String),
744    /// A [`MakepkgOption`] used for building a package.
745    Option(MakepkgOption),
746    /// A [`RelativeFilePath`] for file in a package that should be backed up.
747    Backup(RelativeFilePath),
748}
749
750impl SharedMetaProperty {
751    /// Recognizes keyword assignments that may be present in both `pkgbase` and `pkgname` sections
752    /// of SRCINFO data.
753    ///
754    /// # Note
755    ///
756    /// This function relies on the keyword already been parsed and expects it as parameter.
757    /// The cursor should be positioned right after the keyword.
758    fn parser<'a>(
759        keyword: SharedMetaKeyword,
760    ) -> impl Parser<Input<'a>, Self, ErrMode<ParseStack<'a>>> {
761        let parser = move |input: &mut Input<'a>| -> PResult<'a, Self> {
762            // Expect the ` = ` separator between the key-value pair
763            let _ = delimiter.parse_next(input)?;
764
765            let property = match keyword {
766                SharedMetaKeyword::PkgDesc => till_line_end
767                    .map(|s| SharedMetaProperty::Description(PackageDescription::from(s)))
768                    .parse_next(input)?,
769                SharedMetaKeyword::Url => till_line_end
770                    .try_map(Url::from_str)
771                    .map(SharedMetaProperty::Url)
772                    .layer("url")
773                    .parse_next(input)?,
774                SharedMetaKeyword::License => till_line_end
775                    .try_map(License::from_str)
776                    .map(SharedMetaProperty::License)
777                    .layer("license")
778                    .parse_next(input)?,
779                SharedMetaKeyword::Arch => Architecture::parser_until_line_ending_inclusive
780                    .map(SharedMetaProperty::Architecture)
781                    .parse_next(input)?,
782                SharedMetaKeyword::Changelog => till_line_end
783                    .try_map(Changelog::from_str)
784                    .map(SharedMetaProperty::Changelog)
785                    .layer("changelog")
786                    .parse_next(input)?,
787                SharedMetaKeyword::Install => till_line_end
788                    .try_map(Install::from_str)
789                    .map(SharedMetaProperty::Install)
790                    .layer("install file path")
791                    .parse_next(input)?,
792                SharedMetaKeyword::Groups => till_line_end
793                    .map(|s| SharedMetaProperty::Group(Group::from(s)))
794                    .parse_next(input)?,
795                SharedMetaKeyword::Options => MakepkgOption::parser_until_line_ending_inclusive
796                    .map(SharedMetaProperty::Option)
797                    .parse_next(input)?,
798                SharedMetaKeyword::Backup => till_line_end
799                    .try_map(Backup::from_str)
800                    .layer("backup file path")
801                    .map(SharedMetaProperty::Backup)
802                    .parse_next(input)?,
803            };
804
805            Ok(property)
806        };
807
808        cut_err(parser)
809    }
810}
811
812/// Keywords that describe [alpm-package-relations].
813///
814/// [alpm-package-relations]: https://alpm.archlinux.page/specifications/alpm-package-relation.7.html
815#[derive(Clone, Copy, Debug, EnumString, VariantNames)]
816#[strum(serialize_all = "lowercase")]
817pub enum RelationKeyword {
818    /// A run-time dependency.
819    Depends,
820    /// An optional dependency.
821    OptDepends,
822    /// A provision.
823    Provides,
824    /// A conflict.
825    Conflicts,
826    /// A replacement.
827    Replaces,
828}
829
830impl RelationKeyword {
831    /// Recognizes a [`RelationKeyword`] in a string slice.
832    pub fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, RelationKeyword> {
833        // Read until we hit something non alphabetical.
834        // This could be either a space or a `_` in case there's an architecture specifier.
835        alpha1.try_map(RelationKeyword::from_str).parse_next(input)
836    }
837}
838
839/// Properties related to package relations.
840///
841/// This only handles the shared package relations that can be used in both `pkgbase` and `pkgname`
842/// sections.
843/// `pkgbase` specific relations are explicitly handled in the [`RawPackageBase`] enum.
844/// See [alpm-package-relation] for further details on package relations and [alpm-sonamev1] for
845/// information on _soname_ handling.
846/// [alpm-package-relation]: <https://alpm.archlinux.page/specifications/alpm-package-relation.7.html>
847/// [alpm-sonamev1]: <https://alpm.archlinux.page/specifications/alpm-sonamev1.7.html>
848#[derive(Debug)]
849pub enum RelationProperty {
850    /// An [`ArchProperty<RelationOrSoname>`] for a run-time dependency.
851    Dependency(ArchProperty<RelationOrSoname>),
852    /// An [`ArchProperty<OptionalDependency>`] for an optional dependency.
853    OptionalDependency(ArchProperty<OptionalDependency>),
854    /// An [`ArchProperty<RelationOrSoname>`] for a provision.
855    Provides(ArchProperty<RelationOrSoname>),
856    /// An [`ArchProperty<PackageRelation>`] for a conflict.
857    Conflicts(ArchProperty<PackageRelation>),
858    /// An [`ArchProperty<PackageRelation>`] for a replacement.
859    Replaces(ArchProperty<PackageRelation>),
860}
861
862impl RelationProperty {
863    /// Recognizes package relation keyword assignments that may be present in both `pkgbase` and
864    /// `pkgname` sections in SRCINFO data.
865    ///
866    /// # Note
867    ///
868    /// This function relies on the keyword already been parsed and expects it as parameter.
869    /// The cursor should be positioned right after the keyword.
870    fn parser<'a>(
871        keyword: RelationKeyword,
872    ) -> impl Parser<Input<'a>, Self, ErrMode<ParseStack<'a>>> {
873        let parser = move |input: &mut Input<'a>| -> PResult<'a, Self> {
874            // All of these properties can be architecture specific and may have an architecture
875            // suffix. Get it if there's one.
876            let architecture = architecture_suffix.parse_next(input)?;
877
878            // Expect the ` = ` separator between the key-value pair
879            let _ = delimiter.parse_next(input)?;
880
881            let property = match keyword {
882                // Handle these together in a single blob as they all deserialize to the same base
883                // type.
884                RelationKeyword::Conflicts | RelationKeyword::Replaces => {
885                    // Read and parse the generic architecture specific PackageRelation.
886                    let value = PackageRelation::parser_until_line_ending.parse_next(input)?;
887                    let arch_property = ArchProperty {
888                        architecture,
889                        value,
890                    };
891
892                    // Now map the generic relation to the specific relation type.
893                    match keyword {
894                        RelationKeyword::Replaces => RelationProperty::Replaces(arch_property),
895                        RelationKeyword::Conflicts => RelationProperty::Conflicts(arch_property),
896                        _ => unreachable!(),
897                    }
898                }
899                RelationKeyword::Depends | RelationKeyword::Provides => {
900                    // Read and parse the generic architecture specific RelationOrSoname.
901                    let value =
902                        RelationOrSoname::parser_until_line_ending_inclusive.parse_next(input)?;
903                    let arch_property = ArchProperty {
904                        architecture,
905                        value,
906                    };
907
908                    // Now map the generic relation to the specific relation type.
909                    match keyword {
910                        RelationKeyword::Depends => RelationProperty::Dependency(arch_property),
911                        RelationKeyword::Provides => RelationProperty::Provides(arch_property),
912                        _ => unreachable!(),
913                    }
914                }
915                RelationKeyword::OptDepends => {
916                    OptionalDependency::parser_until_line_ending_inclusive
917                        .map(|value| {
918                            RelationProperty::OptionalDependency(ArchProperty {
919                                architecture: architecture.clone(),
920                                value,
921                            })
922                        })
923                        .parse_next(input)?
924                }
925            };
926
927            Ok(property)
928        };
929
930        cut_err(parser)
931    }
932
933    /// Returns the [`Architecture`] of the current variant.
934    ///
935    /// Can be used to extract the architecture without knowing which variant this is.
936    pub fn architecture(&self) -> Option<&Architecture> {
937        match self {
938            RelationProperty::Dependency(arch_property) => &arch_property.architecture,
939            RelationProperty::OptionalDependency(arch_property) => &arch_property.architecture,
940            RelationProperty::Provides(arch_property) => &arch_property.architecture,
941            RelationProperty::Conflicts(arch_property) => &arch_property.architecture,
942            RelationProperty::Replaces(arch_property) => &arch_property.architecture,
943        }
944        .as_ref()
945    }
946}
947
948/// Package source keywords that are exclusive to the `pkgbase` section in SRCINFO data.
949#[derive(Clone, Copy, Debug, EnumString, VariantNames)]
950#[strum(serialize_all = "lowercase")]
951pub enum SourceKeyword {
952    /// A source entry.
953    Source,
954    /// A noextract entry.
955    NoExtract,
956    /// A blake2 hash digest.
957    B2sums,
958    /// An MD-5 hash digest.
959    Md5sums,
960    /// An SHA-1 hash digest.
961    Sha1sums,
962    /// An SHA-224 hash digest.
963    Sha224sums,
964    /// An SHA-256 hash digest.
965    Sha256sums,
966    /// An SHA-384 hash digest.
967    Sha384sums,
968    /// An SHA-512 hash digest.
969    Sha512sums,
970    /// An CRC-32/CKSUM hash digest.
971    Cksums,
972}
973
974impl SourceKeyword {
975    /// Parse a [`SourceKeyword`].
976    pub fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, SourceKeyword> {
977        // Read until we hit something non alphabetical.
978        // This could be either a space or a `_` in case there's an architecture specifier.
979        alphanumeric1
980            .try_map(SourceKeyword::from_str)
981            .parse_next(input)
982    }
983}
984
985/// Properties related to package sources.
986///
987/// Sources and related properties can be architecture specific.
988///
989/// The `source`, `noextract` and checksum related keywords in SRCINFO data correlate in ordering:
990/// `noextract` and any checksum entries are ordered in the same way as the respective `source`
991/// entry they relate to. The representation of this correlation is normalized after initial
992/// parsing.
993#[derive(Debug)]
994pub enum SourceProperty {
995    /// An [`ArchProperty<Source>`] for a source entry.
996    Source(ArchProperty<Source>),
997    /// An [`ArchProperty<String>`] for a noextract entry.
998    NoExtract(String),
999    /// An [`ArchProperty<SkippableChecksum<Blake2b512>>`] for a blake2 hash digest.
1000    B2Checksum(ArchProperty<SkippableChecksum<Blake2b512>>),
1001    /// An [`ArchProperty<SkippableChecksum<Md5>>`] for an MD-5 hash digest.
1002    Md5Checksum(ArchProperty<SkippableChecksum<Md5>>),
1003    /// An [`ArchProperty<SkippableChecksum<Sha1>>`] for a SHA-1 hash digest.
1004    Sha1Checksum(ArchProperty<SkippableChecksum<Sha1>>),
1005    /// An [`ArchProperty<SkippableChecksum<Sha256>>`] for a SHA-256 hash digest.
1006    Sha256Checksum(ArchProperty<SkippableChecksum<Sha256>>),
1007    /// An [`ArchProperty<SkippableChecksum<Sha224>>`] for a SHA-224 hash digest.
1008    Sha224Checksum(ArchProperty<SkippableChecksum<Sha224>>),
1009    /// An [`ArchProperty<SkippableChecksum<Sha384>>`] for a SHA-384 hash digest.
1010    Sha384Checksum(ArchProperty<SkippableChecksum<Sha384>>),
1011    /// An [`ArchProperty<SkippableChecksum<Sha512>>`] for a SHA-512 hash digest.
1012    Sha512Checksum(ArchProperty<SkippableChecksum<Sha512>>),
1013    /// An [`ArchProperty<SkippableChecksum<Crc32Cksum>>`] for a CRC-32/CKSUM hash digest.
1014    CrcChecksum(ArchProperty<SkippableChecksum<Crc32Cksum>>),
1015}
1016
1017impl SourceProperty {
1018    /// Recognizes package source related keyword assignments in SRCINFO data.
1019    ///
1020    /// # Note
1021    ///
1022    /// This function relies on the keyword already been parsed and expects it as parameter.
1023    /// The cursor should be positioned right after the keyword.
1024    fn parser<'a>(keyword: SourceKeyword) -> impl Parser<Input<'a>, Self, ErrMode<ParseStack<'a>>> {
1025        let parser = move |input: &mut Input<'a>| -> PResult<'a, Self> {
1026            let property = match keyword {
1027                SourceKeyword::NoExtract => {
1028                    // Expect the ` = ` separator between the key-value pair
1029                    let _ = delimiter.parse_next(input)?;
1030
1031                    till_line_end
1032                        .map(|s| SourceProperty::NoExtract(s.to_string()))
1033                        .parse_next(input)?
1034                }
1035                SourceKeyword::Source
1036                | SourceKeyword::B2sums
1037                | SourceKeyword::Md5sums
1038                | SourceKeyword::Sha1sums
1039                | SourceKeyword::Sha224sums
1040                | SourceKeyword::Sha256sums
1041                | SourceKeyword::Sha384sums
1042                | SourceKeyword::Sha512sums
1043                | SourceKeyword::Cksums => {
1044                    // All other properties may be architecture specific and thereby have an
1045                    // architecture suffix.
1046                    let architecture = architecture_suffix.parse_next(input)?;
1047
1048                    // Expect the ` = ` separator between the key-value pair
1049                    let _ = delimiter.parse_next(input)?;
1050
1051                    match keyword {
1052                        SourceKeyword::Source => Source::parser_until_line_ending_inclusive
1053                            .map(|value| {
1054                                SourceProperty::Source(ArchProperty {
1055                                    architecture: architecture.clone(),
1056                                    value,
1057                                })
1058                            })
1059                            .parse_next(input)?,
1060                        // all checksum properties are parsed the same way.
1061                        SourceKeyword::B2sums => SourceProperty::B2Checksum(ArchProperty {
1062                            architecture,
1063                            value: SkippableChecksum::parser_until_line_ending.parse_next(input)?,
1064                        }),
1065                        SourceKeyword::Md5sums => SourceProperty::Md5Checksum(ArchProperty {
1066                            architecture,
1067                            value: SkippableChecksum::parser_until_line_ending.parse_next(input)?,
1068                        }),
1069                        SourceKeyword::Sha1sums => SourceProperty::Sha1Checksum(ArchProperty {
1070                            architecture,
1071                            value: SkippableChecksum::parser_until_line_ending.parse_next(input)?,
1072                        }),
1073                        SourceKeyword::Sha224sums => SourceProperty::Sha224Checksum(ArchProperty {
1074                            architecture,
1075                            value: SkippableChecksum::parser_until_line_ending.parse_next(input)?,
1076                        }),
1077                        SourceKeyword::Sha256sums => SourceProperty::Sha256Checksum(ArchProperty {
1078                            architecture,
1079                            value: SkippableChecksum::parser_until_line_ending.parse_next(input)?,
1080                        }),
1081                        SourceKeyword::Sha384sums => SourceProperty::Sha384Checksum(ArchProperty {
1082                            architecture,
1083                            value: SkippableChecksum::parser_until_line_ending.parse_next(input)?,
1084                        }),
1085                        SourceKeyword::Sha512sums => SourceProperty::Sha512Checksum(ArchProperty {
1086                            architecture,
1087                            value: SkippableChecksum::parser_until_line_ending.parse_next(input)?,
1088                        }),
1089                        SourceKeyword::Cksums => SourceProperty::CrcChecksum(ArchProperty {
1090                            architecture,
1091                            value: SkippableChecksum::parser_until_line_ending.parse_next(input)?,
1092                        }),
1093                        SourceKeyword::NoExtract => unreachable!(),
1094                    }
1095                }
1096            };
1097
1098            Ok(property)
1099        };
1100
1101        cut_err(parser)
1102    }
1103}
1104
1105/// Properties used in `pkgname` sections that can be cleared.
1106///
1107/// Some variants of this enum are architecture-specific, as they might only be cleared for a
1108/// specific architecture, but not for another.
1109///
1110/// Clearing a keyword in SRCINFO data is achieved by an empty keyword assignment, e.g.:
1111///
1112/// ```txt
1113/// depends =
1114/// ```
1115#[derive(Clone, Debug)]
1116pub enum ClearableProperty {
1117    /// The description for a package.
1118    Description,
1119    /// The upstream URL for a package.
1120    Url,
1121    /// The licenses that apply to a package.
1122    Licenses,
1123    /// The changelog for a package.
1124    Changelog,
1125    /// The alpm-install-scriptlet for a package.
1126    Install,
1127    /// The alpm-package-groups a package is part of.
1128    Groups,
1129    /// The build tool options used for building a package.
1130    Options,
1131    /// The path to a file in a package that should be backed up.
1132    Backups,
1133    /// The alpm-architecture of run-time dependencies.
1134    Dependencies(Option<Architecture>),
1135    /// The alpm-architecture of optional dependencies.
1136    OptionalDependencies(Option<Architecture>),
1137    /// The alpm-architecture of provisions.
1138    Provides(Option<Architecture>),
1139    /// The alpm-architecture of conflicts.
1140    Conflicts(Option<Architecture>),
1141    /// The alpm-architecture of replacements.
1142    Replaces(Option<Architecture>),
1143}
1144
1145impl ClearableProperty {
1146    /// Recognizes all keyword assignments in SRCINFO data that represent a cleared
1147    /// [`SharedMetaProperty`].
1148    ///
1149    /// A cleared property is represented by a keyword that is assigned an empty value.
1150    /// It indicates that the keyword assignment should remain empty for a given package.
1151    ///
1152    /// Example:
1153    /// ```txt
1154    /// pkgdesc =
1155    /// depends =
1156    /// ```
1157    ///
1158    /// The above properties would indicate that both `pkgdesc` and the `depends` array are to be
1159    /// cleared and left empty for a given package.
1160    ///
1161    /// # Note
1162    ///
1163    /// This function relies on the keyword already been parsed and expects it as parameter.
1164    /// The cursor should be positioned right after the keyword.
1165    fn shared_meta_parser<'a>(
1166        keyword: SharedMetaKeyword,
1167    ) -> impl Parser<Input<'a>, Self, ErrMode<ParseStack<'a>>> {
1168        move |input: &mut Input<'a>| -> PResult<'a, Self> {
1169            // Now check if it's actually a clear.
1170            // This parser fails and backtracks in case there's anything but spaces and a newline
1171            // after the delimiter, which indicates that there's an actual value that is
1172            // set for this property.
1173            let _ = (" =", space0, newline).parse_next(input)?;
1174
1175            let property = match keyword {
1176                // The `Arch` property matches the keyword, but isn't clearable.
1177                SharedMetaKeyword::Arch => {
1178                    return Err(ErrMode::Backtrack(ParserError::from_input(input)));
1179                }
1180                SharedMetaKeyword::PkgDesc => ClearableProperty::Description,
1181                SharedMetaKeyword::Url => ClearableProperty::Url,
1182                SharedMetaKeyword::License => ClearableProperty::Licenses,
1183                SharedMetaKeyword::Changelog => ClearableProperty::Changelog,
1184                SharedMetaKeyword::Install => ClearableProperty::Install,
1185                SharedMetaKeyword::Groups => ClearableProperty::Groups,
1186                SharedMetaKeyword::Options => ClearableProperty::Options,
1187                SharedMetaKeyword::Backup => ClearableProperty::Backups,
1188            };
1189
1190            Ok(property)
1191        }
1192    }
1193
1194    /// Same as [`Self::shared_meta_parser`], but for clearable [RelationProperty].
1195    ///
1196    /// # Note
1197    ///
1198    /// This function relies on the keyword already been parsed and expects it as parameter.
1199    /// The cursor should be positioned right after the keyword.
1200    fn relation_parser<'a>(
1201        keyword: RelationKeyword,
1202    ) -> impl Parser<Input<'a>, Self, ErrMode<ParseStack<'a>>> {
1203        move |input: &mut Input<'a>| -> PResult<'a, Self> {
1204            // All relations may be architecture specific.
1205            let architecture = architecture_suffix.parse_next(input)?;
1206
1207            // Now check if it's actually a clear.
1208            // This parser fails and backtracks in case there's anything but spaces and a newline
1209            // after the delimiter, which indicates that there's an actual value that is
1210            // set for this property.
1211            let _ = (" =", space0, newline).parse_next(input)?;
1212
1213            let property = match keyword {
1214                RelationKeyword::Depends => ClearableProperty::Dependencies(architecture),
1215                RelationKeyword::OptDepends => {
1216                    ClearableProperty::OptionalDependencies(architecture)
1217                }
1218                RelationKeyword::Provides => ClearableProperty::Provides(architecture),
1219                RelationKeyword::Conflicts => ClearableProperty::Conflicts(architecture),
1220                RelationKeyword::Replaces => ClearableProperty::Replaces(architecture),
1221            };
1222
1223            Ok(property)
1224        }
1225    }
1226}