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)) .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}