alpm_pkginfo/
utils.rs

1//! Utilities used by some or all PackageInfo versions.
2use std::{
3    fmt::{Display, Formatter},
4    str::FromStr,
5};
6
7use alpm_types::{PackageRelation, SonameV1, SonameV2};
8use winnow::{
9    ModalResult,
10    Parser,
11    combinator::{cut_err, fail},
12    error::{StrContext, StrContextValue},
13    stream::Stream,
14    token::rest,
15};
16
17use crate::Error;
18#[cfg(doc)]
19use crate::{PackageInfoV1, PackageInfoV2};
20
21/// Provides either a [`PackageRelation`] or a [`SonameV1`].
22///
23/// This enum is used for [alpm-package-relations] of type _run-time dependency_ and _provision_ in
24/// [`PackageInfoV1`] or [`PackageInfoV2`].
25///
26/// [alpm-package-relations]: https://alpm.archlinux.page/specifications/alpm-package-relation.7.html
27#[derive(Clone, Debug, Eq, PartialEq)]
28pub enum RelationOrSoname {
29    /// An [alpm-package-relation].
30    ///
31    /// [alpm-package-relation]: https://alpm.archlinux.page/specifications/alpm-package-relation.7.html
32    Relation(PackageRelation),
33    /// An [alpm-sonamev1].
34    ///
35    /// [alpm-sonamev1]: https://alpm.archlinux.page/specifications/alpm-sonamev1.7.html
36    SonameV1(SonameV1),
37    /// An [alpm-sonamev2].
38    ///
39    /// [alpm-sonamev2]: https://alpm.archlinux.page/specifications/alpm-sonamev2.7.html
40    SonameV2(SonameV2),
41}
42
43impl RelationOrSoname {
44    /// Recognizes a [`SonameV2`], a [`SonameV1`] or a [`PackageRelation`] in a string slice.
45    ///
46    /// First attempts to recognize a [`SonameV2`], then a [`SonameV1`] and if that fails, falls
47    /// back to recognizing a [`PackageRelation`].
48    /// Depending on recognized type, a [`RelationOrSoname`] is created accordingly.
49    pub fn parser(input: &mut &str) -> ModalResult<Self> {
50        // Implement a custom `winnow::combinator::alt`, as all type parsers are built in
51        // such a way that they return errors on unexpected input instead of backtracking.
52        let checkpoint = input.checkpoint();
53        let sonamev2_result = SonameV2::parser.parse_next(input);
54        if sonamev2_result.is_ok() {
55            let sonamev2 = sonamev2_result?;
56            return Ok(RelationOrSoname::SonameV2(sonamev2));
57        }
58
59        input.reset(&checkpoint);
60        let sonamev1_result = SonameV1::parser.parse_next(input);
61        if sonamev1_result.is_ok() {
62            let sonamev1 = sonamev1_result?;
63            return Ok(RelationOrSoname::SonameV1(sonamev1));
64        }
65
66        input.reset(&checkpoint);
67        let relation_result = rest.and_then(PackageRelation::parser).parse_next(input);
68        if relation_result.is_ok() {
69            let relation = relation_result?;
70            return Ok(RelationOrSoname::Relation(relation));
71        }
72
73        cut_err(fail)
74            .context(StrContext::Expected(StrContextValue::Description(
75                "alpm-sonamev2, alpm-sonamev1 or alpm-package-relation",
76            )))
77            .parse_next(input)
78    }
79}
80
81impl Display for RelationOrSoname {
82    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
83        match self {
84            RelationOrSoname::Relation(version) => write!(f, "{version}"),
85            RelationOrSoname::SonameV1(soname) => write!(f, "{soname}"),
86            RelationOrSoname::SonameV2(soname) => write!(f, "{soname}"),
87        }
88    }
89}
90
91impl FromStr for RelationOrSoname {
92    type Err = Error;
93
94    /// Creates a [`RelationOrSoname`] from a string slice.
95    ///
96    /// Relies on [`RelationOrSoname::parser`] to recognize types in `input` and create a
97    /// [`RelationOrSoname`] accordingly.
98    ///
99    /// # Errors
100    ///
101    /// Returns an error if no [`RelationOrSoname`] can be created from `input`.
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use alpm_pkginfo::RelationOrSoname;
107    /// use alpm_types::{PackageRelation, SonameV1, SonameV2};
108    ///
109    /// # fn main() -> Result<(), alpm_pkginfo::Error> {
110    /// let relation: RelationOrSoname = "example=1.0.0".parse()?;
111    /// assert_eq!(
112    ///     relation,
113    ///     RelationOrSoname::Relation(PackageRelation::new(
114    ///         "example".parse()?,
115    ///         Some("=1.0.0".parse()?)
116    ///     ))
117    /// );
118    ///
119    /// let sonamev2: RelationOrSoname = "lib:example.so.1".parse()?;
120    /// assert_eq!(
121    ///     sonamev2,
122    ///     RelationOrSoname::SonameV2(SonameV2::new("lib".parse()?, "example.so.1".parse()?))
123    /// );
124    ///
125    /// let sonamev1: RelationOrSoname = "example.so".parse()?;
126    /// assert_eq!(
127    ///     sonamev1,
128    ///     RelationOrSoname::SonameV1(SonameV1::new("example.so".parse()?, None, None)?)
129    /// );
130    /// # Ok(())
131    /// # }
132    /// ```
133    fn from_str(s: &str) -> Result<Self, Self::Err> {
134        Self::parser
135            .parse(s)
136            .map_err(|error| Error::AlpmType(alpm_types::Error::ParseError(error.to_string())))
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use alpm_types::{ElfArchitectureFormat, VersionOrSoname};
143    use rstest::rstest;
144    use testresult::TestResult;
145
146    use super::*;
147
148    #[rstest]
149    #[case(
150        "example",
151        RelationOrSoname::Relation(PackageRelation::new("example".parse().unwrap(), None))
152    )]
153    #[case(
154        "example=1.0.0",
155        RelationOrSoname::Relation(PackageRelation::new("example".parse().unwrap(), "=1.0.0".parse().ok()))
156    )]
157    #[case(
158        "example>=1.0.0",
159        RelationOrSoname::Relation(PackageRelation::new("example".parse().unwrap(), ">=1.0.0".parse().ok()))
160    )]
161    #[case(
162        "lib:example.so.1",
163        RelationOrSoname::SonameV2(
164            SonameV2::new(
165                "lib".parse().unwrap(),
166                "example.so.1".parse().unwrap(),
167            )
168        )
169    )]
170    #[case(
171        "lib:example.so",
172        RelationOrSoname::SonameV2(
173            SonameV2::new(
174                "lib".parse().unwrap(),
175                "example.so".parse().unwrap(),
176            )
177        )
178    )]
179    #[case(
180        "example.so",
181        RelationOrSoname::SonameV1(
182            SonameV1::new(
183                "example.so".parse().unwrap(),
184                None,
185                None,
186            ).unwrap()
187        )
188    )]
189    #[case(
190        "example.so=1.0.0-64",
191        RelationOrSoname::SonameV1(
192            SonameV1::new(
193                "example.so".parse().unwrap(),
194                Some(VersionOrSoname::Version("1.0.0".parse().unwrap())),
195                Some(ElfArchitectureFormat::Bit64),
196            ).unwrap()
197        )
198    )]
199    #[case(
200        "libexample.so=otherlibexample.so-64",
201        RelationOrSoname::SonameV1(
202            SonameV1::new(
203                "libexample.so".parse().unwrap(),
204                Some(VersionOrSoname::Soname("otherlibexample.so".parse().unwrap())),
205                Some(ElfArchitectureFormat::Bit64),
206            ).unwrap()
207        )
208
209    )]
210    fn test_relation_or_soname_parser(
211        #[case] mut input: &str,
212        #[case] expected: RelationOrSoname,
213    ) -> TestResult {
214        let input_str = input.to_string();
215        let result = RelationOrSoname::parser(&mut input)?;
216        assert_eq!(result, expected);
217        assert_eq!(result.to_string(), input_str);
218        Ok(())
219    }
220}