use std::{collections::HashMap, str::FromStr};
use alpm_parsers::iter_str_context;
#[cfg(doc)]
use alpm_pkgbuild::bridge::BridgeOutput;
use alpm_pkgbuild::bridge::{ClearableValue, Keyword, RawPackageName};
use alpm_types::{
Architecture,
Backup,
Changelog,
Group,
Install,
License,
MakepkgOption,
Name,
OptionalDependency,
PackageDescription,
PackageRelation,
RelationOrSoname,
SystemArchitecture,
Url,
};
use strum::VariantNames;
use winnow::{
ModalResult,
Parser,
combinator::{alt, cut_err},
error::{ContextError, ErrMode, ParseError, StrContext},
token::rest,
};
use super::ensure_no_suffix;
use crate::{
pkgbuild_bridge::error::BridgeError,
source_info::{
parser::{RelationKeyword, SharedMetaKeyword},
v1::package::{Override, Package, PackageArchitecture},
},
};
pub(crate) fn handle_packages(
base_package: Name,
valid_packages: Vec<Name>,
raw_values: HashMap<RawPackageName, HashMap<Keyword, ClearableValue>>,
) -> Result<Vec<Package>, BridgeError> {
let mut package_map: HashMap<Name, Package> = HashMap::new();
for (name, values) in raw_values {
let name = if let Some(name) = name.0 {
Name::parser
.parse(&name)
.map_err(|err| BridgeError::InvalidPackageName {
name: name.clone(),
error: err.into(),
})?
} else {
if valid_packages.len() > 1 {
return Err(BridgeError::UnusedPackageFunction(base_package));
}
base_package.clone()
};
if !valid_packages.contains(&name) {
return Err(BridgeError::UndeclaredPackageName(name.to_string()));
}
let package = package_map.entry(name.clone()).or_insert(name.into());
handle_package(package, values)?;
}
let mut packages = Vec::new();
for name in valid_packages {
let Some(package) = package_map.remove(&name) else {
packages.push(name.into());
continue;
};
packages.push(package);
}
Ok(packages)
}
enum PackageKeyword {
Relation(RelationKeyword),
SharedMeta(SharedMetaKeyword),
}
impl PackageKeyword {
pub fn parser(input: &mut &str) -> ModalResult<PackageKeyword> {
cut_err(alt((
RelationKeyword::parser.map(PackageKeyword::Relation),
SharedMetaKeyword::parser.map(PackageKeyword::SharedMeta),
)))
.context(StrContext::Label("package base property type"))
.context_with(iter_str_context!([
RelationKeyword::VARIANTS,
SharedMetaKeyword::VARIANTS,
]))
.parse_next(input)
}
}
fn ensure_single_clearable_value<'a>(
keyword: &Keyword,
value: &'a ClearableValue,
) -> Result<&'a Option<String>, BridgeError> {
match value {
ClearableValue::Single(value) => Ok(value),
ClearableValue::Array(values) => Err(BridgeError::UnexpectedArray {
keyword: keyword.clone(),
values: values.clone().unwrap_or_default().clone(),
}),
}
}
fn parse_clearable_value<'a, O, P: Parser<&'a str, O, ErrMode<ContextError>>>(
keyword: &Keyword,
value: &'a ClearableValue,
mut parser: P,
) -> Result<Override<O>, BridgeError> {
let value = ensure_single_clearable_value(keyword, value)?;
let Some(value) = value else {
return Ok(Override::Clear);
};
let parsed_value = parser.parse(value).map_err(|err| (keyword.clone(), err))?;
Ok(Override::Yes {
value: parsed_value,
})
}
fn parse_clearable_value_array<'a, O, P: Parser<&'a str, O, ErrMode<ContextError>>>(
keyword: &Keyword,
value: &'a ClearableValue,
mut parser: P,
) -> Result<Override<Vec<O>>, BridgeError> {
let values = match value {
ClearableValue::Single(value) => {
let Some(value) = value else {
return Ok(Override::Clear);
};
if value.is_empty() {
return Ok(Override::Clear);
}
let value = parser.parse(value).map_err(|err| (keyword.clone(), err))?;
vec![value]
}
ClearableValue::Array(values) => {
let Some(values) = values else {
return Ok(Override::Clear);
};
values
.iter()
.map(|item| parser.parse(item).map_err(|err| (keyword.clone(), err)))
.collect::<Result<Vec<O>, (Keyword, ParseError<&'a str, ContextError>)>>()?
}
};
Ok(Override::Yes { value: values })
}
macro_rules! package_value_array {
(
$keyword:expr,
$value:expr,
$package:ident,
$field_name:ident,
$architecture:ident,
$parser:expr,
) => {
if let Some(architecture) = $architecture {
let architecture_properties = $package
.architecture_properties
.entry(architecture)
.or_insert(PackageArchitecture::default());
architecture_properties.$field_name =
parse_clearable_value_array($keyword, $value, $parser)?;
} else {
$package.$field_name = parse_clearable_value_array($keyword, $value, $parser)?;
}
};
}
fn handle_package(
package: &mut Package,
values: HashMap<Keyword, ClearableValue>,
) -> Result<(), BridgeError> {
for (raw_keyword, value) in values {
let keyword = PackageKeyword::parser
.parse(&raw_keyword.keyword)
.map_err(|err| (raw_keyword.clone(), err))?;
let architecture = match &raw_keyword.suffix {
Some(suffix) => {
let arch = SystemArchitecture::parser
.parse(suffix)
.map_err(|err| (raw_keyword.clone(), err))?;
Some(arch)
}
None => None,
};
match keyword {
PackageKeyword::Relation(keyword) => match keyword {
RelationKeyword::Depends => package_value_array!(
&raw_keyword,
&value,
package,
dependencies,
architecture,
RelationOrSoname::parser,
),
RelationKeyword::OptDepends => package_value_array!(
&raw_keyword,
&value,
package,
optional_dependencies,
architecture,
OptionalDependency::parser,
),
RelationKeyword::Provides => package_value_array!(
&raw_keyword,
&value,
package,
provides,
architecture,
RelationOrSoname::parser,
),
RelationKeyword::Conflicts => package_value_array!(
&raw_keyword,
&value,
package,
conflicts,
architecture,
PackageRelation::parser,
),
RelationKeyword::Replaces => package_value_array!(
&raw_keyword,
&value,
package,
replaces,
architecture,
PackageRelation::parser,
),
},
PackageKeyword::SharedMeta(keyword) => match keyword {
SharedMetaKeyword::PkgDesc => {
ensure_no_suffix(&raw_keyword, architecture)?;
package.description = parse_clearable_value(
&raw_keyword,
&value,
rest.try_map(PackageDescription::from_str),
)?;
}
SharedMetaKeyword::Url => {
ensure_no_suffix(&raw_keyword, architecture)?;
package.url =
parse_clearable_value(&raw_keyword, &value, rest.try_map(Url::from_str))?;
}
SharedMetaKeyword::License => {
ensure_no_suffix(&raw_keyword, architecture)?;
package.licenses = parse_clearable_value_array(
&raw_keyword,
&value,
rest.try_map(License::from_str),
)?;
}
SharedMetaKeyword::Arch => {
ensure_no_suffix(&raw_keyword, architecture)?;
let archs = parse_clearable_value_array(
&raw_keyword,
&value,
rest.try_map(Architecture::from_str),
)?;
package.architectures = match archs {
Override::No => None,
Override::Clear => {
return Err(BridgeError::UnclearableValue {
keyword: raw_keyword,
});
}
Override::Yes { value } => Some(value.try_into()?),
};
}
SharedMetaKeyword::Changelog => {
ensure_no_suffix(&raw_keyword, architecture)?;
package.changelog = parse_clearable_value(
&raw_keyword,
&value,
rest.try_map(Changelog::from_str),
)?;
}
SharedMetaKeyword::Install => {
ensure_no_suffix(&raw_keyword, architecture)?;
package.install = parse_clearable_value(
&raw_keyword,
&value,
rest.try_map(Install::from_str),
)?;
}
SharedMetaKeyword::Groups => {
ensure_no_suffix(&raw_keyword, architecture)?;
package.groups = parse_clearable_value_array(
&raw_keyword,
&value,
rest.try_map(Group::from_str),
)?;
}
SharedMetaKeyword::Options => {
ensure_no_suffix(&raw_keyword, architecture)?;
package.options = parse_clearable_value_array(
&raw_keyword,
&value,
rest.try_map(MakepkgOption::from_str),
)?;
}
SharedMetaKeyword::Backup => {
ensure_no_suffix(&raw_keyword, architecture)?;
package.backups = parse_clearable_value_array(
&raw_keyword,
&value,
rest.try_map(Backup::from_str),
)?;
}
},
}
}
Ok(())
}