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.
This commit is contained in:
parent
6b888bbfe4
commit
4d20ffeac4
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
6
justfile
6
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
|
||||
|
50
src/app.rs
50
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),
|
||||
|
@ -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",
|
||||
);
|
||||
}
|
||||
|
||||
|
205
src/lib.rs
205
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<T: Display>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_error_context(
|
||||
f: &mut fmt::Formatter,
|
||||
text: &str,
|
||||
index: usize,
|
||||
line: usize,
|
||||
column: usize,
|
||||
width: Option<usize>,
|
||||
) -> 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::<String>()));
|
||||
try!(writeln!(f, "`\\{}` is not a valid escape sequence",
|
||||
character.escape_default().collect::<String>()));
|
||||
}
|
||||
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::<Vec<_>>())))
|
||||
},
|
||||
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<Vec<Token>, 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()});
|
||||
|
Loading…
Reference in New Issue
Block a user