1use std::fmt;
4use std::num::ParseIntError;
5use std::str::FromStr;
6
7#[derive(Clone, Copy, Debug, Default, PartialOrd, Ord, PartialEq, Eq, Hash)]
13#[repr(C)]
14pub struct Version {
15 pub major: u64,
17 pub minor: u64,
19 pub patch: u64,
21}
22
23impl From<Version> for (u64, u64, u64) {
24 #[inline]
25 fn from(Version { major, minor, patch }: Version) -> Self {
26 (major, minor, patch)
27 }
28}
29
30impl From<(u64, u64, u64)> for Version {
31 #[inline]
32 fn from((major, minor, patch): (u64, u64, u64)) -> Self {
33 Version { major, minor, patch }
34 }
35}
36
37impl From<(u64, u64)> for Version {
38 #[inline]
39 fn from((major, minor): (u64, u64)) -> Self {
40 Version { major, minor, ..Default::default() }
41 }
42}
43
44impl From<(u64,)> for Version {
45 #[inline]
46 fn from((major,): (u64,)) -> Self {
47 major.into()
48 }
49}
50
51impl From<u64> for Version {
52 #[inline]
53 fn from(major: u64) -> Self {
54 Version { major, ..Default::default() }
55 }
56}
57
58impl From<OsVersion> for Version {
59 #[inline]
60 fn from(version: OsVersion) -> Version {
61 *version.as_version()
62 }
63}
64
65impl fmt::Display for Version {
66 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
68 }
69}
70
71impl FromStr for Version {
72 type Err = ParseVersionError;
73
74 fn from_str(s: &str) -> Result<Version, ParseVersionError> {
75 use self::ParseVersionError::*;
76
77 let mut vers = Version::default();
78 let mut iter = s.split('.');
79
80 let major = iter.next().ok_or(EmptyInput)?;
81 let minor = iter.next();
82 let patch = iter.next();
83
84 if iter.next().is_some() {
85 return Err(ExtraInput);
86 }
87
88 vers.major = major.parse().map_err(|e| MajorInt(e))?;
89
90 if let Some(minor) = minor {
91 vers.minor = minor.parse().map_err(|e| MinorInt(e))?;
92 }
93
94 if let Some(patch) = patch {
95 vers.patch = patch.parse().map_err(|e| PatchInt(e))?;
96 }
97
98 Ok(vers)
99 }
100}
101
102impl Version {
103 #[inline]
105 pub fn new(major: u64, minor: u64, patch: u64) -> Version {
106 Version { major, minor, patch }
107 }
108
109 #[inline]
111 pub fn parse(version: &str) -> Result<Version, ParseVersionError> {
112 version.parse()
113 }
114}
115
116#[derive(Clone, Copy, Debug, Default, PartialOrd, Ord, PartialEq, Eq, Hash)]
118#[repr(C)]
119pub struct OsVersion {
120 pub major: u64,
122 pub minor: u64,
124 pub patch: u64,
126
127 #[cfg(target_os = "windows")]
129 pub build: u64,
130}
131
132impl AsRef<Version> for OsVersion {
133 #[inline]
134 fn as_ref(&self) -> &Version {
135 self.as_version()
136 }
137}
138
139impl AsMut<Version> for OsVersion {
140 #[inline]
141 fn as_mut(&mut self) -> &mut Version {
142 self.as_version_mut()
143 }
144}
145
146impl OsVersion {
147 #[cfg(target_os = "macos")]
148 fn _get() -> Option<Self> {
149 use cocoa::appkit::*;
150 use cocoa::base::nil;
151 use cocoa::foundation::{NSInteger, NSProcessInfo};
152
153 let version = unsafe { NSAppKitVersionNumber };
154
155 let (major, minor, patch) = if version < NSAppKitVersionNumber10_7 {
156 return None;
157 } else if version < NSAppKitVersionNumber10_7_2 {
158 (10, 7, 0)
159 } else if version < NSAppKitVersionNumber10_7_3 {
160 (10, 7, 2)
161 } else if version < NSAppKitVersionNumber10_7_4 {
162 (10, 7, 3)
163 } else if version < NSAppKitVersionNumber10_8 {
164 (10, 7, 4)
165 } else if version < NSAppKitVersionNumber10_9 {
166 (10, 8, 0)
167 } else if version < NSAppKitVersionNumber10_10 {
168 (10, 9, 0)
169 } else {
170 #[repr(C)]
172 #[allow(dead_code)] struct NSOperatingSystemVersion {
174 major: NSInteger,
175 minor: NSInteger,
176 patch: NSInteger,
177 }
178
179 let NSOperatingSystemVersion { major, minor, patch } = unsafe {
181 let proc_info = NSProcessInfo::processInfo(nil);
182 msg_send![proc_info, operatingSystemVersion]
183 };
184
185 (major as u64, minor as u64, patch as u64)
186 };
187 Some(OsVersion { major, minor, patch })
188 }
189
190 #[cfg(target_os = "windows")]
191 fn _get() -> Option<Self> {
192 use std::mem;
193 use winapi::um::{
194 sysinfoapi::GetVersionExA,
195 winnt::OSVERSIONINFOA,
196 };
197 unsafe {
198 let mut info = mem::zeroed::<OSVERSIONINFOA>();
199 info.dwOSVersionInfoSize = mem::size_of_val(&info) as _;
200 if GetVersionExA(&mut info) == 0 {
201 None
202 } else {
203 Some(OsVersion {
204 major: info.dwMajorVersion as u64,
205 minor: info.dwMinorVersion as u64,
206 patch: 0,
207 build: info.dwBuildNumber as u64,
208 })
209 }
210 }
211 }
212
213 #[cfg(target_os = "linux")]
214 fn _get() -> Option<Self> {
215 None
216 }
217
218 pub fn get() -> Option<Self> {
220 Self::_get()
221 }
222
223 #[inline]
225 pub fn as_version(&self) -> &Version {
226 unsafe { &*(self as *const OsVersion as *const Version) }
227 }
228
229 #[inline]
231 pub fn as_version_mut(&mut self) -> &mut Version {
232 unsafe { &mut *(self as *mut OsVersion as *mut Version) }
233 }
234}
235
236#[derive(Clone, Debug, PartialEq, Eq)]
238pub enum ParseVersionError {
239 MajorInt(ParseIntError),
241 MinorInt(ParseIntError),
243 PatchInt(ParseIntError),
245 EmptyInput,
247 ExtraInput,
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 type VersionTriple = (u64, u64, u64);
256
257 #[test]
258 fn cmp() {
259 let pairs: &[(VersionTriple, VersionTriple)] = &[
260 ((0, 0, 1), (0, 0, 0)),
261 ((0, 1, 0), (0, 0, 1)),
262 ((0, 1, 1), (0, 1, 0)),
263 ((1, 0, 0), (0, 0, 1)),
264 ((1, 0, 0), (0, 1, 0)),
265 ];
266 for &(a, b) in pairs {
267 let a = Version::from(a);
268 let b = Version::from(b);
269 assert!(a > b);
270 assert!(a != b);
271 assert!(b < a);
272 }
273 }
274
275 #[test]
276 fn parse_success() {
277 let pairs: &[(&str, VersionTriple)] = &[
278 ("0", Default::default()),
279 ("0.1", ( 0, 1, 0)),
280 ("0.1.2", ( 0, 1, 2)),
281 ("16.04", (16, 4, 0)),
282 ];
283 for &(string, version) in pairs {
284 let version = Version::from(version);
285 let strings: [&str; 2] = [string, &version.to_string()];
286
287 for string in strings.iter() {
288 assert_eq!(string.parse(), Ok(version));
289 }
290 }
291 }
292
293 #[test]
294 fn parse_failure() {
295 let strings: &[&str] = &[
296 "",
297 ".",
298 "..",
299 "...",
300 "....",
301 "0.",
302 "0.0.",
303 "0.0.0.",
304 "0.0.0.0",
305 ];
306 for s in strings {
307 Version::parse(s).expect_err(&format!("parsing {:?}", s));
308 }
309 }
310}