use std::cmp::Ordering;
pub fn vercmp(left: &str, right: &str) -> Ordering {
let (left_epoch, left_rest) = split_epoch(left);
let (right_epoch, right_rest) = split_epoch(right);
match left_epoch.cmp(&right_epoch) {
Ordering::Equal => {}
other => return other,
}
let (left_ver, left_rel) = split_release(left_rest);
let (right_ver, right_rel) = split_release(right_rest);
match compare_version_part(left_ver, right_ver) {
Ordering::Equal => compare_version_part(left_rel.unwrap_or(""), right_rel.unwrap_or("")),
other => other,
}
}
fn split_epoch(value: &str) -> (u64, &str) {
if let Some((epoch, rest)) = value.split_once(':') {
(epoch.parse().unwrap_or(0), rest)
} else {
(0, value)
}
}
fn split_release(value: &str) -> (&str, Option<&str>) {
if let Some(index) = value.rfind('-') {
(&value[..index], Some(&value[index + 1..]))
} else {
(value, None)
}
}
fn compare_version_part(left: &str, right: &str) -> Ordering {
let left = left.as_bytes();
let right = right.as_bytes();
let mut li = 0;
let mut ri = 0;
loop {
while li < left.len() && !left[li].is_ascii_alphanumeric() {
li += 1;
}
while ri < right.len() && !right[ri].is_ascii_alphanumeric() {
ri += 1;
}
if li == left.len() || ri == right.len() {
return left[li..].len().cmp(&right[ri..].len());
}
let left_digit = left[li].is_ascii_digit();
let right_digit = right[ri].is_ascii_digit();
if left_digit && right_digit {
let l_start = li;
let r_start = ri;
while li < left.len() && left[li].is_ascii_digit() {
li += 1;
}
while ri < right.len() && right[ri].is_ascii_digit() {
ri += 1;
}
let l_trim = trim_zeroes(&left[l_start..li]);
let r_trim = trim_zeroes(&right[r_start..ri]);
match l_trim.len().cmp(&r_trim.len()) {
Ordering::Equal => match l_trim.cmp(r_trim) {
Ordering::Equal => {}
other => return other,
},
other => return other,
}
} else if !left_digit && !right_digit {
let l_start = li;
let r_start = ri;
while li < left.len() && left[li].is_ascii_alphabetic() {
li += 1;
}
while ri < right.len() && right[ri].is_ascii_alphabetic() {
ri += 1;
}
match left[l_start..li].cmp(&right[r_start..ri]) {
Ordering::Equal => {}
other => return other,
}
} else {
return if left_digit {
Ordering::Greater
} else {
Ordering::Less
};
}
}
}
fn trim_zeroes(mut value: &[u8]) -> &[u8] {
while value.first() == Some(&b'0') {
value = &value[1..];
}
value
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compares_numeric_segments() {
assert_eq!(vercmp("1.10", "1.9"), Ordering::Greater);
assert_eq!(vercmp("1.02", "1.2"), Ordering::Equal);
}
#[test]
fn compares_epoch_before_version() {
assert_eq!(vercmp("1:1.0", "2.0"), Ordering::Greater);
}
#[test]
fn compares_pkgrel_after_pkgver() {
assert_eq!(vercmp("1.0-2", "1.0-1"), Ordering::Greater);
}
}