diff --git a/src/cargo/core/shell.rs b/src/cargo/core/shell.rs index 58ed88f3f28..e3cda491515 100644 --- a/src/cargo/core/shell.rs +++ b/src/cargo/core/shell.rs @@ -2,7 +2,7 @@ use std::fmt; use std::io::prelude::*; use std::io; -use term::color::{Color, BLACK, RED, GREEN, YELLOW}; +use term::color::{Color, BLACK, RED, GREEN, YELLOW, CYAN}; use term::{self, Terminal, TerminfoTerminal, color, Attr}; use self::AdequateTerminal::{NoColor, Colored}; @@ -18,6 +18,137 @@ pub enum Verbosity { Quiet } +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Style { + color: Color, + attr: Option, +} + +impl Default for Style { + fn default() -> Self { + Style { + color: BLACK, + attr: None, + } + } +} + +impl Style { + fn from_str(config: &str, mut color: Color, mut attr: Option) -> Self { + let parts = config.split(";").collect::>(); + if let Some(p) = parts.get(0) { + if let Ok(p) = p.parse() { + match p { + 0 if attr == Some(Attr::Bold) => { + attr = None; + } + 1 => { + attr = Some(Attr::Bold); + } + 3 => { + attr = Some(Attr::Italic(true)); + } + 4 => { + attr = Some(Attr::Underline(true)); + } + 5 | 6 => { + attr = Some(Attr::Blink) + } + i if i >= 30 && i <= 39 => { + color = i + } + _ => { + // ignore everything else + } + } + } + } + if let Some(p) = parts.get(1) { + if let Ok(p) = p.parse() { + color = p; + } + } + Style { + color: color, + attr: attr, + } + } + + fn apply(&self, shell: &mut Shell) -> CargoResult<()> { + if self.color != BLACK { shell.fg(self.color)?; } + if let Some(attr) = self.attr { + if shell.supports_attr(attr) { + shell.attr(attr)?; + } + } + Ok(()) + } + + pub fn new(color: Color, attr: Option) -> Self { + Style { + color: color, + attr: attr + } + } +} + +#[derive(Clone, Copy, PartialEq)] +pub struct Styles { + pub status: Style, + pub warning: Style, + pub error: Style, + pub default: Style, + pub blocked: Style, +} + + +impl Default for Styles { + fn default() -> Self { + Styles { + status: Style { + color: GREEN, + attr: Some(Attr::Bold), + }, + warning: Style { + color: YELLOW, + attr: Some(Attr::Bold), + }, + error: Style { + color: RED, + attr: Some(Attr::Bold), + }, + default: Style::default(), + blocked: Style { + color: CYAN, + attr: Some(Attr::Bold), + } + } + } +} + +impl Styles { + fn from_env() -> Styles { + use std::env::var; + let mut ret = Styles::default(); + if let Ok(config) = var("CARGO_COLORS") { + for p in config.split(":") { + if p.starts_with("status=") { + ret.status = Style::from_str(&p[7..], ret.status.color, ret.status.attr); + } else if p.starts_with("warning=") { + ret.warning = Style::from_str(&p[8..], ret.warning.color, ret.warning.attr); + } else if p.starts_with("error=") { + ret.error = Style::from_str(&p[6..], ret.error.color, ret.error.attr); + } else if p.starts_with("default=") { + ret.default = Style::from_str(&p[8..], ret.default.color, ret.default.attr); + } else if p.starts_with("blocked=") { + ret.blocked = Style::from_str(&p[8..], ret.blocked.color, ret.blocked.attr); + } + } + } + ret + } +} + #[derive(Clone, Copy, PartialEq)] pub enum ColorConfig { Auto, @@ -54,12 +185,13 @@ pub struct Shell { pub struct MultiShell { out: Shell, err: Shell, - verbosity: Verbosity + verbosity: Verbosity, + pub styles: Styles, } impl MultiShell { pub fn new(out: Shell, err: Shell, verbosity: Verbosity) -> MultiShell { - MultiShell { out: out, err: err, verbosity: verbosity } + MultiShell { out: out, err: err, verbosity: verbosity, styles: Styles::from_env() } } // Create a quiet, basic shell from supplied writers. @@ -71,6 +203,7 @@ impl MultiShell { out: out, err: err, verbosity: Verbosity::Quiet, + styles: Styles::from_env(), } } @@ -82,11 +215,11 @@ impl MultiShell { &mut self.err } - pub fn say(&mut self, message: T, color: Color) + pub fn say(&mut self, message: T, style: Style) -> CargoResult<()> { match self.verbosity { Quiet => Ok(()), - _ => self.out().say(message, color) + _ => self.out().say(message, style) } } @@ -95,7 +228,10 @@ impl MultiShell { { match self.verbosity { Quiet => Ok(()), - _ => self.err().say_status(status, message, GREEN, true) + _ => { + let color = self.styles.status; + self.err().say_status(status, message, color, true) + } } } @@ -118,13 +254,17 @@ impl MultiShell { } pub fn error(&mut self, message: T) -> CargoResult<()> { - self.err().say_status("error:", message, RED, false) + let color = self.styles.error; + self.err().say_status("error:", message, color, false) } pub fn warn(&mut self, message: T) -> CargoResult<()> { match self.verbosity { Quiet => Ok(()), - _ => self.err().say_status("warning:", message, YELLOW, false), + _ => { + let color = self.styles.warning; + self.err().say_status("warning:", message, color, false) + }, } } @@ -217,9 +357,9 @@ impl Shell { self.config.color_config = color_config; } - pub fn say(&mut self, message: T, color: Color) -> CargoResult<()> { + pub fn say(&mut self, message: T, style: Style) -> CargoResult<()> { self.reset()?; - if color != BLACK { self.fg(color)?; } + style.apply(self)?; write!(self, "{}\n", message.to_string())?; self.reset()?; self.flush()?; @@ -229,14 +369,13 @@ impl Shell { pub fn say_status(&mut self, status: T, message: U, - color: Color, + style: Style, justified: bool) -> CargoResult<()> where T: fmt::Display, U: fmt::Display { self.reset()?; - if color != BLACK { self.fg(color)?; } - if self.supports_attr(Attr::Bold) { self.attr(Attr::Bold)?; } + style.apply(self)?; if justified { write!(self, "{:>12}", status.to_string())?; } else { diff --git a/src/cargo/lib.rs b/src/cargo/lib.rs index 736a9c04da4..cb3d2db1b27 100755 --- a/src/cargo/lib.rs +++ b/src/cargo/lib.rs @@ -38,7 +38,6 @@ use docopt::Docopt; use core::{Shell, MultiShell, ShellConfig, Verbosity, ColorConfig}; use core::shell::Verbosity::{Verbose}; -use term::color::{BLACK}; pub use util::{CargoError, CargoResult, CliError, CliResult, human, Config, ChainError}; @@ -185,12 +184,14 @@ pub fn exit_with_error(err: CliError, shell: &mut MultiShell) -> ! { } else if fatal { shell.error(&error) } else { - shell.say(&error, BLACK) + let color = shell.styles.default; + shell.say(&error, color) }; if !handle_cause(&error, shell) || hide { + let color = shell.styles.default; let _ = shell.err().say("\nTo learn more, run the command again \ - with --verbose.".to_string(), BLACK); + with --verbose.".to_string(), color); } } @@ -223,8 +224,9 @@ fn handle_cause(mut cargo_err: &CargoError, shell: &mut MultiShell) -> bool { } fn print(error: String, shell: &mut MultiShell) { - let _ = shell.err().say("\nCaused by:", BLACK); - let _ = shell.err().say(format!(" {}", error), BLACK); + let color = shell.styles.default; + let _ = shell.err().say("\nCaused by:", color); + let _ = shell.err().say(format!(" {}", error), color); } } diff --git a/src/cargo/ops/cargo_new.rs b/src/cargo/ops/cargo_new.rs index d8faedf4da7..0c68f266136 100644 --- a/src/cargo/ops/cargo_new.rs +++ b/src/cargo/ops/cargo_new.rs @@ -6,8 +6,6 @@ use rustc_serialize::{Decodable, Decoder}; use git2::Config as GitConfig; -use term::color::BLACK; - use handlebars::{Handlebars, no_escape}; use tempdir::TempDir; use time; @@ -193,10 +191,11 @@ fn get_name<'a>(path: &'a Path, opts: &'a NewOptions, config: &Config) -> CargoR } else { let new_name = strip_rust_affixes(dir_name); if new_name != dir_name { + let color = config.shell().styles.default; let message = format!( "note: package will be named `{}`; use --name to override", new_name); - config.shell().say(&message, BLACK)?; + config.shell().say(&message, color)?; } Ok(new_name) } diff --git a/src/cargo/ops/cargo_rustc/job_queue.rs b/src/cargo/ops/cargo_rustc/job_queue.rs index 7a380851eee..3a3830439b7 100644 --- a/src/cargo/ops/cargo_rustc/job_queue.rs +++ b/src/cargo/ops/cargo_rustc/job_queue.rs @@ -5,7 +5,6 @@ use std::io::Write; use std::sync::mpsc::{channel, Sender, Receiver}; use crossbeam::{self, Scope}; -use term::color::YELLOW; use core::{PackageId, Target, Profile}; use util::{Config, DependencyQueue, Fresh, Dirty, Freshness}; @@ -185,9 +184,10 @@ impl<'a> JobQueue<'a> { if self.active > 0 { error = Some(human("build failed")); handle_error(&*e, &mut *cx.config.shell()); + let color = cx.config.shell().styles.warning; cx.config.shell().say( "Build failed, waiting for other \ - jobs to finish...", YELLOW)?; + jobs to finish...", color)?; } if error.is_none() { error = Some(e); diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index c2915afcf55..3fa56fb0c96 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -8,7 +8,6 @@ use std::time::Duration; use curl::easy::{Easy, SslOpt}; use git2; use registry::{Registry, NewCrate, NewCrateDependency}; -use term::color::BLACK; use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET}; @@ -419,6 +418,7 @@ pub fn search(query: &str, .max() .unwrap_or(0); + let color = config.shell().styles.default; for (name, description) in list_items.into_iter() { let line = match description { Some(desc) => { @@ -428,24 +428,22 @@ pub fn search(query: &str, } None => name }; - config.shell().say(line, BLACK)?; + config.shell().say(line, color)?; } let search_max_limit = 100; if total_crates > limit as u32 && limit < search_max_limit { config.shell().say( format!("... and {} crates more (use --limit N to see more)", - total_crates - limit as u32), - BLACK) + total_crates - limit as u32), color) ?; - } else if total_crates > limit as u32 && limit >= search_max_limit { + } else if total_crates > limit as u32 && limit >= search_max_limit { config.shell().say( format!( "... and {} crates more (go to http://crates.io/search?q={} to see more)", total_crates - limit as u32, percent_encode(query.as_bytes(), QUERY_ENCODE_SET) - ), - BLACK) + ), color) ?; } diff --git a/src/cargo/util/flock.rs b/src/cargo/util/flock.rs index 64151bff5d5..6c88ac99d34 100644 --- a/src/cargo/util/flock.rs +++ b/src/cargo/util/flock.rs @@ -3,7 +3,6 @@ use std::io::*; use std::io; use std::path::{Path, PathBuf, Display}; -use term::color::CYAN; use fs2::{FileExt, lock_contended_error}; #[allow(unused_imports)] use libc; @@ -288,7 +287,8 @@ fn acquire(config: &Config, } } let msg = format!("waiting for file lock on {}", msg); - config.shell().err().say_status("Blocking", &msg, CYAN, true)?; + let color = config.shell().styles.blocked; + config.shell().err().say_status("Blocking", &msg, color, true)?; return block().chain_error(|| { human(format!("failed to lock file: {}", path.display())) @@ -299,7 +299,7 @@ fn acquire(config: &Config, use std::ffi::CString; use std::mem; use std::os::unix::prelude::*; - + let path = match CString::new(path.as_os_str().as_bytes()) { Ok(path) => path, Err(_) => return false, diff --git a/tests/shell.rs b/tests/shell.rs index 8dc0786fc3f..3a1848b494b 100644 --- a/tests/shell.rs +++ b/tests/shell.rs @@ -8,7 +8,7 @@ use std::io; use std::sync::{Arc, Mutex}; use cargo::core::shell::ColorConfig::{Auto,Always, Never}; -use cargo::core::shell::{Shell, ShellConfig}; +use cargo::core::shell::{Shell, ShellConfig, Style}; use cargo::util::CargoResult; use cargotest::support::{Tap, execs, shell_writes}; use hamcrest::{assert_that}; @@ -29,7 +29,7 @@ fn non_tty() { let a = Arc::new(Mutex::new(Vec::new())); Shell::create(|| Box::new(Sink(a.clone())), config).tap(|shell| { - shell.say("Hey Alex", color::RED).unwrap(); + shell.say("Hey Alex", Style::default()).unwrap(); }); let buf = a.lock().unwrap().clone(); assert_that(&buf[..], shell_writes("Hey Alex\n")); @@ -44,7 +44,7 @@ fn color_explicitly_disabled() { let a = Arc::new(Mutex::new(Vec::new())); Shell::create(|| Box::new(Sink(a.clone())), config).tap(|shell| { - shell.say("Hey Alex", color::RED).unwrap(); + shell.say("Hey Alex", Style::default()).unwrap(); }); let buf = a.lock().unwrap().clone(); assert_that(&buf[..], shell_writes("Hey Alex\n")); @@ -59,7 +59,7 @@ fn colored_shell() { let a = Arc::new(Mutex::new(Vec::new())); Shell::create(|| Box::new(Sink(a.clone())), config).tap(|shell| { - shell.say("Hey Alex", color::RED).unwrap(); + shell.say("Hey Alex", Style::new(color::RED, None)).unwrap(); }); let buf = a.lock().unwrap().clone(); let expected_output = if term.unwrap().supports_color() { @@ -80,7 +80,7 @@ fn color_explicitly_enabled() { let a = Arc::new(Mutex::new(Vec::new())); Shell::create(|| Box::new(Sink(a.clone())), config).tap(|shell| { - shell.say("Hey Alex", color::RED).unwrap(); + shell.say("Hey Alex", Style::new(color::RED, None)).unwrap(); }); let buf = a.lock().unwrap().clone(); assert_that(&buf[..],