1use crate::error::{Result, SemverError};
2use std::{
3 cmp::Ordering,
4 fmt::{self, Display},
5 mem,
6};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum Stability {
12 Dev,
13 Alpha,
14 Beta,
15 RC,
16 Stable,
17}
18
19impl Stability {
20 #[must_use]
23 pub fn parse(s: &str) -> Option<Self> {
24 match s.to_lowercase().as_str() {
25 "rc" => Some(Self::RC),
26 "beta" | "b" => Some(Self::Beta),
27 "alpha" | "a" => Some(Self::Alpha),
28 "dev" => Some(Self::Dev),
29 "stable" | "patch" | "pl" | "p" => Some(Self::Stable), _ => None,
31 }
32 }
33
34 pub fn normalize(s: &str) -> Result<Self> {
39 let lower = s.to_lowercase();
40 match lower.as_str() {
41 "stable" | "rc" | "beta" | "alpha" | "dev" => {
42 Self::parse(&lower).ok_or_else(|| SemverError::InvalidStability(s.to_string()))
43 },
44 _ => Err(SemverError::InvalidStability(s.to_string())),
45 }
46 }
47
48 #[must_use]
50 pub const fn as_str(&self) -> &'static str {
51 match self {
52 Self::Dev => "dev",
53 Self::Alpha => "alpha",
54 Self::Beta => "beta",
55 Self::RC => "RC",
56 Self::Stable => "stable",
57 }
58 }
59
60 const fn order(self) -> i32 {
62 match self {
63 Self::Dev => -4,
64 Self::Alpha => -3,
65 Self::Beta => -2,
66 Self::RC => -1,
67 Self::Stable => 0,
68 }
69 }
70}
71
72impl PartialOrd for Stability {
73 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
74 Some(self.cmp(other))
75 }
76}
77
78impl Ord for Stability {
79 fn cmp(&self, other: &Self) -> Ordering {
80 (*self).order().cmp(&(*other).order())
81 }
82}
83
84impl Display for Stability {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 write!(f, "{}", self.as_str())
87 }
88}
89
90#[must_use]
94pub fn expand_stability(stability: &str) -> &'static str {
95 match stability.to_lowercase().as_str() {
96 "a" | "alpha" => "alpha",
97 "b" | "beta" => "beta",
98 "p" | "pl" | "patch" => "patch",
99 "rc" => "RC",
100 "dev" => "dev",
101 _ => "stable",
103 }
104}
105
106#[must_use]
109pub fn stability_order(s: &str) -> i32 {
110 match s.to_lowercase().as_str() {
111 "dev" => -4,
112 "alpha" | "a" => -3,
113 "beta" | "b" => -2,
114 "rc" => -1,
115 "patch" | "pl" | "p" => 1,
116 _ => 0, }
118}
119
120#[must_use]
123pub fn version_compare(a: &str, b: &str) -> Ordering {
124 let a_parts = split_version(a);
125 let b_parts = split_version(b);
126
127 let max_len = a_parts.len().max(b_parts.len());
128
129 for i in 0..max_len {
130 let a_part = a_parts.get(i).map_or("", String::as_str);
131 let b_part = b_parts.get(i).map_or("", String::as_str);
132
133 let cmp = compare_parts(a_part, b_part);
134 if cmp != Ordering::Equal {
135 return cmp;
136 }
137 }
138
139 Ordering::Equal
140}
141
142fn split_version(version: &str) -> Vec<String> {
145 let mut parts = Vec::new();
146 let mut current = String::new();
147
148 for ch in version.chars() {
149 if ch == '.' || ch == '-' || ch == '_' {
150 if !current.is_empty() {
151 parts.push(mem::take(&mut current));
152 }
153 } else {
154 current.push(ch);
155 }
156 }
157
158 if !current.is_empty() {
159 parts.push(current);
160 }
161
162 parts
163}
164
165fn split_stability_and_number(s: &str) -> (&str, Option<i64>) {
168 let lower = s.to_lowercase();
170
171 for (prefix, canonical) in &[
173 ("alpha", "alpha"),
174 ("beta", "beta"),
175 ("patch", "patch"),
176 ("dev", "dev"),
177 ("rc", "RC"),
178 ("pl", "patch"),
179 ("a", "alpha"),
180 ("b", "beta"),
181 ("p", "patch"),
182 ] {
183 if lower.starts_with(prefix) {
184 let rest = &s[prefix.len()..];
185 let rest = rest.trim_start_matches(['.', '-']);
187 let num = rest.parse::<i64>().ok();
188 return (canonical, num);
189 }
190 }
191
192 if let Ok(n) = s.parse::<i64>() {
194 return ("", Some(n));
195 }
196
197 (s, None)
199}
200
201fn compare_parts(a: &str, b: &str) -> Ordering {
203 if a.is_empty() && b.is_empty() {
205 return Ordering::Equal;
206 }
207 if a.is_empty() {
208 let b_stability = stability_order(b);
210 if b_stability != 0 {
211 return Ordering::Greater; }
213 return Ordering::Less;
214 }
215 if b.is_empty() {
216 let a_stability = stability_order(a);
217 if a_stability != 0 {
218 return Ordering::Less;
219 }
220 return Ordering::Greater;
221 }
222
223 let (a_stability_str, a_num) = split_stability_and_number(a);
225 let (b_stability_str, b_num) = split_stability_and_number(b);
226
227 let a_stability_ord = stability_order(a_stability_str);
229 let b_stability_ord = stability_order(b_stability_str);
230
231 if a_stability_ord != 0 || b_stability_ord != 0 {
233 if a_stability_ord != b_stability_ord {
235 return a_stability_ord.cmp(&b_stability_ord);
236 }
237 match (a_num, b_num) {
239 (Some(an), Some(bn)) => return an.cmp(&bn),
240 (Some(_), None) => return Ordering::Greater,
241 (None, Some(_)) => return Ordering::Less,
242 (None, None) => return Ordering::Equal,
243 }
244 }
245
246 match (a_num, b_num) {
248 (Some(an), Some(bn)) => an.cmp(&bn),
249 (Some(_), None) => {
250 Ordering::Greater
252 },
253 (None, Some(_)) => {
254 Ordering::Less
256 },
257 (None, None) => {
258 a.to_lowercase().cmp(&b.to_lowercase())
260 },
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn test_stability_ordering() {
270 assert!(Stability::Dev < Stability::Alpha);
271 assert!(Stability::Alpha < Stability::Beta);
272 assert!(Stability::Beta < Stability::RC);
273 assert!(Stability::RC < Stability::Stable);
274 }
275
276 #[test]
277 fn test_stability_parse() {
278 assert_eq!(Stability::parse("dev"), Some(Stability::Dev));
279 assert_eq!(Stability::parse("alpha"), Some(Stability::Alpha));
280 assert_eq!(Stability::parse("a"), Some(Stability::Alpha));
281 assert_eq!(Stability::parse("beta"), Some(Stability::Beta));
282 assert_eq!(Stability::parse("b"), Some(Stability::Beta));
283 assert_eq!(Stability::parse("rc"), Some(Stability::RC));
284 assert_eq!(Stability::parse("RC"), Some(Stability::RC));
285 assert_eq!(Stability::parse("stable"), Some(Stability::Stable));
286 assert_eq!(Stability::parse("patch"), Some(Stability::Stable));
287 assert_eq!(Stability::parse("invalid"), None);
288 }
289
290 #[test]
291 fn test_version_compare() {
292 assert_eq!(version_compare("1.0.0", "1.0.0"), Ordering::Equal);
293 assert_eq!(version_compare("1.0.0", "1.0.1"), Ordering::Less);
294 assert_eq!(version_compare("1.0.1", "1.0.0"), Ordering::Greater);
295 assert_eq!(version_compare("1.0", "1.0.0"), Ordering::Less);
296 assert_eq!(version_compare("1.0.0", "1.0"), Ordering::Greater);
297 assert_eq!(version_compare("1.0.0-dev", "1.0.0"), Ordering::Less);
298 assert_eq!(version_compare("1.0.0-alpha", "1.0.0-beta"), Ordering::Less);
299 assert_eq!(version_compare("1.0.0-RC", "1.0.0"), Ordering::Less);
300 }
301
302 #[test]
303 fn test_version_compare_short_forms() {
304 assert_eq!(version_compare("2.0-b2", "2.0-beta2"), Ordering::Equal);
306 assert_eq!(version_compare("2.0-a1", "2.0-alpha1"), Ordering::Equal);
307 assert_eq!(version_compare("2.0-RC1", "2.0-rc1"), Ordering::Equal);
308 assert_eq!(version_compare("3.0-b2", "3.0-beta2"), Ordering::Equal);
309
310 assert_eq!(version_compare("2.0-alpha1", "2.0-beta1"), Ordering::Less);
312 assert_eq!(version_compare("2.0-a1", "2.0-b1"), Ordering::Less);
313
314 assert_eq!(version_compare("2.0-beta1", "2.0-beta2"), Ordering::Less);
316 assert_eq!(version_compare("2.0-b1", "2.0-b2"), Ordering::Less);
317 }
318
319 #[test]
320 fn test_expand_stability() {
321 assert_eq!(expand_stability("a"), "alpha");
322 assert_eq!(expand_stability("b"), "beta");
323 assert_eq!(expand_stability("p"), "patch");
324 assert_eq!(expand_stability("pl"), "patch");
325 assert_eq!(expand_stability("rc"), "RC");
326 assert_eq!(expand_stability("alpha"), "alpha");
327 }
328}