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}