use std::cmp::Ordering;
use std::convert::TryFrom;
use std::error::Error;
use std::fmt;
use std::io;
use std::process::Command;
use beta::{BetaNum, ParseBetaError};
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum Channel {
Stable,
Beta(BetaNum),
Nightly,
}
impl Channel {
pub fn is_stable(self) -> bool {
match self {
Channel::Stable => true,
_ => false,
}
}
pub fn is_nightly(self) -> bool {
match self {
Channel::Nightly => true,
_ => false,
}
}
pub fn is_beta(self) -> bool {
match self {
Channel::Beta(_) => true,
_ => false,
}
}
pub fn is_beta_num(self, n: u8) -> bool {
match self {
Channel::Beta(m) => n == m,
_ => false,
}
}
pub fn as_type_str(self) -> &'static str {
match self {
Channel::Stable => "stable",
Channel::Beta(_) => "beta",
Channel::Nightly => "nightly",
}
}
pub fn rustup_update(&self) -> io::Result<()> {
let success = Command::new("rustup")
.args(&["update", self.as_type_str()])
.status()?
.success();
if success {
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid exit code",
))
}
}
pub fn comparable(self) -> OrdChannel {
OrdChannel(self)
}
}
impl fmt::Display for Channel {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Channel::Stable => f.pad("stable"),
Channel::Beta(b) => fmt::Display::fmt(&b, f),
Channel::Nightly => f.pad("nightly"),
}
}
}
impl From<BetaNum> for Channel {
fn from(beta: BetaNum) -> Channel {
Channel::Beta(beta)
}
}
pub use self::Channel::*;
#[derive(Copy, Clone, Debug)]
pub struct OrdChannel(pub Channel);
impl PartialEq<Channel> for OrdChannel {
fn eq(&self, rhs: &Channel) -> bool {
match (self.0, *rhs) {
(Stable, Stable) |
(Nightly, Nightly) => true,
(Beta(n), Beta(m)) => n.num() == m.num(),
_ => false,
}
}
}
impl PartialEq for OrdChannel {
fn eq(&self, rhs: &OrdChannel) -> bool {
self == &rhs.0
}
}
impl PartialEq<OrdChannel> for Channel {
fn eq(&self, rhs: &OrdChannel) -> bool {
rhs == self
}
}
impl Eq for OrdChannel {}
impl PartialOrd<Channel> for OrdChannel {
fn partial_cmp(&self, rhs: &Channel) -> Option<Ordering> {
match (self.0, *rhs) {
(Beta(n), Beta(m)) => n.partial_cmp(&m),
(lhs, rhs) => lhs.partial_cmp(&rhs),
}
}
}
impl PartialOrd<OrdChannel> for Channel {
fn partial_cmp(&self, rhs: &OrdChannel) -> Option<Ordering> {
rhs.partial_cmp(self).map(Ordering::reverse)
}
}
impl PartialOrd for OrdChannel {
fn partial_cmp(&self, rhs: &OrdChannel) -> Option<Ordering> {
self.partial_cmp(&rhs.0)
}
}
impl Ord for OrdChannel {
fn cmp(&self, rhs: &OrdChannel) -> Ordering {
self.partial_cmp(rhs).unwrap()
}
}
impl<'a> TryFrom<&'a str> for Channel {
type Error = ParseChannelError<'a>;
fn try_from(s: &'a str) -> Result<Channel, ParseChannelError<'a>> {
Channel::try_from(s.as_bytes())
}
}
impl<'a> TryFrom<&'a [u8]> for Channel {
type Error = ParseChannelError<'a>;
fn try_from(bytes: &'a [u8]) -> Result<Channel, ParseChannelError<'a>> {
if bytes == b"stable" {
Ok(Channel::Stable)
} else if bytes == b"nightly" {
Ok(Channel::Nightly)
} else {
BetaNum::try_from(bytes).map(Channel::Beta).map_err(
From::from,
)
}
}
}
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
pub enum ParseChannelError<'a> {
Format(&'a [u8]),
Number(&'a [u8]),
Overflow(&'a [u8]),
}
impl<'a> From<ParseBetaError<'a>> for ParseChannelError<'a> {
fn from(err: ParseBetaError<'a>) -> ParseChannelError<'a> {
match err {
ParseBetaError::Format(bytes) => ParseChannelError::Format(bytes),
ParseBetaError::Number(bytes) => ParseChannelError::Number(bytes),
ParseBetaError::Overflow(bytes) => ParseChannelError::Overflow(bytes),
}
}
}
impl<'a> fmt::Debug for ParseChannelError<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ParseChannelError::Format(bytes) => {
f.debug_tuple("ParseChannelError::Format")
.field(&String::from_utf8_lossy(bytes))
.finish()
}
ParseChannelError::Number(bytes) => {
f.debug_tuple("ParseChannelError::Number")
.field(&String::from_utf8_lossy(bytes))
.finish()
}
ParseChannelError::Overflow(bytes) => {
f.debug_tuple("ParseChannelError::Overflow")
.field(&String::from_utf8_lossy(bytes))
.finish()
}
}
}
}
impl<'a> fmt::Display for ParseChannelError<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ParseChannelError::Format(bytes) => {
write!(
f,
"could not parse {:?} as a valid Rust channel",
String::from_utf8_lossy(bytes)
)
}
ParseChannelError::Number(bytes) => {
write!(
f,
"could not parse {:?} as a positive number",
String::from_utf8_lossy(bytes)
)
}
ParseChannelError::Overflow(bytes) => {
write!(
f,
"could not parse {:?}; was greater than 255",
String::from_utf8_lossy(bytes)
)
}
}
}
}
impl<'a> Error for ParseChannelError<'a> {
fn description(&self) -> &str {
match *self {
ParseChannelError::Format(_) => "could not parse as a valid Rust channel",
ParseChannelError::Number(_) => {
"could not parse \"beta.N\" with \"N\" as a positive number"
}
ParseChannelError::Overflow(_) => {
"could not parse \"beta.N\" because \"N\" was greater than 255"
}
}
}
}
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
use super::{Channel, ParseChannelError, BetaNum};
#[test]
fn parse() {
assert_eq!(Channel::try_from("stable").unwrap(), Channel::Stable);
assert_eq!(
Channel::try_from("beta").unwrap().comparable(),
Channel::Beta(BetaNum::ambiguous())
);
assert_eq!(
Channel::try_from("beta.2").unwrap().comparable(),
Channel::Beta(BetaNum::new(2))
);
assert_eq!(
Channel::try_from("beta.255").unwrap().comparable(),
Channel::Beta(BetaNum::new(255))
);
assert_eq!(Channel::try_from("nightly").unwrap(), Channel::Nightly);
}
#[test]
fn parse_fail() {
assert_eq!(
Channel::try_from("blorp"),
Err(ParseChannelError::Format(b"blorp"))
);
assert_eq!(
Channel::try_from("beta.1000"),
Err(ParseChannelError::Overflow(b"1000"))
);
assert_eq!(
Channel::try_from("beta.whoops"),
Err(ParseChannelError::Number(b"whoops"))
);
assert_eq!(
Channel::try_from("beta.-10"),
Err(ParseChannelError::Number(b"-10"))
);
}
}