use std::cmp::Ordering;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Version {
pub major: u32,
pub minor: u32,
pub patch: u32,
pub is_nightly: bool,
pub year: Option<u32>,
pub month: Option<u32>,
pub day: Option<u32>,
}
impl Version {
pub fn is_stable(&self) -> bool {
!self.is_nightly
}
pub fn is_nightly(&self) -> bool {
self.is_nightly
}
pub fn extract(version: &str) -> Option<Self> {
let mut is_nightly = false;
let version_parts = version
.trim()
.split(" ")
.nth(1)?
.split(".")
.filter_map(|v| {
if let Some(rest) = v.strip_suffix("-nightly") {
is_nightly = true;
rest.parse().ok()
} else {
v.parse().ok()
}
})
.collect::<Vec<u32>>();
if version_parts.len() != 3 {
return None
}
let date_parts = version
.split(" ")
.nth(3)
.map(|date| {
date.split("-")
.filter_map(|v| v.trim().strip_suffix(")").unwrap_or(v).parse().ok())
.collect::<Vec<u32>>()
})
.unwrap_or_default();
Some(Version {
major: version_parts[0],
minor: version_parts[1],
patch: version_parts[2],
is_nightly,
year: date_parts.get(0).copied(),
month: date_parts.get(1).copied(),
day: date_parts.get(2).copied(),
})
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
if self == other {
return Ordering::Equal
}
if self.is_stable() && other.is_nightly() {
return Ordering::Greater
} else if self.is_nightly() && other.is_stable() {
return Ordering::Less
}
let to_compare = [
(Some(self.major), Some(other.major)),
(Some(self.minor), Some(other.minor)),
(Some(self.patch), Some(other.patch)),
(self.year, other.year),
(self.month, other.month),
(self.day, other.day),
];
to_compare
.iter()
.find_map(|(l, r)| if l != r { l.partial_cmp(&r) } else { None })
.unwrap_or(Ordering::Equal)
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_compare_and_extract_works() {
let version_1_66_0 = Version::extract("cargo 1.66.0 (d65d197ad 2022-11-15)").unwrap();
let version_1_66_1 = Version::extract("cargo 1.66.1 (d65d197ad 2022-11-15)").unwrap();
let version_1_66_0_nightly =
Version::extract("cargo 1.66.0-nightly (d65d197ad 2022-10-15)").unwrap();
let version_1_66_0_nightly_older_date =
Version::extract("cargo 1.66.0-nightly (d65d197ad 2022-10-14)").unwrap();
let version_1_65_0 = Version::extract("cargo 1.65.0 (d65d197ad 2022-10-15)").unwrap();
let version_1_65_0_older_date =
Version::extract("cargo 1.65.0 (d65d197ad 2022-10-14)").unwrap();
assert!(version_1_66_1 > version_1_66_0);
assert!(version_1_66_1 > version_1_65_0);
assert!(version_1_66_1 > version_1_66_0_nightly);
assert!(version_1_66_1 > version_1_66_0_nightly_older_date);
assert!(version_1_66_1 > version_1_65_0_older_date);
assert!(version_1_66_0 > version_1_65_0);
assert!(version_1_66_0 > version_1_66_0_nightly);
assert!(version_1_66_0 > version_1_66_0_nightly_older_date);
assert!(version_1_66_0 > version_1_65_0_older_date);
assert!(version_1_65_0 > version_1_66_0_nightly);
assert!(version_1_65_0 > version_1_66_0_nightly_older_date);
assert!(version_1_65_0 > version_1_65_0_older_date);
let mut versions = vec![
version_1_66_0,
version_1_66_0_nightly,
version_1_66_0_nightly_older_date,
version_1_65_0_older_date,
version_1_65_0,
version_1_66_1,
];
versions.sort_by(|a, b| b.cmp(a));
let expected_versions_order = vec![
version_1_66_1,
version_1_66_0,
version_1_65_0,
version_1_65_0_older_date,
version_1_66_0_nightly,
version_1_66_0_nightly_older_date,
];
assert_eq!(expected_versions_order, versions);
}
#[test]
fn parse_with_newline() {
let version_1_66_0 = Version::extract("cargo 1.66.0 (d65d197ad 2022-11-15)\n").unwrap();
assert_eq!(
Version {
major: 1,
minor: 66,
patch: 0,
is_nightly: false,
year: Some(2022),
month: Some(11),
day: Some(15),
},
version_1_66_0,
);
}
#[test]
fn version_without_hash_and_date() {
let version_1_69_0 = Version::extract("cargo 1.69.0-nightly").unwrap();
assert_eq!(
Version {
major: 1,
minor: 69,
patch: 0,
is_nightly: true,
year: None,
month: None,
day: None,
},
version_1_69_0,
);
}
#[test]
fn parse_rustc_version() {
let version = Version::extract("rustc 1.73.0 (cc66ad468 2023-10-03)").unwrap();
assert_eq!(
version,
Version {
major: 1,
minor: 73,
patch: 0,
is_nightly: false,
year: Some(2023),
month: Some(10),
day: Some(03),
}
);
}
}