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