Report line number in recipe failure messages (#125)
The grammar now permits blank lines in recipes. Note that inside of recipes, the token `NEWLINE` is used instead of the non-terminal `eol`. This is because an `eol` optionally includes a comment, whereas inside recipes bodies comments get no special treatment.
This commit is contained in:
parent
07634d9390
commit
cef117f8bd
@ -61,7 +61,8 @@ dependencies : NAME+
|
||||
|
||||
body : INDENT line+ DEDENT
|
||||
|
||||
line : LINE (TEXT | interpolation)+ eol
|
||||
line : LINE (TEXT | interpolation)+ NEWLINE
|
||||
| NEWLINE
|
||||
|
||||
interpolation : '{{' expression '}}'
|
||||
```
|
||||
|
5
justfile
5
justfile
@ -46,6 +46,11 @@ push GITHUB-BRANCH:
|
||||
git diff --no-ext-diff --quiet --exit-code
|
||||
git push github master:refs/heads/{{GITHUB-BRANCH}}
|
||||
|
||||
push-f GITHUB-BRANCH:
|
||||
git branch | grep '* master'
|
||||
git diff --no-ext-diff --quiet --exit-code
|
||||
git push github -f master:refs/heads/{{GITHUB-BRANCH}}
|
||||
|
||||
# install just from crates.io
|
||||
install:
|
||||
cargo install -f just
|
||||
|
@ -283,14 +283,17 @@ recipe:
|
||||
fn status_passthrough() {
|
||||
let text =
|
||||
"
|
||||
|
||||
hello:
|
||||
|
||||
recipe:
|
||||
@exit 100";
|
||||
@exit 100";
|
||||
integration_test(
|
||||
&[],
|
||||
&["recipe"],
|
||||
text,
|
||||
100,
|
||||
"",
|
||||
"error: Recipe `recipe` failed with exit code 100\n",
|
||||
"error: Recipe `recipe` failed on line 6 with exit code 100\n",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1022,13 +1025,14 @@ fn color_auto() {
|
||||
fn colors_no_context() {
|
||||
let text ="
|
||||
recipe:
|
||||
@exit 100";
|
||||
@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",
|
||||
"\u{1b}[1;31merror:\u{1b}[0m \u{1b}[1m\
|
||||
Recipe `recipe` failed on line 3 with exit code 100\u{1b}[0m\n",
|
||||
);
|
||||
}
|
||||
|
||||
|
59
src/lib.rs
59
src/lib.rs
@ -163,17 +163,25 @@ impl<'a> Display for Expression<'a> {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn error_from_signal(recipe: &str, exit_status: process::ExitStatus) -> RunError {
|
||||
fn error_from_signal(
|
||||
recipe: &str,
|
||||
line_number: Option<usize>,
|
||||
exit_status: process::ExitStatus
|
||||
) -> RunError {
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
match exit_status.signal() {
|
||||
Some(signal) => RunError::Signal{recipe: recipe, signal: signal},
|
||||
None => RunError::UnknownFailure{recipe: recipe},
|
||||
Some(signal) => RunError::Signal{recipe: recipe, line_number: line_number, signal: signal},
|
||||
None => RunError::UnknownFailure{recipe: recipe, line_number: line_number},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn error_from_signal(recipe: &str, exit_status: process::ExitStatus) -> RunError {
|
||||
RunError::UnknownFailure{recipe: recipe}
|
||||
fn error_from_signal(
|
||||
recipe: &str,
|
||||
line_number: Option<usize>,
|
||||
exit_status: process::ExitStatus
|
||||
) -> RunError {
|
||||
RunError::UnknownFailure{recipe: recipe, line_number: line_number}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
@ -362,16 +370,17 @@ impl<'a> Recipe<'a> {
|
||||
match command.status() {
|
||||
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
||||
if code != 0 {
|
||||
return Err(RunError::Code{recipe: self.name, code: code})
|
||||
return Err(RunError::Code{recipe: self.name, line_number: None, code: code})
|
||||
}
|
||||
} else {
|
||||
return Err(error_from_signal(self.name, exit_status))
|
||||
return Err(error_from_signal(self.name, None, exit_status))
|
||||
},
|
||||
Err(io_error) => return Err(RunError::TmpdirIoError{
|
||||
recipe: self.name, io_error: io_error})
|
||||
};
|
||||
} else {
|
||||
let mut lines = self.lines.iter().peekable();
|
||||
let mut line_number = self.line_number + 1;
|
||||
loop {
|
||||
if lines.peek().is_none() {
|
||||
break;
|
||||
@ -382,6 +391,7 @@ impl<'a> Recipe<'a> {
|
||||
break;
|
||||
}
|
||||
let line = lines.next().unwrap();
|
||||
line_number += 1;
|
||||
evaluated += &evaluator.evaluate_line(line, &argument_map)?;
|
||||
if line.last().map(Fragment::continuation).unwrap_or(false) {
|
||||
evaluated.pop();
|
||||
@ -422,10 +432,12 @@ impl<'a> Recipe<'a> {
|
||||
match cmd.status() {
|
||||
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
||||
if code != 0 {
|
||||
return Err(RunError::Code{recipe: self.name, code: code});
|
||||
return Err(RunError::Code{
|
||||
recipe: self.name, line_number: Some(line_number), code: code
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Err(error_from_signal(self.name, exit_status));
|
||||
return Err(error_from_signal(self.name, Some(line_number), exit_status));
|
||||
},
|
||||
Err(io_error) => return Err(RunError::IoError{
|
||||
recipe: self.name, io_error: io_error}),
|
||||
@ -1263,13 +1275,13 @@ impl<'a> Display for Justfile<'a> {
|
||||
#[derive(Debug)]
|
||||
enum RunError<'a> {
|
||||
ArgumentCountMismatch{recipe: &'a str, found: usize, min: usize, max: usize},
|
||||
Code{recipe: &'a str, code: i32},
|
||||
Code{recipe: &'a str, line_number: Option<usize>, code: i32},
|
||||
InternalError{message: String},
|
||||
IoError{recipe: &'a str, io_error: io::Error},
|
||||
NonLeadingRecipeWithParameters{recipe: &'a str},
|
||||
Signal{recipe: &'a str, signal: i32},
|
||||
Signal{recipe: &'a str, line_number: Option<usize>, signal: i32},
|
||||
TmpdirIoError{recipe: &'a str, io_error: io::Error},
|
||||
UnknownFailure{recipe: &'a str},
|
||||
UnknownFailure{recipe: &'a str, line_number: Option<usize>},
|
||||
UnknownRecipes{recipes: Vec<&'a str>, suggestion: Option<&'a str>},
|
||||
UnknownOverrides{overrides: Vec<&'a str>},
|
||||
BacktickCode{token: Token<'a>, code: i32},
|
||||
@ -1319,14 +1331,25 @@ impl<'a> Display for RunError<'a> {
|
||||
recipe, found, maybe_s(found), max)?;
|
||||
}
|
||||
},
|
||||
Code{recipe, code} => {
|
||||
write!(f, "Recipe `{}` failed with exit code {}", recipe, code)?;
|
||||
Code{recipe, line_number, code} => {
|
||||
if let Some(n) = line_number {
|
||||
write!(f, "Recipe `{}` failed on line {} with exit code {}", recipe, n, code)?;
|
||||
} else {
|
||||
write!(f, "Recipe `{}` failed with exit code {}", recipe, code)?;
|
||||
}
|
||||
},
|
||||
Signal{recipe, signal} => {
|
||||
write!(f, "Recipe `{}` was terminated by signal {}", recipe, signal)?;
|
||||
Signal{recipe, line_number, signal} => {
|
||||
if let Some(n) = line_number {
|
||||
write!(f, "Recipe `{}` was terminated on line {} by signal {}", recipe, n, signal)?;
|
||||
} else {
|
||||
write!(f, "Recipe `{}` was terminated by signal {}", recipe, signal)?;
|
||||
}
|
||||
}
|
||||
UnknownFailure{recipe} => {
|
||||
write!(f, "Recipe `{}` failed for an unknown reason", recipe)?;
|
||||
UnknownFailure{recipe, line_number} => {
|
||||
if let Some(n) = line_number {
|
||||
write!(f, "Recipe `{}` failed on line {} for an unknown reason", recipe, n)?;
|
||||
} else {
|
||||
}
|
||||
},
|
||||
IoError{recipe, ref io_error} => {
|
||||
match io_error.kind() {
|
||||
|
12
src/unit.rs
12
src/unit.rs
@ -862,9 +862,10 @@ a:
|
||||
";
|
||||
|
||||
match parse_success(text).run(&["a"], &Default::default()).unwrap_err() {
|
||||
RunError::Code{recipe, code} => {
|
||||
RunError::Code{recipe, line_number, code} => {
|
||||
assert_eq!(recipe, "a");
|
||||
assert_eq!(code, 200);
|
||||
assert_eq!(line_number, None);
|
||||
},
|
||||
other => panic!("expected an code run error, but got: {}", other),
|
||||
}
|
||||
@ -874,9 +875,10 @@ a:
|
||||
fn code_error() {
|
||||
match parse_success("fail:\n @exit 100")
|
||||
.run(&["fail"], &Default::default()).unwrap_err() {
|
||||
RunError::Code{recipe, code} => {
|
||||
RunError::Code{recipe, line_number, code} => {
|
||||
assert_eq!(recipe, "fail");
|
||||
assert_eq!(code, 100);
|
||||
assert_eq!(line_number, Some(2));
|
||||
},
|
||||
other => panic!("expected a code run error, but got: {}", other),
|
||||
}
|
||||
@ -889,9 +891,10 @@ a return code:
|
||||
@x() { {{return}} {{code + "0"}}; }; x"#;
|
||||
|
||||
match parse_success(text).run(&["a", "return", "15"], &Default::default()).unwrap_err() {
|
||||
RunError::Code{recipe, code} => {
|
||||
RunError::Code{recipe, line_number, code} => {
|
||||
assert_eq!(recipe, "a");
|
||||
assert_eq!(code, 150);
|
||||
assert_eq!(line_number, Some(3));
|
||||
},
|
||||
other => panic!("expected an code run error, but got: {}", other),
|
||||
}
|
||||
@ -1017,8 +1020,9 @@ wut:
|
||||
};
|
||||
|
||||
match parse_success(text).run(&["wut"], &options).unwrap_err() {
|
||||
RunError::Code{code: _, recipe} => {
|
||||
RunError::Code{code: _, line_number, recipe} => {
|
||||
assert_eq!(recipe, "wut");
|
||||
assert_eq!(line_number, Some(8));
|
||||
},
|
||||
other => panic!("expected a recipe code errror, but got: {}", other),
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user