New version: 0.9.

This commit is contained in:
Sergio Benitez 2019-05-23 18:56:26 -07:00
parent 96a6d6ccbe
commit 98871bdc8e
7 changed files with 795 additions and 190 deletions

6
.travis.yml Normal file
View File

@ -0,0 +1,6 @@
language: rust
rust:
- 1.0.0
- stable
- beta
- nightly

View File

@ -1,6 +1,6 @@
[package] [package]
name = "version_check" name = "version_check"
version = "0.1.5" version = "0.9.0"
authors = ["Sergio Benitez <sb@sergio.bz>"] authors = ["Sergio Benitez <sb@sergio.bz>"]
description = "Tiny crate to check the version of the installed/running rustc." description = "Tiny crate to check the version of the installed/running rustc."
documentation = "https://docs.rs/version_check/" documentation = "https://docs.rs/version_check/"

View File

@ -1,5 +1,9 @@
# version\_check # version\_check
[![Build Status](https://travis-ci.com/SergioBenitez/version_check.svg?branch=master)](https://travis-ci.com/SergioBenitez/version_check)
[![Current Crates.io Version](https://img.shields.io/crates/v/version_check.svg)](https://crates.io/crates/version_check)
[![rustdocs on docs.rs](https://docs.rs/version_check/badge.svg)](https://docs.rs/version_check)
This tiny crate checks that the running or installed `rustc` meets some version This tiny crate checks that the running or installed `rustc` meets some version
requirements. The version is queried by calling the Rust compiler with requirements. The version is queried by calling the Rust compiler with
`--version`. The path to the compiler is determined first via the `RUSTC` `--version`. The path to the compiler is determined first via the `RUSTC`
@ -12,54 +16,57 @@ Add to your `Cargo.toml` file, typically as a build dependency:
```toml ```toml
[build-dependencies] [build-dependencies]
version_check = "0.1" version_check = "0.9"
``` ```
`version_check` is compatible and compiles with Rust 1.0.0 and beyond. `version_check` is compatible and compiles with Rust 1.0.0 and beyond.
## Examples ## Examples
Check that the running compiler is a nightly release: Set a `cfg` flag in `build.rs` if the running compiler was determined to be
at least version `1.13.0`:
```rust ```rust
extern crate version_check; extern crate version_check as rustc;
match version_check::is_nightly() { if rustc::is_min_version("1.13.0").unwrap_or(false) {
Some(true) => "running a nightly", println!("cargo:rustc-cfg=question_mark_operator");
Some(false) => "not nightly", }
None => "couldn't figure it out" ```
Check that the running compiler was released on or after `2018-12-18`:
```rust
extern crate version_check as rustc;
match rustc::is_min_date("2018-12-18") {
Some(true) => "Yep! It's recent!",
Some(false) => "No, it's older.",
None => "Couldn't determine the rustc version."
}; };
``` ```
Check that the running compiler is at least version `1.13.0`: Check that the running compiler supports feature flags:
```rust ```rust
extern crate version_check; extern crate version_check as rustc;
match version_check::is_min_version("1.13.0") { match rustc::is_feature_flaggable() {
Some((true, version)) => format!("Yes! It's: {}", version), Some(true) => "Yes! It's a dev or nightly release!",
Some((false, version)) => format!("No! {} is too old!", version), Some(false) => "No, it's stable or beta.",
None => "couldn't figure it out".into() None => "Couldn't determine the rustc version."
}; };
``` ```
Check that the running compiler was released on or after `2016-12-18`: See the [rustdocs](https://docs.rs/version_check) for more examples and complete
documentation.
```rust
extern crate version_check;
match version_check::is_min_date("2016-12-18") {
Some((true, date)) => format!("Yes! It's: {}", date),
Some((false, date)) => format!("No! {} is too long ago!", date),
None => "couldn't figure it out".into()
};
```
## Alternatives ## Alternatives
This crate is dead simple with no dependencies. If you need something more and This crate is dead simple with no dependencies. If you need something more
don't care about panicking if the version cannot be obtained or adding and don't care about panicking if the version cannot be obtained, or if you
dependencies, see [rustc_version](https://crates.io/crates/rustc_version). don't mind adding dependencies, see
[rustc_version](https://crates.io/crates/rustc_version).
## License ## License

192
src/channel.rs Normal file
View File

@ -0,0 +1,192 @@
use std::fmt;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
enum Kind {
Dev,
Nightly,
Beta,
Stable,
}
/// Release channel: "dev", "nightly", "beta", or "stable".
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct Channel(Kind);
impl Channel {
/// Reads the release channel of the running compiler. If it cannot be
/// determined (see the [top-level documentation](crate)), returns `None`.
///
/// # Example
///
/// ```rust
/// use version_check::Channel;
///
/// match Channel::read() {
/// Some(c) => format!("The channel is: {}", c),
/// None => format!("Failed to read the release channel.")
/// };
/// ```
pub fn read() -> Option<Channel> {
::get_version_and_date()
.and_then(|(version, _)| version)
.and_then(|version| Channel::parse(&version))
}
/// Parse a Rust release channel from a Rust release version string (of the
/// form `major[.minor[.patch[-channel]]]`). Returns `None` if `version` is
/// not a valid Rust version string.
///
/// # Example
///
/// ```rust
/// use version_check::Channel;
///
/// let dev = Channel::parse("1.3.0-dev").unwrap();
/// assert!(dev.is_dev());
///
/// let nightly = Channel::parse("1.42.2-nightly").unwrap();
/// assert!(nightly.is_nightly());
///
/// let beta = Channel::parse("1.32.0-beta").unwrap();
/// assert!(beta.is_beta());
///
/// let stable = Channel::parse("1.4.0").unwrap();
/// assert!(stable.is_stable());
/// ```
pub fn parse(version: &str) -> Option<Channel> {
if version.contains("-dev") {
Some(Channel(Kind::Dev))
} else if version.contains("-nightly") {
Some(Channel(Kind::Nightly))
} else if version.contains("-beta") {
Some(Channel(Kind::Beta))
} else if !version.contains("-") {
Some(Channel(Kind::Stable))
} else {
None
}
}
/// Returns the name of the release channel.
fn as_str(&self) -> &'static str {
match self.0 {
Kind::Dev => "dev",
Kind::Beta => "beta",
Kind::Nightly => "nightly",
Kind::Stable => "stable",
}
}
/// Returns `true` if this channel supports feature flags. In other words,
/// returns `true` if the channel is either `dev` or `nightly`.
///
/// # Example
///
/// ```rust
/// use version_check::Channel;
///
/// let dev = Channel::parse("1.3.0-dev").unwrap();
/// assert!(dev.supports_features());
///
/// let nightly = Channel::parse("1.42.2-nightly").unwrap();
/// assert!(nightly.supports_features());
///
/// let beta = Channel::parse("1.32.0-beta").unwrap();
/// assert!(!beta.supports_features());
///
/// let stable = Channel::parse("1.4.0").unwrap();
/// assert!(!stable.supports_features());
/// ```
pub fn supports_features(&self) -> bool {
match self.0 {
Kind::Dev | Kind::Nightly => true,
Kind::Beta | Kind::Stable => false
}
}
/// Returns `true` if this channel is `dev` and `false` otherwise.
///
/// # Example
///
/// ```rust
/// use version_check::Channel;
///
/// let dev = Channel::parse("1.3.0-dev").unwrap();
/// assert!(dev.is_dev());
///
/// let stable = Channel::parse("1.0.0").unwrap();
/// assert!(!stable.is_dev());
/// ```
pub fn is_dev(&self) -> bool {
match self.0 {
Kind::Dev => true,
_ => false
}
}
/// Returns `true` if this channel is `nightly` and `false` otherwise.
///
/// # Example
///
/// ```rust
/// use version_check::Channel;
///
/// let nightly = Channel::parse("1.3.0-nightly").unwrap();
/// assert!(nightly.is_nightly());
///
/// let stable = Channel::parse("1.0.0").unwrap();
/// assert!(!stable.is_nightly());
/// ```
pub fn is_nightly(&self) -> bool {
match self.0 {
Kind::Nightly => true,
_ => false
}
}
/// Returns `true` if this channel is `beta` and `false` otherwise.
///
/// # Example
///
/// ```rust
/// use version_check::Channel;
///
/// let beta = Channel::parse("1.3.0-beta").unwrap();
/// assert!(beta.is_beta());
///
/// let stable = Channel::parse("1.0.0").unwrap();
/// assert!(!stable.is_beta());
/// ```
pub fn is_beta(&self) -> bool {
match self.0 {
Kind::Beta => true,
_ => false
}
}
/// Returns `true` if this channel is `stable` and `false` otherwise.
///
/// # Example
///
/// ```rust
/// use version_check::Channel;
///
/// let stable = Channel::parse("1.0.0").unwrap();
/// assert!(stable.is_stable());
///
/// let beta = Channel::parse("1.3.0-beta").unwrap();
/// assert!(!beta.is_stable());
/// ```
pub fn is_stable(&self) -> bool {
match self.0 {
Kind::Stable => true,
_ => false
}
}
}
impl fmt::Display for Channel {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}

146
src/date.rs Normal file
View File

@ -0,0 +1,146 @@
use std::fmt;
/// Release date including year, month, and day.
#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord)]
pub struct Date(u32);
impl Date {
/// Reads the release date of the running compiler. If it cannot be
/// determined (see the [top-level documentation](crate)), returns `None`.
///
/// # Example
///
/// ```rust
/// use version_check::Date;
///
/// match Date::read() {
/// Some(d) => format!("The release date is: {}", d),
/// None => format!("Failed to read the release date.")
/// };
/// ```
pub fn read() -> Option<Date> {
::get_version_and_date()
.and_then(|(_, date)| date)
.and_then(|date| Date::parse(&date))
}
/// Return the original (YYYY, MM, DD).
fn to_ymd(&self) -> (u8, u8, u8) {
let y = self.0 >> 9;
let m = (self.0 << 23) >> 28;
let d = (self.0 << 27) >> 27;
(y as u8, m as u8, d as u8)
}
/// Parse a release date of the form `%Y-%m-%d`. Returns `None` if `date` is
/// not in `%Y-%m-%d` format.
///
/// # Example
///
/// ```rust
/// use version_check::Date;
///
/// let date = Date::parse("2016-04-20").unwrap();
///
/// assert!(date.at_least("2016-01-10"));
/// assert!(date.at_most("2016-04-20"));
/// assert!(date.exactly("2016-04-20"));
///
/// assert!(Date::parse("March 13, 2018").is_none());
/// assert!(Date::parse("1-2-3-4-5").is_none());
/// ```
pub fn parse(date: &str) -> Option<Date> {
let ymd: Vec<u32> = date.split("-")
.filter_map(|s| s.parse::<u32>().ok())
.collect();
if ymd.len() != 3 {
return None
}
let (y, m, d) = (ymd[0], ymd[1], ymd[2]);
Some(Date((y << 9) | (m << 5) | d))
}
/// Returns `true` if `self` occurs on or after `date`.
///
/// If `date` occurs before `self`, or if `date` is not in `%Y-%m-%d`
/// format, returns `false`.
///
/// # Example
///
/// ```rust
/// use version_check::Date;
///
/// let date = Date::parse("2020-01-01").unwrap();
///
/// assert!(date.at_least("2019-12-31"));
/// assert!(date.at_least("2020-01-01"));
/// assert!(date.at_least("2014-04-31"));
///
/// assert!(!date.at_least("2020-01-02"));
/// assert!(!date.at_least("2024-08-18"));
/// ```
pub fn at_least(&self, date: &str) -> bool {
Date::parse(date)
.map(|date| self >= &date)
.unwrap_or(false)
}
/// Returns `true` if `self` occurs on or before `date`.
///
/// If `date` occurs after `self`, or if `date` is not in `%Y-%m-%d`
/// format, returns `false`.
///
/// # Example
///
/// ```rust
/// use version_check::Date;
///
/// let date = Date::parse("2020-01-01").unwrap();
///
/// assert!(date.at_most("2020-01-01"));
/// assert!(date.at_most("2020-01-02"));
/// assert!(date.at_most("2024-08-18"));
///
/// assert!(!date.at_most("2019-12-31"));
/// assert!(!date.at_most("2014-04-31"));
/// ```
pub fn at_most(&self, date: &str) -> bool {
Date::parse(date)
.map(|date| self <= &date)
.unwrap_or(false)
}
/// Returns `true` if `self` occurs exactly on `date`.
///
/// If `date` is not exactly `self`, or if `date` is not in `%Y-%m-%d`
/// format, returns `false`.
///
/// # Example
///
/// ```rust
/// use version_check::Date;
///
/// let date = Date::parse("2020-01-01").unwrap();
///
/// assert!(date.exactly("2020-01-01"));
///
/// assert!(!date.exactly("2019-12-31"));
/// assert!(!date.exactly("2014-04-31"));
/// assert!(!date.exactly("2020-01-02"));
/// assert!(!date.exactly("2024-08-18"));
/// ```
pub fn exactly(&self, date: &str) -> bool {
Date::parse(date)
.map(|date| self == &date)
.unwrap_or(false)
}
}
impl fmt::Display for Date {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let (y, m, d) = self.to_ymd();
write!(f, "{}-{}-{}", y, m, d)
}
}

View File

@ -4,96 +4,96 @@
//! `RUSTC` environment variable. If it is not set, then `rustc` is used. If //! `RUSTC` environment variable. If it is not set, then `rustc` is used. If
//! that fails, no determination is made, and calls return `None`. //! that fails, no determination is made, and calls return `None`.
//! //!
//! # Example //! # Examples
//! //!
//! Check that the running compiler is a nightly release: //! Set a `cfg` flag in `build.rs` if the running compiler was determined to be
//! at least version `1.13.0`:
//! //!
//! ```rust //! ```rust
//! extern crate version_check; //! extern crate version_check as rustc;
//! //!
//! match version_check::is_nightly() { //! if rustc::is_min_version("1.13.0").unwrap_or(false) {
//! Some(true) => "running a nightly", //! println!("cargo:rustc-cfg=question_mark_operator");
//! Some(false) => "not nightly", //! }
//! None => "couldn't figure it out" //! ```
//!
//! See [`is_max_version`] or [`is_exact_version`] to check if the compiler
//! is _at most_ or _exactly_ a certain version.
//!
//! Check that the running compiler was released on or after `2018-12-18`:
//!
//! ```rust
//! extern crate version_check as rustc;
//!
//! match rustc::is_min_date("2018-12-18") {
//! Some(true) => "Yep! It's recent!",
//! Some(false) => "No, it's older.",
//! None => "Couldn't determine the rustc version."
//! }; //! };
//! ``` //! ```
//! //!
//! Check that the running compiler is at least version `1.13.0`: //! See [`is_max_date`] or [`is_exact_date`] to check if the compiler was
//! released _prior to_ or _exactly on_ a certain date.
//!
//! Check that the running compiler supports feature flags:
//! //!
//! ```rust //! ```rust
//! extern crate version_check; //! extern crate version_check as rustc;
//! //!
//! match version_check::is_min_version("1.13.0") { //! match rustc::is_feature_flaggable() {
//! Some((true, version)) => format!("Yes! It's: {}", version), //! Some(true) => "Yes! It's a dev or nightly release!",
//! Some((false, version)) => format!("No! {} is too old!", version), //! Some(false) => "No, it's stable or beta.",
//! None => "couldn't figure it out".into() //! None => "Couldn't determine the rustc version."
//! }; //! };
//! ``` //! ```
//! //!
//! Check that the running compiler was released on or after `2016-12-18`: //! Check that the running compiler is on the stable channel:
//! //!
//! ```rust //! ```rust
//! extern crate version_check; //! extern crate version_check as rustc;
//! //!
//! match version_check::is_min_date("2016-12-18") { //! match rustc::Channel::read() {
//! Some((true, date)) => format!("Yes! It's: {}", date), //! Some(c) if c.is_stable() => format!("Yes! It's stable."),
//! Some((false, date)) => format!("No! {} is too long ago!", date), //! Some(c) => format!("No, the channel {} is not stable.", c),
//! None => "couldn't figure it out".into() //! None => format!("Couldn't determine the rustc version.")
//! }; //! };
//! ``` //! ```
//! //!
//! To interact with the version, release date, and release channel as structs,
//! use [`Version`], [`Date`], and [`Channel`], respectively. The [`triple()`]
//! function returns all three values efficiently.
//!
//! # Alternatives //! # Alternatives
//! //!
//! This crate is dead simple with no dependencies. If you need something more //! This crate is dead simple with no dependencies. If you need something more
//! and don't care about panicking if the version cannot be obtained or adding //! and don't care about panicking if the version cannot be obtained, or if you
//! dependencies, see [rustc_version](https://crates.io/crates/rustc_version). //! don't mind adding dependencies, see
//! [rustc_version](https://crates.io/crates/rustc_version).
#![allow(deprecated)]
mod version;
mod channel;
mod date;
use std::env; use std::env;
use std::process::Command; use std::process::Command;
// Convert a string of %Y-%m-%d to a single u32 maintaining ordering. #[doc(inline)] pub use version::*;
fn str_to_ymd(ymd: &str) -> Option<u32> { #[doc(inline)] pub use channel::*;
let ymd: Vec<u32> = ymd.split("-").filter_map(|s| s.parse::<u32>().ok()).collect(); #[doc(inline)] pub use date::*;
if ymd.len() != 3 {
return None
}
let (y, m, d) = (ymd[0], ymd[1], ymd[2]); /// Parses (version, date) as available from rustc version string.
Some((y << 9) | (m << 5) | d)
}
// Convert a string with prefix major-minor-patch to a single u64 maintaining
// ordering. Assumes none of the components are > 1048576.
fn str_to_mmp(mmp: &str) -> Option<u64> {
let mut mmp: Vec<u16> = mmp.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((maj << 32) | (min << 16) | patch)
}
/// Returns (version, date) as available.
fn version_and_date_from_rustc_version(s: &str) -> (Option<String>, Option<String>) { fn version_and_date_from_rustc_version(s: &str) -> (Option<String>, Option<String>) {
let last_line = s.lines().last().unwrap_or(s); let last_line = s.lines().last().unwrap_or(s);
let mut components = last_line.trim().split(" "); let mut components = last_line.trim().split(" ");
let version = components.nth(1); let version = components.nth(1);
let date = components.nth(1).map(|s| s.trim_right().trim_right_matches(")")); let date = components.filter(|c| c.ends_with(')')).next()
.map(|s| s.trim_right().trim_right_matches(")").trim_left().trim_left_matches('('));
(version.map(|s| s.to_string()), date.map(|s| s.to_string())) (version.map(|s| s.to_string()), date.map(|s| s.to_string()))
} }
/// Returns (version, date) as available. /// Returns (version, date) as available from `rustc --version`.
fn get_version_and_date() -> Option<(Option<String>, Option<String>)> { fn get_version_and_date() -> Option<(Option<String>, Option<String>)> {
env::var("RUSTC").ok() env::var("RUSTC").ok()
.and_then(|rustc| Command::new(rustc).arg("--version").output().ok()) .and_then(|rustc| Command::new(rustc).arg("--version").output().ok())
@ -102,154 +102,187 @@ fn get_version_and_date() -> Option<(Option<String>, Option<String>)> {
.map(|s| version_and_date_from_rustc_version(&s)) .map(|s| version_and_date_from_rustc_version(&s))
} }
/// Checks that the running or installed `rustc` was released no earlier than /// Reads the triple of [`Version`], [`Channel`], and [`Date`] of the installed
/// or running `rustc`.
///
/// If any attribute cannot be determined (see the [top-level
/// documentation](crate)), returns `None`.
///
/// To obtain only one of three attributes, use [`Version::read()`],
/// [`Channel::read()`], or [`Date::read()`].
pub fn triple() -> Option<(Version, Channel, Date)> {
let (version_str, date_str) = match get_version_and_date() {
Some((Some(version), Some(date))) => (version, date),
_ => return None
};
// Can't use `?` or `try!` for `Option` in 1.0.0.
match Version::parse(&version_str) {
Some(version) => match Channel::parse(&version_str) {
Some(channel) => match Date::parse(&date_str) {
Some(date) => Some((version, channel, date)),
_ => None,
},
_ => None,
},
_ => None
}
}
/// Checks that the running or installed `rustc` was released **on or after**
/// some date. /// some date.
/// ///
/// The format of `min_date` must be YYYY-MM-DD. For instance: `2016-12-20` or /// The format of `min_date` must be YYYY-MM-DD. For instance: `2016-12-20` or
/// `2017-01-09`. /// `2017-01-09`.
/// ///
/// If the date cannot be retrieved or parsed, or if `min_date` could not be /// If the date cannot be retrieved or parsed, or if `min_date` could not be
/// parsed, returns `None`. Otherwise returns a tuple where the first value is /// parsed, returns `None`. Otherwise returns `true` if the installed `rustc`
/// `true` if the installed `rustc` is at least from `min_date` and the second /// was release on or after `min_date` and `false` otherwise.
/// value is the date (in YYYY-MM-DD) of the installed `rustc`. pub fn is_min_date(min_date: &str) -> Option<bool> {
pub fn is_min_date(min_date: &str) -> Option<(bool, String)> { match (Date::read(), Date::parse(min_date)) {
if let Some((_, Some(actual_date_str))) = get_version_and_date() { (Some(rustc_date), Some(min_date)) => Some(rustc_date >= min_date),
str_to_ymd(&actual_date_str) _ => None
.and_then(|actual| str_to_ymd(min_date).map(|min| (min, actual)))
.map(|(min, actual)| (actual >= min, actual_date_str))
} else {
None
} }
} }
/// Checks that the running or installed `rustc` is at least some minimum /// Checks that the running or installed `rustc` was released **on or before**
/// some date.
///
/// The format of `max_date` must be YYYY-MM-DD. For instance: `2016-12-20` or
/// `2017-01-09`.
///
/// If the date cannot be retrieved or parsed, or if `max_date` could not be
/// parsed, returns `None`. Otherwise returns `true` if the installed `rustc`
/// was release on or before `max_date` and `false` otherwise.
pub fn is_max_date(max_date: &str) -> Option<bool> {
match (Date::read(), Date::parse(max_date)) {
(Some(rustc_date), Some(max_date)) => Some(rustc_date <= max_date),
_ => None
}
}
/// Checks that the running or installed `rustc` was released **exactly** on
/// some date.
///
/// The format of `date` must be YYYY-MM-DD. For instance: `2016-12-20` or
/// `2017-01-09`.
///
/// If the date cannot be retrieved or parsed, or if `date` could not be parsed,
/// returns `None`. Otherwise returns `true` if the installed `rustc` was
/// release on `date` and `false` otherwise.
pub fn is_exact_date(date: &str) -> Option<bool> {
match (Date::read(), Date::parse(date)) {
(Some(rustc_date), Some(date)) => Some(rustc_date == date),
_ => None
}
}
/// Checks that the running or installed `rustc` is **at least** some minimum
/// version. /// version.
/// ///
/// The format of `min_version` is a semantic version: `1.3.0`, `1.15.0-beta`, /// The format of `min_version` is a semantic version: `1.3.0`, `1.15.0-beta`,
/// `1.14.0`, `1.16.0-nightly`, etc. /// `1.14.0`, `1.16.0-nightly`, etc.
/// ///
/// If the version cannot be retrieved or parsed, or if `min_version` could not /// If the version cannot be retrieved or parsed, or if `min_version` could not
/// be parsed, returns `None`. Otherwise returns a tuple where the first value /// be parsed, returns `None`. Otherwise returns `true` if the installed `rustc`
/// is `true` if the installed `rustc` is at least `min_version` and the second /// is at least `min_version` and `false` otherwise.
/// value is the version (semantic) of the installed `rustc`. pub fn is_min_version(min_version: &str) -> Option<bool> {
pub fn is_min_version(min_version: &str) -> Option<(bool, String)> { match (Version::read(), Version::parse(min_version)) {
if let Some((Some(actual_version_str), _)) = get_version_and_date() { (Some(rustc_ver), Some(min_ver)) => Some(rustc_ver >= min_ver),
str_to_mmp(&actual_version_str) _ => None
.and_then(|actual| str_to_mmp(min_version).map(|min| (min, actual)))
.map(|(min, actual)| (actual >= min, actual_version_str))
} else {
None
} }
} }
fn version_channel_is(channel: &str) -> Option<bool> { /// Checks that the running or installed `rustc` is **at most** some maximum
get_version_and_date() /// version.
.and_then(|(version_str_opt, _)| version_str_opt)
.map(|version_str| version_str.contains(channel))
}
/// Determines whether the running or installed `rustc` is on the nightly
/// channel.
/// ///
/// If the version could not be determined, returns `None`. Otherwise returns /// The format of `max_version` is a semantic version: `1.3.0`, `1.15.0-beta`,
/// `Some(true)` if the running version is a nightly release, and `Some(false)` /// `1.14.0`, `1.16.0-nightly`, etc.
/// otherwise.
pub fn is_nightly() -> Option<bool> {
version_channel_is("nightly")
}
/// Determines whether the running or installed `rustc` is on the beta channel.
/// ///
/// If the version could not be determined, returns `None`. Otherwise returns /// If the version cannot be retrieved or parsed, or if `max_version` could not
/// `Some(true)` if the running version is a beta release, and `Some(false)` /// be parsed, returns `None`. Otherwise returns `true` if the installed `rustc`
/// otherwise. /// is at most `max_version` and `false` otherwise.
pub fn is_beta() -> Option<bool> { pub fn is_max_version(max_version: &str) -> Option<bool> {
version_channel_is("beta") match (Version::read(), Version::parse(max_version)) {
(Some(rustc_ver), Some(max_ver)) => Some(rustc_ver <= max_ver),
_ => None
}
} }
/// Determines whether the running or installed `rustc` is on the dev channel. /// Checks that the running or installed `rustc` is **exactly** some version.
/// ///
/// If the version could not be determined, returns `None`. Otherwise returns /// The format of `version` is a semantic version: `1.3.0`, `1.15.0-beta`,
/// `Some(true)` if the running version is a dev release, and `Some(false)` /// `1.14.0`, `1.16.0-nightly`, etc.
/// otherwise. ///
pub fn is_dev() -> Option<bool> { /// If the version cannot be retrieved or parsed, or if `version` could not be
version_channel_is("dev") /// parsed, returns `None`. Otherwise returns `true` if the installed `rustc` is
/// exactly `version` and `false` otherwise.
pub fn is_exact_version(version: &str) -> Option<bool> {
match (Version::read(), Version::parse(version)) {
(Some(rustc_ver), Some(version)) => Some(rustc_ver == version),
_ => None
}
} }
/// Determines whether the running or installed `rustc` supports feature flags. /// Checks whether the running or installed `rustc` supports feature flags.
///
/// In other words, if the channel is either "nightly" or "dev". /// In other words, if the channel is either "nightly" or "dev".
/// ///
/// If the version could not be determined, returns `None`. Otherwise returns /// If the version could not be determined, returns `None`. Otherwise returns
/// `Some(true)` if the running version supports features, and `Some(false)` /// `true` if the running version supports feature flags and `false` otherwise.
/// otherwise. pub fn is_feature_flaggable() -> Option<bool> {
pub fn supports_features() -> Option<bool> { Channel::read().map(|c| c.supports_features())
match is_nightly() {
b@Some(true) => b,
_ => is_dev()
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::version_and_date_from_rustc_version; use super::version_and_date_from_rustc_version;
use super::str_to_mmp;
macro_rules! check_mmp { macro_rules! check_parse {
($string:expr => ($x:expr, $y:expr, $z:expr)) => ( ($s:expr => $v:expr, $d:expr) => (
if let Some(mmp) = str_to_mmp($string) { if let (Some(v), d) = version_and_date_from_rustc_version($s) {
let expected = $x << 32 | $y << 16 | $z; let e_d: Option<&str> = $d.into();
if mmp != expected { assert_eq!((v, d), ($v.into(), e_d.map(|s| s.into())));
panic!("{:?} didn't parse as {}.{}.{}.", $string, $x, $y, $z);
}
} else {
panic!("{:?} didn't parse for mmp testing.", $string);
}
)
}
macro_rules! check_version {
($s:expr => ($x:expr, $y:expr, $z:expr)) => (
if let (Some(version_str), _) = version_and_date_from_rustc_version($s) {
check_mmp!(&version_str => ($x, $y, $z));
} else { } else {
panic!("{:?} didn't parse for version testing.", $s); panic!("{:?} didn't parse for version testing.", $s);
} }
) )
} }
#[test]
fn test_str_to_mmp() {
check_mmp!("1.18.0" => (1, 18, 0));
check_mmp!("1.19.0" => (1, 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));
}
#[test] #[test]
fn test_version_parse() { fn test_version_parse() {
check_version!("rustc 1.18.0" => (1, 18, 0)); check_parse!("rustc 1.18.0" => "1.18.0", None);
check_version!("rustc 1.8.0" => (1, 8, 0)); check_parse!("rustc 1.8.0" => "1.8.0", None);
check_version!("rustc 1.20.0-nightly" => (1, 20, 0)); check_parse!("rustc 1.20.0-nightly" => "1.20.0-nightly", None);
check_version!("rustc 1.20" => (1, 20, 0)); check_parse!("rustc 1.20" => "1.20", None);
check_version!("rustc 1.3" => (1, 3, 0)); check_parse!("rustc 1.3" => "1.3", None);
check_version!("rustc 1" => (1, 0, 0)); check_parse!("rustc 1" => "1", None);
check_version!("rustc 1.2.5.6" => (1, 2, 5)); check_parse!("rustc 1.5.1-beta" => "1.5.1-beta", None);
check_version!("rustc 1.5.1-beta" => (1, 5, 1));
check_version!("rustc 1.20.0-nightly (d84693b93 2017-07-09)" => (1, 20, 0));
check_version!("rustc 1.20.0 (d84693b93 2017-07-09)" => (1, 20, 0));
check_version!("rustc 1.20.0 (2017-07-09)" => (1, 20, 0));
check_version!("rustc 1.20.0-dev (2017-07-09)" => (1, 20, 0));
check_version!("warning: invalid logging spec 'warning', ignoring it // Because of 1.0.0, we can't use Option<T>: From<T>.
rustc 1.30.0-nightly (3bc2ca7e4 2018-09-20)" => (1, 30, 0)); check_parse!("rustc 1.20.0 (2017-07-09)"
check_version!("warning: invalid logging spec 'warning', ignoring it\n => "1.20.0", Some("2017-07-09"));
rustc 1.30.0-nightly (3bc2ca7e4 2018-09-20)" => (1, 30, 0));
check_version!("warning: invalid logging spec 'warning', ignoring it check_parse!("rustc 1.20.0-dev (2017-07-09)"
warning: something else went wrong => "1.20.0-dev", Some("2017-07-09"));
rustc 1.30.0-nightly (3bc2ca7e4 2018-09-20)" => (1, 30, 0));
check_parse!("rustc 1.20.0-nightly (d84693b93 2017-07-09)"
=> "1.20.0-nightly", Some("2017-07-09"));
check_parse!("rustc 1.20.0 (d84693b93 2017-07-09)"
=> "1.20.0", Some("2017-07-09"));
check_parse!("warning: invalid logging spec 'warning', ignoring it
rustc 1.30.0-nightly (3bc2ca7e4 2018-09-20)"
=> "1.30.0-nightly", Some("2018-09-20"));
check_parse!("warning: invalid logging spec 'warning', ignoring it\n
rustc 1.30.0-nightly (3bc2ca7e4 2018-09-20)"
=> "1.30.0-nightly", Some("2018-09-20"));
check_parse!("warning: invalid logging spec 'warning', ignoring it
warning: something else went wrong
rustc 1.30.0-nightly (3bc2ca7e4 2018-09-20)"
=> "1.30.0-nightly", Some("2018-09-20"));
} }
} }

221
src/version.rs Normal file
View File

@ -0,0 +1,221 @@
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_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());
}
}