#![warn(missing_docs)]
use colored::*;
use linefeed::Terminal;
use reqwest;
use semver::Version;
use std::cmp::Ordering;
use std::fmt;
use std::io::{self, Write};
pub use linefeed::DefaultTerminal;
#[derive(Debug, PartialEq)]
pub enum Status {
Behind(Version),
Equal(Version),
Ahead(Version),
}
#[derive(Debug)]
pub enum Error {
ParseError,
SemVerError(semver::SemVerError),
RequestError(reqwest::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::ParseError => write!(f, "Max version parsing failed"),
Error::SemVerError(e) => write!(f, "SemVer Error: {}", e),
Error::RequestError(e) => write!(f, "Request Error: {}", e),
}
}
}
struct Writer<'a, T: Terminal>(&'a T);
impl<'a, T: Terminal> Writer<'a, T> {
pub fn overwrite_current_console_line(&self, line: &str) -> io::Result<()> {
let mut wtr = self.0.lock_write();
wtr.move_to_first_column()?;
wtr.clear_to_screen_end()?;
wtr.write(line)
}
}
impl<'a, T: Terminal> Write for Writer<'a, T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut wtr = self.0.lock_write();
wtr.write(&String::from_utf8_lossy(buf)).unwrap();
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
pub fn get(crate_name: &str) -> Result<Version, Error> {
Version::parse(parse(&web_req(crate_name)?)?).map_err(|e| Error::SemVerError(e))
}
pub fn query(crate_name: &str, version: &str) -> Result<Status, Error> {
let version = Version::parse(version).map_err(|e| Error::SemVerError(e))?;
Ok(cmp(&version, get(crate_name)?))
}
pub fn output(crate_name: &str, version: &str) -> io::Result<()> {
Ok(output_with_term(
crate_name,
version,
&linefeed::DefaultTerminal::new()?,
))
}
pub fn output_with_term<Term: Terminal>(crate_name: &str, version: &str, terminal: &Term) {
println!("{}", "Checking for later version...".bright_yellow());
let print_line = print_line(crate_name, version);
let mut wtr = Writer(terminal);
wtr.overwrite_current_console_line(&print_line).unwrap();
writeln!(wtr, "",).unwrap();
}
pub fn output_to_writer<W: Write>(
crate_name: &str,
version: &str,
writer: &mut W,
) -> io::Result<()> {
writeln!(
writer,
"{}",
"Checking for later version...".bright_yellow()
)?;
writeln!(writer, "{}", print_line(crate_name, version))
}
fn print_line(crate_name: &str, version: &str) -> String {
match query(crate_name, version) {
Ok(status) => match status {
Status::Equal(ver) => format!(
"{}{}",
format!("Running the latest {} version ", crate_name).bright_green(),
ver.to_string().bright_green()
),
Status::Behind(ver) => format!(
"{}",
format!(
"The current {} version {} is old, please update to {}",
crate_name, version, ver
)
.bright_red()
),
Status::Ahead(ver) => format!(
"{}",
format!(
"The current {} version {} is ahead of the crates.io version {}",
crate_name, version, ver
)
.bright_purple()
),
},
Err(e) => format!("{} {}", "Failed to query crates.io:".bright_yellow(), e),
}
}
fn parse(text: &str) -> Result<&str, Error> {
match text.split('\"').skip_while(|&x| x != "max_version").nth(2) {
Some(ver) => Ok(ver),
None => Err(Error::ParseError),
}
}
fn web_req(crate_name: &str) -> Result<String, Error> {
reqwest::get(&format!("https://crates.io/api/v1/crates/{}", crate_name))
.map_err(|e| Error::RequestError(e))?
.text()
.map_err(|e| Error::RequestError(e))
}
fn cmp(current: &Version, cratesio: Version) -> Status {
match current.cmp(&cratesio) {
Ordering::Less => Status::Behind(cratesio),
Ordering::Equal => Status::Equal(cratesio),
Ordering::Greater => Status::Ahead(cratesio),
}
}
#[test]
fn parse_test() {
assert_eq!(parse(r#""max_version":"0.4.2""#).unwrap(), "0.4.2");
assert_eq!(parse(r#""max_version":"0..2""#).unwrap(), "0..2");
}
#[test]
fn test_web_req() {
let req = web_req("papyrus");
match req {
Err(_) => panic!("failed to query crates.io"),
Ok(text) => {
assert!(text.starts_with(r#"{"crate":{"id":"papyrus","name":"papyrus","#));
}
}
}
#[test]
fn cmp_test() {
let one_pt_oh = Version::parse("1.0.0").unwrap();
let pt_one_oh = Version::parse("0.1.0").unwrap();
assert_eq!(
cmp(&one_pt_oh, one_pt_oh.clone(),),
Status::Equal(one_pt_oh.clone())
);
assert_eq!(
cmp(&pt_one_oh, one_pt_oh.clone(),),
Status::Behind(one_pt_oh.clone())
);
assert_eq!(
cmp(&one_pt_oh, pt_one_oh.clone()),
Status::Ahead(pt_one_oh.clone())
);
}