const_semver/
lib.rs

1use std::cmp::Ordering;
2
3#[cfg(feature = "macros")]
4#[macro_use]
5mod macros;
6
7#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
8pub enum Identifier {
9    Numeric(u64),
10    Alphanumeric(&'static str),
11}
12
13#[derive(Clone, Eq, Debug)]
14pub struct Version {
15    pub major: u64,
16    pub minor: u64,
17    pub patch: u64,
18    pub pre: &'static [Identifier],
19    pub build: &'static [Identifier],
20}
21
22impl Version {
23    pub const fn new(major: u64, minor: u64, patch: u64) -> Self {
24        Self {
25            major,
26            minor,
27            patch,
28            pre: &[],
29            build: &[],
30        }
31    }
32
33    pub const fn pre(self, identifiers: &'static [Identifier]) -> Self {
34        Self {
35            major: self.major,
36            minor: self.minor,
37            patch: self.patch,
38            pre: identifiers,
39            build: self.build,
40        }
41    }
42
43    pub const fn build(self, build: &'static [Identifier]) -> Self {
44        Self {
45            major: self.major,
46            minor: self.minor,
47            patch: self.patch,
48            pre: self.pre,
49            build,
50        }
51    }
52}
53
54#[cfg(feature = "semver")]
55impl From<&Identifier> for semver::Identifier {
56    fn from(ident: &Identifier) -> Self {
57        match ident {
58            Identifier::Numeric(n) => semver::Identifier::Numeric(*n),
59            Identifier::Alphanumeric(s) => semver::Identifier::AlphaNumeric(s.to_string()),
60        }
61    }
62}
63
64#[cfg(feature = "semver")]
65impl From<Identifier> for semver::Identifier {
66    fn from(ident: Identifier) -> Self {
67        match ident {
68            Identifier::Numeric(n) => semver::Identifier::Numeric(n),
69            Identifier::Alphanumeric(s) => semver::Identifier::AlphaNumeric(s.to_string()),
70        }
71    }
72}
73
74#[cfg(feature = "semver")]
75impl From<Version> for semver::Version {
76    fn from(version: Version) -> semver::Version {
77        semver::Version {
78            major: version.major,
79            minor: version.minor,
80            patch: version.patch,
81            pre: version.pre.iter().map(From::from).collect(),
82            build: version.build.iter().map(From::from).collect(),
83        }
84    }
85}
86
87impl std::hash::Hash for Version {
88    fn hash<H: std::hash::Hasher>(&self, into: &mut H) {
89        self.major.hash(into);
90        self.minor.hash(into);
91        self.patch.hash(into);
92        self.pre.hash(into);
93    }
94}
95
96impl std::cmp::PartialEq for Version {
97    #[inline]
98    fn eq(&self, other: &Version) -> bool {
99        self.major == other.major
100            && self.minor == other.minor
101            && self.patch == other.patch
102            && self.pre == other.pre
103    }
104}
105
106impl std::cmp::PartialOrd for Version {
107    fn partial_cmp(&self, other: &Version) -> Option<Ordering> {
108        Some(self.cmp(other))
109    }
110}
111
112impl std::cmp::Ord for Version {
113    fn cmp(&self, other: &Version) -> Ordering {
114        match self.major.cmp(&other.major) {
115            Ordering::Equal => {}
116            r => return r,
117        }
118
119        match self.minor.cmp(&other.minor) {
120            Ordering::Equal => {}
121            r => return r,
122        }
123
124        match self.patch.cmp(&other.patch) {
125            Ordering::Equal => {}
126            r => return r,
127        }
128
129        // NB: semver spec says 0.0.0-pre < 0.0.0
130        // but the version of ord defined for vec
131        // says that [] < [pre] so we alter it here
132        match (self.pre.len(), other.pre.len()) {
133            (0, 0) => Ordering::Equal,
134            (0, _) => Ordering::Greater,
135            (_, 0) => Ordering::Less,
136            (_, _) => self.pre.cmp(&other.pre),
137        }
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::{Identifier, Version};
144
145    #[test]
146    fn build() {
147        let constructed = Version::new(1, 2, 3)
148            .pre(&[Identifier::Alphanumeric("alpha")])
149            .build(&[Identifier::Alphanumeric("amd64")]);
150
151        let with_build = Version {
152            major: 1,
153            minor: 2,
154            patch: 3,
155            pre: &[Identifier::Alphanumeric("alpha")],
156            build: &[Identifier::Alphanumeric("amd64")],
157        };
158
159        let without_build = Version {
160            major: 1,
161            minor: 2,
162            patch: 3,
163            pre: &[Identifier::Alphanumeric("alpha")],
164            build: &[],
165        };
166
167        assert_eq!(with_build, constructed);
168        assert_eq!(without_build, constructed);
169    }
170
171    #[test]
172    fn convert() {
173        let version = semver::Version::parse("1.2.3-alpha.3+amd64").unwrap();
174
175        let const_correct: semver::Version = Version::new(1, 2, 3)
176            .pre(&[Identifier::Alphanumeric("alpha"), Identifier::Numeric(3)])
177            .build(&[Identifier::Alphanumeric("amd64")])
178            .into();
179
180        assert_eq!(version, const_correct);
181    }
182
183    #[test]
184    #[cfg(feature = "macros")]
185    fn test_macro() {
186        let handmade = Version {
187            major: 1,
188            minor: 2,
189            patch: 3,
190            pre: &[Identifier::Alphanumeric("alpha"), Identifier::Numeric(4)],
191            build: &[],
192        };
193
194        let version = version!(1, 2, 3 - alpha, 4);
195
196        assert_eq!(version, handmade);
197    }
198}