Terminal colors
authorYehuda Katz <wycats@gmail.com>
Sun, 22 Jun 2014 01:53:07 +0000 (18:53 -0700)
committerYehuda Katz <wycats@gmail.com>
Sun, 22 Jun 2014 01:53:07 +0000 (18:53 -0700)
libs/hammer.rs
src/bin/cargo-compile.rs
src/bin/cargo.rs
src/cargo/core/mod.rs
src/cargo/core/shell.rs
src/cargo/lib.rs
src/cargo/ops/cargo_rustc.rs
src/cargo/util/errors.rs
tests/support/mod.rs
tests/test_cargo_compile.rs
tests/test_shell.rs

index 6d26b74a66ce16f9d146a407a2384aa6ef431f7c..6c442daa3550d791333c4e382587d63fd12c89d2 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 6d26b74a66ce16f9d146a407a2384aa6ef431f7c
+Subproject commit 6c442daa3550d791333c4e382587d63fd12c89d2
index c4cff306e634db1086fa9b3bcf44cee185aee552..d410131e1840a0daf83616ea42fb28c3c6e9e06d 100644 (file)
@@ -22,7 +22,7 @@ pub struct Options {
     manifest_path: Option<String>
 }
 
-hammer_config!(Options)
+hammer_config!(Options "Compile the current project")
 
 fn main() {
     execute_main_without_stdin(execute);
index dde0dbe27c17f3c196591fa45f33ed7d9f77e3f4..ffe3f454a10fa666d5c5ac3afa968532311cf1d5 100644 (file)
@@ -11,7 +11,7 @@ use hammer::{FlagConfig,FlagConfiguration};
 use std::os;
 use std::io::process::{Command,InheritFd,ExitStatus,ExitSignal};
 use serialize::Encodable;
-use cargo::{NoFlags,execute_main_without_stdin,handle_error};
+use cargo::{GlobalFlags, NoFlags, execute_main_without_stdin, handle_error};
 use cargo::util::important_paths::find_project;
 use cargo::util::{CliError, CliResult, Require, config, human};
 
@@ -37,32 +37,41 @@ fn execute() {
         Err(err) => return handle_error(err, false)
     };
 
-    if cmd == "config-for-key".to_str() {
-        log!(4, "cmd == config-for-key");
-        execute_main_without_stdin(config_for_key)
-    }
-    else if cmd == "config-list".to_str() {
-        log!(4, "cmd == config-list");
-        execute_main_without_stdin(config_list)
-    }
-    else if cmd == "locate-project".to_str() {
-        log!(4, "cmd == locate-project");
-        execute_main_without_stdin(locate_project)
-    }
-    else {
-        let command = Command::new(format!("cargo-{}", cmd))
-            .args(args.as_slice())
-            .stdin(InheritFd(0))
-            .stdout(InheritFd(1))
-            .stderr(InheritFd(2))
-            .status();
-
-        match command {
-            Ok(ExitStatus(0)) => (),
-            Ok(ExitStatus(i)) | Ok(ExitSignal(i)) => {
-                handle_error(CliError::new("", i as uint), false)
+    match cmd.as_slice() {
+        "config-for-key" => {
+            log!(4, "cmd == config-for-key");
+            execute_main_without_stdin(config_for_key)
+        },
+        "config-list" => {
+            log!(4, "cmd == config-list");
+            execute_main_without_stdin(config_list)
+        },
+        "locate-project" => {
+            log!(4, "cmd == locate-project");
+            execute_main_without_stdin(locate_project)
+        },
+        "--help" | "-h" | "help" | "-?" => {
+            println!("Commands:");
+            println!("  compile          # compile the current project\n");
+
+            let (_, options) = hammer::usage::<GlobalFlags>(false);
+            println!("Options (for all commands):\n\n{}", options);
+        },
+        _ => {
+            let command = Command::new(format!("cargo-{}", cmd))
+                .args(args.as_slice())
+                .stdin(InheritFd(0))
+                .stdout(InheritFd(1))
+                .stderr(InheritFd(2))
+                .status();
+
+            match command {
+                Ok(ExitStatus(0)) => (),
+                Ok(ExitStatus(i)) | Ok(ExitSignal(i)) => {
+                    handle_error(CliError::new("", i as uint), false)
+                }
+                Err(_) => handle_error(CliError::new("No such subcommand", 127), false)
             }
-            Err(_) => handle_error(CliError::new("No such subcommand", 127), false)
         }
     }
 }
index 9d041299e0cf850bbad4ab286545d384f4124484..fd6ff3211ac8aac720f222c442a77203e8ebab6a 100644 (file)
@@ -30,6 +30,11 @@ pub use self::summary::{
     Summary
 };
 
+pub use self::shell::{
+    Shell,
+    ShellConfig
+};
+
 pub use self::dependency::Dependency;
 pub use self::version_req::VersionReq;
 
index 984bc7c9c6e8c084b6dbf42129c21f75e14247d6..32d0f8ed0fce2ab5c8991184a88803800ddd3297 100644 (file)
@@ -1,8 +1,8 @@
 use term;
 use term::{Terminal,color};
-use term::color::Color;
+use term::color::{Color, BLACK};
 use term::attr::Attr;
-use std::io::IoResult;
+use std::io::{IoResult, stderr};
 
 pub struct ShellConfig {
     pub color: bool,
@@ -10,31 +10,33 @@ pub struct ShellConfig {
     pub tty: bool
 }
 
-enum AdequateTerminal<T> {
-    NoColor(T),
-    Color(Box<Terminal<T>>)
+enum AdequateTerminal {
+    NoColor(Box<Writer>),
+    Color(Box<Terminal<Box<Writer>>>)
 }
 
-pub struct Shell<T> {
-    terminal: AdequateTerminal<T>,
+pub struct Shell {
+    terminal: AdequateTerminal,
     config: ShellConfig
 }
 
-impl<T: Writer + Send> Shell<T> {
-    pub fn create(out: T, config: ShellConfig) -> Option<Shell<T>> {
+impl Shell {
+    pub fn create(out: Box<Writer>, config: ShellConfig) -> Shell {
         if config.tty && config.color {
-            let term: Option<term::TerminfoTerminal<T>> = Terminal::new(out);
+            let term: Option<term::TerminfoTerminal<Box<Writer>>> = Terminal::new(out);
             term.map(|t| Shell {
-                terminal: Color(box t as Box<Terminal<T>>),
+                terminal: Color(box t as Box<Terminal<Box<Writer>>>),
                 config: config
+            }).unwrap_or_else(|| {
+                Shell { terminal: NoColor(box stderr() as Box<Writer>), config: config }
             })
         } else {
-            Some(Shell { terminal: NoColor(out), config: config })
+            Shell { terminal: NoColor(out), config: config }
         }
     }
 
     pub fn verbose(&mut self,
-                   callback: |&mut Shell<T>| -> IoResult<()>) -> IoResult<()> {
+                   callback: |&mut Shell| -> IoResult<()>) -> IoResult<()> {
         if self.config.verbose {
             return callback(self)
         }
@@ -42,22 +44,25 @@ impl<T: Writer + Send> Shell<T> {
         Ok(())
     }
 
-    pub fn say<T: Str>(&mut self, message: T, color: Color) -> IoResult<()> {
+    pub fn say<T: ToStr>(&mut self, message: T, color: Color) -> IoResult<()> {
         try!(self.reset());
-        try!(self.fg(color));
-        try!(self.write_line(message.as_slice()));
+        if color != BLACK { try!(self.fg(color)); }
+        try!(self.write_line(message.to_str().as_slice()));
         try!(self.reset());
         try!(self.flush());
         Ok(())
     }
 }
 
-impl<T: Writer + Send> Terminal<T> for Shell<T> {
-    fn new(out: T) -> Option<Shell<T>> {
-        Shell::create(out, ShellConfig {
-            color: true,
-            verbose: false,
-            tty: false,
+impl Terminal<Box<Writer>> for Shell {
+    fn new(out: Box<Writer>) -> Option<Shell> {
+        Some(Shell {
+            terminal: NoColor(out),
+            config: ShellConfig {
+                color: true,
+                verbose: false,
+                tty: false,
+            }
         })
     }
 
@@ -96,18 +101,18 @@ impl<T: Writer + Send> Terminal<T> for Shell<T> {
         }
     }
 
-    fn unwrap(self) -> T {
+    fn unwrap(self) -> Box<Writer> {
         fail!("Can't unwrap a Shell");
     }
 
-    fn get_ref<'a>(&'a self) -> &'a T {
+    fn get_ref<'a>(&'a self) -> &'a Box<Writer> {
         match self.terminal {
             Color(ref c) => c.get_ref(),
             NoColor(ref w) => w
         }
     }
 
-    fn get_mut<'a>(&'a mut self) -> &'a mut T {
+    fn get_mut<'a>(&'a mut self) -> &'a mut Box<Writer> {
         match self.terminal {
             Color(ref mut c) => c.get_mut(),
             NoColor(ref mut w) => w
@@ -115,7 +120,7 @@ impl<T: Writer + Send> Terminal<T> for Shell<T> {
     }
 }
 
-impl<T: Writer + Send> Writer for Shell<T> {
+impl Writer for Shell {
     fn write(&mut self, buf: &[u8]) -> IoResult<()> {
         match self.terminal {
             Color(ref mut c) => c.write(buf),
index f2ed95c1b8883183087c459e52e87e8d3afbf478..6945438083a5cce66e2fed6284e2f76d60212fe3 100644 (file)
@@ -21,7 +21,12 @@ extern crate hamcrest;
 
 use serialize::{Decoder, Encoder, Decodable, Encodable, json};
 use std::io;
-use hammer::{FlagDecoder, FlagConfig, UsageDecoder, HammerError, FlagConfiguration};
+use std::io::stderr;
+use std::io::stdio::stderr_raw;
+use hammer::{FlagDecoder, FlagConfig, UsageDecoder, HammerError};
+
+use core::{Shell, ShellConfig};
+use term::color::{RED, BLACK};
 
 pub use util::{CargoError, CliError, CliResult, human};
 
@@ -62,25 +67,12 @@ trait FlagParse : FlagConfig {
     fn decode_flags(d: &mut FlagDecoder) -> Result<Self, HammerError>;
 }
 
-trait FlagUsage : FlagConfig {
-    fn decode_usage(d: &mut UsageDecoder) -> Result<Self, HammerError>;
-}
-
-//impl<T: FlagConfig + Decodable<FlagDecoder, HammerError>> FlagParse for T {}
-//impl<T: FlagConfig + Decodable<UsageDecoder, HammerError>> FlagUsage for T {}
-
 impl<T: FlagConfig + Decodable<FlagDecoder, HammerError>> FlagParse for T {
     fn decode_flags(d: &mut FlagDecoder) -> Result<T, HammerError> {
         Decodable::decode(d)
     }
 }
 
-impl<T: FlagConfig + Decodable<UsageDecoder, HammerError>> FlagUsage for T {
-    fn decode_usage(d: &mut UsageDecoder) -> Result<T, HammerError> {
-        Decodable::decode(d)
-    }
-}
-
 trait RepresentsFlags : FlagParse + Decodable<UsageDecoder, HammerError> {}
 impl<T: FlagParse + Decodable<UsageDecoder, HammerError>> RepresentsFlags for T {}
 
@@ -150,10 +142,16 @@ pub fn execute_main_without_stdin<'a,
         Err(e) => handle_error(e, true),
         Ok(val) => {
             if val.help {
-                println!("Usage:\n");
+                let (desc, options) = hammer::usage::<T>(true);
+
+                desc.map(|d| println!("{}\n", d));
 
-                print!("{}", hammer::usage::<T>(true));
-                print!("{}", hammer::usage::<GlobalFlags>(false));
+                println!("Options:\n");
+
+                print!("{}", options);
+
+                let (_, options) = hammer::usage::<GlobalFlags>(false);
+                print!("{}", options);
             } else {
                 process_executed(call(exec, val.rest.as_slice()), val)
             }
@@ -180,21 +178,37 @@ pub fn process_executed<'a,
 pub fn handle_error(err: CliError, verbose: bool) {
     log!(4, "handle_error; err={}", err);
 
-    let CliError { error, exit_code, .. } = err;
-    let _ = write!(&mut std::io::stderr(), "{}", error);
+
+    let CliError { error, exit_code, unknown, .. } = err;
+
+    let tty = stderr_raw().isatty();
+    let stderr = box stderr() as Box<Writer>;
+
+    let config = ShellConfig { color: true, verbose: false, tty: tty };
+    let mut shell = Shell::create(stderr, config);
+
+    if unknown {
+        let _ = shell.say("An unknown error occurred", RED);
+    } else {
+        let _ = shell.say(error.to_str(), RED);
+    }
+
+    if unknown && !verbose {
+        let _ = shell.say("\nTo learn more, run the command again with --verbose.", BLACK);
+    }
 
     if verbose {
-        error.cause().map(handle_cause);
+        handle_cause(error, &mut shell);
     }
 
     std::os::set_exit_status(exit_code as int);
 }
 
-fn handle_cause(err: &CargoError) {
-    println!("\nCaused by:");
-    println!("  {}", err.description());
+fn handle_cause(err: &CargoError, shell: &mut Shell) {
+    let _ = shell.say("\nCaused by:", BLACK);
+    let _ = shell.say(format!("  {}", err.description()), BLACK);
 
-    err.cause().map(handle_cause);
+    err.cause().map(|e| handle_cause(e, shell));
 }
 
 fn args() -> Vec<String> {
index 99ff2cf8ece60c2c4e6eab64453e24e549e0d4fa..334467c3afa8d15eda19295cff818409ae488fe6 100644 (file)
@@ -1,6 +1,6 @@
 use std::os::args;
 use std::io;
-use std::io::File;
+use std::io::{File, IoError};
 use std::str;
 
 use core::{Package, PackageSet, Target};
@@ -31,8 +31,14 @@ pub fn compile_packages(pkg: &Package, deps: &PackageSet) -> CargoResult<()> {
 
     // First ensure that the destination directory exists
     debug!("creating target dir; path={}", target_dir.display());
-    try!(mk_target(&target_dir));
-    try!(mk_target(&deps_target_dir));
+
+    try!(mk_target(&target_dir).chain_error(||
+        internal(format!("Couldn't create the target directory for {} at {}",
+                 pkg.get_name(), target_dir.display()))));
+
+    try!(mk_target(&deps_target_dir).chain_error(||
+        internal(format!("Couldn't create the directory for dependencies for {} at {}",
+                 pkg.get_name(), deps_target_dir.display()))));
 
     let mut cx = Context {
         dest: &deps_target_dir,
@@ -119,10 +125,8 @@ fn is_fresh(dep: &Package, loc: &Path,
     Ok((old_fingerprint == new_fingerprint, new_fingerprint))
 }
 
-fn mk_target(target: &Path) -> CargoResult<()> {
-    io::fs::mkdir_recursive(target, io::UserRWX).chain_error(|| {
-        internal("could not create target directory")
-    })
+fn mk_target(target: &Path) -> Result<(), IoError> {
+    io::fs::mkdir_recursive(target, io::UserRWX)
 }
 
 fn compile_custom(pkg: &Package, cmd: &str, cx: &Context) -> CargoResult<()> {
index c3c1ea4faae917b646a2350b70b198feae914e12..fb937f01f7c82bc36c6dfe44238a4056290e8700 100644 (file)
@@ -240,6 +240,7 @@ pub type CliResult<T> = Result<T, CliError>;
 #[deriving(Show)]
 pub struct CliError {
     pub error: Box<CargoError>,
+    pub unknown: bool,
     pub exit_code: uint
 }
 
@@ -263,13 +264,8 @@ impl CliError {
     }
 
     pub fn from_boxed(error: Box<CargoError>, code: uint) -> CliError {
-        let error = if error.is_human() {
-            error
-        } else {
-            human("An unknown error occurred").with_cause(error)
-        };
-
-        CliError { error: error, exit_code: code }
+        let human = error.is_human();
+        CliError { error: error, exit_code: code, unknown: !human }
     }
 }
 
index a0ad6aa3ea46c556b0608e6dc2a2f7d99045d8f2..b1bc4c0ba34cfeae32f903ba26cc1cb9be70a56f 100644 (file)
@@ -301,13 +301,14 @@ impl ham::SelfDescribing for ShellWrites {
     }
 }
 
-impl<'a> ham::Matcher<&'a mut shell::Shell<std::io::MemWriter>> for ShellWrites {
-    fn matches(&self, actual: &mut shell::Shell<std::io::MemWriter>)
+impl<'a> ham::Matcher<&'a [u8]> for ShellWrites {
+    fn matches(&self, actual: &[u8])
         -> ham::MatchResult
     {
         use term::Terminal;
 
-        let actual = std::str::from_utf8_lossy(actual.get_ref().get_ref());
+        println!("{}", actual);
+        let actual = std::str::from_utf8_lossy(actual);
         let actual = actual.to_str();
         ham::expect(actual == self.expected, actual)
     }
index c005a1e8e803b7652db79b038886d0f0232e6add..f3a03a3c8abc3a1bdec0e1bcb4388fe21bf4a708 100644 (file)
@@ -45,7 +45,7 @@ test!(cargo_compile_with_invalid_manifest {
     assert_that(p.cargo_process("cargo-compile"),
         execs()
         .with_status(101)
-        .with_stderr("Cargo.toml is not a valid manifest"));
+        .with_stderr("Cargo.toml is not a valid manifest\n"));
 })
 
 test!(cargo_compile_without_manifest {
@@ -55,7 +55,7 @@ test!(cargo_compile_without_manifest {
         execs()
         .with_status(102)
         .with_stderr("Could not find Cargo.toml in this directory or any \
-                      parent directory"));
+                      parent directory\n"));
 })
 
 test!(cargo_compile_with_invalid_code {
@@ -73,7 +73,7 @@ src/foo.rs:1:1: 1:8 error: expected item but found `invalid`
 src/foo.rs:1 invalid rust code!
              ^~~~~~~
 Could not execute process \
-`rustc src/foo.rs --crate-type bin --out-dir {} -L {} -L {}` (status=101)",
+`rustc src/foo.rs --crate-type bin --out-dir {} -L {} -L {}` (status=101)\n",
             target.display(),
             target.display(),
             target.join("deps").display()).as_slice()));
@@ -386,6 +386,7 @@ test!(custom_build_failure {
 Could not execute process `{}` (status=101)
 --- stderr
 task '<main>' failed at 'nope', src/foo.rs:2
+
 ", build.root().join("target/foo").display())));
 })
 
index 4450055bc1a5070f7b0bc981c98f73d89fba7110..6a83dd7aa00de39f2300981a1eb0c75178553d8c 100644 (file)
@@ -1,6 +1,6 @@
 use support::{ResultTest,Tap,shell_writes};
 use hamcrest::{assert_that};
-use std::io::{MemWriter,IoResult};
+use std::io::{MemWriter, BufWriter, IoResult};
 use std::str::from_utf8_lossy;
 use cargo::core::shell::{Shell,ShellConfig};
 use term::{Terminal,TerminfoTerminal,color};
@@ -8,27 +8,37 @@ use term::{Terminal,TerminfoTerminal,color};
 fn setup() {
 }
 
+fn writer(buf: &mut [u8]) -> Box<Writer> {
+    box BufWriter::new(buf) as Box<Writer>
+}
+
 test!(non_tty {
     let config = ShellConfig { color: true, verbose: true, tty: false };
-    Shell::create(MemWriter::new(), config).assert().tap(|shell| {
+    let mut buf: Vec<u8> = Vec::from_elem(9, 0 as u8);
+
+    Shell::create(writer(buf.as_mut_slice()), config).tap(|shell| {
         shell.say("Hey Alex", color::RED).assert();
-        assert_that(shell, shell_writes("Hey Alex\n"));
+        assert_that(buf.as_slice(), shell_writes("Hey Alex\n"));
     });
 })
 
 test!(color_explicitly_disabled {
     let config = ShellConfig { color: false, verbose: true, tty: true };
-    Shell::create(MemWriter::new(), config).assert().tap(|shell| {
+    let mut buf: Vec<u8> = Vec::from_elem(9, 0 as u8);
+
+    Shell::create(writer(buf.as_mut_slice()), config).tap(|shell| {
         shell.say("Hey Alex", color::RED).assert();
-        assert_that(shell, shell_writes("Hey Alex\n"));
+        assert_that(buf.as_slice(), shell_writes("Hey Alex\n"));
     });
 })
 
 test!(colored_shell {
     let config = ShellConfig { color: true, verbose: true, tty: true };
-    Shell::create(MemWriter::new(), config).assert().tap(|shell| {
+    let mut buf: Vec<u8> = Vec::from_elem(22, 0 as u8);
+
+    Shell::create(writer(buf.as_mut_slice()), config).tap(|shell| {
         shell.say("Hey Alex", color::RED).assert();
-        assert_that(shell, shell_writes(colored_output("Hey Alex\n",
+        assert_that(buf.as_slice(), shell_writes(colored_output("Hey Alex\n",
                                                        color::RED).assert()));
     });
 })