long_version/
lib.rs

1use smallvec::SmallVec;
2use std::{
3    cmp::{max, Ordering},
4    iter,
5    str::FromStr,
6};
7
8#[derive(Eq)]
9pub struct Version(VersionInner);
10
11type VersionInner = SmallVec<[u64; 4]>;
12
13impl Version {
14    fn normalize(left: &Self, right: &Self) -> (VersionInner, VersionInner) {
15        let Self(left) = left;
16        let Self(right) = right;
17        let length = max(left.len(), right.len());
18        let left = Self::with_padding(&left, length);
19        let right = Self::with_padding(&right, length);
20        debug_assert_eq!(left.len(), right.len());
21        (left, right)
22    }
23
24    fn with_padding(items: &[u64], total_length: usize) -> VersionInner {
25        let zeros = iter::once(0).take(total_length - items.len());
26        items.iter().copied().chain(zeros).collect::<VersionInner>()
27    }
28}
29
30impl PartialEq for Version {
31    fn eq(&self, other: &Self) -> bool {
32        let (this, other) = Self::normalize(self, other);
33        this.eq(&other)
34    }
35}
36
37impl PartialOrd for Version {
38    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
39        let (this, other) = Self::normalize(self, other);
40        this.partial_cmp(&other)
41    }
42}
43
44impl Ord for Version {
45    fn cmp(&self, other: &Self) -> Ordering {
46        let (this, other) = Self::normalize(self, other);
47        this.cmp(&other)
48    }
49}
50
51impl FromStr for Version {
52    type Err = anyhow::Error;
53
54    fn from_str(version: &str) -> Result<Self, Self::Err> {
55        let inner = version
56            .split('.')
57            .map(|i| i.parse().unwrap_or(0)) // ignore things like "alpha1", "rc1", etc. for simplicity
58            .collect();
59        Ok(Self(inner))
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use anyhow::Result;
67
68    #[test]
69    fn compare_versions() -> Result<()> {
70        assert!("1.2.3.4".parse::<Version>()? == "1.2.3.4".parse::<Version>()?);
71        assert!("1.2.3.3".parse::<Version>()? < "1.2.3.4".parse::<Version>()?);
72        assert!("1.2.0.4".parse::<Version>()? < "1.2.3.4".parse::<Version>()?);
73        assert!("2.1.1.9".parse::<Version>()? > "2.1.1.8".parse::<Version>()?);
74        assert!("1.1.1".parse::<Version>()? < "1.1.1.2".parse::<Version>()?);
75        assert!("1.0.0".parse::<Version>()? < "1.1.0.0".parse::<Version>()?);
76        assert!("2.0.0".parse::<Version>()? > "1.1.0.0".parse::<Version>()?);
77        assert!("1.1.0".parse::<Version>()? == "1.1.0.0".parse::<Version>()?);
78        assert!("1.1".parse::<Version>()? == "1.1.0".parse::<Version>()?);
79        assert!("1.1-rc1".parse::<Version>()? == "1.1-rc2".parse::<Version>()?);
80        Ok(())
81    }
82}