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:
Casey Rodarmor 2016-11-16 22:18:55 -08:00 committed by GitHub
parent 07634d9390
commit cef117f8bd
5 changed files with 65 additions and 28 deletions

View File

@ -61,7 +61,8 @@ dependencies : NAME+
body : INDENT line+ DEDENT
line : LINE (TEXT | interpolation)+ eol
line : LINE (TEXT | interpolation)+ NEWLINE
| NEWLINE
interpolation : '{{' expression '}}'
```

View File

@ -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

View File

@ -283,14 +283,17 @@ recipe:
fn status_passthrough() {
let text =
"
hello:
recipe:
@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",
);
}
@ -1028,7 +1031,8 @@ recipe:
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",
);
}

View File

@ -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} => {
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} => {
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() {

View File

@ -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),
}