use std::cmp::Ordering;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::slice::Iter;
use std::str::FromStr;
use std::vec::IntoIter as VecIntoIter;
use anyhow::Error as AnyError;
use serde::Serialize;
use thiserror::Error;
use crate::expr::parser;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ParseError {
#[error("Could not parse '{0}' as a version string")]
ParseFailure(String, #[source] AnyError),
#[error("Could not parse '{0}' as a version string: {1} bytes left over")]
ParseLeftovers(String, usize),
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
#[allow(clippy::module_name_repetitions)]
pub struct VersionComponent {
pub num: Option<u32>,
pub rest: String,
}
fn compare_single(left: &VersionComponent, right: &VersionComponent) -> Ordering {
left.num.map_or_else(
|| {
if right.num.is_some() {
Ordering::Less
} else {
left.rest.cmp(&right.rest)
}
},
|ver_left| {
right.num.map_or(Ordering::Greater, |ver_right| {
let res = ver_left.cmp(&ver_right);
if res == Ordering::Equal {
left.rest.cmp(&right.rest)
} else {
res
}
})
},
)
}
fn compare_components(left: &[VersionComponent], right: &[VersionComponent]) -> Ordering {
left.split_first().map_or_else(
|| {
right.get(0).map_or(Ordering::Equal, |ver_right| {
if ver_right.num.is_some() {
Ordering::Less
} else {
Ordering::Greater
}
})
},
|(comp_left, rest_left)| {
right.split_first().map_or_else(
|| {
if comp_left.num.is_some() {
Ordering::Greater
} else {
Ordering::Less
}
},
|(comp_right, rest_right)| {
let res = compare_single(comp_left, comp_right);
if res == Ordering::Equal {
compare_components(rest_left, rest_right)
} else {
res
}
},
)
},
)
}
#[derive(Debug, Clone, Serialize)]
#[serde(transparent)]
pub struct Version {
value: String,
#[serde(skip)]
components: Vec<VersionComponent>,
}
impl Version {
#[inline]
#[must_use]
pub fn new(value: String, components: Vec<VersionComponent>) -> Self {
Self { value, components }
}
#[inline]
pub fn iter(&self) -> Iter<'_, VersionComponent> {
self.components.iter()
}
}
impl FromStr for Version {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
parser::parse_version(s)
}
}
impl AsRef<str> for Version {
#[inline]
fn as_ref(&self) -> &str {
&self.value
}
}
impl Display for Version {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.as_ref())
}
}
impl PartialEq for Version {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for Version {}
impl PartialOrd for Version {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Version {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
compare_components(&self.components, &other.components)
}
}
impl IntoIterator for Version {
type Item = VersionComponent;
type IntoIter = VecIntoIter<Self::Item>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.components.into_iter()
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::default_numeric_fallback)]
#![allow(clippy::panic_in_result_fn)]
use std::error::Error;
#[test]
fn test_num_only() -> Result<(), Box<dyn Error>> {
let expected: [super::VersionComponent; 1] = [super::VersionComponent {
num: Some(616),
rest: "".to_owned(),
}];
let ver: super::Version = "616".parse()?;
let components = ver.into_iter().collect::<Vec<_>>();
assert_eq!(&expected[..], &*components);
Ok(())
}
#[test]
fn test_rest_only() -> Result<(), Box<dyn Error>> {
let expected: [super::VersionComponent; 1] = [super::VersionComponent {
num: None,
rest: "whee".to_owned(),
}];
let ver: super::Version = "whee".parse()?;
let components = ver.into_iter().collect::<Vec<_>>();
assert_eq!(&expected[..], &*components);
Ok(())
}
#[test]
fn test_both() -> Result<(), Box<dyn Error>> {
let expected: [super::VersionComponent; 1] = [super::VersionComponent {
num: Some(29),
rest: "palms".to_owned(),
}];
let ver: super::Version = "29palms".parse()?;
let components = ver.into_iter().collect::<Vec<_>>();
assert_eq!(&expected[..], &*components);
Ok(())
}
#[test]
fn test_three() -> Result<(), Box<dyn Error>> {
let expected: [super::VersionComponent; 3] = [
super::VersionComponent {
num: Some(1),
rest: "".to_owned(),
},
super::VersionComponent {
num: Some(5),
rest: "a".to_owned(),
},
super::VersionComponent {
num: None,
rest: "beta3".to_owned(),
},
];
let ver: super::Version = "1.5a.beta3".parse()?;
let components = ver.into_iter().collect::<Vec<_>>();
assert_eq!(&expected[..], &*components);
Ok(())
}
}