browserslist/
semver.rs

1use std::{cmp::Ordering, fmt, num::ParseIntError, str::FromStr};
2
3/// Semver
4#[derive(PartialEq, Eq, PartialOrd, Ord, Default, Debug, Copy, Clone)]
5pub struct Version(pub u16, pub u16, pub u16);
6
7impl Version {
8    #[inline]
9    pub fn major(&self) -> u16 {
10        self.0
11    }
12
13    #[inline]
14    pub fn minor(&self) -> u16 {
15        self.1
16    }
17
18    #[inline]
19    pub fn patch(&self) -> u16 {
20        self.2
21    }
22
23    pub fn parse(s: &str) -> Result<Self, ParseIntError> {
24        let mut segments = s.split('.');
25        let major = match segments.next() {
26            Some(n) => n.parse()?,
27            None => 0,
28        };
29        let minor = match segments.next() {
30            Some(n) => n.parse()?,
31            None => 0,
32        };
33        let patch = match segments.next() {
34            Some(n) => n.parse()?,
35            None => 0,
36        };
37        Ok(Self(major, minor, patch))
38    }
39
40    pub fn loose_compare(&self, b: &str) -> Ordering {
41        let mut b = b.split('.');
42        let Some(first) = b.next() else {
43            return Ordering::Equal;
44        };
45        let first: u16 = first.parse().unwrap_or_default();
46        let x = self.0.cmp(&first);
47        if !x.is_eq() {
48            return x;
49        }
50        let Some(second) = b.next() else {
51            return Ordering::Equal;
52        };
53        let second: u16 = second.parse().unwrap_or_default();
54        self.1.cmp(&second)
55    }
56}
57
58impl FromStr for Version {
59    type Err = ParseIntError;
60
61    fn from_str(s: &str) -> Result<Self, Self::Err> {
62        // this allows something like `4.4.3-4.4.4`
63        let s = s.split_once('-').map_or(s, |(v, _)| v);
64        Self::parse(s)
65    }
66}
67
68impl fmt::Display for Version {
69    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70        write!(f, "{}.{}.{}", self.0, self.1, self.2)
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn parse_version() {
80        assert_eq!(Ok(Version(1, 0, 0)), "1".parse());
81        assert_eq!(Ok(Version(1, 2, 0)), "1.2".parse());
82        assert_eq!(Ok(Version(1, 2, 3)), "1.2.3".parse());
83        assert_eq!(Ok(Version(12, 34, 56)), "12.34.56".parse());
84
85        assert_eq!(Ok(Version(1, 0, 0)), "1-2".parse());
86        assert_eq!(Ok(Version(1, 2, 0)), "1.2-1.3".parse());
87        assert_eq!(Ok(Version(1, 2, 3)), "1.2.3-1.2.4".parse());
88        assert_eq!(Ok(Version(12, 34, 56)), "12.34.56-78.9".parse());
89
90        assert!("tp".parse::<Version>().is_err());
91    }
92}