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}