1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Version {
pub major: u32,
pub minor: Option<u32>,
pub patch: Option<u32>,
pub pre_release: Option<String>,
pub build: Option<String>,
}
impl Version {
pub fn parse(version_str: &str) -> Option<Self> {
let version_str = version_str.trim();
let (core_version, pre_and_build) = if let Some(pos) = version_str.find('-') {
(&version_str[..pos], Some(&version_str[pos + 1..]))
} else {
(version_str, None)
};
let (pre_release, build) = if let Some(pre_and_build) = pre_and_build {
if let Some(pos) = pre_and_build.find('+') {
(
Some(pre_and_build[..pos].to_string()),
Some(pre_and_build[pos + 1..].to_string()),
)
} else {
(Some(pre_and_build.to_string()), None)
}
} else if let Some(pos) = core_version.find('+') {
let (_core, build_part) = core_version.split_at(pos);
(None, Some(build_part[1..].to_string()))
} else {
(None, None)
};
let core_version = if let Some(pos) = core_version.find('+') {
&core_version[..pos]
} else {
core_version
};
let parts: Vec<&str> = core_version.split('.').collect();
// Ensure we have at least a major version
if parts.is_empty() {
return None;
}
let major = parts[0].parse().ok()?;
let minor = parts.get(1).and_then(|v| v.parse().ok());
let patch = parts.get(2).and_then(|v| v.parse().ok());
Some(Version {
major,
minor,
patch,
pre_release,
build,
})
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.major)?;
if let Some(minor) = self.minor {
write!(f, ".{minor}")?;
if let Some(patch) = self.patch {
write!(f, ".{patch}")?;
}
}
if let Some(pre) = &self.pre_release {
write!(f, "-{pre}")?;
}
if let Some(build) = &self.build {
write!(f, "+{build}")?;
}
Ok(())
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// Compare major version first
match self.major.cmp(&other.major) {
std::cmp::Ordering::Equal => {
// If either version has no minor, they are equal at major level
match (&self.minor, &other.minor) {
(None, None) => {
// Both have no minor, handle pre-release comparison
match (&self.pre_release, &other.pre_release) {
(None, None) => std::cmp::Ordering::Equal,
(Some(_), None) => std::cmp::Ordering::Less, // Pre-release < normal
(None, Some(_)) => std::cmp::Ordering::Greater, // Normal > pre-release
(Some(a), Some(b)) => a.cmp(b), // Compare pre-release strings
}
}
(None, Some(_)) | (Some(_), None) => std::cmp::Ordering::Equal,
(Some(self_minor), Some(other_minor)) => {
// Compare minor versions
match self_minor.cmp(other_minor) {
std::cmp::Ordering::Equal => {
// If either version has no patch, they are equal at minor level
match (&self.patch, &other.patch) {
(None, None) => {
// Both have no patch, handle pre-release comparison
match (&self.pre_release, &other.pre_release) {
(None, None) => std::cmp::Ordering::Equal,
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(Some(a), Some(b)) => a.cmp(b),
}
}
(None, Some(_)) | (Some(_), None) => std::cmp::Ordering::Equal,
(Some(self_patch), Some(other_patch)) => {
// Compare patch versions
match self_patch.cmp(other_patch) {
std::cmp::Ordering::Equal => {
// Handle pre-release comparison
match (&self.pre_release, &other.pre_release) {
(None, None) => std::cmp::Ordering::Equal,
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(Some(a), Some(b)) => a.cmp(b),
}
}
patch_ordering => patch_ordering,
}
}
}
}
minor_ordering => minor_ordering,
}
}
}
}
major_ordering => major_ordering,
}
}
}