blob: a0738d983a0bbbbd4c0f633bb6cc41ac02d17d7c [file] [log] [blame]
use std::fmt;
/// Version number: `major.minor.patch`, ignoring release channel.
#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord)]
pub struct Version(u64);
impl Version {
fn to_mmp(&self) -> (u16, u16, u16) {
let major = self.0 >> 32;
let minor = (self.0 << 32) >> 48;
let patch = (self.0 << 48) >> 48;
(major as u16, minor as u16, patch as u16)
}
/// Reads the version of the running compiler. If it cannot be determined
/// (see the [top-level documentation](crate)), returns `None`.
///
/// # Example
///
/// ```rust
/// use version_check::Version;
///
/// match Version::read() {
/// Some(d) => format!("Version is: {}", d),
/// None => format!("Failed to read the version.")
/// };
/// ```
pub fn read() -> Option<Version> {
::get_version_and_date()
.and_then(|(version, _)| version)
.and_then(|version| Version::parse(&version))
}
/// Parse a Rust release version (of the form
/// `major[.minor[.patch[-channel]]]`), ignoring the release channel, if
/// any. Returns `None` if `version` is not a valid Rust version string.
///
/// # Example
///
/// ```rust
/// use version_check::Version;
///
/// let version = Version::parse("1.18.0").unwrap();
/// assert!(version.exactly("1.18.0"));
///
/// let version = Version::parse("1.20.0-nightly").unwrap();
/// assert!(version.exactly("1.20.0"));
/// assert!(version.exactly("1.20.0-beta"));
///
/// let version = Version::parse("1.3").unwrap();
/// assert!(version.exactly("1.3.0"));
///
/// let version = Version::parse("1").unwrap();
/// assert!(version.exactly("1.0.0"));
///
/// assert!(Version::parse("one.two.three").is_none());
/// ```
pub fn parse(version: &str) -> Option<Version> {
let mut mmp: Vec<u16> = version.split('-')
.nth(0)
.unwrap_or("")
.split('.')
.filter_map(|s| s.parse::<u16>().ok())
.collect();
if mmp.is_empty() {
return None
}
while mmp.len() < 3 {
mmp.push(0);
}
let (maj, min, patch) = (mmp[0] as u64, mmp[1] as u64, mmp[2] as u64);
Some(Version((maj << 32) | (min << 16) | patch))
}
/// Returns `true` if `self` is greater than or equal to `version`.
///
/// If `version` is greater than `self`, or if `version` is not a valid Rust
/// version string, returns `false`.
///
/// # Example
///
/// ```rust
/// use version_check::Version;
///
/// let version = Version::parse("1.35.0").unwrap();
///
/// assert!(version.at_least("1.33.0"));
/// assert!(version.at_least("1.35.0"));
/// assert!(version.at_least("1.13.2"));
///
/// assert!(!version.at_least("1.35.1"));
/// assert!(!version.at_least("1.55.0"));
/// ```
pub fn at_least(&self, version: &str) -> bool {
Version::parse(version)
.map(|version| self >= &version)
.unwrap_or(false)
}
/// Returns `true` if `self` is less than or equal to `version`.
///
/// If `version` is less than `self`, or if `version` is not a valid Rust
/// version string, returns `false`.
///
/// # Example
///
/// ```rust
/// use version_check::Version;
///
/// let version = Version::parse("1.35.0").unwrap();
///
/// assert!(version.at_most("1.35.1"));
/// assert!(version.at_most("1.55.0"));
/// assert!(version.at_most("1.35.0"));
///
/// assert!(!version.at_most("1.33.0"));
/// assert!(!version.at_most("1.13.2"));
/// ```
pub fn at_most(&self, version: &str) -> bool {
Version::parse(version)
.map(|version| self <= &version)
.unwrap_or(false)
}
/// Returns `true` if `self` is exactly equal to `version`.
///
/// If `version` is not equal to `self`, or if `version` is not a valid Rust
/// version string, returns `false`.
///
/// # Example
///
/// ```rust
/// use version_check::Version;
///
/// let version = Version::parse("1.35.0").unwrap();
///
/// assert!(version.exactly("1.35.0"));
///
/// assert!(!version.exactly("1.33.0"));
/// assert!(!version.exactly("1.35.1"));
/// assert!(!version.exactly("1.13.2"));
/// ```
pub fn exactly(&self, version: &str) -> bool {
Version::parse(version)
.map(|version| self == &version)
.unwrap_or(false)
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let (major, minor, patch) = self.to_mmp();
write!(f, "{}.{}.{}", major, minor, patch)
}
}
#[cfg(test)]
mod tests {
use super::Version;
macro_rules! check_mmp {
($s:expr => ($x:expr, $y:expr, $z:expr)) => (
if let Some(v) = Version::parse($s) {
if v.to_mmp() != ($x, $y, $z) {
panic!("{:?} ({}) didn't parse as {}.{}.{}.", $s, v, $x, $y, $z);
}
} else {
panic!("{:?} didn't parse for mmp testing.", $s);
}
)
}
#[test]
fn test_str_to_mmp() {
check_mmp!("1.18.0" => (1, 18, 0));
check_mmp!("3.19.0" => (3, 19, 0));
check_mmp!("1.19.0-nightly" => (1, 19, 0));
check_mmp!("1.12.2349" => (1, 12, 2349));
check_mmp!("0.12" => (0, 12, 0));
check_mmp!("1.12.5" => (1, 12, 5));
check_mmp!("1.12" => (1, 12, 0));
check_mmp!("1" => (1, 0, 0));
check_mmp!("1.4.4-nightly (d84693b93 2017-07-09)" => (1, 4, 4));
check_mmp!("1.58879.4478-dev" => (1, 58879, 4478));
check_mmp!("1.58879.4478-dev (d84693b93 2017-07-09)" => (1, 58879, 4478));
}
#[test]
fn test_comparisons() {
let version = Version::parse("1.18.0").unwrap();
assert!(version.exactly("1.18.0"));
assert!(version.at_least("1.12.0"));
assert!(version.at_least("1.12"));
assert!(version.at_least("1"));
assert!(version.at_most("1.18.1"));
assert!(!version.exactly("1.19.0"));
assert!(!version.exactly("1.18.1"));
let version = Version::parse("1.20.0-nightly").unwrap();
assert!(version.exactly("1.20.0-beta"));
assert!(version.exactly("1.20.0-nightly"));
assert!(version.exactly("1.20.0"));
assert!(!version.exactly("1.19"));
let version = Version::parse("1.3").unwrap();
assert!(version.exactly("1.3.0"));
assert!(version.exactly("1.3.0-stable"));
assert!(version.exactly("1.3"));
assert!(!version.exactly("1.5.0-stable"));
let version = Version::parse("1").unwrap();
assert!(version.exactly("1.0.0"));
assert!(version.exactly("1.0"));
assert!(version.exactly("1"));
assert!(Version::parse("one.two.three").is_none());
}
macro_rules! reflexive_display {
($s:expr) => (
assert_eq!(Version::parse($s).unwrap().to_string(), $s);
)
}
#[test]
fn display() {
reflexive_display!("1.0.0");
reflexive_display!("1.2.3");
reflexive_display!("1.12.1438");
reflexive_display!("1.44.0");
reflexive_display!("2.44.0");
reflexive_display!("23459.28923.3483");
}
}