alpm_types/version/
buildtool.rs1use std::{
4 fmt::{Display, Formatter},
5 str::FromStr,
6};
7
8use alpm_parsers::prelude::*;
9#[cfg(feature = "serde")]
10use serde::Serialize;
11use winnow::{combinator::opt, error::ErrMode};
12
13#[cfg(doc)]
14use crate::BuildTool;
15use crate::{Architecture, Error, FullVersion, MinimalVersion, Version};
16
17#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
69#[cfg_attr(feature = "serde", derive(Serialize))]
70pub enum BuildToolVersion {
71 Makepkg(MinimalVersion),
75 DevTools {
79 version: FullVersion,
81 architecture: Architecture,
83 },
84}
85
86impl BuildToolVersion {
87 pub fn architecture(&self) -> Option<Architecture> {
93 if let Self::DevTools {
94 version: _,
95 architecture,
96 } = self
97 {
98 Some(architecture.clone())
99 } else {
100 None
101 }
102 }
103
104 pub fn version(&self) -> Version {
106 match self {
107 Self::Makepkg(version) => Version::from(version),
108 Self::DevTools {
109 version,
110 architecture: _,
111 } => Version::from(version),
112 }
113 }
114}
115
116impl AlpmParser for BuildToolVersion {
117 fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, Self> {
125 let parser = move |input: &mut Input<'a>| -> PResult<'a, Self> {
126 let full_version = opt(FullVersion::parser).parse_next(input)?;
133
134 if let Some(version) = full_version {
135 "-".label("buildtool version")
136 .expected_text(
137 "'-' delimiter between full alpm-package-version and alpm-architecture",
138 )
139 .parse_next(input)?;
140
141 let architecture = Architecture::parser.parse_next(input)?;
142 return Ok(BuildToolVersion::DevTools {
143 version,
144 architecture,
145 });
146 }
147
148 let minimal_version = MinimalVersion::parser
149 .expected_text("a minimal alpm-package-version")
150 .expected_text(
151 "or a full alpm-package-version followed by a '-' and an alpm-architecture",
152 )
153 .parse_next(input)?;
154
155 Ok(BuildToolVersion::Makepkg(minimal_version))
156 };
157
158 parser.layer("buildtool version").parse_next(input)
159 }
160
161 fn delimiter_error_context<'a, O, P>(
162 parser: P,
163 ) -> impl Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>
164 where
165 P: Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>,
166 {
167 parser
168 .expected_text("a minimal alpm-package-version")
169 .expected_text(
170 "or a full alpm-package-version followed by a '-' and an alpm-architecture",
171 )
172 .layer("buildtool version")
173 }
174}
175
176impl FromStr for BuildToolVersion {
177 type Err = Error;
178 fn from_str(s: &str) -> Result<Self, Self::Err> {
186 Ok(Self::parser_until_eof.parse(Input::new(s))?)
187 }
188}
189
190impl Display for BuildToolVersion {
191 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
192 match self {
193 Self::Makepkg(version) => write!(f, "{version}"),
194 Self::DevTools {
195 version,
196 architecture,
197 } => write!(f, "{version}-{architecture}"),
198 }
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use insta::assert_snapshot;
205 use rstest::rstest;
206 use testresult::TestResult;
207
208 use super::*;
209 use crate::configure_insta;
210
211 #[rstest]
214 #[case::devtools_full(
215 "1.0.0-1-any",
216 BuildToolVersion::DevTools{version: FullVersion::from_str("1.0.0-1")?, architecture: Architecture::from_str("any")?},
217 )]
218 #[case::devtools_full_with_epoch(
219 "1:1.0.0-1-any",
220 BuildToolVersion::DevTools{version: FullVersion::from_str("1:1.0.0-1")?, architecture: Architecture::from_str("any")?},
221 )]
222 #[case::makepkg_minimal(
223 "1.0.0",
224 BuildToolVersion::Makepkg(MinimalVersion::from_str("1.0.0")?),
225 )]
226 #[case::makepkg_minimal_with_epoch(
227 "1:1.0.0",
228 BuildToolVersion::Makepkg(MinimalVersion::from_str("1:1.0.0")?),
229 )]
230 fn valid_buildtool_version(
231 #[case] input: &str,
232 #[case] expected: BuildToolVersion,
233 ) -> TestResult {
234 let version = match BuildToolVersion::from_str(input) {
235 Ok(version) => version,
236 Err(err) => {
237 panic!("Expected BuildToolVersion parsing of string {input} to succeed:\n{err}")
238 }
239 };
240
241 assert_eq!(
242 version, expected,
243 "Expected '{expected:#?}' when parsing '{input}' but got '{version:#?}'"
244 );
245
246 Ok(())
247 }
248
249 #[rstest]
250 #[case::full_version_with_architecture("1.0.0-any")]
251 #[case::minimal_version_with_epoch_and_architecture("1:1.0.0-any")]
252 #[case::bad_package_version("ß-1-any")]
253 fn invalid_buildtool_version(#[case] input: &str) -> TestResult {
254 let (test_name, _guard) = configure_insta();
255
256 let err = match BuildToolVersion::from_str(input) {
257 Err(err) => err,
258 Ok(_) => {
259 panic!("Expected BuildToolVersion parsing of string {input} to fail")
260 }
261 };
262
263 assert_snapshot!(test_name, err.to_string());
264
265 Ok(())
266 }
267}