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>()); 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 /// ``` 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 >> 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 /// ``` 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