version_check/
version.rs

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, minor, patch) = 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}