1use std::cmp::Ordering;
4use std::fmt;
5use std::str::FromStr;
6
7use serde::{Deserialize, Serialize};
8
9use crate::Error;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct Version {
14 pub epoch: u32,
16 pub release: Vec<u32>,
18 pub pre: Option<PreRelease>,
20 pub post: Option<u32>,
22 pub dev: Option<u32>,
24 pub local: Option<String>,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
30pub enum PreRelease {
31 Alpha(u32),
32 Beta(u32),
33 Rc(u32),
34}
35
36impl PreRelease {
37 fn order_key(&self) -> (u8, u32) {
39 match self {
40 PreRelease::Alpha(n) => (0, *n),
41 PreRelease::Beta(n) => (1, *n),
42 PreRelease::Rc(n) => (2, *n),
43 }
44 }
45}
46
47impl PartialOrd for PreRelease {
48 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
49 Some(self.cmp(other))
50 }
51}
52
53impl Ord for PreRelease {
54 fn cmp(&self, other: &Self) -> Ordering {
55 self.order_key().cmp(&other.order_key())
56 }
57}
58
59impl Version {
60 pub fn new(release: Vec<u32>) -> Self {
62 Self {
63 epoch: 0,
64 release,
65 pre: None,
66 post: None,
67 dev: None,
68 local: None,
69 }
70 }
71
72 pub fn parse(s: &str) -> Result<Self, Error> {
74 let s = s.trim();
75 if s.is_empty() {
76 return Err(Error::InvalidVersion(s.to_string()));
77 }
78
79 let mut epoch = 0u32;
80 let mut remaining = s;
81
82 if let Some(excl_pos) = remaining.find('!') {
84 epoch = remaining[..excl_pos]
85 .parse()
86 .map_err(|_| Error::InvalidVersion(s.to_string()))?;
87 remaining = &remaining[excl_pos + 1..];
88 }
89
90 let (version_part, local) = if let Some(plus_pos) = remaining.find('+') {
92 let local = remaining[plus_pos + 1..].to_string();
93 (&remaining[..plus_pos], Some(local))
94 } else {
95 (remaining, None)
96 };
97
98 let (release, pre, post, dev) = Self::parse_version_part(version_part, s)?;
100
101 Ok(Self {
102 epoch,
103 release,
104 pre,
105 post,
106 dev,
107 local,
108 })
109 }
110
111 #[allow(clippy::type_complexity)]
112 fn parse_version_part(
113 s: &str,
114 original: &str,
115 ) -> Result<(Vec<u32>, Option<PreRelease>, Option<u32>, Option<u32>), Error> {
116 let mut pre = None;
117 let mut post = None;
118 let mut dev = None;
119
120 let mut release_end = 0;
123 let chars: Vec<char> = s.chars().collect();
124 let mut i = 0;
125
126 while i < chars.len() {
127 let c = chars[i];
128 if c.is_numeric() {
129 while i < chars.len() && chars[i].is_numeric() {
131 i += 1;
132 }
133 release_end = i;
134 if i < chars.len() && chars[i] == '.' {
136 if i + 1 < chars.len() && chars[i + 1].is_numeric() {
138 i += 1; continue;
140 }
141 }
142 break;
143 } else if c == '.' {
144 i += 1;
145 } else {
146 break;
147 }
148 }
149
150 let release_str = &s[..release_end];
151 let remaining = s[release_end..].trim_start_matches('.');
152
153 let release: Result<Vec<u32>, _> = release_str
155 .split('.')
156 .filter(|p| !p.is_empty())
157 .map(|p| p.parse::<u32>())
158 .collect();
159
160 let release = release.map_err(|_| Error::InvalidVersion(original.to_string()))?;
161
162 if release.is_empty() {
163 return Err(Error::InvalidVersion(original.to_string()));
164 }
165
166 let remaining_lower = remaining.to_lowercase();
168 let mut pos = 0;
169
170 while pos < remaining_lower.len() {
171 let rest = &remaining_lower[pos..];
172
173 if rest.starts_with("dev") {
174 let num_start = 3;
175 let num_end = rest[num_start..]
176 .find(|c: char| !c.is_numeric())
177 .map(|i| num_start + i)
178 .unwrap_or(rest.len());
179
180 let num: u32 = if num_end > num_start {
181 rest[num_start..num_end].parse().unwrap_or(0)
182 } else {
183 0
184 };
185 dev = Some(num);
186 pos += num_end;
187 } else if rest.starts_with("post") || rest.starts_with("-") || rest.starts_with("r") {
188 let prefix_len = if rest.starts_with("post") { 4 } else { 1 };
189 let num_end = rest[prefix_len..]
190 .find(|c: char| !c.is_numeric())
191 .map(|i| prefix_len + i)
192 .unwrap_or(rest.len());
193
194 let num: u32 = if num_end > prefix_len {
195 rest[prefix_len..num_end].parse().unwrap_or(0)
196 } else {
197 0
198 };
199 post = Some(num);
200 pos += num_end;
201 } else if rest.starts_with("alpha") || rest.starts_with('a') {
202 let prefix_len = if rest.starts_with("alpha") { 5 } else { 1 };
203 let num_end = rest[prefix_len..]
204 .find(|c: char| !c.is_numeric())
205 .map(|i| prefix_len + i)
206 .unwrap_or(rest.len());
207
208 let num: u32 = if num_end > prefix_len {
209 rest[prefix_len..num_end].parse().unwrap_or(0)
210 } else {
211 0
212 };
213 pre = Some(PreRelease::Alpha(num));
214 pos += num_end;
215 } else if rest.starts_with("beta") || rest.starts_with('b') {
216 let prefix_len = if rest.starts_with("beta") { 4 } else { 1 };
217 let num_end = rest[prefix_len..]
218 .find(|c: char| !c.is_numeric())
219 .map(|i| prefix_len + i)
220 .unwrap_or(rest.len());
221
222 let num: u32 = if num_end > prefix_len {
223 rest[prefix_len..num_end].parse().unwrap_or(0)
224 } else {
225 0
226 };
227 pre = Some(PreRelease::Beta(num));
228 pos += num_end;
229 } else if rest.starts_with("rc") || rest.starts_with('c') || rest.starts_with("preview")
230 {
231 let prefix_len = if rest.starts_with("preview") {
232 7
233 } else if rest.starts_with("rc") {
234 2
235 } else {
236 1
237 };
238 let num_end = rest[prefix_len..]
239 .find(|c: char| !c.is_numeric())
240 .map(|i| prefix_len + i)
241 .unwrap_or(rest.len());
242
243 let num: u32 = if num_end > prefix_len {
244 rest[prefix_len..num_end].parse().unwrap_or(0)
245 } else {
246 0
247 };
248 pre = Some(PreRelease::Rc(num));
249 pos += num_end;
250 } else {
251 pos += 1;
253 }
254 }
255
256 Ok((release, pre, post, dev))
257 }
258}
259
260impl FromStr for Version {
261 type Err = Error;
262
263 fn from_str(s: &str) -> Result<Self, Self::Err> {
264 Self::parse(s)
265 }
266}
267
268impl fmt::Display for Version {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 if self.epoch > 0 {
271 write!(f, "{}!", self.epoch)?;
272 }
273
274 let release: Vec<String> = self.release.iter().map(|n| n.to_string()).collect();
275 write!(f, "{}", release.join("."))?;
276
277 if let Some(ref pre) = self.pre {
278 match pre {
279 PreRelease::Alpha(n) => write!(f, "a{}", n)?,
280 PreRelease::Beta(n) => write!(f, "b{}", n)?,
281 PreRelease::Rc(n) => write!(f, "rc{}", n)?,
282 }
283 }
284
285 if let Some(post) = self.post {
286 write!(f, ".post{}", post)?;
287 }
288
289 if let Some(dev) = self.dev {
290 write!(f, ".dev{}", dev)?;
291 }
292
293 if let Some(ref local) = self.local {
294 write!(f, "+{}", local)?;
295 }
296
297 Ok(())
298 }
299}
300
301impl PartialOrd for Version {
302 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
303 Some(self.cmp(other))
304 }
305}
306
307impl Ord for Version {
308 fn cmp(&self, other: &Self) -> Ordering {
309 match self.epoch.cmp(&other.epoch) {
311 Ordering::Equal => {}
312 ord => return ord,
313 }
314
315 let max_len = self.release.len().max(other.release.len());
317 for i in 0..max_len {
318 let a = self.release.get(i).unwrap_or(&0);
319 let b = other.release.get(i).unwrap_or(&0);
320 match a.cmp(b) {
321 Ordering::Equal => {}
322 ord => return ord,
323 }
324 }
325
326 match (&self.pre, &other.pre) {
330 (None, Some(_)) => {
331 if self.dev.is_some() && other.dev.is_none() {
335 return Ordering::Less;
336 }
337 return Ordering::Greater;
338 }
339 (Some(_), None) => {
340 if other.dev.is_some() && self.dev.is_none() {
342 return Ordering::Greater;
343 }
344 return Ordering::Less;
345 }
346 (Some(a), Some(b)) => match a.cmp(b) {
347 Ordering::Equal => {}
348 ord => return ord,
349 },
350 (None, None) => {}
351 }
352
353 match (&self.dev, &other.dev) {
355 (None, Some(_)) => return Ordering::Greater,
356 (Some(_), None) => return Ordering::Less,
357 (Some(a), Some(b)) => match a.cmp(b) {
358 Ordering::Equal => {}
359 ord => return ord,
360 },
361 (None, None) => {}
362 }
363
364 match (&self.post, &other.post) {
366 (None, Some(_)) => return Ordering::Less,
367 (Some(_), None) => return Ordering::Greater,
368 (Some(a), Some(b)) => match a.cmp(b) {
369 Ordering::Equal => {}
370 ord => return ord,
371 },
372 (None, None) => {}
373 }
374
375 match (&self.local, &other.local) {
377 (None, Some(_)) => Ordering::Less,
378 (Some(_), None) => Ordering::Greater,
379 (Some(a), Some(b)) => a.cmp(b),
380 (None, None) => Ordering::Equal,
381 }
382 }
383}
384
385impl pubgrub::version::Version for Version {
387 fn lowest() -> Self {
389 Self {
390 epoch: 0,
391 release: vec![0],
392 pre: Some(PreRelease::Alpha(0)),
393 dev: Some(0),
394 post: None,
395 local: None,
396 }
397 }
398
399 fn bump(&self) -> Self {
401 let mut bumped = self.clone();
402
403 bumped.local = None;
405
406 if let Some(dev) = bumped.dev {
408 bumped.dev = Some(dev + 1);
409 return bumped;
410 }
411
412 if let Some(post) = bumped.post {
414 bumped.post = Some(post + 1);
415 return bumped;
416 }
417
418 if let Some(ref pre) = bumped.pre {
420 match pre {
421 PreRelease::Alpha(n) => bumped.pre = Some(PreRelease::Alpha(n + 1)),
422 PreRelease::Beta(n) => bumped.pre = Some(PreRelease::Beta(n + 1)),
423 PreRelease::Rc(n) => bumped.pre = Some(PreRelease::Rc(n + 1)),
424 }
425 return bumped;
426 }
427
428 if let Some(last) = bumped.release.last_mut() {
430 *last += 1;
431 } else {
432 bumped.release.push(1);
433 }
434
435 bumped
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442 use pubgrub::version::Version as PubgrubVersion;
443
444 #[test]
445 fn test_parse_simple() {
446 let v = Version::parse("1.2.3").unwrap();
447 assert_eq!(v.release, vec![1, 2, 3]);
448 }
449
450 #[test]
451 fn test_parse_with_epoch() {
452 let v = Version::parse("1!2.3.4").unwrap();
453 assert_eq!(v.epoch, 1);
454 assert_eq!(v.release, vec![2, 3, 4]);
455 }
456
457 #[test]
458 fn test_parse_with_pre() {
459 let v = Version::parse("1.0a1").unwrap();
460 assert_eq!(v.pre, Some(PreRelease::Alpha(1)));
461
462 let v = Version::parse("1.0b2").unwrap();
463 assert_eq!(v.pre, Some(PreRelease::Beta(2)));
464
465 let v = Version::parse("1.0rc3").unwrap();
466 assert_eq!(v.pre, Some(PreRelease::Rc(3)));
467 }
468
469 #[test]
470 fn test_parse_with_post() {
471 let v = Version::parse("1.0.post1").unwrap();
472 assert_eq!(v.post, Some(1));
473 }
474
475 #[test]
476 fn test_parse_with_dev() {
477 let v = Version::parse("1.0.dev1").unwrap();
478 assert_eq!(v.dev, Some(1));
479 }
480
481 #[test]
482 fn test_parse_with_local() {
483 let v = Version::parse("1.0+local.1").unwrap();
484 assert_eq!(v.local, Some("local.1".to_string()));
485 }
486
487 #[test]
488 fn test_parse_complex() {
489 let v = Version::parse("1!2.3.4a1.post2.dev3+local").unwrap();
490 assert_eq!(v.epoch, 1);
491 assert_eq!(v.release, vec![2, 3, 4]);
492 assert_eq!(v.pre, Some(PreRelease::Alpha(1)));
493 assert_eq!(v.post, Some(2));
494 assert_eq!(v.dev, Some(3));
495 assert_eq!(v.local, Some("local".to_string()));
496 }
497
498 #[test]
499 fn test_display() {
500 let v = Version::new(vec![1, 2, 3]);
501 assert_eq!(v.to_string(), "1.2.3");
502
503 let mut v = Version::new(vec![1, 0]);
504 v.pre = Some(PreRelease::Alpha(1));
505 assert_eq!(v.to_string(), "1.0a1");
506 }
507
508 #[test]
509 fn test_ordering_release() {
510 let v1 = Version::parse("1.0.0").unwrap();
511 let v2 = Version::parse("1.0.1").unwrap();
512 let v3 = Version::parse("1.1.0").unwrap();
513
514 assert!(v1 < v2);
515 assert!(v2 < v3);
516 assert!(v1 < v3);
517 }
518
519 #[test]
520 fn test_ordering_pre() {
521 let v1 = Version::parse("1.0a1").unwrap();
522 let v2 = Version::parse("1.0a2").unwrap();
523 let v3 = Version::parse("1.0b1").unwrap();
524 let v4 = Version::parse("1.0rc1").unwrap();
525 let v5 = Version::parse("1.0").unwrap();
526
527 assert!(v1 < v2);
528 assert!(v2 < v3);
529 assert!(v3 < v4);
530 assert!(v4 < v5); }
532
533 #[test]
534 fn test_ordering_post() {
535 let v1 = Version::parse("1.0").unwrap();
536 let v2 = Version::parse("1.0.post1").unwrap();
537 let v3 = Version::parse("1.0.post2").unwrap();
538
539 assert!(v1 < v2);
540 assert!(v2 < v3);
541 }
542
543 #[test]
544 fn test_ordering_dev() {
545 let v1 = Version::parse("1.0.dev1").unwrap();
546 let v2 = Version::parse("1.0.dev2").unwrap();
547 let v3 = Version::parse("1.0").unwrap();
548
549 assert!(v1 < v2);
550 assert!(v2 < v3); }
552
553 #[test]
554 fn test_ordering_epoch() {
555 let v1 = Version::parse("1.0").unwrap();
556 let v2 = Version::parse("1!0.1").unwrap();
557
558 assert!(v1 < v2); }
560
561 #[test]
562 fn test_bump() {
563 let v1 = Version::parse("1.0.0").unwrap();
564 let v2 = v1.bump();
565 assert!(v1 < v2);
566
567 let v3 = Version::parse("1.0.dev1").unwrap();
568 let v4 = v3.bump();
569 assert!(v3 < v4);
570 }
571
572 #[test]
573 fn test_lowest() {
574 let lowest = Version::lowest();
575 let v1 = Version::parse("0.0.1").unwrap();
576 assert!(lowest < v1);
577 }
578}