use std::str::FromStr;
use alpm_parsers::iter_str_context;
use alpm_types::{
Architecture,
Backup,
Changelog,
Epoch,
Group,
Install,
License,
MakepkgOption,
Name,
OpenPGPIdentifier,
OptionalDependency,
PackageDescription,
PackageRelation,
PackageRelease,
PackageVersion,
RelationOrSoname,
RelativeFilePath,
SkippableChecksum,
Source,
Url,
digests::{Blake2b512, Crc32Cksum, Md5, Sha1, Sha224, Sha256, Sha384, Sha512},
};
use strum::{EnumString, VariantNames};
use winnow::{
ModalResult,
Parser,
ascii::{alpha1, alphanumeric1, line_ending, multispace0, newline, space0, till_line_ending},
combinator::{
alt,
cut_err,
eof,
fail,
opt,
peek,
preceded,
repeat,
repeat_till,
terminated,
trace,
},
error::{ErrMode, ParserError, StrContext, StrContextValue},
token::{take_till, take_until},
};
fn delimiter<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
cut_err(" = ")
.context(StrContext::Label("delimiter"))
.context(StrContext::Expected(StrContextValue::Description(
"an equal sign surrounded by spaces: ' = '.",
)))
.parse_next(input)
}
fn till_line_end<'s>(input: &mut &'s str) -> ModalResult<&'s str> {
let out = till_line_ending.parse_next(input)?;
opt(line_ending).parse_next(input)?;
Ok(out)
}
#[derive(Debug)]
pub struct ArchProperty<T> {
pub architecture: Option<Architecture>,
pub value: T,
}
pub fn architecture_suffix(input: &mut &str) -> ModalResult<Option<Architecture>> {
let underscore = opt('_').parse_next(input)?;
if underscore.is_none() {
return Ok(None);
}
let architecture =
cut_err(take_till(0.., |c| c == ' ' || c == '=').and_then(Architecture::parser))
.parse_next(input)?;
Ok(Some(architecture))
}
#[derive(Debug)]
pub enum Ignored {
EmptyLine,
Comment(String),
}
#[derive(Debug)]
pub struct SourceInfoContent {
pub preceding_lines: Vec<Ignored>,
pub package_base: RawPackageBase,
pub packages: Vec<RawPackage>,
}
impl SourceInfoContent {
fn preceding_lines_parser(input: &mut &str) -> ModalResult<Ignored> {
trace(
"preceding_lines",
alt((
terminated(("#", take_until(0.., "\n")).take(), line_ending)
.map(|s: &str| Ignored::Comment(s.to_string())),
terminated(space0, line_ending).map(|_s: &str| Ignored::EmptyLine),
)),
)
.parse_next(input)
}
pub fn parser(input: &mut &str) -> ModalResult<SourceInfoContent> {
let preceding_lines: Vec<Ignored> =
repeat(0.., Self::preceding_lines_parser).parse_next(input)?;
let package_base = RawPackageBase::parser.parse_next(input)?;
let _ = multispace0.parse_next(input)?;
multispace0.parse_next(input)?;
let (packages, _eof): (Vec<RawPackage>, _) =
repeat_till(0.., terminated(RawPackage::parser, multispace0), eof).parse_next(input)?;
if packages.is_empty() {
fail.context(StrContext::Expected(StrContextValue::Description(
"a pkgname section",
)))
.parse_next(input)?;
}
Ok(SourceInfoContent {
preceding_lines,
package_base,
packages,
})
}
}
#[derive(Debug)]
pub struct RawPackageBase {
pub name: Name,
pub properties: Vec<PackageBaseProperty>,
}
impl RawPackageBase {
fn parser(input: &mut &str) -> ModalResult<RawPackageBase> {
cut_err("pkgbase")
.context(StrContext::Label("pkgbase section header"))
.parse_next(input)?;
cut_err(" = ")
.context(StrContext::Label("pkgbase section header delimiter"))
.context(StrContext::Expected(StrContextValue::Description("' = '")))
.parse_next(input)?;
let name = till_line_end
.and_then(Name::parser)
.context(StrContext::Label("package base name"))
.context(StrContext::Expected(StrContextValue::Description(
"the name of the base package",
)))
.parse_next(input)?;
let properties: Vec<PackageBaseProperty> =
repeat(0.., PackageBaseProperty::parser).parse_next(input)?;
Ok(RawPackageBase { name, properties })
}
}
#[derive(Debug)]
pub struct RawPackage {
pub name: Name,
pub properties: Vec<PackageProperty>,
}
impl RawPackage {
fn parser(input: &mut &str) -> ModalResult<RawPackage> {
cut_err("pkgname")
.context(StrContext::Label("pkgname section header"))
.parse_next(input)?;
cut_err(" = ")
.context(StrContext::Label("pkgname section header delimiter"))
.context(StrContext::Expected(StrContextValue::Description("' = '")))
.parse_next(input)?;
let name = till_line_end
.and_then(Name::parser)
.context(StrContext::Label("package name"))
.context(StrContext::Expected(StrContextValue::Description(
"the name of a package",
)))
.parse_next(input)?;
space0.parse_next(input)?;
let properties: Vec<PackageProperty> =
repeat(0.., terminated(PackageProperty::parser, space0)).parse_next(input)?;
Ok(RawPackage { name, properties })
}
}
#[derive(Debug, EnumString, VariantNames)]
#[strum(serialize_all = "lowercase")]
pub enum PackageBaseKeyword {
CheckDepends,
MakeDepends,
PkgVer,
PkgRel,
Epoch,
ValidPGPKeys,
}
impl PackageBaseKeyword {
pub fn parser(input: &mut &str) -> ModalResult<PackageBaseKeyword> {
trace(
"package_base_keyword",
alpha1.try_map(PackageBaseKeyword::from_str),
)
.parse_next(input)
}
}
#[derive(Debug)]
pub enum PackageBaseProperty {
EmptyLine,
Comment(String),
MetaProperty(SharedMetaProperty),
PackageVersion(PackageVersion),
PackageRelease(PackageRelease),
PackageEpoch(Epoch),
ValidPgpKeys(OpenPGPIdentifier),
RelationProperty(RelationProperty),
CheckDependency(ArchProperty<PackageRelation>),
MakeDependency(ArchProperty<PackageRelation>),
SourceProperty(SourceProperty),
}
impl PackageBaseProperty {
fn parser(input: &mut &str) -> ModalResult<PackageBaseProperty> {
let _ = multispace0.parse_next(input)?;
let pkgname = peek(opt(alt(("pkgname", eof)))).parse_next(input)?;
if pkgname.is_some() {
return Err(ErrMode::Backtrack(ParserError::from_input(input)));
}
trace(
"package_base_line",
alt((
preceded(("#", take_until(0.., "\n")), line_ending)
.map(|s: &str| PackageBaseProperty::Comment(s.to_string())),
preceded(space0, line_ending).map(|_| PackageBaseProperty::EmptyLine),
Self::property_parser,
)),
)
.parse_next(input)
}
fn property_parser(input: &mut &str) -> ModalResult<PackageBaseProperty> {
trace(
"pkgbase_property",
alt((
SourceProperty::parser.map(PackageBaseProperty::SourceProperty),
SharedMetaProperty::parser.map(PackageBaseProperty::MetaProperty),
RelationProperty::parser.map(PackageBaseProperty::RelationProperty),
PackageBaseProperty::exclusive_property_parser,
cut_err(fail)
.context(StrContext::Label("package base property type"))
.context(StrContext::Expected(StrContextValue::Description(
"one of the allowed pkgbase section properties:",
)))
.context_with(iter_str_context!([
PackageBaseKeyword::VARIANTS,
RelationKeyword::VARIANTS,
SharedMetaKeyword::VARIANTS,
SourceKeyword::VARIANTS,
])),
)),
)
.parse_next(input)
}
fn exclusive_property_parser(input: &mut &str) -> ModalResult<PackageBaseProperty> {
let keyword =
trace("exclusive_pkgbase_property", PackageBaseKeyword::parser).parse_next(input)?;
let architecture = match keyword {
PackageBaseKeyword::MakeDepends | PackageBaseKeyword::CheckDepends => {
architecture_suffix.parse_next(input)?
}
_ => None,
};
let _ = delimiter.parse_next(input)?;
let property = match keyword {
PackageBaseKeyword::PkgVer => cut_err(
till_line_end
.and_then(PackageVersion::parser)
.map(PackageBaseProperty::PackageVersion),
)
.parse_next(input)?,
PackageBaseKeyword::PkgRel => cut_err(
till_line_end
.and_then(PackageRelease::parser)
.map(PackageBaseProperty::PackageRelease),
)
.parse_next(input)?,
PackageBaseKeyword::Epoch => cut_err(
till_line_end
.and_then(Epoch::parser)
.map(PackageBaseProperty::PackageEpoch),
)
.parse_next(input)?,
PackageBaseKeyword::ValidPGPKeys => cut_err(
till_line_end
.try_map(OpenPGPIdentifier::from_str)
.map(PackageBaseProperty::ValidPgpKeys),
)
.parse_next(input)?,
PackageBaseKeyword::MakeDepends | PackageBaseKeyword::CheckDepends => {
let value =
cut_err(till_line_end.and_then(PackageRelation::parser)).parse_next(input)?;
let arch_property = ArchProperty {
architecture,
value,
};
match keyword {
PackageBaseKeyword::CheckDepends => {
PackageBaseProperty::CheckDependency(arch_property)
}
PackageBaseKeyword::MakeDepends => {
PackageBaseProperty::MakeDependency(arch_property)
}
_ => unreachable!(),
}
}
};
Ok(property)
}
}
#[derive(Debug)]
pub enum PackageProperty {
EmptyLine,
Comment(String),
MetaProperty(SharedMetaProperty),
RelationProperty(RelationProperty),
Clear(ClearableProperty),
}
impl PackageProperty {
fn parser(input: &mut &str) -> ModalResult<PackageProperty> {
let pkgname = peek(opt("pkgname")).parse_next(input)?;
if pkgname.is_some() {
return Err(ErrMode::Backtrack(ParserError::from_input(input)));
}
let eof_found = opt(eof).parse_next(input)?;
if eof_found.is_some() {
return Err(ErrMode::Backtrack(ParserError::from_input(input)));
}
trace(
"package_line",
alt((
preceded("#", till_line_end).map(|s: &str| PackageProperty::Comment(s.to_string())),
line_ending.map(|_| PackageProperty::EmptyLine),
Self::property_parser,
)),
)
.parse_next(input)
}
fn property_parser(input: &mut &str) -> ModalResult<PackageProperty> {
trace(
"pkgname_property",
alt((
ClearableProperty::relation_parser.map(PackageProperty::Clear),
ClearableProperty::shared_meta_parser.map(PackageProperty::Clear),
SharedMetaProperty::parser.map(PackageProperty::MetaProperty),
RelationProperty::parser.map(PackageProperty::RelationProperty),
cut_err(fail)
.context(StrContext::Label("package property type"))
.context(StrContext::Expected(StrContextValue::Description(
"one of the allowed package section properties:",
)))
.context_with(iter_str_context!([
RelationKeyword::VARIANTS,
SharedMetaKeyword::VARIANTS
])),
)),
)
.parse_next(input)
}
}
#[derive(Debug, EnumString, VariantNames)]
#[strum(serialize_all = "lowercase")]
pub enum SharedMetaKeyword {
PkgDesc,
Url,
License,
Arch,
Changelog,
Install,
Groups,
Options,
Backup,
}
impl SharedMetaKeyword {
pub fn parser(input: &mut &str) -> ModalResult<SharedMetaKeyword> {
trace(
"shared_meta_keyword",
alpha1.try_map(SharedMetaKeyword::from_str),
)
.parse_next(input)
}
}
#[derive(Debug)]
pub enum SharedMetaProperty {
Description(PackageDescription),
Url(Url),
License(License),
Architecture(Architecture),
Changelog(RelativeFilePath),
Install(RelativeFilePath),
Group(String),
Option(MakepkgOption),
Backup(RelativeFilePath),
}
impl SharedMetaProperty {
fn parser(input: &mut &str) -> ModalResult<SharedMetaProperty> {
let keyword = SharedMetaKeyword::parser.parse_next(input)?;
let _ = delimiter.parse_next(input)?;
let property = match keyword {
SharedMetaKeyword::PkgDesc => cut_err(
till_line_end.map(|s| SharedMetaProperty::Description(PackageDescription::from(s))),
)
.parse_next(input)?,
SharedMetaKeyword::Url => cut_err(
till_line_end
.try_map(Url::from_str)
.map(SharedMetaProperty::Url),
)
.parse_next(input)?,
SharedMetaKeyword::License => cut_err(
till_line_end
.try_map(License::from_str)
.map(SharedMetaProperty::License),
)
.parse_next(input)?,
SharedMetaKeyword::Arch => cut_err(
till_line_end
.and_then(Architecture::parser)
.map(SharedMetaProperty::Architecture),
)
.parse_next(input)?,
SharedMetaKeyword::Changelog => cut_err(
till_line_end
.try_map(Changelog::from_str)
.map(SharedMetaProperty::Changelog),
)
.parse_next(input)?,
SharedMetaKeyword::Install => cut_err(
till_line_end
.try_map(Install::from_str)
.map(SharedMetaProperty::Install),
)
.parse_next(input)?,
SharedMetaKeyword::Groups => {
cut_err(till_line_end.map(|s| SharedMetaProperty::Group(Group::from(s))))
.parse_next(input)?
}
SharedMetaKeyword::Options => cut_err(
till_line_end
.try_map(MakepkgOption::from_str)
.map(SharedMetaProperty::Option),
)
.parse_next(input)?,
SharedMetaKeyword::Backup => cut_err(
till_line_end
.try_map(Backup::from_str)
.map(SharedMetaProperty::Backup),
)
.parse_next(input)?,
};
Ok(property)
}
}
#[derive(Debug, EnumString, VariantNames)]
#[strum(serialize_all = "lowercase")]
pub enum RelationKeyword {
Depends,
OptDepends,
Provides,
Conflicts,
Replaces,
}
impl RelationKeyword {
pub fn parser(input: &mut &str) -> ModalResult<RelationKeyword> {
trace(
"relation_keyword",
alpha1.try_map(RelationKeyword::from_str),
)
.parse_next(input)
}
}
#[derive(Debug)]
pub enum RelationProperty {
Dependency(ArchProperty<RelationOrSoname>),
OptionalDependency(ArchProperty<OptionalDependency>),
Provides(ArchProperty<RelationOrSoname>),
Conflicts(ArchProperty<PackageRelation>),
Replaces(ArchProperty<PackageRelation>),
}
impl RelationProperty {
fn parser(input: &mut &str) -> ModalResult<RelationProperty> {
let keyword = RelationKeyword::parser.parse_next(input)?;
let architecture = architecture_suffix.parse_next(input)?;
let _ = delimiter.parse_next(input)?;
let property = match keyword {
RelationKeyword::Conflicts | RelationKeyword::Replaces => {
let value =
cut_err(till_line_end.and_then(PackageRelation::parser)).parse_next(input)?;
let arch_property = ArchProperty {
architecture,
value,
};
match keyword {
RelationKeyword::Replaces => RelationProperty::Replaces(arch_property),
RelationKeyword::Conflicts => RelationProperty::Conflicts(arch_property),
_ => unreachable!(),
}
}
RelationKeyword::Depends | RelationKeyword::Provides => {
let value =
cut_err(till_line_end.try_map(RelationOrSoname::from_str)).parse_next(input)?;
let arch_property = ArchProperty {
architecture,
value,
};
match keyword {
RelationKeyword::Depends => RelationProperty::Dependency(arch_property),
RelationKeyword::Provides => RelationProperty::Provides(arch_property),
_ => unreachable!(),
}
}
RelationKeyword::OptDepends => cut_err(
till_line_end
.and_then(OptionalDependency::parser)
.map(|value| {
RelationProperty::OptionalDependency(ArchProperty {
architecture: architecture.clone(),
value,
})
}),
)
.parse_next(input)?,
};
Ok(property)
}
pub fn architecture(&self) -> Option<&Architecture> {
match self {
RelationProperty::Dependency(arch_property) => &arch_property.architecture,
RelationProperty::OptionalDependency(arch_property) => &arch_property.architecture,
RelationProperty::Provides(arch_property) => &arch_property.architecture,
RelationProperty::Conflicts(arch_property) => &arch_property.architecture,
RelationProperty::Replaces(arch_property) => &arch_property.architecture,
}
.as_ref()
}
}
#[derive(Debug, EnumString, VariantNames)]
#[strum(serialize_all = "lowercase")]
pub enum SourceKeyword {
Source,
NoExtract,
B2sums,
Md5sums,
Sha1sums,
Sha224sums,
Sha256sums,
Sha384sums,
Sha512sums,
Cksums,
}
impl SourceKeyword {
pub fn parser(input: &mut &str) -> ModalResult<SourceKeyword> {
trace(
"source_keyword",
alphanumeric1.try_map(SourceKeyword::from_str),
)
.parse_next(input)
}
}
#[derive(Debug)]
pub enum SourceProperty {
Source(ArchProperty<Source>),
NoExtract(String),
B2Checksum(ArchProperty<SkippableChecksum<Blake2b512>>),
Md5Checksum(ArchProperty<SkippableChecksum<Md5>>),
Sha1Checksum(ArchProperty<SkippableChecksum<Sha1>>),
Sha256Checksum(ArchProperty<SkippableChecksum<Sha256>>),
Sha224Checksum(ArchProperty<SkippableChecksum<Sha224>>),
Sha384Checksum(ArchProperty<SkippableChecksum<Sha384>>),
Sha512Checksum(ArchProperty<SkippableChecksum<Sha512>>),
CrcChecksum(ArchProperty<SkippableChecksum<Crc32Cksum>>),
}
impl SourceProperty {
fn parser(input: &mut &str) -> ModalResult<SourceProperty> {
let keyword = SourceKeyword::parser.parse_next(input)?;
let property = match keyword {
SourceKeyword::NoExtract => {
let _ = delimiter.parse_next(input)?;
cut_err(till_line_end.map(|s| SourceProperty::NoExtract(s.to_string())))
.parse_next(input)?
}
SourceKeyword::Source
| SourceKeyword::B2sums
| SourceKeyword::Md5sums
| SourceKeyword::Sha1sums
| SourceKeyword::Sha224sums
| SourceKeyword::Sha256sums
| SourceKeyword::Sha384sums
| SourceKeyword::Sha512sums
| SourceKeyword::Cksums => {
let architecture = architecture_suffix.parse_next(input)?;
let _ = delimiter.parse_next(input)?;
match keyword {
SourceKeyword::Source => {
cut_err(till_line_end.try_map(Source::from_str).map(|value| {
SourceProperty::Source(ArchProperty {
architecture: architecture.clone(),
value,
})
}))
.parse_next(input)?
}
SourceKeyword::B2sums => SourceProperty::B2Checksum(ArchProperty {
architecture,
value: till_line_end
.and_then(SkippableChecksum::parser)
.parse_next(input)?,
}),
SourceKeyword::Md5sums => SourceProperty::Md5Checksum(ArchProperty {
architecture,
value: till_line_end
.and_then(SkippableChecksum::parser)
.parse_next(input)?,
}),
SourceKeyword::Sha1sums => SourceProperty::Sha1Checksum(ArchProperty {
architecture,
value: till_line_end
.and_then(SkippableChecksum::parser)
.parse_next(input)?,
}),
SourceKeyword::Sha224sums => SourceProperty::Sha224Checksum(ArchProperty {
architecture,
value: till_line_end
.and_then(SkippableChecksum::parser)
.parse_next(input)?,
}),
SourceKeyword::Sha256sums => SourceProperty::Sha256Checksum(ArchProperty {
architecture,
value: till_line_end
.and_then(SkippableChecksum::parser)
.parse_next(input)?,
}),
SourceKeyword::Sha384sums => SourceProperty::Sha384Checksum(ArchProperty {
architecture,
value: till_line_end
.and_then(SkippableChecksum::parser)
.parse_next(input)?,
}),
SourceKeyword::Sha512sums => SourceProperty::Sha512Checksum(ArchProperty {
architecture,
value: till_line_end
.and_then(SkippableChecksum::parser)
.parse_next(input)?,
}),
SourceKeyword::Cksums => SourceProperty::CrcChecksum(ArchProperty {
architecture,
value: till_line_end
.and_then(SkippableChecksum::parser)
.parse_next(input)?,
}),
SourceKeyword::NoExtract => unreachable!(),
}
}
};
Ok(property)
}
}
#[derive(Clone, Debug)]
pub enum ClearableProperty {
Description,
Url,
Licenses,
Changelog,
Install,
Groups,
Options,
Backups,
Dependencies(Option<Architecture>),
OptionalDependencies(Option<Architecture>),
Provides(Option<Architecture>),
Conflicts(Option<Architecture>),
Replaces(Option<Architecture>),
}
impl ClearableProperty {
fn shared_meta_parser(input: &mut &str) -> ModalResult<ClearableProperty> {
let keyword =
trace("clearable_shared_meta_property", SharedMetaKeyword::parser).parse_next(input)?;
let _ = (" =", space0, newline).parse_next(input)?;
let property = match keyword {
SharedMetaKeyword::Arch => {
return Err(ErrMode::Backtrack(ParserError::from_input(input)));
}
SharedMetaKeyword::PkgDesc => ClearableProperty::Description,
SharedMetaKeyword::Url => ClearableProperty::Url,
SharedMetaKeyword::License => ClearableProperty::Licenses,
SharedMetaKeyword::Changelog => ClearableProperty::Changelog,
SharedMetaKeyword::Install => ClearableProperty::Install,
SharedMetaKeyword::Groups => ClearableProperty::Groups,
SharedMetaKeyword::Options => ClearableProperty::Options,
SharedMetaKeyword::Backup => ClearableProperty::Backups,
};
Ok(property)
}
fn relation_parser(input: &mut &str) -> ModalResult<ClearableProperty> {
let keyword = trace("clearable_property", RelationKeyword::parser).parse_next(input)?;
let architecture = architecture_suffix.parse_next(input)?;
let _ = (" =", space0, newline).parse_next(input)?;
let property = match keyword {
RelationKeyword::Depends => ClearableProperty::Dependencies(architecture),
RelationKeyword::OptDepends => ClearableProperty::OptionalDependencies(architecture),
RelationKeyword::Provides => ClearableProperty::Provides(architecture),
RelationKeyword::Conflicts => ClearableProperty::Conflicts(architecture),
RelationKeyword::Replaces => ClearableProperty::Replaces(architecture),
};
Ok(property)
}
}