1use std::{cmp::Ordering, num::ParseIntError, str::FromStr};
2
3#[derive(PartialEq, Eq, PartialOrd, Ord, Default, Debug, Clone)]
4pub(crate) struct Version(u32, u32, u32);
5
6impl Version {
7 #[inline]
8 pub(crate) fn major(&self) -> u32 {
9 self.0
10 }
11}
12
13impl FromStr for Version {
14 type Err = ParseIntError;
15
16 fn from_str(s: &str) -> Result<Self, Self::Err> {
17 let mut segments = s.split_once('-').map(|(v, _)| v).unwrap_or(s).split('.');
19 let major = match segments.next() {
20 Some(n) => n.parse()?,
21 None => 0,
22 };
23 let minor = match segments.next() {
24 Some(n) => n.parse()?,
25 None => 0,
26 };
27 let patch = match segments.next() {
28 Some(n) => n.parse()?,
29 None => 0,
30 };
31
32 Ok(Self(major, minor, patch))
33 }
34}
35
36pub(crate) fn compare(a: &str, b: &str) -> Ordering {
37 a.parse::<Version>()
38 .unwrap_or_default()
39 .cmp(&b.parse().unwrap_or_default())
40}
41
42pub(crate) fn loose_compare(a: &str, b: &str) -> Ordering {
43 a.split('.')
44 .take(2)
45 .zip(b.split('.').take(2))
46 .fold(Ordering::Equal, |ord, (a, b)| {
47 if ord == Ordering::Equal {
48 a.parse::<i32>()
49 .unwrap_or_default()
50 .cmp(&b.parse::<i32>().unwrap_or_default())
51 } else {
52 ord
53 }
54 })
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn parse_version() {
63 assert_eq!(Ok(Version(1, 0, 0)), "1".parse());
64 assert_eq!(Ok(Version(1, 2, 0)), "1.2".parse());
65 assert_eq!(Ok(Version(1, 2, 3)), "1.2.3".parse());
66 assert_eq!(Ok(Version(12, 34, 56)), "12.34.56".parse());
67
68 assert_eq!(Ok(Version(1, 0, 0)), "1-2".parse());
69 assert_eq!(Ok(Version(1, 2, 0)), "1.2-1.3".parse());
70 assert_eq!(Ok(Version(1, 2, 3)), "1.2.3-1.2.4".parse());
71 assert_eq!(Ok(Version(12, 34, 56)), "12.34.56-78.9".parse());
72
73 assert!("tp".parse::<Version>().is_err());
74 }
75}