1 | use std::fmt; |
2 | |
3 | /// Version number: `major.minor.patch`, ignoring release channel. |
4 | #[derive (PartialEq, Eq, Copy, Clone, PartialOrd, Ord)] |
5 | pub struct Version(u64); |
6 | |
7 | impl Version { |
8 | /// Reads the version of the running compiler. If it cannot be determined |
9 | /// (see the [top-level documentation](crate)), returns `None`. |
10 | /// |
11 | /// # Example |
12 | /// |
13 | /// ```rust |
14 | /// use version_check::Version; |
15 | /// |
16 | /// match Version::read() { |
17 | /// Some(d) => format!("Version is: {}" , d), |
18 | /// None => format!("Failed to read the version." ) |
19 | /// }; |
20 | /// ``` |
21 | pub fn read() -> Option<Version> { |
22 | ::get_version_and_date() |
23 | .and_then(|(version, _)| version) |
24 | .and_then(|version| Version::parse(&version)) |
25 | } |
26 | |
27 | |
28 | /// Parse a Rust release version (of the form |
29 | /// `major[.minor[.patch[-channel]]]`), ignoring the release channel, if |
30 | /// any. Returns `None` if `version` is not a valid Rust version string. |
31 | /// |
32 | /// # Example |
33 | /// |
34 | /// ```rust |
35 | /// use version_check::Version; |
36 | /// |
37 | /// let version = Version::parse("1.18.0" ).unwrap(); |
38 | /// assert!(version.exactly("1.18.0" )); |
39 | /// |
40 | /// let version = Version::parse("1.20.0-nightly" ).unwrap(); |
41 | /// assert!(version.exactly("1.20.0" )); |
42 | /// assert!(version.exactly("1.20.0-beta" )); |
43 | /// |
44 | /// let version = Version::parse("1.3" ).unwrap(); |
45 | /// assert!(version.exactly("1.3.0" )); |
46 | /// |
47 | /// let version = Version::parse("1" ).unwrap(); |
48 | /// assert!(version.exactly("1.0.0" )); |
49 | /// |
50 | /// assert!(Version::parse("one.two.three" ).is_none()); |
51 | /// assert!(Version::parse("1.65536.2" ).is_none()); |
52 | /// assert!(Version::parse("1. 2" ).is_none()); |
53 | /// assert!(Version::parse("" ).is_none()); |
54 | /// assert!(Version::parse("1." ).is_none()); |
55 | /// assert!(Version::parse("1.2.3.4" ).is_none()); |
56 | /// ``` |
57 | pub fn parse(version: &str) -> Option<Version> { |
58 | let splits = version.split('-' ) |
59 | .nth(0) |
60 | .unwrap_or("" ) |
61 | .split('.' ) |
62 | .map(|s| s.parse::<u16>()); |
63 | |
64 | let mut mmp = [0u16; 3]; |
65 | for (i, split) in splits.enumerate() { |
66 | mmp[i] = match (i, split) { |
67 | (3, _) | (_, Err(_)) => return None, |
68 | (_, Ok(v)) => v, |
69 | }; |
70 | } |
71 | |
72 | let (maj, min, patch) = (mmp[0], mmp[1], mmp[2]); |
73 | Some(Version::from_mmp(maj, min, patch)) |
74 | } |
75 | |
76 | /// Creates a `Version` from `(major, minor, patch)` version components. |
77 | /// |
78 | /// # Example |
79 | /// |
80 | /// ```rust |
81 | /// use version_check::Version; |
82 | /// |
83 | /// assert!(Version::from_mmp(1, 35, 0).exactly("1.35.0" )); |
84 | /// assert!(Version::from_mmp(1, 33, 0).exactly("1.33.0" )); |
85 | /// assert!(Version::from_mmp(1, 35, 1).exactly("1.35.1" )); |
86 | /// assert!(Version::from_mmp(1, 13, 2).exactly("1.13.2" )); |
87 | /// ``` |
88 | pub fn from_mmp(major: u16, minor: u16, patch: u16) -> Version { |
89 | Version(((major as u64) << 32) | ((minor as u64) << 16) | patch as u64) |
90 | } |
91 | |
92 | /// Returns the `(major, minor, patch)` version components of `self`. |
93 | /// |
94 | /// # Example |
95 | /// |
96 | /// ```rust |
97 | /// use version_check::Version; |
98 | /// |
99 | /// assert_eq!(Version::parse("1.35.0" ).unwrap().to_mmp(), (1, 35, 0)); |
100 | /// assert_eq!(Version::parse("1.33.0" ).unwrap().to_mmp(), (1, 33, 0)); |
101 | /// assert_eq!(Version::parse("1.35.1" ).unwrap().to_mmp(), (1, 35, 1)); |
102 | /// assert_eq!(Version::parse("1.13.2" ).unwrap().to_mmp(), (1, 13, 2)); |
103 | /// ``` |
104 | pub fn to_mmp(&self) -> (u16, u16, u16) { |
105 | let major = self.0 >> 32; |
106 | let minor = self.0 >> 16; |
107 | let patch = self.0; |
108 | (major as u16, minor as u16, patch as u16) |
109 | } |
110 | |
111 | /// Returns `true` if `self` is greater than or equal to `version`. |
112 | /// |
113 | /// If `version` is greater than `self`, or if `version` is not a valid Rust |
114 | /// version string, returns `false`. |
115 | /// |
116 | /// # Example |
117 | /// |
118 | /// ```rust |
119 | /// use version_check::Version; |
120 | /// |
121 | /// let version = Version::parse("1.35.0" ).unwrap(); |
122 | /// |
123 | /// assert!(version.at_least("1.33.0" )); |
124 | /// assert!(version.at_least("1.35.0" )); |
125 | /// assert!(version.at_least("1.13.2" )); |
126 | /// |
127 | /// assert!(!version.at_least("1.35.1" )); |
128 | /// assert!(!version.at_least("1.55.0" )); |
129 | /// |
130 | /// let version = Version::parse("1.12.5" ).unwrap(); |
131 | /// |
132 | /// assert!(version.at_least("1.12.0" )); |
133 | /// assert!(!version.at_least("1.35.0" )); |
134 | /// ``` |
135 | pub fn at_least(&self, version: &str) -> bool { |
136 | Version::parse(version) |
137 | .map(|version| self >= &version) |
138 | .unwrap_or(false) |
139 | } |
140 | |
141 | /// Returns `true` if `self` is less than or equal to `version`. |
142 | /// |
143 | /// If `version` is less than `self`, or if `version` is not a valid Rust |
144 | /// version string, returns `false`. |
145 | /// |
146 | /// # Example |
147 | /// |
148 | /// ```rust |
149 | /// use version_check::Version; |
150 | /// |
151 | /// let version = Version::parse("1.35.0" ).unwrap(); |
152 | /// |
153 | /// assert!(version.at_most("1.35.1" )); |
154 | /// assert!(version.at_most("1.55.0" )); |
155 | /// assert!(version.at_most("1.35.0" )); |
156 | /// |
157 | /// assert!(!version.at_most("1.33.0" )); |
158 | /// assert!(!version.at_most("1.13.2" )); |
159 | /// ``` |
160 | pub fn at_most(&self, version: &str) -> bool { |
161 | Version::parse(version) |
162 | .map(|version| self <= &version) |
163 | .unwrap_or(false) |
164 | } |
165 | |
166 | /// Returns `true` if `self` is exactly equal to `version`. |
167 | /// |
168 | /// If `version` is not equal to `self`, or if `version` is not a valid Rust |
169 | /// version string, returns `false`. |
170 | /// |
171 | /// # Example |
172 | /// |
173 | /// ```rust |
174 | /// use version_check::Version; |
175 | /// |
176 | /// let version = Version::parse("1.35.0" ).unwrap(); |
177 | /// |
178 | /// assert!(version.exactly("1.35.0" )); |
179 | /// |
180 | /// assert!(!version.exactly("1.33.0" )); |
181 | /// assert!(!version.exactly("1.35.1" )); |
182 | /// assert!(!version.exactly("1.13.2" )); |
183 | /// ``` |
184 | pub fn exactly(&self, version: &str) -> bool { |
185 | Version::parse(version) |
186 | .map(|version| self == &version) |
187 | .unwrap_or(false) |
188 | } |
189 | } |
190 | |
191 | impl fmt::Display for Version { |
192 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
193 | let (major: u16, minor: u16, patch: u16) = self.to_mmp(); |
194 | write!(f, " {}. {}. {}" , major, minor, patch) |
195 | } |
196 | } |
197 | |
198 | impl fmt::Debug for Version { |
199 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
200 | // We don't use `debug_*` because it's not available in `1.0.0`. |
201 | write!(f, "Version( {:?}, {:?})" , self.0, self.to_mmp()) |
202 | } |
203 | } |
204 | |
205 | #[cfg (test)] |
206 | mod tests { |
207 | use super::Version; |
208 | |
209 | macro_rules! assert_to_mmp { |
210 | // We don't use `.into::<Option<_>>` because it's not available in 1.0. |
211 | // We don't use the message part of `assert!` for the same reason. |
212 | ($s:expr, None) => ( |
213 | assert_eq!(Version::parse($s), None); |
214 | ); |
215 | ($s:expr, $mmp:expr) => ( |
216 | assert_eq!(Version::parse($s).map(|v| v.to_mmp()), Some($mmp)); |
217 | ) |
218 | } |
219 | |
220 | macro_rules! assert_from_mmp { |
221 | (($x:expr, $y:expr, $z:expr) => $s:expr) => { |
222 | assert_eq!(Some(Version::from_mmp($x, $y, $z)), Version::parse($s)); |
223 | }; |
224 | } |
225 | |
226 | #[test ] |
227 | fn test_str_to_mmp() { |
228 | assert_to_mmp!("1" , (1, 0, 0)); |
229 | assert_to_mmp!("1.2" , (1, 2, 0)); |
230 | assert_to_mmp!("1.18.0" , (1, 18, 0)); |
231 | assert_to_mmp!("3.19.0" , (3, 19, 0)); |
232 | assert_to_mmp!("1.19.0-nightly" , (1, 19, 0)); |
233 | assert_to_mmp!("1.12.2349" , (1, 12, 2349)); |
234 | assert_to_mmp!("0.12" , (0, 12, 0)); |
235 | assert_to_mmp!("1.12.5" , (1, 12, 5)); |
236 | assert_to_mmp!("1.12" , (1, 12, 0)); |
237 | assert_to_mmp!("1" , (1, 0, 0)); |
238 | assert_to_mmp!("1.4.4-nightly (d84693b93 2017-07-09))" , (1, 4, 4)); |
239 | assert_to_mmp!("1.58879.4478-dev" , (1, 58879, 4478)); |
240 | assert_to_mmp!("1.58879.4478-dev (d84693b93 2017-07-09))" , (1, 58879, 4478)); |
241 | } |
242 | |
243 | #[test ] |
244 | fn test_malformed() { |
245 | assert_to_mmp!("1.65536.2" , None); |
246 | assert_to_mmp!("-1.2.3" , None); |
247 | assert_to_mmp!("1. 2" , None); |
248 | assert_to_mmp!("" , None); |
249 | assert_to_mmp!(" " , None); |
250 | assert_to_mmp!("." , None); |
251 | assert_to_mmp!("one" , None); |
252 | assert_to_mmp!("1." , None); |
253 | assert_to_mmp!("1.2.3.4.5.6" , None); |
254 | } |
255 | |
256 | #[test ] |
257 | fn test_from_mmp() { |
258 | assert_from_mmp!((1, 18, 0) => "1.18.0" ); |
259 | assert_from_mmp!((3, 19, 0) => "3.19.0" ); |
260 | assert_from_mmp!((1, 19, 0) => "1.19.0" ); |
261 | assert_from_mmp!((1, 12, 2349) => "1.12.2349" ); |
262 | assert_from_mmp!((0, 12, 0) => "0.12" ); |
263 | assert_from_mmp!((1, 12, 5) => "1.12.5" ); |
264 | assert_from_mmp!((1, 12, 0) => "1.12" ); |
265 | assert_from_mmp!((1, 0, 0) => "1" ); |
266 | assert_from_mmp!((1, 4, 4) => "1.4.4" ); |
267 | assert_from_mmp!((1, 58879, 4478) => "1.58879.4478" ); |
268 | } |
269 | |
270 | #[test ] |
271 | fn test_comparisons() { |
272 | let version = Version::parse("1.18.0" ).unwrap(); |
273 | assert!(version.exactly("1.18.0" )); |
274 | assert!(version.at_least("1.12.0" )); |
275 | assert!(version.at_least("1.12" )); |
276 | assert!(version.at_least("1" )); |
277 | assert!(version.at_most("1.18.1" )); |
278 | assert!(!version.exactly("1.19.0" )); |
279 | assert!(!version.exactly("1.18.1" )); |
280 | |
281 | let version = Version::parse("1.20.0-nightly" ).unwrap(); |
282 | assert!(version.exactly("1.20.0-beta" )); |
283 | assert!(version.exactly("1.20.0-nightly" )); |
284 | assert!(version.exactly("1.20.0" )); |
285 | assert!(!version.exactly("1.19" )); |
286 | |
287 | let version = Version::parse("1.3" ).unwrap(); |
288 | assert!(version.exactly("1.3.0" )); |
289 | assert!(version.exactly("1.3.0-stable" )); |
290 | assert!(version.exactly("1.3" )); |
291 | assert!(!version.exactly("1.5.0-stable" )); |
292 | |
293 | let version = Version::parse("1" ).unwrap(); |
294 | assert!(version.exactly("1.0.0" )); |
295 | assert!(version.exactly("1.0" )); |
296 | assert!(version.exactly("1" )); |
297 | |
298 | assert!(Version::parse("one.two.three" ).is_none()); |
299 | } |
300 | |
301 | macro_rules! reflexive_display { |
302 | ($s:expr) => ( |
303 | assert_eq!(Version::parse($s).unwrap().to_string(), $s); |
304 | ) |
305 | } |
306 | |
307 | #[test ] |
308 | fn display() { |
309 | reflexive_display!("1.0.0" ); |
310 | reflexive_display!("1.2.3" ); |
311 | reflexive_display!("1.12.1438" ); |
312 | reflexive_display!("1.44.0" ); |
313 | reflexive_display!("2.44.0" ); |
314 | reflexive_display!("23459.28923.3483" ); |
315 | } |
316 | } |
317 | |