semver_sort/
semver.rs

1use regex::Regex;
2
3static SEMVER_REGEX: &str = r"^v?(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$";
4
5enum SemverValue<'a> {
6    Number(u32),
7    String(&'a str),
8}
9
10fn parse_type(semver: &str) -> (&str, SemverValue) {
11    match semver.parse::<u32>() {
12        Ok(num) => ("number", SemverValue::Number(num)),
13        Err(_) => ("string", SemverValue::String(semver)),
14    }
15}
16
17#[derive(Debug, PartialEq, PartialOrd)]
18pub struct Semver<'a> {
19    pub major: &'a str,
20    pub minor: &'a str,
21    pub patch: &'a str,
22    pub prerelease: Option<&'a str>,
23    pub buildmetadata: Option<&'a str>,
24}
25
26impl<'a> Semver<'a> {
27    pub fn new(
28        major: Option<&'a str>,
29        minor: Option<&'a str>,
30        patch: Option<&'a str>,
31        prerelease: Option<&'a str>,
32        buildmetadata: Option<&'a str>,
33    ) -> Self {
34        Self {
35            major: major.unwrap_or("0"),
36            minor: minor.unwrap_or("0"),
37            patch: patch.unwrap_or("0"),
38            prerelease: prerelease,
39            buildmetadata: buildmetadata,
40        }
41    }
42}
43
44pub fn semver_regex(semver: &str) -> Semver {
45    let re = Regex::new(SEMVER_REGEX).unwrap();
46    let captures = re.captures(semver).unwrap();
47
48    return Semver::new(
49        captures.name("major").map(|c| c.as_str()),
50        captures.name("minor").map(|c| c.as_str()),
51        captures.name("patch").map(|c| c.as_str()),
52        captures.name("prerelease").map(|c| c.as_str()),
53        captures.name("buildmetadata").map(|c| c.as_str()),
54    );
55}
56
57fn compare_version(left: Option<&str>, right: Option<&str>) -> bool {
58    match (left, right) {
59        (Some(left), Some(right)) => {
60            let left_type = parse_type(left);
61            let right_type = parse_type(right);
62
63            match (left_type.1, right_type.1) {
64                (SemverValue::Number(left), SemverValue::Number(right)) => return left < right,
65                (SemverValue::String(left), SemverValue::String(right)) => {
66                    return left.cmp(right) == std::cmp::Ordering::Greater
67                }
68                _ => return false,
69            }
70        }
71
72        (Some(_), None) => return false,
73        (None, Some(_)) => return true,
74        (None, None) => return false,
75    }
76}
77
78pub fn semver_compare(left: &str, right: &str, asc: bool) -> bool {
79    let left_semver = semver_regex(if asc { left } else { right });
80    let right_semver = semver_regex(if asc { right } else { left });
81
82    if left_semver.major != right_semver.major {
83        return compare_version(Some(left_semver.major), Some(right_semver.major));
84    } else if left_semver.minor != right_semver.minor {
85        return compare_version(Some(left_semver.minor), Some(right_semver.minor));
86    } else if left_semver.patch != right_semver.patch {
87        return compare_version(Some(left_semver.patch), Some(right_semver.patch));
88    } else if left_semver.prerelease != right_semver.prerelease {
89        return compare_version(left_semver.prerelease, right_semver.prerelease);
90    } else if left_semver.buildmetadata != right_semver.buildmetadata {
91        return compare_version(left_semver.buildmetadata, right_semver.buildmetadata);
92    }
93
94    false
95}