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