From 53b2c42080b55d632a8de378cac5f42623382da4 Mon Sep 17 00:00:00 2001 From: Georg Semmler Date: Tue, 28 Mar 2017 20:57:42 +0200 Subject: [PATCH 1/2] Allow to configure colors using an environment variable This commit add support for configuring colors used by cargo through a environment variable (CARGO_COLORS). The used syntax for the environment variable is similar to that one used by gcc (GCC_COLORS). Example: CARGO_COLORS="status=01;2:warn=01;3:error=01;1:default:01;231:blocked=01;4" --- src/cargo/core/shell.rs | 158 ++++++++++++++++++++++--- src/cargo/lib.rs | 12 +- src/cargo/ops/cargo_new.rs | 5 +- src/cargo/ops/cargo_rustc/job_queue.rs | 4 +- src/cargo/ops/registry.rs | 12 +- src/cargo/util/flock.rs | 4 +- 6 files changed, 162 insertions(+), 33 deletions(-) diff --git a/src/cargo/core/shell.rs b/src/cargo/core/shell.rs index 7ec566ac77d..d3fb4a798fd 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,130 @@ 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(()) + } +} + +#[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 +178,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 +196,7 @@ impl MultiShell { out: out, err: err, verbosity: Verbosity::Quiet, + styles: Styles::from_env(), } } @@ -82,15 +208,15 @@ 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) } } - pub fn status_with_color(&mut self, status: T, message: U, color: Color) + pub fn status_with_color(&mut self, status: T, message: U, color: Style) -> CargoResult<()> where T: fmt::Display, U: fmt::Display { @@ -103,7 +229,8 @@ impl MultiShell { pub fn status(&mut self, status: T, message: U) -> CargoResult<()> where T: fmt::Display, U: fmt::Display { - self.status_with_color(status, message, GREEN) + let color = self.styles.status; + self.status_with_color(status, message, color) } pub fn verbose(&mut self, mut callback: F) -> CargoResult<()> @@ -125,13 +252,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) + }, } } @@ -224,9 +355,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()?; @@ -236,14 +367,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 2fe2a572b13..f97f229c584 100755 --- a/src/cargo/lib.rs +++ b/src/cargo/lib.rs @@ -43,7 +43,6 @@ use docopt::Docopt; use core::{Shell, MultiShell, ShellConfig, Verbosity, ColorConfig}; use core::shell::Verbosity::{Verbose}; -use term::color::{BLACK}; pub use util::{CargoError, CargoErrorKind, CargoResult, CliError, CliResult, Config}; @@ -190,12 +189,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); } } @@ -212,8 +213,9 @@ pub fn handle_error(err: CargoError, shell: &mut MultiShell) { fn handle_cause(cargo_err: E, shell: &mut MultiShell) -> bool where E: ChainedError + 'static { 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); } //Error inspection in non-verbose mode requires inspecting the diff --git a/src/cargo/ops/cargo_new.rs b/src/cargo/ops/cargo_new.rs index 1d1f1bc7c32..e0a77bf7953 100644 --- a/src/cargo/ops/cargo_new.rs +++ b/src/cargo/ops/cargo_new.rs @@ -7,8 +7,6 @@ use rustc_serialize::{Decodable, Decoder}; use git2::Config as GitConfig; -use term::color::BLACK; - use core::Workspace; use ops::is_bad_artifact_name; use util::{GitRepo, HgRepo, PijulRepo, internal}; @@ -108,10 +106,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 fa008289dda..4b5ef6c1cd2 100644 --- a/src/cargo/ops/cargo_rustc/job_queue.rs +++ b/src/cargo/ops/cargo_rustc/job_queue.rs @@ -7,7 +7,6 @@ use std::sync::mpsc::{channel, Sender, Receiver}; use crossbeam::{self, Scope}; use jobserver::{Acquired, HelperThread}; -use term::color::YELLOW; use core::{PackageId, Target, Profile}; use util::{Config, DependencyQueue, Fresh, Dirty, Freshness}; @@ -236,9 +235,10 @@ impl<'a> JobQueue<'a> { if self.active > 0 { error = Some("build failed".into()); 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)?; } else { error = Some(e); diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index 6496715631f..65a8f78905f 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}; @@ -420,6 +419,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) => { @@ -429,24 +429,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 505a88a008a..fd7b55a9d7f 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; @@ -290,7 +289,8 @@ fn acquire(config: &Config, } } let msg = format!("waiting for file lock on {}", msg); - config.shell().status_with_color("Blocking", &msg, CYAN)?; + let color = config.shell().styles.blocked; + config.shell().status_with_color("Blocking", &msg, color)?; return block().chain_err(|| { format!("failed to lock file: {}", path.display()) From 4c7142eae3f944dbbe1649ea42bc071be7b0a5a6 Mon Sep 17 00:00:00 2001 From: Georg Semmler Date: Wed, 29 Mar 2017 00:23:27 +0200 Subject: [PATCH 2/2] Fix tests --- src/cargo/core/shell.rs | 7 +++++++ tests/shell.rs | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/cargo/core/shell.rs b/src/cargo/core/shell.rs index d3fb4a798fd..745c2c471c1 100644 --- a/src/cargo/core/shell.rs +++ b/src/cargo/core/shell.rs @@ -83,6 +83,13 @@ impl Style { } Ok(()) } + + pub fn new(color: Color, attr: Option) -> Self { + Style { + color: color, + attr: attr + } + } } #[derive(Clone, Copy, PartialEq)] 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[..],