Skip to content

Commit

Permalink
fix: compare semver with different length
Browse files Browse the repository at this point in the history
  • Loading branch information
alexng353 committed Feb 21, 2025
1 parent d0bc0e3 commit a1943d7
Showing 1 changed file with 90 additions and 54 deletions.
144 changes: 90 additions & 54 deletions src/utils/compare_semver.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,77 @@
use std::cmp::Ordering;

// N=3, much faster than BTreeMap
fn get_precedence(a: &str) -> u8 {
match a {
"" => 5,
"rc" => 4,
"beta" => 3,
"alpha" => 2,
_ => 1,
}
}

fn compare_precedence(a: &str, b: &str) -> Ordering {
if a == b {
return Ordering::Equal;
}

let (a_major, a_numeric) = a.split_once('.').unwrap_or((a, ""));
let (b_major, b_numeric) = b.split_once('.').unwrap_or((b, ""));

let a_precedence = get_precedence(a_major.trim());
let b_precedence = get_precedence(b_major.trim());

a_precedence.cmp(&b_precedence).then(
a_numeric
.parse::<u8>()
.unwrap_or(0)
.cmp(&b_numeric.parse::<u8>().unwrap_or(0)),
)
}

fn parse_version(a: &str) -> [u8; 3] {
let mut out = [0; 3];
for (i, n) in a
.split('.')
.filter_map(|s| s.parse::<u8>().ok())
.take(3)
.enumerate()
{
out[i] = n;
}
out
}

/// Compare two semver strings. This function assumes that no numerical parts
/// of the version are larger than u8::MAX (255).
pub fn compare_semver(a: &str, b: &str) -> Ordering {
let (a, _) = a.split_once('+').unwrap_or((a, ""));
let (b, _) = b.split_once('+').unwrap_or((b, ""));

if a == b {
return Ordering::Equal;
}

let (a_version, a_build) = a.split_once('-').unwrap_or((a, ""));
let (b_version, b_build) = b.split_once('-').unwrap_or((b, ""));

let a_version = parse_version(a_version);
let b_version = parse_version(b_version);

for (a, b) in a_version.iter().zip(b_version.iter()) {
match a.cmp(b) {
Ordering::Equal => continue,
x => return x,
}
}

a_build
.is_empty()
.cmp(&b_build.is_empty())
.then(compare_precedence(a_build, b_build))
}

#[cfg(test)]
mod tests {
use super::compare_semver;
Expand Down Expand Up @@ -175,67 +247,31 @@ mod tests {
);
assert_eq!(compare_semver("1.0.0-rc.1", "1.0.0"), Ordering::Less);
}
}

// N=3, much faster than BTreeMap
fn get_precedence(a: &str) -> u8 {
match a {
"" => 5,
"rc" => 4,
"beta" => 3,
"alpha" => 2,
_ => 1,
}
}
#[test]
fn test_different_lengths() {
assert_eq!(compare_semver("1.0", "1.0.2"), Ordering::Less);
assert_eq!(compare_semver("1.0.2", "1.0"), Ordering::Greater);

fn compare_precedence(a: &str, b: &str) -> Ordering {
if a == b {
return Ordering::Equal;
}
assert_eq!(compare_semver("1.0-alpha", "1.0.2"), Ordering::Less);
assert_eq!(compare_semver("1.0.2", "1.0-alpha"), Ordering::Greater);

let (a_major, a_numeric) = a.split_once('.').unwrap_or((a, ""));
let (b_major, b_numeric) = b.split_once('.').unwrap_or((b, ""));
assert_eq!(compare_semver("1", "1.0.2"), Ordering::Less);
assert_eq!(compare_semver("1.0.2", "1"), Ordering::Greater);

let a_precedence = get_precedence(a_major.trim());
let b_precedence = get_precedence(b_major.trim());
assert_eq!(compare_semver("1", "2"), Ordering::Less);
assert_eq!(compare_semver("2", "1"), Ordering::Greater);

a_precedence.cmp(&b_precedence).then(
a_numeric
.parse::<u8>()
.unwrap_or(0)
.cmp(&b_numeric.parse::<u8>().unwrap_or(0)),
)
}
assert_eq!(compare_semver("1-alpha", "2"), Ordering::Less);
assert_eq!(compare_semver("2", "1-alpha"), Ordering::Greater);

fn parse_version(a: &str) -> Vec<u8> {
a.split('.').filter_map(|s| s.parse::<u8>().ok()).collect()
}
assert_eq!(compare_semver("1", "1.1"), Ordering::Less);
assert_eq!(compare_semver("1.1", "1"), Ordering::Greater);

/// Compare two semver strings. This function assumes that no numerical parts
/// of the version are larger than u8::MAX (255).
pub fn compare_semver(a: &str, b: &str) -> Ordering {
let (a, _) = a.split_once('+').unwrap_or((a, ""));
let (b, _) = b.split_once('+').unwrap_or((b, ""));
assert_eq!(compare_semver("1.0", "1"), Ordering::Equal);
assert_eq!(compare_semver("1", "1.0"), Ordering::Equal);

if a == b {
return Ordering::Equal;
assert_eq!(compare_semver("0.1", "1"), Ordering::Less);
assert_eq!(compare_semver("1", "0.1"), Ordering::Greater);
}

let (a_version, a_build) = a.split_once('-').unwrap_or((a, ""));
let (b_version, b_build) = b.split_once('-').unwrap_or((b, ""));

let a_version: Vec<u8> = parse_version(a_version);
let b_version: Vec<u8> = parse_version(b_version);

for (a, b) in a_version.iter().zip(b_version.iter()) {
match a.cmp(b) {
Ordering::Equal => continue,
x => return x,
}
}

a_build
.is_empty()
.cmp(&b_build.is_empty())
.then(compare_precedence(a_build, b_build))
}

0 comments on commit a1943d7

Please sign in to comment.