use std::cmp;
use std::fmt;
use std::str::FromStr;
quick_error! {
#[derive(Debug)]
pub enum ParseError {
EmptyComponent {
display("Empty version component")
}
InvalidComponent(comp: String) {
display( "Could not parse '{}' as a version component", comp)
}
NotAnInteger(num: String, err: String) {
display("Could not parse '{}' as an unsigned integer: {}", num, err)
}
}
}
const RE_VERSION_COMP_NUMERIC: &str = r"(?x) (?P<num> (?: 0 | [1-9][0-9]* )? ) (?P<rest> .* ) $";
#[derive(Debug, Clone)]
pub struct VersionComponent {
pub num: Option<i32>,
pub rest: String,
}
#[derive(Debug, Clone)]
pub struct Version {
value: String,
components: Vec<VersionComponent>,
}
impl Version {
fn compare_single(&self, left: &VersionComponent, right: &VersionComponent) -> cmp::Ordering {
match left.num {
Some(vleft) => match right.num {
Some(vright) => match vleft.cmp(&vright) {
cmp::Ordering::Equal => left.rest.cmp(&right.rest),
other => other,
},
None => cmp::Ordering::Greater,
},
None => match right.num.is_some() {
true => cmp::Ordering::Less,
false => left.rest.cmp(&right.rest),
},
}
}
fn compare_components(
&self,
left: &[VersionComponent],
right: &[VersionComponent],
) -> cmp::Ordering {
match left.get(0) {
Some(cleft) => match right.get(0) {
Some(cright) => match self.compare_single(cleft, cright) {
cmp::Ordering::Equal => self.compare_components(&left[1..], &right[1..]),
other => other,
},
None => match cleft.num.is_some() {
true => cmp::Ordering::Greater,
false => cmp::Ordering::Less,
},
},
None => match right.get(0) {
Some(vright) => match vright.num.is_some() {
true => cmp::Ordering::Less,
false => cmp::Ordering::Greater,
},
None => cmp::Ordering::Equal,
},
}
}
pub fn iter(&self) -> std::slice::Iter<VersionComponent> {
self.components.iter()
}
}
impl FromStr for Version {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let re_comp = regex::Regex::new(RE_VERSION_COMP_NUMERIC).unwrap();
let res: Vec<VersionComponent> = s
.split('.')
.map(|comp| match comp.is_empty() {
true => Err(ParseError::EmptyComponent),
false => match re_comp.captures(comp) {
Some(caps) => {
let num = &caps["num"];
let rest = &caps["rest"];
match num.is_empty() {
true => Ok(VersionComponent {
num: None,
rest: rest.to_string(),
}),
false => match num.parse::<i32>() {
Ok(value) => Ok(VersionComponent {
num: Some(value),
rest: rest.to_string(),
}),
Err(err) => {
Err(ParseError::NotAnInteger(num.to_string(), err.to_string()))
}
},
}
}
None => Err(ParseError::InvalidComponent(comp.to_string())),
},
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Self {
value: s.to_string(),
components: res,
})
}
}
impl AsRef<str> for Version {
fn as_ref(&self) -> &str {
&self.value
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_ref())
}
}
impl PartialEq for Version {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == cmp::Ordering::Equal
}
}
impl Eq for Version {}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.compare_components(&self.components, &other.components)
}
}
impl IntoIterator for Version {
type Item = VersionComponent;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.components.into_iter()
}
}