anodizer_core/git/
semver.rs1use anyhow::Result;
2use regex::Regex;
3use std::sync::LazyLock;
4
5#[derive(Debug, Clone)]
6pub struct SemVer {
7 pub major: u64,
8 pub minor: u64,
9 pub patch: u64,
10 pub prerelease: Option<String>,
11 pub build_metadata: Option<String>,
12}
13
14impl SemVer {
15 pub fn is_prerelease(&self) -> bool {
16 self.prerelease.is_some()
17 }
18}
19
20impl PartialEq for SemVer {
21 fn eq(&self, other: &Self) -> bool {
22 self.major == other.major
23 && self.minor == other.minor
24 && self.patch == other.patch
25 && self.prerelease == other.prerelease
26 }
27}
28
29impl Eq for SemVer {}
30
31impl PartialOrd for SemVer {
32 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
33 Some(self.cmp(other))
34 }
35}
36
37impl Ord for SemVer {
38 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
39 self.major
40 .cmp(&other.major)
41 .then(self.minor.cmp(&other.minor))
42 .then(self.patch.cmp(&other.patch))
43 .then(match (&self.prerelease, &other.prerelease) {
44 (Some(_), None) => std::cmp::Ordering::Less, (None, Some(_)) => std::cmp::Ordering::Greater, (Some(a), Some(b)) => compare_prerelease(a, b),
47 (None, None) => std::cmp::Ordering::Equal,
48 })
49 }
50}
51
52pub(super) fn compare_prerelease(a: &str, b: &str) -> std::cmp::Ordering {
60 use std::cmp::Ordering;
61
62 let a_ids: Vec<&str> = a.split('.').collect();
63 let b_ids: Vec<&str> = b.split('.').collect();
64
65 for (ai, bi) in a_ids.iter().zip(b_ids.iter()) {
66 let ord = match (ai.parse::<u64>(), bi.parse::<u64>()) {
67 (Ok(an), Ok(bn)) => an.cmp(&bn), (Ok(_), Err(_)) => Ordering::Less, (Err(_), Ok(_)) => Ordering::Greater, (Err(_), Err(_)) => ai.cmp(bi), };
72 if ord != Ordering::Equal {
73 return ord;
74 }
75 }
76 a_ids.len().cmp(&b_ids.len())
78}
79
80static SEMVER_RE: LazyLock<Regex> =
85 LazyLock::new(|| crate::util::static_regex(r"^v?(\d+)\.(\d+)\.(\d+)(?:-([^+]+))?(?:\+(.+))?$"));
86
87pub fn parse_semver(tag: &str) -> Result<SemVer> {
93 let caps = SEMVER_RE
94 .captures(tag)
95 .ok_or_else(|| anyhow::anyhow!("not a valid semver tag: {}", tag))?;
96 Ok(SemVer {
97 major: caps[1].parse()?,
98 minor: caps[2].parse()?,
99 patch: caps[3].parse()?,
100 prerelease: caps.get(4).map(|m| m.as_str().to_string()),
101 build_metadata: caps.get(5).map(|m| m.as_str().to_string()),
102 })
103}
104
105pub fn parse_semver_tag(tag: &str) -> Result<SemVer> {
111 if let Ok(sv) = parse_semver(tag) {
113 return Ok(sv);
114 }
115 static PREFIX_RE: LazyLock<Regex> =
117 LazyLock::new(|| crate::util::static_regex(r"[-_/](v?\d+\.\d+\.\d+(?:-[^+]+)?(?:\+.+)?)$"));
118 if let Some(caps) = PREFIX_RE.captures(tag) {
119 return parse_semver(&caps[1]);
120 }
121 anyhow::bail!("not a valid semver tag: {}", tag)
122}