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