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]
name = "version_check"
version = "0.1.5"
version = "0.9.0"
authors = ["Sergio Benitez <sb@sergio.bz>"]
description = "Tiny crate to check the version of the installed/running rustc."
documentation = "https://docs.rs/version_check/"

View File

@ -1,5 +1,9 @@
# 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
requirements. The version is queried by calling the Rust compiler with
`--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
[build-dependencies]
version_check = "0.1"
version_check = "0.9"
```
`version_check` is compatible and compiles with Rust 1.0.0 and beyond.
## 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
extern crate version_check;
extern crate version_check as rustc;
match version_check::is_nightly() {
Some(true) => "running a nightly",
Some(false) => "not nightly",
None => "couldn't figure it out"
if rustc::is_min_version("1.13.0").unwrap_or(false) {
println!("cargo:rustc-cfg=question_mark_operator");
}
```
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
extern crate version_check;
extern crate version_check as rustc;
match version_check::is_min_version("1.13.0") {
Some((true, version)) => format!("Yes! It's: {}", version),
Some((false, version)) => format!("No! {} is too old!", version),
None => "couldn't figure it out".into()
match rustc::is_feature_flaggable() {
Some(true) => "Yes! It's a dev or nightly release!",
Some(false) => "No, it's stable or beta.",
None => "Couldn't determine the rustc version."
};
```
Check that the running compiler was released on or after `2016-12-18`:
```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()
};
```
See the [rustdocs](https://docs.rs/version_check) for more examples and complete
documentation.
## Alternatives
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
dependencies, see [rustc_version](https://crates.io/crates/rustc_version).
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 if you
don't mind adding dependencies, see
[rustc_version](https://crates.io/crates/rustc_version).
## 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
//! 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
//! extern crate version_check;
//! extern crate version_check as rustc;
//!
//! match version_check::is_nightly() {
//! Some(true) => "running a nightly",
//! Some(false) => "not nightly",
//! None => "couldn't figure it out"
//! if rustc::is_min_version("1.13.0").unwrap_or(false) {
//! println!("cargo:rustc-cfg=question_mark_operator");
//! }
//! ```
//!
//! 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
//! extern crate version_check;
//! extern crate version_check as rustc;
//!
//! match version_check::is_min_version("1.13.0") {
//! Some((true, version)) => format!("Yes! It's: {}", version),
//! Some((false, version)) => format!("No! {} is too old!", version),
//! None => "couldn't figure it out".into()
//! match rustc::is_feature_flaggable() {
//! Some(true) => "Yes! It's a dev or nightly release!",
//! Some(false) => "No, it's stable or beta.",
//! 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
//! extern crate version_check;
//! extern crate version_check as rustc;
//!
//! 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()
//! match rustc::Channel::read() {
//! Some(c) if c.is_stable() => format!("Yes! It's stable."),
//! Some(c) => format!("No, the channel {} is not stable.", c),
//! 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
//!
//! 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
//! dependencies, see [rustc_version](https://crates.io/crates/rustc_version).
//! and don't care about panicking if the version cannot be obtained, or if you
//! 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::process::Command;
// Convert a string of %Y-%m-%d to a single u32 maintaining ordering.
fn str_to_ymd(ymd: &str) -> Option<u32> {
let ymd: Vec<u32> = ymd.split("-").filter_map(|s| s.parse::<u32>().ok()).collect();
if ymd.len() != 3 {
return None
}
#[doc(inline)] pub use version::*;
#[doc(inline)] pub use channel::*;
#[doc(inline)] pub use date::*;
let (y, m, d) = (ymd[0], ymd[1], ymd[2]);
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.
/// Parses (version, date) as available from rustc version string.
fn version_and_date_from_rustc_version(s: &str) -> (Option<String>, Option<String>) {
let last_line = s.lines().last().unwrap_or(s);
let mut components = last_line.trim().split(" ");
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()))
}
/// Returns (version, date) as available.
/// Returns (version, date) as available from `rustc --version`.
fn get_version_and_date() -> Option<(Option<String>, Option<String>)> {
env::var("RUSTC").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))
}
/// 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.
///
/// The format of `min_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 `min_date` could not be
/// parsed, returns `None`. Otherwise returns a tuple where the first value is
/// `true` if the installed `rustc` is at least from `min_date` and the second
/// value is the date (in YYYY-MM-DD) of the installed `rustc`.
pub fn is_min_date(min_date: &str) -> Option<(bool, String)> {
if let Some((_, Some(actual_date_str))) = get_version_and_date() {
str_to_ymd(&actual_date_str)
.and_then(|actual| str_to_ymd(min_date).map(|min| (min, actual)))
.map(|(min, actual)| (actual >= min, actual_date_str))
} else {
None
/// parsed, returns `None`. Otherwise returns `true` if the installed `rustc`
/// was release on or after `min_date` and `false` otherwise.
pub fn is_min_date(min_date: &str) -> Option<bool> {
match (Date::read(), Date::parse(min_date)) {
(Some(rustc_date), Some(min_date)) => Some(rustc_date >= min_date),
_ => 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.
///
/// The format of `min_version` is a semantic version: `1.3.0`, `1.15.0-beta`,
/// `1.14.0`, `1.16.0-nightly`, etc.
///
/// 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
/// is `true` if the installed `rustc` is at least `min_version` and the second
/// value is the version (semantic) of the installed `rustc`.
pub fn is_min_version(min_version: &str) -> Option<(bool, String)> {
if let Some((Some(actual_version_str), _)) = get_version_and_date() {
str_to_mmp(&actual_version_str)
.and_then(|actual| str_to_mmp(min_version).map(|min| (min, actual)))
.map(|(min, actual)| (actual >= min, actual_version_str))
} else {
None
/// be parsed, returns `None`. Otherwise returns `true` if the installed `rustc`
/// is at least `min_version` and `false` otherwise.
pub fn is_min_version(min_version: &str) -> Option<bool> {
match (Version::read(), Version::parse(min_version)) {
(Some(rustc_ver), Some(min_ver)) => Some(rustc_ver >= min_ver),
_ => None
}
}
fn version_channel_is(channel: &str) -> Option<bool> {
get_version_and_date()
.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.
/// Checks that the running or installed `rustc` is **at most** some maximum
/// version.
///
/// If the version could not be determined, returns `None`. Otherwise returns
/// `Some(true)` if the running version is a nightly release, and `Some(false)`
/// otherwise.
pub fn is_nightly() -> Option<bool> {
version_channel_is("nightly")
}
/// Determines whether the running or installed `rustc` is on the beta channel.
/// The format of `max_version` is a semantic version: `1.3.0`, `1.15.0-beta`,
/// `1.14.0`, `1.16.0-nightly`, etc.
///
/// If the version could not be determined, returns `None`. Otherwise returns
/// `Some(true)` if the running version is a beta release, and `Some(false)`
/// otherwise.
pub fn is_beta() -> Option<bool> {
version_channel_is("beta")
/// If the version cannot be retrieved or parsed, or if `max_version` could not
/// be parsed, returns `None`. Otherwise returns `true` if the installed `rustc`
/// is at most `max_version` and `false` otherwise.
pub fn is_max_version(max_version: &str) -> Option<bool> {
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
/// `Some(true)` if the running version is a dev release, and `Some(false)`
/// otherwise.
pub fn is_dev() -> Option<bool> {
version_channel_is("dev")
/// The format of `version` is a semantic version: `1.3.0`, `1.15.0-beta`,
/// `1.14.0`, `1.16.0-nightly`, etc.
///
/// If the version cannot be retrieved or parsed, or if `version` could not be
/// 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".
///
/// If the version could not be determined, returns `None`. Otherwise returns
/// `Some(true)` if the running version supports features, and `Some(false)`
/// otherwise.
pub fn supports_features() -> Option<bool> {
match is_nightly() {
b@Some(true) => b,
_ => is_dev()
}
/// `true` if the running version supports feature flags and `false` otherwise.
pub fn is_feature_flaggable() -> Option<bool> {
Channel::read().map(|c| c.supports_features())
}
#[cfg(test)]
mod tests {
use super::version_and_date_from_rustc_version;
use super::str_to_mmp;
macro_rules! check_mmp {
($string:expr => ($x:expr, $y:expr, $z:expr)) => (
if let Some(mmp) = str_to_mmp($string) {
let expected = $x << 32 | $y << 16 | $z;
if mmp != expected {
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));
macro_rules! check_parse {
($s:expr => $v:expr, $d:expr) => (
if let (Some(v), d) = version_and_date_from_rustc_version($s) {
let e_d: Option<&str> = $d.into();
assert_eq!((v, d), ($v.into(), e_d.map(|s| s.into())));
} else {
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]
fn test_version_parse() {
check_version!("rustc 1.18.0" => (1, 18, 0));
check_version!("rustc 1.8.0" => (1, 8, 0));
check_version!("rustc 1.20.0-nightly" => (1, 20, 0));
check_version!("rustc 1.20" => (1, 20, 0));
check_version!("rustc 1.3" => (1, 3, 0));
check_version!("rustc 1" => (1, 0, 0));
check_version!("rustc 1.2.5.6" => (1, 2, 5));
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_parse!("rustc 1.18.0" => "1.18.0", None);
check_parse!("rustc 1.8.0" => "1.8.0", None);
check_parse!("rustc 1.20.0-nightly" => "1.20.0-nightly", None);
check_parse!("rustc 1.20" => "1.20", None);
check_parse!("rustc 1.3" => "1.3", None);
check_parse!("rustc 1" => "1", None);
check_parse!("rustc 1.5.1-beta" => "1.5.1-beta", None);
check_version!("warning: invalid logging spec 'warning', ignoring it
rustc 1.30.0-nightly (3bc2ca7e4 2018-09-20)" => (1, 30, 0));
check_version!("warning: invalid logging spec 'warning', ignoring it\n
rustc 1.30.0-nightly (3bc2ca7e4 2018-09-20)" => (1, 30, 0));
check_version!("warning: invalid logging spec 'warning', ignoring it
warning: something else went wrong
rustc 1.30.0-nightly (3bc2ca7e4 2018-09-20)" => (1, 30, 0));
// Because of 1.0.0, we can't use Option<T>: From<T>.
check_parse!("rustc 1.20.0 (2017-07-09)"
=> "1.20.0", Some("2017-07-09"));
check_parse!("rustc 1.20.0-dev (2017-07-09)"
=> "1.20.0-dev", Some("2017-07-09"));
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());
}
}