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     /// ```
read() -> Option<Version>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     /// ```
parse(version: &str) -> Option<Version>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>().ok());
63 
64         let mut mmp = [0u16; 3];
65         for (i, split) in splits.enumerate() {
66             mmp[i] = match (i, split) {
67                 (3, _) | (_, None) => return None,
68                 (_, Some(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     /// ```
from_mmp(major: u16, minor: u16, patch: u16) -> Version88     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     /// ```
to_mmp(&self) -> (u16, u16, u16)104     pub fn to_mmp(&self) -> (u16, u16, u16) {
105         let major = self.0 >> 32;
106         let minor = (self.0 << 32) >> 48;
107         let patch = (self.0 << 48) >> 48;
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     /// ```
at_least(&self, version: &str) -> bool135     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     /// ```
at_most(&self, version: &str) -> bool160     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     /// ```
exactly(&self, version: &str) -> bool184     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 {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result192     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 
198 impl fmt::Debug for Version {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result199     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]
test_str_to_mmp()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]
test_malformed()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]
test_from_mmp()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]
test_comparisons()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]
display()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