1use std::fmt;
2
3/// Version number: `major.minor.patch`, ignoring release channel.
4#[derive(PartialEq, Eq, Copy, Clone, PartialOrd, Ord)]
5pub struct Version(u64);
6
7impl 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
191impl 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
198impl 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)]
206mod 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