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"
|
name = "just"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
dependencies = [
|
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)",
|
"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)",
|
"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)",
|
"itertools 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -23,6 +25,16 @@ name = "ansi_term"
|
|||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@ -195,6 +207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
[metadata]
|
[metadata]
|
||||||
"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
|
"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 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 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 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"
|
"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"
|
homepage = "https://github.com/casey/just"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ansi_term = "^0.9.0"
|
||||||
|
atty = "^0.2.1"
|
||||||
brev = "^0.1.6"
|
brev = "^0.1.6"
|
||||||
clap = "^2.0.0"
|
clap = "^2.0.0"
|
||||||
itertools = "^0.5.5"
|
itertools = "^0.5.5"
|
||||||
|
6
justfile
6
justfile
@ -42,8 +42,14 @@ install-nightly:
|
|||||||
sloc:
|
sloc:
|
||||||
@cat src/*.rs | wc -l
|
@cat src/*.rs | wc -l
|
||||||
|
|
||||||
|
long:
|
||||||
|
! grep --color -n '.\{100\}' src/*.rs
|
||||||
|
|
||||||
nop:
|
nop:
|
||||||
|
|
||||||
|
fail:
|
||||||
|
exit 1
|
||||||
|
|
||||||
# make a quine, compile it, and verify it
|
# make a quine, compile it, and verify it
|
||||||
quine: create
|
quine: create
|
||||||
cc tmp/gen0.c -o tmp/gen0
|
cc tmp/gen0.c -o tmp/gen0
|
||||||
|
48
src/app.rs
48
src/app.rs
@ -1,5 +1,6 @@
|
|||||||
extern crate clap;
|
extern crate clap;
|
||||||
extern crate regex;
|
extern crate regex;
|
||||||
|
extern crate atty;
|
||||||
|
|
||||||
use std::{io, fs, env, process};
|
use std::{io, fs, env, process};
|
||||||
use std::collections::BTreeMap;
|
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() {
|
pub fn app() {
|
||||||
let matches = App::new("just")
|
let matches = App::new("just")
|
||||||
.version(concat!("v", env!("CARGO_PKG_VERSION")))
|
.version(concat!("v", env!("CARGO_PKG_VERSION")))
|
||||||
@ -41,6 +68,12 @@ pub fn app() {
|
|||||||
.arg(Arg::with_name("evaluate")
|
.arg(Arg::with_name("evaluate")
|
||||||
.long("evaluate")
|
.long("evaluate")
|
||||||
.help("Print evaluated variables"))
|
.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")
|
.arg(Arg::with_name("show")
|
||||||
.short("s")
|
.short("s")
|
||||||
.long("show")
|
.long("show")
|
||||||
@ -79,6 +112,9 @@ pub fn app() {
|
|||||||
die!("--dry-run and --quiet may not be used together");
|
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 justfile_option = matches.value_of("justfile");
|
||||||
let working_directory_option = matches.value_of("working-directory");
|
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));
|
.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 matches.is_present("list") {
|
||||||
if justfile.count() == 0 {
|
if justfile.count() == 0 {
|
||||||
@ -185,8 +227,12 @@ pub fn app() {
|
|||||||
|
|
||||||
if let Err(run_error) = justfile.run(&arguments, &options) {
|
if let Err(run_error) = justfile.run(&arguments, &options) {
|
||||||
if !options.quiet {
|
if !options.quiet {
|
||||||
|
if use_color.should_color_stream(atty::Stream::Stderr) {
|
||||||
|
warn!("{:#}", run_error);
|
||||||
|
} else {
|
||||||
warn!("{}", run_error);
|
warn!("{}", run_error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
match run_error {
|
match run_error {
|
||||||
RunError::Code{code, .. } | RunError::BacktickCode{code, ..} => process::exit(code),
|
RunError::Code{code, .. } | RunError::BacktickCode{code, ..} => process::exit(code),
|
||||||
_ => process::exit(-1),
|
_ => process::exit(-1),
|
||||||
|
@ -13,7 +13,8 @@ fn integration_test(
|
|||||||
expected_stderr: &str,
|
expected_stderr: &str,
|
||||||
) {
|
) {
|
||||||
let tmp = TempDir::new("just-integration")
|
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();
|
let mut path = tmp.path().to_path_buf();
|
||||||
path.push("justfile");
|
path.push("justfile");
|
||||||
brev::dump(path, justfile);
|
brev::dump(path, justfile);
|
||||||
@ -300,7 +301,7 @@ recipe:
|
|||||||
text,
|
text,
|
||||||
100,
|
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`}}'",
|
"b = a\na = `exit 100`\nbar:\n echo '{{`exit 200`}}'",
|
||||||
100,
|
100,
|
||||||
"",
|
"",
|
||||||
"backtick failed with exit code 100
|
"error: backtick failed with exit code 100
|
||||||
|
|
|
|
||||||
2 | a = `exit 100`
|
2 | a = `exit 100`
|
||||||
| ^^^^^^^^^^
|
| ^^^^^^^^^^
|
||||||
@ -363,7 +364,7 @@ fn backtick_code_interpolation() {
|
|||||||
"b = a\na = `echo hello`\nbar:\n echo '{{`exit 200`}}'",
|
"b = a\na = `echo hello`\nbar:\n echo '{{`exit 200`}}'",
|
||||||
200,
|
200,
|
||||||
"",
|
"",
|
||||||
"backtick failed with exit code 200
|
"error: backtick failed with exit code 200
|
||||||
|
|
|
|
||||||
4 | echo '{{`exit 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`}}'",
|
"\n\n\n\n\n\nb = a\na = `echo hello`\nbar:\n echo '{{`exit 200`}}'",
|
||||||
200,
|
200,
|
||||||
"",
|
"",
|
||||||
"backtick failed with exit code 200
|
"error: backtick failed with exit code 200
|
||||||
|
|
|
|
||||||
10 | echo '{{`exit 200`}}'
|
10 | echo '{{`exit 200`}}'
|
||||||
| ^^^^^^^^^^
|
| ^^^^^^^^^^
|
||||||
@ -396,7 +397,7 @@ fn shebang_backtick_failure() {
|
|||||||
echo {{`exit 123`}}",
|
echo {{`exit 123`}}",
|
||||||
123,
|
123,
|
||||||
"",
|
"",
|
||||||
"backtick failed with exit code 123
|
"error: backtick failed with exit code 123
|
||||||
|
|
|
|
||||||
4 | echo {{`exit 123`}}
|
4 | echo {{`exit 123`}}
|
||||||
| ^^^^^^^^^^
|
| ^^^^^^^^^^
|
||||||
@ -413,7 +414,7 @@ fn command_backtick_failure() {
|
|||||||
echo {{`exit 123`}}",
|
echo {{`exit 123`}}",
|
||||||
123,
|
123,
|
||||||
"hello\n",
|
"hello\n",
|
||||||
"echo hello\nbacktick failed with exit code 123
|
"echo hello\nerror: backtick failed with exit code 123
|
||||||
|
|
|
|
||||||
3 | echo {{`exit 123`}}
|
3 | echo {{`exit 123`}}
|
||||||
| ^^^^^^^^^^
|
| ^^^^^^^^^^
|
||||||
@ -431,7 +432,7 @@ fn assignment_backtick_failure() {
|
|||||||
a = `exit 222`",
|
a = `exit 222`",
|
||||||
222,
|
222,
|
||||||
"",
|
"",
|
||||||
"backtick failed with exit code 222
|
"error: backtick failed with exit code 222
|
||||||
|
|
|
|
||||||
4 | a = `exit 222`
|
4 | a = `exit 222`
|
||||||
| ^^^^^^^^^^
|
| ^^^^^^^^^^
|
||||||
@ -449,7 +450,7 @@ fn unknown_override_options() {
|
|||||||
a = `exit 222`",
|
a = `exit 222`",
|
||||||
255,
|
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`",
|
a = `exit 222`",
|
||||||
255,
|
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`",
|
a = `exit 222`",
|
||||||
255,
|
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,
|
255,
|
||||||
"",
|
"",
|
||||||
"Recipe `foo` got 3 arguments but only takes 2\n"
|
"error: Recipe `foo` got 3 arguments but only takes 2\n",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn argument_mismatch_fewer() {
|
fn argument_mismatch_fewer() {
|
||||||
integration_test(
|
integration_test(
|
||||||
@ -800,7 +800,7 @@ foo A B:
|
|||||||
",
|
",
|
||||||
255,
|
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:",
|
"hello:",
|
||||||
255,
|
255,
|
||||||
"",
|
"",
|
||||||
"Justfile does not contain recipe `foo`\n",
|
"error: Justfile does not contain recipe `foo`\n",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -822,6 +822,32 @@ fn unknown_recipes() {
|
|||||||
"hello:",
|
"hello:",
|
||||||
255,
|
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 regex;
|
||||||
extern crate tempdir;
|
extern crate tempdir;
|
||||||
extern crate itertools;
|
extern crate itertools;
|
||||||
|
extern crate ansi_term;
|
||||||
|
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
|
||||||
@ -136,17 +137,23 @@ fn error_from_signal(recipe: &str, exit_status: process::ExitStatus) -> RunError
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[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;
|
use std::os::unix::process::ExitStatusExt;
|
||||||
match exit_status.signal() {
|
match exit_status.signal() {
|
||||||
Some(signal) => RunError::BacktickSignal{signal: signal},
|
Some(signal) => RunError::BacktickSignal{token: token.clone(), signal: signal},
|
||||||
None => RunError::BacktickUnknownFailure,
|
None => RunError::BacktickUnknownFailure{token: token.clone()},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn backtick_error_from_signal(exit_status: process::ExitStatus) -> RunError<'static> {
|
fn backtick_error_from_signal<'a>(
|
||||||
RunError::BacktickUnknownFailure
|
token: &Token<'a>,
|
||||||
|
exit_status: process::ExitStatus
|
||||||
|
) -> RunError<'a> {
|
||||||
|
RunError::BacktickUnknownFailure{token: token.clone()}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn export_env<'a>(
|
fn export_env<'a>(
|
||||||
@ -197,10 +204,10 @@ fn run_backtick<'a>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} 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) {
|
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(utf8) => {
|
||||||
Ok(if utf8.ends_with('\n') {
|
Ok(if utf8.ends_with('\n') {
|
||||||
&utf8[0..utf8.len()-1]
|
&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
|
// make the script executable
|
||||||
let current_mode = perms.mode();
|
let current_mode = perms.mode();
|
||||||
perms.set_mode(current_mode | 0o100);
|
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!
|
// run it!
|
||||||
let mut command = process::Command::new(path);
|
let mut command = process::Command::new(path);
|
||||||
@ -373,7 +381,8 @@ impl<'a> Display for Recipe<'a> {
|
|||||||
}
|
}
|
||||||
match *piece {
|
match *piece {
|
||||||
Fragment::Text{ref text} => try!(write!(f, "{}", text.lexeme)),
|
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() {
|
if i + 1 < self.lines.len() {
|
||||||
@ -765,27 +774,87 @@ fn conjoin<T: Display>(
|
|||||||
Ok(())
|
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> {
|
impl<'a> Display for Error<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
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 {
|
match self.kind {
|
||||||
ErrorKind::CircularRecipeDependency{recipe, ref circle} => {
|
ErrorKind::CircularRecipeDependency{recipe, ref circle} => {
|
||||||
if circle.len() == 2 {
|
if circle.len() == 2 {
|
||||||
try!(write!(f, "recipe `{}` depends on itself", recipe));
|
try!(write!(f, "recipe `{}` depends on itself", recipe));
|
||||||
} else {
|
} 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} => {
|
ErrorKind::CircularVariableDependency{variable, ref circle} => {
|
||||||
if circle.len() == 2 {
|
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 {
|
} 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} => {
|
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} => {
|
ErrorKind::DuplicateParameter{recipe, parameter} => {
|
||||||
try!(writeln!(f, "recipe `{}` has duplicate parameter `{}`", 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));
|
recipe, first, self.line));
|
||||||
}
|
}
|
||||||
ErrorKind::DependencyHasParameters{recipe, dependency} => {
|
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} => {
|
ErrorKind::ParameterShadowsVariable{parameter} => {
|
||||||
try!(writeln!(f, "parameter `{}` shadows variable of the same name", parameter));
|
try!(writeln!(f, "parameter `{}` shadows variable of the same name", parameter));
|
||||||
}
|
}
|
||||||
ErrorKind::MixedLeadingWhitespace{whitespace} => {
|
ErrorKind::MixedLeadingWhitespace{whitespace} => {
|
||||||
try!(writeln!(f,
|
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)
|
show_whitespace(whitespace)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -840,23 +911,15 @@ impl<'a> Display for Error<'a> {
|
|||||||
try!(writeln!(f, "unterminated string"));
|
try!(writeln!(f, "unterminated string"));
|
||||||
}
|
}
|
||||||
ErrorKind::InternalError{ref message} => {
|
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) {
|
try!(write!(f, "{}", bold.suffix()));
|
||||||
Some(line) => {
|
|
||||||
let displayed_line = self.line + 1;
|
try!(write_error_context(f, self.text, self.index, self.line, self.column, self.width));
|
||||||
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))
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -1018,15 +1081,18 @@ enum RunError<'a> {
|
|||||||
UnknownFailure{recipe: &'a str},
|
UnknownFailure{recipe: &'a str},
|
||||||
UnknownRecipes{recipes: Vec<&'a str>},
|
UnknownRecipes{recipes: Vec<&'a str>},
|
||||||
UnknownOverrides{overrides: Vec<&'a str>},
|
UnknownOverrides{overrides: Vec<&'a str>},
|
||||||
BacktickCode{code: i32, token: Token<'a>},
|
BacktickCode{token: Token<'a>, code: i32},
|
||||||
BacktickIoError{io_error: io::Error},
|
BacktickIoError{token: Token<'a>, io_error: io::Error},
|
||||||
BacktickSignal{signal: i32},
|
BacktickSignal{token: Token<'a>, signal: i32},
|
||||||
BacktickUtf8Error{utf8_error: std::str::Utf8Error},
|
BacktickUtf8Error{token: Token<'a>, utf8_error: std::str::Utf8Error},
|
||||||
BacktickUnknownFailure,
|
BacktickUnknownFailure{token: Token<'a>},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Display for RunError<'a> {
|
impl<'a> Display for RunError<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
let red = maybe_red(f.alternate());
|
||||||
|
try!(write!(f, "{} ", red.paint("error:")));
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
RunError::UnknownRecipes{ref recipes} => {
|
RunError::UnknownRecipes{ref recipes} => {
|
||||||
try!(write!(f, "Justfile does not contain recipe{} {}",
|
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<_>>())))
|
And(&overrides.iter().map(Tick).collect::<Vec<_>>())))
|
||||||
},
|
},
|
||||||
RunError::NonLeadingRecipeWithParameters{recipe} => {
|
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} => {
|
RunError::ArgumentCountMismatch{recipe, found, expected} => {
|
||||||
try!(write!(f, "Recipe `{}` got {} argument{} but {}takes {}",
|
try!(write!(f, "Recipe `{}` got {} argument{} but {}takes {}",
|
||||||
@ -1047,58 +1114,61 @@ impl<'a> Display for RunError<'a> {
|
|||||||
if expected < found { "only " } else { "" }, expected));
|
if expected < found { "only " } else { "" }, expected));
|
||||||
},
|
},
|
||||||
RunError::Code{recipe, code} => {
|
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} => {
|
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} => {
|
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} => {
|
RunError::IoError{recipe, ref io_error} => {
|
||||||
try!(match io_error.kind() {
|
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::NotFound => write!(f,
|
||||||
io::ErrorKind::PermissionDenied => write!(f, "Recipe \"{}\" could not be run because just could not run `sh`:\n{}", recipe, io_error),
|
"Recipe `{}` could not be run because just could not find `sh` the command:\n{}",
|
||||||
_ => write!(f, "Recipe \"{}\" could not be run because of an IO error while launching `sh`:\n{}", recipe, io_error),
|
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} =>
|
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} => {
|
RunError::BacktickCode{code, ref token} => {
|
||||||
try!(write!(f, "backtick failed with exit code {}\n", code));
|
try!(write!(f, "backtick failed with exit code {}\n", code));
|
||||||
match token.text.lines().nth(token.line) {
|
try!(write_token_error_context(f, token));
|
||||||
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))
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
RunError::BacktickSignal{ref token, signal} => {
|
||||||
RunError::BacktickSignal{signal} => {
|
|
||||||
try!(write!(f, "backtick was terminated by signal {}", 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!(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() {
|
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::NotFound => write!(
|
||||||
io::ErrorKind::PermissionDenied => write!(f, "backtick could not be run because just could not run `sh`:\n{}", io_error),
|
f, "backtick could not be run because just could not find `sh` the command:\n{}",
|
||||||
_ => write!(f, "backtick could not be run because of an IO error while launching `sh`:\n{}", io_error),
|
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!(f, "backtick succeeded but stdout was not utf8: {}", utf8_error));
|
||||||
|
try!(write_token_error_context(f, token));
|
||||||
}
|
}
|
||||||
RunError::InternalError{ref message} => {
|
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) =
|
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();
|
let line = captures.at(0).unwrap();
|
||||||
if !line.starts_with(indent) {
|
if !line.starts_with(indent) {
|
||||||
return error!(ErrorKind::InternalError{message: "unexpected indent".to_string()});
|
return error!(ErrorKind::InternalError{message: "unexpected indent".to_string()});
|
||||||
|
Loading…
Reference in New Issue
Block a user