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 insta::assert_snapshot;
541 use rstest::rstest;
542 use strum::ParseError;
543
544 use super::*;
545 use crate::configure_insta;
546
547 #[rstest]
548 #[case(
549 SystemArchitecture::Aarch64.into(),
550 Architecture::Some(SystemArchitecture::Aarch64)
551 )]
552 #[case(
553 SystemArchitecture::from_str("f_oo").unwrap().into(),
554 Architecture::Some(SystemArchitecture::from_str("f_oo").unwrap())
555 )]
556 fn system_architecture_into_architecture(
557 #[case] left: Architecture,
558 #[case] right: Architecture,
559 ) {
560 assert_eq!(left, right);
561 }
562
563 #[rstest]
564 #[case("aarch64", SystemArchitecture::Aarch64)]
565 #[case("f_oo", UnknownArchitecture::new("f_oo").unwrap().into())]
566 fn system_architecture_from_string(#[case] s: &str, #[case] arch: SystemArchitecture) {
567 assert_eq!(SystemArchitecture::from_str(s), Ok(arch));
568 }
569
570 #[rstest]
571 #[case("f oo")]
572 #[case("any")]
573 fn invalid_system_architecture_from_string(#[case] input: &str) {
574 let Err(Error::ParseError(err_msg)) = SystemArchitecture::from_str(input) else {
575 panic!("'{input}' erroneously parsed as a SystemArchitecture")
576 };
577
578 let (test_name, _guard) = configure_insta();
579 assert_snapshot!(test_name, err_msg.to_string());
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", Architecture::Any)]
591 #[case("aarch64", SystemArchitecture::Aarch64.into())]
592 #[case("arm", SystemArchitecture::Arm.into())]
593 #[case("armv6h", SystemArchitecture::Armv6h.into())]
594 #[case("armv7h", SystemArchitecture::Armv7h.into())]
595 #[case("i386", SystemArchitecture::I386.into())]
596 #[case("i486", SystemArchitecture::I486.into())]
597 #[case("i686", SystemArchitecture::I686.into())]
598 #[case("pentium4", SystemArchitecture::Pentium4.into())]
599 #[case("riscv32", SystemArchitecture::Riscv32.into())]
600 #[case("riscv64", SystemArchitecture::Riscv64.into())]
601 #[case("x86_64", SystemArchitecture::X86_64.into())]
602 #[case("x86_64_v2", SystemArchitecture::X86_64V2.into())]
603 #[case("x86_64_v3", SystemArchitecture::X86_64V3.into())]
604 #[case("x86_64_v4", SystemArchitecture::X86_64V4.into())]
605 #[case("foo", UnknownArchitecture::new("foo").unwrap().into())]
606 #[case("f_oo", UnknownArchitecture::new("f_oo").unwrap().into())]
607 fn architecture_from_string(#[case] input: &str, #[case] arch: Architecture) {
608 assert_eq!(Architecture::from_str(input), Ok(arch));
609 }
610
611 #[rstest]
612 #[case("f oo")]
613 fn invalid_architecture_from_string(#[case] input: &str) {
614 let Err(Error::ParseError(err_msg)) = Architecture::from_str(input) else {
615 panic!("'{input}' erroneously parsed as a Architecture")
616 };
617
618 let (test_name, _guard) = configure_insta();
619 assert_snapshot!(test_name, err_msg.to_string());
620 }
621
622 #[rstest]
623 #[case(Architecture::Any, "any")]
624 #[case(SystemArchitecture::Aarch64.into(), "aarch64")]
625 #[case(Architecture::from_str("foo").unwrap(), "foo")]
626 fn architecture_format_string(#[case] arch: Architecture, #[case] arch_str: &str) {
627 assert_eq!(arch_str, format!("{arch}"));
628 }
629
630 #[rstest]
631 #[case(vec![Architecture::Any], Ok(Architectures::Any))]
632 #[case(
633 vec![SystemArchitecture::Aarch64.into()],
634 Ok(Architectures::Some(vec![SystemArchitecture::Aarch64]))
635 )]
636 #[case(
637 vec![SystemArchitecture::Arm.into(), SystemArchitecture::I386.into()],
638 Ok(Architectures::Some(vec![SystemArchitecture::Arm, SystemArchitecture::I386]))
639 )]
640 #[case(
642 vec![SystemArchitecture::Arm.into(), SystemArchitecture::Arm.into()],
643 Ok(Architectures::Some(vec![SystemArchitecture::Arm, SystemArchitecture::Arm]))
644 )]
645 #[case(
646 vec![Architecture::Any, SystemArchitecture::I386.into()],
647 Err(Error::InvalidArchitectures {
648 architectures: vec![Architecture::Any, SystemArchitecture::I386.into()],
649 context: "'any' cannot be used in combination with other architectures.",
650 })
651 )]
652 #[case(vec![Architecture::Any, Architecture::Any], Err(Error::InvalidArchitectures {
653 architectures: vec![Architecture::Any, Architecture::Any],
654 context: "'any' cannot be used in combination with other architectures.",
655 }))]
656 #[case(vec![], Ok(Architectures::Some(vec![])))]
657 fn architectures_from_vec(
658 #[case] archs: Vec<Architecture>,
659 #[case] expected: Result<Architectures, Error>,
660 ) {
661 assert_eq!(archs.try_into(), expected);
662 }
663
664 #[rstest]
665 #[case(Architectures::Any, "any")]
666 #[case(Architectures::Some(vec![SystemArchitecture::Aarch64]), "aarch64")]
667 #[case(Architectures::Some(vec![SystemArchitecture::Arm, SystemArchitecture::I386]), "arm, i386")]
668 #[case(Architectures::Some(vec![]), "")]
669 fn architectures_format_display(#[case] archs: Architectures, #[case] archs_str: &str) {
670 assert_eq!(archs_str, format!("{archs}"));
671 }
672
673 #[rstest]
674 #[case("32", Ok(ElfArchitectureFormat::Bit32))]
675 #[case("64", Ok(ElfArchitectureFormat::Bit64))]
676 #[case("foo", Err(ParseError::VariantNotFound))]
677 fn elf_architecture_format_from_string(
678 #[case] s: &str,
679 #[case] arch: Result<ElfArchitectureFormat, ParseError>,
680 ) {
681 assert_eq!(ElfArchitectureFormat::from_str(s), arch);
682 }
683
684 #[rstest]
685 #[case(ElfArchitectureFormat::Bit32, "32")]
686 #[case(ElfArchitectureFormat::Bit64, "64")]
687 fn elf_architecture_format_display(
688 #[case] arch: ElfArchitectureFormat,
689 #[case] arch_str: &str,
690 ) {
691 assert_eq!(arch_str, format!("{arch}"));
692 }
693}