clippy + fmt + fix toml dates in extra arrays

Closes #1048
This commit is contained in:
Vincent Prouillet 2020-06-18 21:11:22 +02:00
parent 5e31a32166
commit ade442a487
16 changed files with 171 additions and 96 deletions

104
Cargo.lock generated
View File

@ -2,9 +2,9 @@
# It is not intended for manual editing.
[[package]]
name = "adler32"
version = "1.0.4"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d"
[[package]]
name = "aho-corasick"
@ -75,9 +75,9 @@ checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
[[package]]
name = "base64"
version = "0.12.1"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42"
checksum = "e223af0dc48c96d4f8342ec01a4974f139df863896b316681efd36742f22cc67"
[[package]]
name = "bincode"
@ -104,7 +104,19 @@ dependencies = [
"block-padding",
"byte-tools",
"byteorder",
"generic-array",
"generic-array 0.12.3",
]
[[package]]
name = "block-buffer"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbcf92448676f82bb7a334c58bbce8b0d43580fb5362a9d608b18879d12a3d31"
dependencies = [
"block-padding",
"byte-tools",
"byteorder",
"generic-array 0.14.2",
]
[[package]]
@ -366,7 +378,16 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array",
"generic-array 0.12.3",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array 0.14.2",
]
[[package]]
@ -377,9 +398,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "dtoa"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
[[package]]
name = "either"
@ -611,6 +632,16 @@ dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac746a5f3bbfdadd6106868134545e684693d54d9d44f6e9588a7d54af0bf980"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getopts"
version = "0.2.21"
@ -912,9 +943,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
[[package]]
name = "jpeg-decoder"
@ -1462,7 +1493,7 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b336d94e8e4ce29bf15bba393164629764744c567e8ad306cc1fdd0119967fd"
dependencies = [
"base64 0.12.1",
"base64 0.12.2",
"chrono",
"indexmap",
"line-wrap",
@ -1615,10 +1646,11 @@ dependencies = [
[[package]]
name = "rayon"
version = "1.3.0"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098"
checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
@ -1626,9 +1658,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.7.0"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9"
checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280"
dependencies = [
"crossbeam-deque",
"crossbeam-queue",
@ -1718,7 +1750,7 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b82c9238b305f26f53443e3a4bc8528d64b8d0bee408ec949eb7bf5635ec680"
dependencies = [
"base64 0.12.1",
"base64 0.12.2",
"bytes 0.5.4",
"encoding_rs",
"futures-core",
@ -1749,9 +1781,9 @@ dependencies = [
[[package]]
name = "ring"
version = "0.16.14"
version = "0.16.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06b3fefa4f12272808f809a0af618501fdaba41a58963c5fb72238ab0be09603"
checksum = "952cd6b98c85bbc30efa1ba5783b8abf12fec8b3287ffa52605b9432313e34e4"
dependencies = [
"cc",
"libc",
@ -1917,20 +1949,20 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
"block-buffer",
"digest",
"block-buffer 0.7.3",
"digest 0.8.1",
"fake-simd",
"opaque-debug",
]
[[package]]
name = "sha2"
version = "0.8.2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69"
checksum = "72377440080fd008550fe9b441e854e43318db116f90181eef92e9ae9aedab48"
dependencies = [
"block-buffer",
"digest",
"block-buffer 0.8.0",
"digest 0.9.0",
"fake-simd",
"opaque-debug",
]
@ -1984,12 +2016,6 @@ dependencies = [
"deunicode",
]
[[package]]
name = "smallvec"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
[[package]]
name = "socket2"
version = "0.3.12"
@ -2131,7 +2157,7 @@ dependencies = [
name = "templates"
version = "0.1.0"
dependencies = [
"base64 0.12.1",
"base64 0.12.2",
"config",
"csv",
"errors",
@ -2232,6 +2258,12 @@ dependencies = [
"winapi 0.3.8",
]
[[package]]
name = "tinyvec"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed"
[[package]]
name = "tokio"
version = "0.2.21"
@ -2391,11 +2423,11 @@ dependencies = [
[[package]]
name = "unicode-normalization"
version = "0.1.12"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4"
checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977"
dependencies = [
"smallvec",
"tinyvec",
]
[[package]]
@ -2698,9 +2730,9 @@ dependencies = [
[[package]]
name = "xmlparser"
version = "0.13.1"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccb4240203dadf40be2de9369e5c6dec1bf427528115b030baca3334c18362d7"
checksum = "52613e655f6f11f63c0fe7d1c3b5ef69e44d96df9b65dab296b441ed0e1125f5"
[[package]]
name = "yaml-rust"

View File

@ -20,7 +20,7 @@ name = "zola"
[dependencies]
atty = "0.2.11"
clap = "2"
clap = { version = "2", default-features = false }
chrono = "0.4"
lazy_static = "1.1.0"
termcolor = "1.0.4"

View File

@ -28,7 +28,7 @@ fn main() {
builder.add_plain_text_syntax();
match builder.add_from_folder(package_dir, true) {
Ok(_) => (),
Err(e) => println!("Loading error: {:?}", e)
Err(e) => println!("Loading error: {:?}", e),
};
let ss = builder.build();
dump_to_file(&ss, packpath_newlines).unwrap();

View File

@ -4,7 +4,6 @@ use std::path::{Path, PathBuf};
use globset::{Glob, GlobSet, GlobSetBuilder};
use serde_derive::{Deserialize, Serialize};
use syntect::parsing::{SyntaxSet, SyntaxSetBuilder};
use toml;
use toml::Value as Toml;
use crate::highlighting::THEME_SET;

View File

@ -3,7 +3,6 @@ use std::collections::HashMap;
use chrono::prelude::*;
use serde_derive::Deserialize;
use tera::{Map, Value};
use toml;
use errors::{bail, Result};
use utils::de::{fix_toml_dates, from_toml_datetime};
@ -57,15 +56,17 @@ pub struct PageFrontMatter {
pub extra: Map<String, Value>,
}
/// Parse a TOML datetime from a string.
/// There are three alternatives: an offset datetime (plain RFC3339), a local datetime
/// (RFC3339 with timezone omitted) and a local date (YYYY-MM-DD). This tries each in
/// order.
fn parse_datetime(d: &String) -> Option<NaiveDateTime> {
/// Parse a string for a datetime coming from one of the supported TOML format
/// There are three alternatives:
/// 1. an offset datetime (plain RFC3339)
/// 2. a local datetime (RFC3339 with timezone omitted)
/// 3. a local date (YYYY-MM-DD).
/// This tries each in order.
fn parse_datetime(d: &str) -> Option<NaiveDateTime> {
DateTime::parse_from_rfc3339(d)
.or(DateTime::parse_from_rfc3339(format!("{}Z", d).as_ref()))
.or_else(|_| DateTime::parse_from_rfc3339(format!("{}Z", d).as_ref()))
.map(|s| s.naive_local())
.or(NaiveDate::parse_from_str(d, "%Y-%m-%d").map(|s| s.and_hms(0, 0, 0)))
.or_else(|_| NaiveDate::parse_from_str(d, "%Y-%m-%d").map(|s| s.and_hms(0, 0, 0)))
.ok()
}
@ -107,7 +108,7 @@ impl PageFrontMatter {
/// Converts the TOML datetime to a Chrono naive datetime
/// Also grabs the year/month/day tuple that will be used in serialization
pub fn date_to_datetime(&mut self) {
self.datetime = self.date.as_ref().and_then(parse_datetime);
self.datetime = self.date.as_ref().map(|s| s.as_ref()).and_then(parse_datetime);
self.datetime_tuple = self.datetime.map(|dt| (dt.year(), dt.month(), dt.day()));
}
@ -319,6 +320,23 @@ mod tests {
assert_eq!(res.unwrap().extra["something"]["some-date"], to_value("2002-14-01").unwrap());
}
#[test]
fn can_parse_fully_nested_dates_in_extra() {
let content = r#"
title = "Hello"
description = "hey there"
[extra]
date_example = 2020-05-04
[[extra.questions]]
date = 2020-05-03
name = "Who is the prime minister of Uganda?""#;
let res = PageFrontMatter::parse(content);
println!("{:?}", res);
assert!(res.is_ok());
assert_eq!(res.unwrap().extra["questions"][0]["date"], to_value("2020-05-03").unwrap());
}
#[test]
fn can_parse_taxonomies() {
let content = r#"

View File

@ -1,6 +1,5 @@
use serde_derive::{Deserialize, Serialize};
use tera::{Map, Value};
use toml;
use super::{InsertAnchor, SortBy};
use errors::{bail, Result};

View File

@ -534,8 +534,11 @@ impl Site {
pub fn register_early_global_fns(&mut self) {
self.tera.register_function(
"get_url",
global_fns::GetUrl::new(self.config.clone(), self.permalinks.clone(),
vec![self.static_path.clone(), self.output_path.clone(), self.content_path.clone()]),
global_fns::GetUrl::new(
self.config.clone(),
self.permalinks.clone(),
vec![self.static_path.clone(), self.output_path.clone(), self.content_path.clone()],
),
);
self.tera.register_function(
"resize_image",
@ -551,9 +554,14 @@ impl Site {
"get_taxonomy_url",
global_fns::GetTaxonomyUrl::new(&self.config.default_language, &self.taxonomies),
);
self.tera.register_function("get_file_hash", global_fns::GetFileHash::new(
vec![self.static_path.clone(), self.output_path.clone(), self.content_path.clone()]
));
self.tera.register_function(
"get_file_hash",
global_fns::GetFileHash::new(vec![
self.static_path.clone(),
self.output_path.clone(),
self.content_path.clone(),
]),
);
}
pub fn register_tera_global_fns(&mut self) {

View File

@ -696,8 +696,11 @@ fn can_cachebust_static_files() {
#[test]
fn can_get_hash_for_static_files() {
let (_, _tmp_dir, public) = build_site("test_site");
assert!(file_contains!(public, "index.html",
"src=\"https://replace-this-with-your-url.com/scripts/hello.js\""));
assert!(file_contains!(
public,
"index.html",
"src=\"https://replace-this-with-your-url.com/scripts/hello.js\""
));
assert!(file_contains!(public, "index.html",
"integrity=\"sha384-01422f31eaa721a6c4ac8c6fa09a27dd9259e0dfcf3c7593d7810d912a9de5ca2f582df978537bcd10f76896db61fbb9\""));
}

View File

@ -13,7 +13,7 @@ toml = "0.5"
csv = "1"
image = "0.23"
serde_json = "1.0"
sha2 = "0.8"
sha2 = "0.9"
url = "2"
errors = { path = "../errors" }

View File

@ -19,6 +19,7 @@ pub fn markdown<S: BuildHasher>(
opts.insert(cmark::Options::ENABLE_TABLES);
opts.insert(cmark::Options::ENABLE_FOOTNOTES);
opts.insert(cmark::Options::ENABLE_STRIKETHROUGH);
opts.insert(cmark::Options::ENABLE_TASKLISTS);
let mut html = String::new();
let parser = cmark::Parser::new_ext(&s, opts);

View File

@ -209,11 +209,9 @@ impl TeraFn for LoadData {
.header(header::ACCEPT, file_format.as_accept_header())
.send()
.and_then(|res| res.error_for_status())
.map_err(|e| {
match e.status() {
Some(status) => format!("Failed to request {}: {}", url, status),
None => format!("Could not get response status for url: {}", url),
}
.map_err(|e| match e.status() {
Some(status) => format!("Failed to request {}: {}", url, status),
None => format!("Could not get response status for url: {}", url),
})?;
response
.text()

View File

@ -1,21 +1,18 @@
use std::collections::HashMap;
use std::ffi::OsStr;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock};
use std::{fs, io, result};
use std::ffi::OsStr;
use sha2::{Digest, Sha256, Sha384, Sha512};
use tera::{from_value, to_value, Error, Function as TeraFn, Result, Value};
use svg_metadata as svg;
use tera::{from_value, to_value, Error, Function as TeraFn, Result, Value};
use config::Config;
use image;
use image::GenericImageView;
use library::{Library, Taxonomy};
use utils::site::resolve_internal_link;
use imageproc;
#[macro_use]
mod macros;
@ -54,13 +51,17 @@ pub struct GetUrl {
search_paths: Vec<PathBuf>,
}
impl GetUrl {
pub fn new(config: Config, permalinks: HashMap<String, String>, search_paths: Vec<PathBuf>) -> Self {
pub fn new(
config: Config,
permalinks: HashMap<String, String>,
search_paths: Vec<PathBuf>,
) -> Self {
Self { config, permalinks, search_paths }
}
}
fn make_path_with_lang(path: String, lang: &str, config: &Config) -> Result<String> {
if lang == &config.default_language {
if lang == config.default_language {
return Ok(path);
}
@ -70,18 +71,18 @@ fn make_path_with_lang(path: String, lang: &str, config: &Config) -> Result<Stri
);
}
let mut splitted_path: Vec<String> = path.split(".").map(String::from).collect();
let mut splitted_path: Vec<String> = path.split('.').map(String::from).collect();
let ilast = splitted_path.len() - 1;
splitted_path[ilast] = format!("{}.{}", lang, splitted_path[ilast]);
Ok(splitted_path.join("."))
}
fn open_file(search_paths: &Vec<PathBuf>, url: &String) -> result::Result<fs::File, io::Error> {
let cleaned_url = url.trim_start_matches("@/").trim_start_matches("/");
fn open_file(search_paths: &[PathBuf], url: &str) -> result::Result<fs::File, io::Error> {
let cleaned_url = url.trim_start_matches("@/").trim_start_matches('/');
for base_path in search_paths {
match fs::File::open(base_path.join(cleaned_url)) {
Ok(f) => return Ok(f),
Err(_) => continue
Err(_) => continue,
};
}
Err(io::Error::from(io::ErrorKind::NotFound))
@ -90,23 +91,28 @@ fn open_file(search_paths: &Vec<PathBuf>, url: &String) -> result::Result<fs::Fi
fn compute_file_sha256(mut file: fs::File) -> result::Result<String, io::Error> {
let mut hasher = Sha256::new();
io::copy(&mut file, &mut hasher)?;
Ok(format!("{:x}", hasher.result()))
Ok(format!("{:x}", hasher.finalize()))
}
fn compute_file_sha384(mut file: fs::File) -> result::Result<String, io::Error> {
let mut hasher = Sha384::new();
io::copy(&mut file, &mut hasher)?;
Ok(format!("{:x}", hasher.result()))
Ok(format!("{:x}", hasher.finalize()))
}
fn compute_file_sha512(mut file: fs::File) -> result::Result<String, io::Error> {
let mut hasher = Sha512::new();
io::copy(&mut file, &mut hasher)?;
Ok(format!("{:x}", hasher.result()))
Ok(format!("{:x}", hasher.finalize()))
}
fn file_not_found_err(search_paths: &Vec<PathBuf>, url: &String) -> Result<Value> {
Err(format!("file `{}` not found; searched in{}", url,
search_paths.iter().fold(String::new(),
|acc, arg| acc + " " + arg.to_str().unwrap())).into())
fn file_not_found_err(search_paths: &[PathBuf], url: &str) -> Result<Value> {
Err(format!(
"file `{}` not found; searched in{}",
url,
search_paths.iter().fold(String::new(), |acc, arg| acc + " " + arg.to_str().unwrap())
)
.into())
}
impl TeraFn for GetUrl {
@ -151,8 +157,8 @@ impl TeraFn for GetUrl {
match open_file(&self.search_paths, &path).and_then(compute_file_sha256) {
Ok(hash) => {
permalink = format!("{}?h={}", permalink, hash);
},
Err(_) => return file_not_found_err(&self.search_paths, &path)
}
Err(_) => return file_not_found_err(&self.search_paths, &path),
};
}
Ok(to_value(permalink).unwrap())
@ -183,20 +189,21 @@ impl TeraFn for GetFileHash {
u16,
args.get("sha_type"),
"`get_file_hash`: `sha_type` must be 256, 384 or 512"
).unwrap_or(DEFAULT_SHA_TYPE);
)
.unwrap_or(DEFAULT_SHA_TYPE);
let compute_hash_fn = match sha_type {
256 => compute_file_sha256,
384 => compute_file_sha384,
512 => compute_file_sha512,
_ => return Err("`get_file_hash`: `sha_type` must be 256, 384 or 512".into())
_ => return Err("`get_file_hash`: `sha_type` must be 256, 384 or 512".into()),
};
let hash = open_file(&self.search_paths, &path).and_then(compute_hash_fn);
match hash {
Ok(digest) => Ok(to_value(digest).unwrap()),
Err(_) => file_not_found_err(&self.search_paths, &path)
Err(_) => file_not_found_err(&self.search_paths, &path),
}
}
}
@ -297,7 +304,7 @@ fn image_dimensions(path: &PathBuf) -> Result<(u32, u32)> {
match (img.height(), img.width(), img.view_box()) {
(Some(h), Some(w), _) => Ok((h as u32, w as u32)),
(_, _, Some(view_box)) => Ok((view_box.height as u32, view_box.width as u32)),
_ => Err("Invalid dimensions: SVG width/height and viewbox not set.".into())
_ => Err("Invalid dimensions: SVG width/height and viewbox not set.".into()),
}
} else {
let img = image::open(&path)
@ -465,7 +472,7 @@ impl TeraFn for GetTaxonomy {
#[cfg(test)]
mod tests {
use super::{GetTaxonomy, GetTaxonomyUrl, GetUrl, Trans, GetFileHash};
use super::{GetFileHash, GetTaxonomy, GetTaxonomyUrl, GetUrl, Trans};
use std::collections::HashMap;
use std::env::temp_dir;
@ -786,7 +793,10 @@ title = "A title"
let mut args = HashMap::new();
args.insert("path".to_string(), to_value("app.css").unwrap());
args.insert("sha_type".to_string(), to_value(256).unwrap());
assert_eq!(static_fn.call(&args).unwrap(), "572e691dc68c3fcd653ae463261bdb38f35dc6f01715d9ce68799319dd158840");
assert_eq!(
static_fn.call(&args).unwrap(),
"572e691dc68c3fcd653ae463261bdb38f35dc6f01715d9ce68799319dd158840"
);
}
#[test]
@ -812,8 +822,10 @@ title = "A title"
let mut args = HashMap::new();
args.insert("path".to_string(), to_value("doesnt-exist").unwrap());
assert_eq!(
format!("file `doesnt-exist` not found; searched in {}",
TEST_CONTEXT.static_path.to_str().unwrap()),
format!(
"file `doesnt-exist` not found; searched in {}",
TEST_CONTEXT.static_path.to_str().unwrap()
),
format!("{}", static_fn.call(&args).unwrap_err())
);
}

View File

@ -1,6 +1,5 @@
use serde::{Deserialize, Deserializer};
use tera::{Map, Value};
use toml;
/// Used as an attribute when we want to convert from TOML to a string date
pub fn from_toml_datetime<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
@ -43,6 +42,16 @@ pub fn fix_toml_dates(table: Map<String, Value>) -> Value {
Value::Object(o) => {
new.insert(key, convert_toml_date(o));
}
Value::Array(arr) => {
let mut new_arr = Vec::with_capacity(arr.len());
for v in arr {
match v {
Value::Object(o) => new_arr.push(fix_toml_dates(o)),
_ => new_arr.push(v),
};
}
new.insert(key, Value::Array(new_arr));
}
_ => {
new.insert(key, value);
}

View File

@ -4,7 +4,7 @@
command = "zola build"
[build.environment]
ZOLA_VERSION = "0.8.0"
ZOLA_VERSION = "0.11.0"
[context.deploy-preview]
command = "zola build --base-url $DEPLOY_PRIME_URL"

View File

@ -35,7 +35,6 @@ use hyper_staticfile::ResolveResult;
use tokio::io::AsyncReadExt;
use chrono::prelude::*;
use ctrlc;
use notify::{watcher, RecursiveMode, Watcher};
use ws::{Message, Sender, WebSocket};
@ -45,8 +44,6 @@ use site::Site;
use utils::fs::copy_file;
use crate::console;
use open;
use rebuild;
#[derive(Debug, PartialEq)]
enum ChangeKind {

View File

@ -5,7 +5,6 @@ use std::error::Error as StdError;
use std::io::Write;
use std::time::Instant;
use atty;
use chrono::Duration;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};