use std::{
fmt::{Display, Formatter},
str::FromStr,
};
use serde::{Deserialize, Serialize};
use strum::{Display, EnumString, VariantNames};
use winnow::{
ModalResult,
Parser,
ascii::{Caseless, space1},
combinator::{alt, cut_err, eof, not, repeat_till},
error::{StrContext, StrContextValue},
token::one_of,
};
use crate::Error;
#[derive(
Clone,
Debug,
Deserialize,
Display,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
VariantNames,
)]
#[strum(serialize_all = "lowercase")]
#[serde(rename_all = "lowercase")]
pub enum SystemArchitecture {
Aarch64,
Arm,
Armv6h,
Armv7h,
I386,
I486,
I686,
Pentium4,
Riscv32,
Riscv64,
X86_64,
#[strum(to_string = "x86_64_v2")]
X86_64V2,
#[strum(to_string = "x86_64_v3")]
X86_64V3,
#[strum(to_string = "x86_64_v4")]
X86_64V4,
#[strum(transparent)]
#[serde(untagged)]
Unknown(UnknownArchitecture),
}
impl SystemArchitecture {
pub fn parser(input: &mut &str) -> ModalResult<SystemArchitecture> {
alt((
("aarch64", eof).value(SystemArchitecture::Aarch64),
("arm", eof).value(SystemArchitecture::Arm),
("armv6h", eof).value(SystemArchitecture::Armv6h),
("armv7h", eof).value(SystemArchitecture::Armv7h),
("i386", eof).value(SystemArchitecture::I386),
("i486", eof).value(SystemArchitecture::I486),
("i686", eof).value(SystemArchitecture::I686),
("pentium4", eof).value(SystemArchitecture::Pentium4),
("riscv32", eof).value(SystemArchitecture::Riscv32),
("riscv64", eof).value(SystemArchitecture::Riscv64),
("x86_64", eof).value(SystemArchitecture::X86_64),
("x86_64_v2", eof).value(SystemArchitecture::X86_64V2),
("x86_64_v3", eof).value(SystemArchitecture::X86_64V3),
("x86_64_v4", eof).value(SystemArchitecture::X86_64V4),
UnknownArchitecture::parser.map(SystemArchitecture::Unknown),
))
.parse_next(input)
}
}
impl FromStr for SystemArchitecture {
type Err = Error;
fn from_str(s: &str) -> Result<SystemArchitecture, Self::Err> {
Ok(Self::parser.parse(s)?)
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct UnknownArchitecture(String);
impl UnknownArchitecture {
pub fn new(name: &str) -> Result<Self, Error> {
Self::from_str(name)
}
pub fn inner(&self) -> &str {
&self.0
}
pub fn parser(input: &mut &str) -> ModalResult<Self> {
cut_err(not(alt((eof, space1))))
.context(StrContext::Label("system architecture"))
.context(StrContext::Expected(StrContextValue::Description(
"a non empty string.",
)))
.parse_next(input)?;
cut_err(not((Caseless("any"), eof)))
.context(StrContext::Label(
"system architecture. 'any' has a special meaning and is not allowed here.",
))
.parse_next(input)?;
let alphanum = |c: char| c.is_ascii_alphanumeric();
let special_chars = ['_'];
cut_err(repeat_till(0.., one_of((alphanum, special_chars)), eof))
.map(|(r, _)| r)
.map(Self)
.context(StrContext::Label("character in system architecture"))
.context(StrContext::Expected(StrContextValue::Description(
"a string containing only ASCII alphanumeric characters and underscores.",
)))
.parse_next(input)
}
}
impl From<UnknownArchitecture> for SystemArchitecture {
fn from(value: UnknownArchitecture) -> Self {
SystemArchitecture::Unknown(value)
}
}
impl From<UnknownArchitecture> for Architecture {
fn from(value: UnknownArchitecture) -> Self {
Architecture::Some(value.into())
}
}
impl FromStr for UnknownArchitecture {
type Err = Error;
fn from_str(s: &str) -> Result<UnknownArchitecture, Self::Err> {
Ok(Self::parser.parse(s)?)
}
}
impl Display for UnknownArchitecture {
fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
write!(fmt, "{}", self.inner())
}
}
impl AsRef<str> for UnknownArchitecture {
fn as_ref(&self) -> &str {
self.inner()
}
}
#[derive(
Clone,
Debug,
Deserialize,
Display,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
VariantNames,
)]
#[strum(serialize_all = "lowercase")]
#[serde(rename_all = "lowercase")]
pub enum Architecture {
Any,
#[strum(transparent)]
#[serde(untagged)]
Some(SystemArchitecture),
}
impl Architecture {
pub fn parser(input: &mut &str) -> ModalResult<Architecture> {
alt((
(Caseless("any"), eof).value(Architecture::Any),
SystemArchitecture::parser.map(Architecture::Some),
))
.context(StrContext::Label("alpm-architecture"))
.parse_next(input)
}
}
impl FromStr for Architecture {
type Err = Error;
fn from_str(s: &str) -> Result<Architecture, Self::Err> {
Ok(Self::parser.parse(s)?)
}
}
impl From<SystemArchitecture> for Architecture {
fn from(value: SystemArchitecture) -> Self {
Architecture::Some(value)
}
}
#[derive(
Clone,
Debug,
Deserialize,
EnumString,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
VariantNames,
)]
#[strum(serialize_all = "lowercase")]
#[serde(rename_all = "lowercase")]
pub enum Architectures {
Any,
#[strum(transparent)]
#[serde(untagged)]
Some(Vec<SystemArchitecture>),
}
impl Architectures {
pub fn len(&self) -> usize {
match self {
Architectures::Any => 1,
Architectures::Some(archs) => archs.len(),
}
}
pub fn is_empty(&self) -> bool {
match self {
Architectures::Any => false,
Architectures::Some(archs) => archs.is_empty(),
}
}
}
impl Display for Architectures {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Architectures::Any => {
write!(f, "any")
}
Architectures::Some(archs) => {
write!(
f,
"{}",
archs
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ")
)
}
}
}
}
impl From<Architecture> for Architectures {
fn from(value: Architecture) -> Self {
match value {
Architecture::Any => Architectures::Any,
Architecture::Some(arch) => Architectures::Some(vec![arch]),
}
}
}
impl From<&Architectures> for Vec<Architecture> {
fn from(value: &Architectures) -> Self {
match value {
Architectures::Any => vec![Architecture::Any],
Architectures::Some(archs) => {
archs.clone().into_iter().map(Architecture::Some).collect()
}
}
}
}
impl IntoIterator for &Architectures {
type Item = Architecture;
type IntoIter = std::vec::IntoIter<Architecture>;
fn into_iter(self) -> Self::IntoIter {
let vec: Vec<Architecture> = self.into();
vec.into_iter()
}
}
impl TryFrom<Vec<&Architecture>> for Architectures {
type Error = Error;
fn try_from(value: Vec<&Architecture>) -> Result<Self, Self::Error> {
if value.contains(&&Architecture::Any) {
if value.len() > 1 {
Err(Error::InvalidArchitectures {
architectures: value.iter().map(|&v| v.clone()).collect(),
context: "'any' cannot be used in combination with other architectures.",
})
} else {
Ok(Architectures::Any)
}
} else {
let archs: Vec<SystemArchitecture> = value
.into_iter()
.map(|arch| {
if let Architecture::Some(specific) = arch {
specific.clone()
} else {
unreachable!()
}
})
.collect();
Ok(Architectures::Some(archs))
}
}
}
impl TryFrom<Vec<Architecture>> for Architectures {
type Error = Error;
fn try_from(value: Vec<Architecture>) -> Result<Self, Self::Error> {
value.iter().collect::<Vec<&Architecture>>().try_into()
}
}
#[derive(
Clone, Copy, Debug, Deserialize, Display, EnumString, Eq, Ord, PartialEq, PartialOrd, Serialize,
)]
#[strum(serialize_all = "lowercase")]
pub enum ElfArchitectureFormat {
#[strum(to_string = "32")]
Bit32 = 32,
#[strum(to_string = "64")]
Bit64 = 64,
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use rstest::rstest;
use strum::ParseError;
use super::*;
const ERROR_ANY: &str = concat!(
"any\n",
"^\n",
"invalid system architecture. 'any' has a special meaning and is not allowed here."
);
const ERROR_FOO: &str = concat!(
"f oo\n",
" ^\n",
"invalid character in system architecture\n",
"expected a string containing only ASCII alphanumeric characters and underscores."
);
#[rstest]
#[case(SystemArchitecture::Aarch64.into(), Architecture::Some(SystemArchitecture::Aarch64))]
#[case(SystemArchitecture::from_str("f_oo").unwrap().into(), Architecture::Some(SystemArchitecture::from_str("f_oo").unwrap()))]
fn system_architecture_into_architecture(
#[case] left: Architecture,
#[case] right: Architecture,
) {
assert_eq!(left, right);
}
#[rstest]
#[case("aarch64", Ok(SystemArchitecture::Aarch64))]
#[case("f_oo", UnknownArchitecture::new("f_oo").map(From::from))]
#[case("f oo", Err(Error::ParseError(ERROR_FOO.to_string())))]
#[case("any", Err(Error::ParseError(ERROR_ANY.to_string())))]
fn system_architecture_from_string(
#[case] s: &str,
#[case] arch: Result<SystemArchitecture, Error>,
) {
assert_eq!(SystemArchitecture::from_str(s), arch);
}
#[rstest]
#[case(SystemArchitecture::Aarch64, "aarch64")]
#[case(SystemArchitecture::from_str("f_o_o").unwrap(), "f_o_o")]
fn system_architecture_format_string(#[case] arch: SystemArchitecture, #[case] arch_str: &str) {
assert_eq!(arch_str, format!("{arch}"));
}
#[rstest]
#[case("any", Ok(Architecture::Any))]
#[case("aarch64", Ok(SystemArchitecture::Aarch64.into()))]
#[case("arm", Ok(SystemArchitecture::Arm.into()))]
#[case("armv6h", Ok(SystemArchitecture::Armv6h.into()))]
#[case("armv7h", Ok(SystemArchitecture::Armv7h.into()))]
#[case("i386", Ok(SystemArchitecture::I386.into()))]
#[case("i486", Ok(SystemArchitecture::I486.into()))]
#[case("i686", Ok(SystemArchitecture::I686.into()))]
#[case("pentium4", Ok(SystemArchitecture::Pentium4.into()))]
#[case("riscv32", Ok(SystemArchitecture::Riscv32.into()))]
#[case("riscv64", Ok(SystemArchitecture::Riscv64.into()))]
#[case("x86_64", Ok(SystemArchitecture::X86_64.into()))]
#[case("x86_64_v2", Ok(SystemArchitecture::X86_64V2.into()))]
#[case("x86_64_v3", Ok(SystemArchitecture::X86_64V3.into()))]
#[case("x86_64_v4", Ok(SystemArchitecture::X86_64V4.into()))]
#[case("foo", UnknownArchitecture::new("foo").map(From::from))]
#[case("f_oo", UnknownArchitecture::new("f_oo").map(From::from))]
#[case("f oo", Err(Error::ParseError(ERROR_FOO.to_string())))]
fn architecture_from_string(#[case] s: &str, #[case] arch: Result<Architecture, Error>) {
assert_eq!(Architecture::from_str(s), arch);
}
#[rstest]
#[case(Architecture::Any, "any")]
#[case(SystemArchitecture::Aarch64.into(), "aarch64")]
#[case(Architecture::from_str("foo").unwrap(), "foo")]
fn architecture_format_string(#[case] arch: Architecture, #[case] arch_str: &str) {
assert_eq!(arch_str, format!("{arch}"));
}
#[rstest]
#[case(vec![Architecture::Any], Ok(Architectures::Any))]
#[case(vec![SystemArchitecture::Aarch64.into()], Ok(Architectures::Some(vec![SystemArchitecture::Aarch64])))]
#[case(vec![SystemArchitecture::Arm.into(), SystemArchitecture::I386.into()], Ok(Architectures::Some(vec![SystemArchitecture::Arm, SystemArchitecture::I386])))]
#[case(vec![SystemArchitecture::Arm.into(), SystemArchitecture::Arm.into()], Ok(Architectures::Some(vec![SystemArchitecture::Arm, SystemArchitecture::Arm])))]
#[case(vec![Architecture::Any, SystemArchitecture::I386.into()], Err(Error::InvalidArchitectures {
architectures: vec![Architecture::Any, SystemArchitecture::I386.into()],
context: "'any' cannot be used in combination with other architectures.",
}))]
#[case(vec![Architecture::Any, Architecture::Any], Err(Error::InvalidArchitectures {
architectures: vec![Architecture::Any, Architecture::Any],
context: "'any' cannot be used in combination with other architectures.",
}))]
#[case(vec![], Ok(Architectures::Some(vec![])))]
fn architectures_from_vec(
#[case] archs: Vec<Architecture>,
#[case] expected: Result<Architectures, Error>,
) {
assert_eq!(archs.try_into(), expected);
}
#[rstest]
#[case(Architectures::Any, "any")]
#[case(Architectures::Some(vec![SystemArchitecture::Aarch64]), "aarch64")]
#[case(Architectures::Some(vec![SystemArchitecture::Arm, SystemArchitecture::I386]), "arm, i386")]
#[case(Architectures::Some(vec![]), "")]
fn architectures_format_display(#[case] archs: Architectures, #[case] archs_str: &str) {
assert_eq!(archs_str, format!("{archs}"));
}
#[rstest]
#[case("32", Ok(ElfArchitectureFormat::Bit32))]
#[case("64", Ok(ElfArchitectureFormat::Bit64))]
#[case("foo", Err(ParseError::VariantNotFound))]
fn elf_architecture_format_from_string(
#[case] s: &str,
#[case] arch: Result<ElfArchitectureFormat, ParseError>,
) {
assert_eq!(ElfArchitectureFormat::from_str(s), arch);
}
#[rstest]
#[case(ElfArchitectureFormat::Bit32, "32")]
#[case(ElfArchitectureFormat::Bit64, "64")]
fn elf_architecture_format_display(
#[case] arch: ElfArchitectureFormat,
#[case] arch_str: &str,
) {
assert_eq!(arch_str, format!("{arch}"));
}
}