From 4d20ffeac4013d533c43f3bc01f63070304f860b Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Mon, 7 Nov 2016 21:01:27 -0800 Subject: [PATCH] Use colors in output This is a pretty gross commit, since it also includes a lot of unrelated refactoring, especially of how error messages are printed. Also adds a lint recipe that prints lines over 100 characters To test, I added a `--color=[auto|always|never]` option that defaults to auto in normal use, but can be forced to `always` for testing. In `auto` mode it defers to `atty` to figure out if the current stream is a terminal and uses color if so. Color printing is controlled by the `alternate` formatting flag. When printing an error message, using `{:#}` will print it with colors and `{}` will print it normally. --- Cargo.lock | 13 +++ Cargo.toml | 2 + justfile | 6 ++ notes | 2 +- src/app.rs | 50 ++++++++++- src/integration.rs | 58 +++++++++---- src/lib.rs | 205 ++++++++++++++++++++++++++++++--------------- 7 files changed, 250 insertions(+), 86 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b33dab4..4d241374 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,8 @@ name = "just" version = "0.2.16" dependencies = [ + "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "brev 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.17.1 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -23,6 +25,16 @@ name = "ansi_term" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "atty" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bitflags" version = "0.7.0" @@ -195,6 +207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" +"checksum atty 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b4cb091c727ebec026331c1b7092981c9cdde34d8df109fa36f29a37532026" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum brev 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "79571b60a8aa293f43b46370d8ba96fed28a5bee1303ea0e015d175ed0c63b40" "checksum clap 2.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "27dac76762fb56019b04aed3ccb43a770a18f80f9c2eb62ee1a18d9fb4ea2430" diff --git a/Cargo.toml b/Cargo.toml index af29d31e..8b25d667 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,8 @@ license = "WTFPL/MIT/Apache-2.0" homepage = "https://github.com/casey/just" [dependencies] +ansi_term = "^0.9.0" +atty = "^0.2.1" brev = "^0.1.6" clap = "^2.0.0" itertools = "^0.5.5" diff --git a/justfile b/justfile index cba2dabb..da398684 100644 --- a/justfile +++ b/justfile @@ -42,8 +42,14 @@ install-nightly: sloc: @cat src/*.rs | wc -l +long: + ! grep --color -n '.\{100\}' src/*.rs + nop: +fail: + exit 1 + # make a quine, compile it, and verify it quine: create cc tmp/gen0.c -o tmp/gen0 diff --git a/notes b/notes index ff8bb299..cffdfee0 100644 --- a/notes +++ b/notes @@ -1,2 +1,2 @@ todo ----- +==== diff --git a/src/app.rs b/src/app.rs index 31b56bd7..73d3d87e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,5 +1,6 @@ extern crate clap; extern crate regex; +extern crate atty; use std::{io, fs, env, process}; use std::collections::BTreeMap; @@ -21,6 +22,32 @@ macro_rules! die { }}; } +#[derive(Copy, Clone)] +enum UseColor { + Auto, + Always, + Never, +} + +impl UseColor { + fn from_argument(use_color: &str) -> UseColor { + match use_color { + "auto" => UseColor::Auto, + "always" => UseColor::Always, + "never" => UseColor::Never, + _ => panic!("Invalid argument to --color. This is a bug in just."), + } + } + + fn should_color_stream(self, stream: atty::Stream) -> bool { + match self { + UseColor::Auto => atty::is(stream), + UseColor::Always => true, + UseColor::Never => true, + } + } +} + pub fn app() { let matches = App::new("just") .version(concat!("v", env!("CARGO_PKG_VERSION"))) @@ -41,6 +68,12 @@ pub fn app() { .arg(Arg::with_name("evaluate") .long("evaluate") .help("Print evaluated variables")) + .arg(Arg::with_name("color") + .long("color") + .takes_value(true) + .possible_values(&["auto", "always", "never"]) + .default_value("auto") + .help("Print colorful output")) .arg(Arg::with_name("show") .short("s") .long("show") @@ -79,6 +112,9 @@ pub fn app() { die!("--dry-run and --quiet may not be used together"); } + let use_color_argument = matches.value_of("color").expect("--color had no value"); + let use_color = UseColor::from_argument(use_color_argument); + let justfile_option = matches.value_of("justfile"); let working_directory_option = matches.value_of("working-directory"); @@ -125,7 +161,13 @@ pub fn app() { .unwrap_or_else(|error| die!("Error reading justfile: {}", error)); } - let justfile = super::parse(&text).unwrap_or_else(|error| die!("{}", error)); + let justfile = super::parse(&text).unwrap_or_else(|error| + if use_color.should_color_stream(atty::Stream::Stderr) { + die!("{:#}", error); + } else { + die!("{}", error); + } + ); if matches.is_present("list") { if justfile.count() == 0 { @@ -185,7 +227,11 @@ pub fn app() { if let Err(run_error) = justfile.run(&arguments, &options) { if !options.quiet { - warn!("{}", run_error); + if use_color.should_color_stream(atty::Stream::Stderr) { + warn!("{:#}", run_error); + } else { + warn!("{}", run_error); + } } match run_error { RunError::Code{code, .. } | RunError::BacktickCode{code, ..} => process::exit(code), diff --git a/src/integration.rs b/src/integration.rs index 283872af..8844867f 100644 --- a/src/integration.rs +++ b/src/integration.rs @@ -13,7 +13,8 @@ fn integration_test( expected_stderr: &str, ) { let tmp = TempDir::new("just-integration") - .unwrap_or_else(|err| panic!("integration test: failed to create temporary directory: {}", err)); + .unwrap_or_else( + |err| panic!("integration test: failed to create temporary directory: {}", err)); let mut path = tmp.path().to_path_buf(); path.push("justfile"); brev::dump(path, justfile); @@ -300,7 +301,7 @@ recipe: text, 100, "", - "Recipe \"recipe\" failed with exit code 100\n", + "error: Recipe `recipe` failed with exit code 100\n", ); } @@ -348,7 +349,7 @@ fn backtick_code_assignment() { "b = a\na = `exit 100`\nbar:\n echo '{{`exit 200`}}'", 100, "", - "backtick failed with exit code 100 + "error: backtick failed with exit code 100 | 2 | a = `exit 100` | ^^^^^^^^^^ @@ -363,7 +364,7 @@ fn backtick_code_interpolation() { "b = a\na = `echo hello`\nbar:\n echo '{{`exit 200`}}'", 200, "", - "backtick failed with exit code 200 + "error: backtick failed with exit code 200 | 4 | echo '{{`exit 200`}}' | ^^^^^^^^^^ @@ -378,7 +379,7 @@ fn backtick_code_long() { "\n\n\n\n\n\nb = a\na = `echo hello`\nbar:\n echo '{{`exit 200`}}'", 200, "", - "backtick failed with exit code 200 + "error: backtick failed with exit code 200 | 10 | echo '{{`exit 200`}}' | ^^^^^^^^^^ @@ -396,7 +397,7 @@ fn shebang_backtick_failure() { echo {{`exit 123`}}", 123, "", - "backtick failed with exit code 123 + "error: backtick failed with exit code 123 | 4 | echo {{`exit 123`}} | ^^^^^^^^^^ @@ -413,7 +414,7 @@ fn command_backtick_failure() { echo {{`exit 123`}}", 123, "hello\n", - "echo hello\nbacktick failed with exit code 123 + "echo hello\nerror: backtick failed with exit code 123 | 3 | echo {{`exit 123`}} | ^^^^^^^^^^ @@ -431,7 +432,7 @@ fn assignment_backtick_failure() { a = `exit 222`", 222, "", - "backtick failed with exit code 222 + "error: backtick failed with exit code 222 | 4 | a = `exit 222` | ^^^^^^^^^^ @@ -449,7 +450,7 @@ fn unknown_override_options() { a = `exit 222`", 255, "", - "Variables `baz` and `foo` overridden on the command line but not present in justfile\n", + "error: Variables `baz` and `foo` overridden on the command line but not present in justfile\n", ); } @@ -463,7 +464,7 @@ fn unknown_override_args() { a = `exit 222`", 255, "", - "Variables `baz` and `foo` overridden on the command line but not present in justfile\n", + "error: Variables `baz` and `foo` overridden on the command line but not present in justfile\n", ); } @@ -477,7 +478,7 @@ fn unknown_override_arg() { a = `exit 222`", 255, "", - "Variable `foo` overridden on the command line but not present in justfile\n", + "error: Variable `foo` overridden on the command line but not present in justfile\n", ); } @@ -786,10 +787,9 @@ foo A B: ", 255, "", - "Recipe `foo` got 3 arguments but only takes 2\n" + "error: Recipe `foo` got 3 arguments but only takes 2\n", ); } - #[test] fn argument_mismatch_fewer() { integration_test( @@ -800,7 +800,7 @@ foo A B: ", 255, "", - "Recipe `foo` got 1 argument but takes 2\n" + "error: Recipe `foo` got 1 argument but takes 2\n" ); } @@ -811,7 +811,7 @@ fn unknown_recipe() { "hello:", 255, "", - "Justfile does not contain recipe `foo`\n", + "error: Justfile does not contain recipe `foo`\n", ); } @@ -822,6 +822,32 @@ fn unknown_recipes() { "hello:", 255, "", - "Justfile does not contain recipes `foo` or `bar`\n", + "error: Justfile does not contain recipes `foo` or `bar`\n", ); } + +#[test] +fn colors_with_context() { + integration_test( + &["--color", "always"], + "b = a\na = `exit 100`\nbar:\n echo '{{`exit 200`}}'", + 100, + "", + "\u{1b}[1;31merror:\u{1b}[0m \u{1b}[1mbacktick failed with exit code 100\n\u{1b}[0m |\n2 | a = `exit 100`\n | \u{1b}[1;31m^^^^^^^^^^\u{1b}[0m\n", + ); +} + +#[test] +fn colors_no_context() { + let text =" +recipe: + @exit 100"; + integration_test( + &["--color=always"], + text, + 100, + "", + "\u{1b}[1;31merror:\u{1b}[0m \u{1b}[1mRecipe `recipe` failed with exit code 100\u{1b}[0m\n", + ); +} + diff --git a/src/lib.rs b/src/lib.rs index f218d208..34c297ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ extern crate lazy_static; extern crate regex; extern crate tempdir; extern crate itertools; +extern crate ansi_term; use std::io::prelude::*; @@ -136,17 +137,23 @@ fn error_from_signal(recipe: &str, exit_status: process::ExitStatus) -> RunError } #[cfg(unix)] -fn backtick_error_from_signal(exit_status: process::ExitStatus) -> RunError<'static> { +fn backtick_error_from_signal<'a>( + token: &Token<'a>, + exit_status: process::ExitStatus +) -> RunError<'a> { use std::os::unix::process::ExitStatusExt; match exit_status.signal() { - Some(signal) => RunError::BacktickSignal{signal: signal}, - None => RunError::BacktickUnknownFailure, + Some(signal) => RunError::BacktickSignal{token: token.clone(), signal: signal}, + None => RunError::BacktickUnknownFailure{token: token.clone()}, } } #[cfg(windows)] -fn backtick_error_from_signal(exit_status: process::ExitStatus) -> RunError<'static> { - RunError::BacktickUnknownFailure +fn backtick_error_from_signal<'a>( + token: &Token<'a>, + exit_status: process::ExitStatus +) -> RunError<'a> { + RunError::BacktickUnknownFailure{token: token.clone()} } fn export_env<'a>( @@ -197,10 +204,10 @@ fn run_backtick<'a>( }); } } else { - return Err(backtick_error_from_signal(output.status)); + return Err(backtick_error_from_signal(token, output.status)); } match std::str::from_utf8(&output.stdout) { - Err(error) => Err(RunError::BacktickUtf8Error{utf8_error: error}), + Err(error) => Err(RunError::BacktickUtf8Error{token: token.clone(), utf8_error: error}), Ok(utf8) => { Ok(if utf8.ends_with('\n') { &utf8[0..utf8.len()-1] @@ -212,7 +219,7 @@ fn run_backtick<'a>( } } } - Err(error) => Err(RunError::BacktickIoError{io_error: error}), + Err(error) => Err(RunError::BacktickIoError{token: token.clone(), io_error: error}), } } @@ -289,7 +296,8 @@ impl<'a> Recipe<'a> { // make the script executable let current_mode = perms.mode(); perms.set_mode(current_mode | 0o100); - try!(fs::set_permissions(&path, perms).map_err(|error| RunError::TmpdirIoError{recipe: self.name, io_error: error})); + try!(fs::set_permissions(&path, perms) + .map_err(|error| RunError::TmpdirIoError{recipe: self.name, io_error: error})); // run it! let mut command = process::Command::new(path); @@ -373,7 +381,8 @@ impl<'a> Display for Recipe<'a> { } match *piece { Fragment::Text{ref text} => try!(write!(f, "{}", text.lexeme)), - Fragment::Expression{ref expression, ..} => try!(write!(f, "{}{}{}", "{{", expression, "}}")), + Fragment::Expression{ref expression, ..} => + try!(write!(f, "{}{}{}", "{{", expression, "}}")), } } if i + 1 < self.lines.len() { @@ -765,27 +774,87 @@ fn conjoin( Ok(()) } +fn write_error_context( + f: &mut fmt::Formatter, + text: &str, + index: usize, + line: usize, + column: usize, + width: Option, +) -> Result<(), fmt::Error> { + let line_number = line + 1; + let red = maybe_red(f.alternate()); + match text.lines().nth(line) { + Some(line) => { + let line_number_width = line_number.to_string().len(); + try!(write!(f, "{0:1$} |\n", "", line_number_width)); + try!(write!(f, "{} | {}\n", line_number, line)); + try!(write!(f, "{0:1$} |", "", line_number_width)); + try!(write!(f, " {0:1$}{2}{3:^<4$}{5}", "", column, + red.prefix(), "", width.unwrap_or(1), red.suffix())); + }, + None => if index != text.len() { + try!(write!(f, "internal error: Error has invalid line number: {}", line_number)) + }, + } + Ok(()) +} + +fn write_token_error_context(f: &mut fmt::Formatter, token: &Token) -> Result<(), fmt::Error> { + write_error_context( + f, + token.text, + token.index, + token.line, + token.column + token.prefix.len(), + Some(token.lexeme.len()) + ) +} + +fn maybe_red(colors: bool) -> ansi_term::Style { + if colors { + ansi_term::Style::new().fg(ansi_term::Color::Red).bold() + } else { + ansi_term::Style::default() + } +} + +fn maybe_bold(colors: bool) -> ansi_term::Style { + if colors { + ansi_term::Style::new().bold() + } else { + ansi_term::Style::default() + } +} + impl<'a> Display for Error<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - try!(write!(f, "error: ")); + let red = maybe_red(f.alternate()); + let bold = maybe_bold(f.alternate()); + + try!(write!(f, "{} {}", red.paint("error:"), bold.prefix())); match self.kind { ErrorKind::CircularRecipeDependency{recipe, ref circle} => { if circle.len() == 2 { try!(write!(f, "recipe `{}` depends on itself", recipe)); } else { - try!(writeln!(f, "recipe `{}` has circular dependency `{}`", recipe, circle.join(" -> "))); + try!(writeln!(f, "recipe `{}` has circular dependency `{}`", + recipe, circle.join(" -> "))); } } ErrorKind::CircularVariableDependency{variable, ref circle} => { if circle.len() == 2 { - try!(writeln!(f, "variable `{}` depends on its own value: `{}`", variable, circle.join(" -> "))); + try!(writeln!(f, "variable `{}` depends on its own value: `{}`", + variable, circle.join(" -> "))); } else { - try!(writeln!(f, "variable `{}` depends on its own value: `{}`", variable, circle.join(" -> "))); + try!(writeln!(f, "variable `{}` depends on its own value: `{}`", + variable, circle.join(" -> "))); } } ErrorKind::InvalidEscapeSequence{character} => { - try!(writeln!(f, "`\\{}` is not a valid escape sequence", character.escape_default().collect::())); + try!(writeln!(f, "`\\{}` is not a valid escape sequence", + character.escape_default().collect::())); } ErrorKind::DuplicateParameter{recipe, parameter} => { try!(writeln!(f, "recipe `{}` has duplicate parameter `{}`", recipe, parameter)); @@ -804,14 +873,16 @@ impl<'a> Display for Error<'a> { recipe, first, self.line)); } ErrorKind::DependencyHasParameters{recipe, dependency} => { - try!(writeln!(f, "recipe `{}` depends on `{}` which requires arguments. dependencies may not require arguments", recipe, dependency)); + try!(writeln!(f, "recipe `{}` depends on `{}` which requires arguments. \ + dependencies may not require arguments", recipe, dependency)); } ErrorKind::ParameterShadowsVariable{parameter} => { try!(writeln!(f, "parameter `{}` shadows variable of the same name", parameter)); } ErrorKind::MixedLeadingWhitespace{whitespace} => { try!(writeln!(f, - "found a mix of tabs and spaces in leading whitespace: `{}`\n leading whitespace may consist of tabs or spaces, but not both", + "found a mix of tabs and spaces in leading whitespace: `{}`\n\ + leading whitespace may consist of tabs or spaces, but not both", show_whitespace(whitespace) )); } @@ -840,23 +911,15 @@ impl<'a> Display for Error<'a> { try!(writeln!(f, "unterminated string")); } ErrorKind::InternalError{ref message} => { - try!(writeln!(f, "internal error, this may indicate a bug in just: {}\n consider filing an issue: https://github.com/casey/just/issues/new", message)); + try!(writeln!(f, "internal error, this may indicate a bug in just: {}\n\ + consider filing an issue: https://github.com/casey/just/issues/new", + message)); } } - match self.text.lines().nth(self.line) { - Some(line) => { - let displayed_line = self.line + 1; - let line_number_width = displayed_line.to_string().len(); - try!(write!(f, "{0:1$} |\n", "", line_number_width)); - try!(write!(f, "{} | {}\n", displayed_line, line)); - try!(write!(f, "{0:1$} |", "", line_number_width)); - try!(write!(f, " {0:1$}{2:^<3$}", "", self.column, "", self.width.unwrap_or(1))); - }, - None => if self.index != self.text.len() { - try!(write!(f, "internal error: Error has invalid line number: {}", self.line + 1)) - }, - }; + try!(write!(f, "{}", bold.suffix())); + + try!(write_error_context(f, self.text, self.index, self.line, self.column, self.width)); Ok(()) } @@ -1018,15 +1081,18 @@ enum RunError<'a> { UnknownFailure{recipe: &'a str}, UnknownRecipes{recipes: Vec<&'a str>}, UnknownOverrides{overrides: Vec<&'a str>}, - BacktickCode{code: i32, token: Token<'a>}, - BacktickIoError{io_error: io::Error}, - BacktickSignal{signal: i32}, - BacktickUtf8Error{utf8_error: std::str::Utf8Error}, - BacktickUnknownFailure, + BacktickCode{token: Token<'a>, code: i32}, + BacktickIoError{token: Token<'a>, io_error: io::Error}, + BacktickSignal{token: Token<'a>, signal: i32}, + BacktickUtf8Error{token: Token<'a>, utf8_error: std::str::Utf8Error}, + BacktickUnknownFailure{token: Token<'a>}, } impl<'a> Display for RunError<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + let red = maybe_red(f.alternate()); + try!(write!(f, "{} ", red.paint("error:"))); + match *self { RunError::UnknownRecipes{ref recipes} => { try!(write!(f, "Justfile does not contain recipe{} {}", @@ -1039,7 +1105,8 @@ impl<'a> Display for RunError<'a> { And(&overrides.iter().map(Tick).collect::>()))) }, RunError::NonLeadingRecipeWithParameters{recipe} => { - try!(write!(f, "Recipe `{}` takes arguments and so must be the first and only recipe specified on the command line", recipe)); + try!(write!(f, "Recipe `{}` takes arguments and so must be the first and only recipe \ + specified on the command line", recipe)); }, RunError::ArgumentCountMismatch{recipe, found, expected} => { try!(write!(f, "Recipe `{}` got {} argument{} but {}takes {}", @@ -1047,58 +1114,61 @@ impl<'a> Display for RunError<'a> { if expected < found { "only " } else { "" }, expected)); }, RunError::Code{recipe, code} => { - try!(write!(f, "Recipe \"{}\" failed with exit code {}", recipe, code)); + try!(write!(f, "Recipe `{}` failed with exit code {}", recipe, code)); }, RunError::Signal{recipe, signal} => { - try!(write!(f, "Recipe \"{}\" wast terminated by signal {}", recipe, signal)); + try!(write!(f, "Recipe `{}` wast terminated by signal {}", recipe, signal)); } RunError::UnknownFailure{recipe} => { - try!(write!(f, "Recipe \"{}\" failed for an unknown reason", recipe)); + try!(write!(f, "Recipe `{}` failed for an unknown reason", recipe)); }, RunError::IoError{recipe, ref io_error} => { try!(match io_error.kind() { - io::ErrorKind::NotFound => write!(f, "Recipe \"{}\" could not be run because just could not find `sh` the command:\n{}", recipe, io_error), - io::ErrorKind::PermissionDenied => write!(f, "Recipe \"{}\" could not be run because just could not run `sh`:\n{}", recipe, io_error), - _ => write!(f, "Recipe \"{}\" could not be run because of an IO error while launching `sh`:\n{}", recipe, io_error), + io::ErrorKind::NotFound => write!(f, + "Recipe `{}` could not be run because just could not find `sh` the command:\n{}", + recipe, io_error), + io::ErrorKind::PermissionDenied => write!( + f, "Recipe `{}` could not be run because just could not run `sh`:\n{}", + recipe, io_error), + _ => write!(f, "Recipe `{}` could not be run because of an IO error while \ + launching `sh`:\n{}", recipe, io_error), }); }, RunError::TmpdirIoError{recipe, ref io_error} => - try!(write!(f, "Recipe \"{}\" could not be run because of an IO error while trying to create a temporary directory or write a file to that directory`:\n{}", recipe, io_error)), + try!(write!(f, "Recipe `{}` could not be run because of an IO error while trying \ + to create a temporary directory or write a file to that directory`:\n{}", + recipe, io_error)), RunError::BacktickCode{code, ref token} => { try!(write!(f, "backtick failed with exit code {}\n", code)); - match token.text.lines().nth(token.line) { - Some(line) => { - let displayed_line = token.line + 1; - let line_number_width = displayed_line.to_string().len(); - try!(write!(f, "{0:1$} |\n", "", line_number_width)); - try!(write!(f, "{} | {}\n", displayed_line, line)); - try!(write!(f, "{0:1$} |", "", line_number_width)); - try!(write!(f, " {0:1$}{2:^<3$}", "", - token.column + token.prefix.len(), "", token.lexeme.len())); - }, - None => if token.index != token.text.len() { - try!(write!(f, "internal error: Error has invalid line number: {}", token.line + 1)) - }, - } + try!(write_token_error_context(f, token)); } - RunError::BacktickSignal{signal} => { + RunError::BacktickSignal{ref token, signal} => { try!(write!(f, "backtick was terminated by signal {}", signal)); + try!(write_token_error_context(f, token)); } - RunError::BacktickUnknownFailure => { + RunError::BacktickUnknownFailure{ref token} => { try!(write!(f, "backtick failed for an uknown reason")); + try!(write_token_error_context(f, token)); } - RunError::BacktickIoError{ref io_error} => { + RunError::BacktickIoError{ref token, ref io_error} => { try!(match io_error.kind() { - io::ErrorKind::NotFound => write!(f, "backtick could not be run because just could not find `sh` the command:\n{}", io_error), - io::ErrorKind::PermissionDenied => write!(f, "backtick could not be run because just could not run `sh`:\n{}", io_error), - _ => write!(f, "backtick could not be run because of an IO error while launching `sh`:\n{}", io_error), + io::ErrorKind::NotFound => write!( + f, "backtick could not be run because just could not find `sh` the command:\n{}", + io_error), + io::ErrorKind::PermissionDenied => write!( + f, "backtick could not be run because just could not run `sh`:\n{}", io_error), + _ => write!(f, "backtick could not be run because of an IO \ + error while launching `sh`:\n{}", io_error), }); + try!(write_token_error_context(f, token)); } - RunError::BacktickUtf8Error{ref utf8_error} => { + RunError::BacktickUtf8Error{ref token, ref utf8_error} => { try!(write!(f, "backtick succeeded but stdout was not utf8: {}", utf8_error)); + try!(write_token_error_context(f, token)); } RunError::InternalError{ref message} => { - try!(write!(f, "internal error, this may indicate a bug in just: {}\n consider filing an issue: https://github.com/casey/just/issues/new", message)); + try!(write!(f, "internal error, this may indicate a bug in just: {} +consider filing an issue: https://github.com/casey/just/issues/new", message)); } } @@ -1303,7 +1373,8 @@ fn tokenize(text: &str) -> Result, Error> { } let (prefix, lexeme, kind) = - if let (0, &State::Indent(indent), Some(captures)) = (column, state.last().unwrap(), LINE.captures(rest)) { + if let (0, &State::Indent(indent), Some(captures)) = + (column, state.last().unwrap(), LINE.captures(rest)) { let line = captures.at(0).unwrap(); if !line.starts_with(indent) { return error!(ErrorKind::InternalError{message: "unexpected indent".to_string()});