1use std::{
2 fmt::{Display, Formatter},
3 str::FromStr,
4};
5
6use serde::{Deserialize, Serialize};
7use strum::{Display, EnumString, VariantNames};
8use winnow::{
9 ModalResult,
10 Parser,
11 ascii::{Caseless, space1},
12 combinator::{alt, cut_err, eof, not, repeat_till},
13 error::{StrContext, StrContextValue},
14 token::one_of,
15};
16
17use crate::Error;
18
19#[derive(
52 Clone,
53 Debug,
54 Deserialize,
55 Display,
56 Eq,
57 Hash,
58 Ord,
59 PartialEq,
60 PartialOrd,
61 Serialize,
62 VariantNames,
63)]
64#[strum(serialize_all = "lowercase")]
65#[serde(rename_all = "lowercase")]
66pub enum SystemArchitecture {
67 Aarch64,
69 Arm,
71 Armv6h,
73 Armv7h,
75 I386,
77 I486,
79 I686,
81 Pentium4,
83 Riscv32,
85 Riscv64,
87 X86_64,
89 #[strum(to_string = "x86_64_v2")]
91 X86_64V2,
92 #[strum(to_string = "x86_64_v3")]
94 X86_64V3,
95 #[strum(to_string = "x86_64_v4")]
97 X86_64V4,
98 #[strum(transparent)]
100 #[serde(untagged)]
101 Unknown(UnknownArchitecture),
102}
103
104impl SystemArchitecture {
105 pub fn parser(input: &mut &str) -> ModalResult<SystemArchitecture> {
109 alt((
110 alt((
112 ("aarch64", eof).value(SystemArchitecture::Aarch64),
113 ("arm", eof).value(SystemArchitecture::Arm),
114 ("armv6h", eof).value(SystemArchitecture::Armv6h),
115 ("armv7h", eof).value(SystemArchitecture::Armv7h),
116 ("i386", eof).value(SystemArchitecture::I386),
117 ("i486", eof).value(SystemArchitecture::I486),
118 ("i686", eof).value(SystemArchitecture::I686),
119 ("pentium4", eof).value(SystemArchitecture::Pentium4),
120 ("riscv32", eof).value(SystemArchitecture::Riscv32),
121 )),
122 alt((
123 ("riscv64", eof).value(SystemArchitecture::Riscv64),
124 ("x86_64", eof).value(SystemArchitecture::X86_64),
125 ("x86_64_v2", eof).value(SystemArchitecture::X86_64V2),
126 ("x86_64_v3", eof).value(SystemArchitecture::X86_64V3),
127 ("x86_64_v4", eof).value(SystemArchitecture::X86_64V4),
128 )),
129 UnknownArchitecture::parser.map(SystemArchitecture::Unknown),
130 ))
131 .parse_next(input)
132 }
133}
134
135impl FromStr for SystemArchitecture {
136 type Err = Error;
137
138 fn from_str(s: &str) -> Result<SystemArchitecture, Self::Err> {
146 Ok(Self::parser.parse(s)?)
147 }
148}
149
150#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
154pub struct UnknownArchitecture(String);
155
156impl UnknownArchitecture {
157 pub fn new(name: &str) -> Result<Self, Error> {
159 Self::from_str(name)
160 }
161
162 pub fn inner(&self) -> &str {
164 &self.0
165 }
166
167 pub fn parser(input: &mut &str) -> ModalResult<Self> {
178 cut_err(not(alt((eof, space1))))
180 .context(StrContext::Label("system architecture"))
181 .context(StrContext::Expected(StrContextValue::Description(
182 "a non empty string.",
183 )))
184 .parse_next(input)?;
185
186 cut_err(not((Caseless("any"), eof)))
188 .context(StrContext::Label(
189 "system architecture. 'any' has a special meaning and is not allowed here.",
190 ))
191 .parse_next(input)?;
192
193 let alphanum = |c: char| c.is_ascii_alphanumeric();
194 let special_chars = ['_'];
195
196 cut_err(repeat_till(0.., one_of((alphanum, special_chars)), eof))
197 .map(|(r, _)| r)
198 .map(Self)
199 .context(StrContext::Label("character in system architecture"))
200 .context(StrContext::Expected(StrContextValue::Description(
201 "a string containing only ASCII alphanumeric characters and underscores.",
202 )))
203 .parse_next(input)
204 }
205}
206
207impl From<UnknownArchitecture> for SystemArchitecture {
208 fn from(value: UnknownArchitecture) -> Self {
210 SystemArchitecture::Unknown(value)
211 }
212}
213
214impl From<UnknownArchitecture> for Architecture {
215 fn from(value: UnknownArchitecture) -> Self {
217 Architecture::Some(value.into())
218 }
219}
220
221impl FromStr for UnknownArchitecture {
222 type Err = Error;
223
224 fn from_str(s: &str) -> Result<UnknownArchitecture, Self::Err> {
232 Ok(Self::parser.parse(s)?)
233 }
234}
235
236impl Display for UnknownArchitecture {
237 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
238 write!(fmt, "{}", self.inner())
239 }
240}
241
242impl AsRef<str> for UnknownArchitecture {
243 fn as_ref(&self) -> &str {
244 self.inner()
245 }
246}
247
248#[derive(
283 Clone,
284 Debug,
285 Deserialize,
286 Display,
287 Eq,
288 Hash,
289 Ord,
290 PartialEq,
291 PartialOrd,
292 Serialize,
293 VariantNames,
294)]
295#[strum(serialize_all = "lowercase")]
296#[serde(rename_all = "lowercase")]
297pub enum Architecture {
298 Any,
300 #[strum(transparent)]
302 #[serde(untagged)]
303 Some(SystemArchitecture),
304}
305
306impl Architecture {
307 pub fn parser(input: &mut &str) -> ModalResult<Architecture> {
311 alt((
312 (Caseless("any"), eof).value(Architecture::Any),
313 SystemArchitecture::parser.map(Architecture::Some),
314 ))
315 .context(StrContext::Label("alpm-architecture"))
316 .parse_next(input)
317 }
318}
319
320impl FromStr for Architecture {
321 type Err = Error;
322
323 fn from_str(s: &str) -> Result<Architecture, Self::Err> {
331 Ok(Self::parser.parse(s)?)
332 }
333}
334
335impl From<SystemArchitecture> for Architecture {
336 fn from(value: SystemArchitecture) -> Self {
338 Architecture::Some(value)
339 }
340}
341
342#[derive(
351 Clone,
352 Debug,
353 Deserialize,
354 EnumString,
355 Eq,
356 Hash,
357 Ord,
358 PartialEq,
359 PartialOrd,
360 Serialize,
361 VariantNames,
362)]
363#[strum(serialize_all = "lowercase")]
364#[serde(rename_all = "lowercase")]
365pub enum Architectures {
366 Any,
368 #[strum(transparent)]
370 #[serde(untagged)]
371 Some(Vec<SystemArchitecture>),
372}
373
374impl Architectures {
375 pub fn len(&self) -> usize {
377 match self {
378 Architectures::Any => 1,
379 Architectures::Some(archs) => archs.len(),
380 }
381 }
382
383 pub fn is_empty(&self) -> bool {
385 match self {
386 Architectures::Any => false,
387 Architectures::Some(archs) => archs.is_empty(),
388 }
389 }
390}
391
392impl Display for Architectures {
393 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395 match self {
396 Architectures::Any => {
397 write!(f, "any")
398 }
399 Architectures::Some(archs) => {
400 write!(
401 f,
402 "{}",
403 archs
404 .iter()
405 .map(ToString::to_string)
406 .collect::<Vec<_>>()
407 .join(", ")
408 )
409 }
410 }
411 }
412}
413
414impl From<Architecture> for Architectures {
415 fn from(value: Architecture) -> Self {
417 match value {
418 Architecture::Any => Architectures::Any,
419 Architecture::Some(arch) => Architectures::Some(vec![arch]),
420 }
421 }
422}
423
424impl From<&Architectures> for Vec<Architecture> {
425 fn from(value: &Architectures) -> Self {
427 match value {
428 Architectures::Any => vec![Architecture::Any],
429 Architectures::Some(archs) => {
430 archs.clone().into_iter().map(Architecture::Some).collect()
431 }
432 }
433 }
434}
435
436impl IntoIterator for &Architectures {
437 type Item = Architecture;
438 type IntoIter = std::vec::IntoIter<Architecture>;
439 fn into_iter(self) -> Self::IntoIter {
441 let vec: Vec<Architecture> = self.into();
442 vec.into_iter()
443 }
444}
445
446impl TryFrom<Vec<&Architecture>> for Architectures {
447 type Error = Error;
448
449 fn try_from(value: Vec<&Architecture>) -> Result<Self, Self::Error> {
456 if value.contains(&&Architecture::Any) {
457 if value.len() > 1 {
458 Err(Error::InvalidArchitectures {
459 architectures: value.iter().map(|&v| v.clone()).collect(),
460 context: "'any' cannot be used in combination with other architectures.",
461 })
462 } else {
463 Ok(Architectures::Any)
464 }
465 } else {
466 let archs: Vec<SystemArchitecture> = value
467 .into_iter()
468 .map(|arch| {
469 if let Architecture::Some(specific) = arch {
470 specific.clone()
471 } else {
472 unreachable!()
474 }
475 })
476 .collect();
477 Ok(Architectures::Some(archs))
478 }
479 }
480}
481
482impl TryFrom<Vec<Architecture>> for Architectures {
483 type Error = Error;
484
485 fn try_from(value: Vec<Architecture>) -> Result<Self, Self::Error> {
489 value.iter().collect::<Vec<&Architecture>>().try_into()
490 }
491}
492
493#[derive(
524 Clone, Copy, Debug, Deserialize, Display, EnumString, Eq, Ord, PartialEq, PartialOrd, Serialize,
525)]
526#[strum(serialize_all = "lowercase")]
527pub enum ElfArchitectureFormat {
528 #[strum(to_string = "32")]
530 Bit32 = 32,
531 #[strum(to_string = "64")]
533 Bit64 = 64,
534}
535
536#[cfg(test)]
537mod tests {
538 use std::str::FromStr;
539
540 use rstest::rstest;
541 use strum::ParseError;
542
543 use super::*;
544
545 const ERROR_ANY: &str = concat!(
547 "any\n",
548 "^\n",
549 "invalid system architecture. 'any' has a special meaning and is not allowed here."
550 );
551
552 const ERROR_FOO: &str = concat!(
554 "f oo\n",
555 " ^\n",
556 "invalid character in system architecture\n",
557 "expected a string containing only ASCII alphanumeric characters and underscores."
558 );
559
560 #[rstest]
561 #[case(SystemArchitecture::Aarch64.into(), Architecture::Some(SystemArchitecture::Aarch64))]
562 #[case(SystemArchitecture::from_str("f_oo").unwrap().into(), Architecture::Some(SystemArchitecture::from_str("f_oo").unwrap()))]
563 fn system_architecture_into_architecture(
564 #[case] left: Architecture,
565 #[case] right: Architecture,
566 ) {
567 assert_eq!(left, right);
568 }
569
570 #[rstest]
571 #[case("aarch64", Ok(SystemArchitecture::Aarch64))]
572 #[case("f_oo", UnknownArchitecture::new("f_oo").map(From::from))]
573 #[case("f oo", Err(Error::ParseError(ERROR_FOO.to_string())))]
574 #[case("any", Err(Error::ParseError(ERROR_ANY.to_string())))]
575 fn system_architecture_from_string(
576 #[case] s: &str,
577 #[case] arch: Result<SystemArchitecture, Error>,
578 ) {
579 assert_eq!(SystemArchitecture::from_str(s), arch);
580 }
581
582 #[rstest]
583 #[case(SystemArchitecture::Aarch64, "aarch64")]
584 #[case(SystemArchitecture::from_str("f_o_o").unwrap(), "f_o_o")]
585 fn system_architecture_format_string(#[case] arch: SystemArchitecture, #[case] arch_str: &str) {
586 assert_eq!(arch_str, format!("{arch}"));
587 }
588
589 #[rstest]
590 #[case("any", Ok(Architecture::Any))]
591 #[case("aarch64", Ok(SystemArchitecture::Aarch64.into()))]
592 #[case("arm", Ok(SystemArchitecture::Arm.into()))]
593 #[case("armv6h", Ok(SystemArchitecture::Armv6h.into()))]
594 #[case("armv7h", Ok(SystemArchitecture::Armv7h.into()))]
595 #[case("i386", Ok(SystemArchitecture::I386.into()))]
596 #[case("i486", Ok(SystemArchitecture::I486.into()))]
597 #[case("i686", Ok(SystemArchitecture::I686.into()))]
598 #[case("pentium4", Ok(SystemArchitecture::Pentium4.into()))]
599 #[case("riscv32", Ok(SystemArchitecture::Riscv32.into()))]
600 #[case("riscv64", Ok(SystemArchitecture::Riscv64.into()))]
601 #[case("x86_64", Ok(SystemArchitecture::X86_64.into()))]
602 #[case("x86_64_v2", Ok(SystemArchitecture::X86_64V2.into()))]
603 #[case("x86_64_v3", Ok(SystemArchitecture::X86_64V3.into()))]
604 #[case("x86_64_v4", Ok(SystemArchitecture::X86_64V4.into()))]
605 #[case("foo", UnknownArchitecture::new("foo").map(From::from))]
606 #[case("f_oo", UnknownArchitecture::new("f_oo").map(From::from))]
607 #[case("f oo", Err(Error::ParseError(ERROR_FOO.to_string())))]
608 fn architecture_from_string(#[case] s: &str, #[case] arch: Result<Architecture, Error>) {
609 assert_eq!(Architecture::from_str(s), arch);
610 }
611
612 #[rstest]
613 #[case(Architecture::Any, "any")]
614 #[case(SystemArchitecture::Aarch64.into(), "aarch64")]
615 #[case(Architecture::from_str("foo").unwrap(), "foo")]
616 fn architecture_format_string(#[case] arch: Architecture, #[case] arch_str: &str) {
617 assert_eq!(arch_str, format!("{arch}"));
618 }
619
620 #[rstest]
621 #[case(vec![Architecture::Any], Ok(Architectures::Any))]
622 #[case(vec![SystemArchitecture::Aarch64.into()], Ok(Architectures::Some(vec![SystemArchitecture::Aarch64])))]
623 #[case(vec![SystemArchitecture::Arm.into(), SystemArchitecture::I386.into()], Ok(Architectures::Some(vec![SystemArchitecture::Arm, SystemArchitecture::I386])))]
624 #[case(vec![SystemArchitecture::Arm.into(), SystemArchitecture::Arm.into()], Ok(Architectures::Some(vec![SystemArchitecture::Arm, SystemArchitecture::Arm])))]
626 #[case(vec![Architecture::Any, SystemArchitecture::I386.into()], Err(Error::InvalidArchitectures {
627 architectures: vec![Architecture::Any, SystemArchitecture::I386.into()],
628 context: "'any' cannot be used in combination with other architectures.",
629 }))]
630 #[case(vec![Architecture::Any, Architecture::Any], Err(Error::InvalidArchitectures {
631 architectures: vec![Architecture::Any, Architecture::Any],
632 context: "'any' cannot be used in combination with other architectures.",
633 }))]
634 #[case(vec![], Ok(Architectures::Some(vec![])))]
635 fn architectures_from_vec(
636 #[case] archs: Vec<Architecture>,
637 #[case] expected: Result<Architectures, Error>,
638 ) {
639 assert_eq!(archs.try_into(), expected);
640 }
641
642 #[rstest]
643 #[case(Architectures::Any, "any")]
644 #[case(Architectures::Some(vec![SystemArchitecture::Aarch64]), "aarch64")]
645 #[case(Architectures::Some(vec![SystemArchitecture::Arm, SystemArchitecture::I386]), "arm, i386")]
646 #[case(Architectures::Some(vec![]), "")]
647 fn architectures_format_display(#[case] archs: Architectures, #[case] archs_str: &str) {
648 assert_eq!(archs_str, format!("{archs}"));
649 }
650
651 #[rstest]
652 #[case("32", Ok(ElfArchitectureFormat::Bit32))]
653 #[case("64", Ok(ElfArchitectureFormat::Bit64))]
654 #[case("foo", Err(ParseError::VariantNotFound))]
655 fn elf_architecture_format_from_string(
656 #[case] s: &str,
657 #[case] arch: Result<ElfArchitectureFormat, ParseError>,
658 ) {
659 assert_eq!(ElfArchitectureFormat::from_str(s), arch);
660 }
661
662 #[rstest]
663 #[case(ElfArchitectureFormat::Bit32, "32")]
664 #[case(ElfArchitectureFormat::Bit64, "64")]
665 fn elf_architecture_format_display(
666 #[case] arch: ElfArchitectureFormat,
667 #[case] arch_str: &str,
668 ) {
669 assert_eq!(arch_str, format!("{arch}"));
670 }
671}