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
|
body : INDENT line+ DEDENT
|
||||||
|
|
||||||
line : LINE (TEXT | interpolation)+ eol
|
line : LINE (TEXT | interpolation)+ NEWLINE
|
||||||
|
| NEWLINE
|
||||||
|
|
||||||
interpolation : '{{' expression '}}'
|
interpolation : '{{' expression '}}'
|
||||||
```
|
```
|
||||||
|
5
justfile
5
justfile
@ -46,6 +46,11 @@ push GITHUB-BRANCH:
|
|||||||
git diff --no-ext-diff --quiet --exit-code
|
git diff --no-ext-diff --quiet --exit-code
|
||||||
git push github master:refs/heads/{{GITHUB-BRANCH}}
|
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 just from crates.io
|
||||||
install:
|
install:
|
||||||
cargo install -f just
|
cargo install -f just
|
||||||
|
@ -283,14 +283,17 @@ recipe:
|
|||||||
fn status_passthrough() {
|
fn status_passthrough() {
|
||||||
let text =
|
let text =
|
||||||
"
|
"
|
||||||
|
|
||||||
|
hello:
|
||||||
|
|
||||||
recipe:
|
recipe:
|
||||||
@exit 100";
|
@exit 100";
|
||||||
integration_test(
|
integration_test(
|
||||||
&[],
|
&["recipe"],
|
||||||
text,
|
text,
|
||||||
100,
|
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() {
|
fn colors_no_context() {
|
||||||
let text ="
|
let text ="
|
||||||
recipe:
|
recipe:
|
||||||
@exit 100";
|
@exit 100";
|
||||||
integration_test(
|
integration_test(
|
||||||
&["--color=always"],
|
&["--color=always"],
|
||||||
text,
|
text,
|
||||||
100,
|
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)]
|
#[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;
|
use std::os::unix::process::ExitStatusExt;
|
||||||
match exit_status.signal() {
|
match exit_status.signal() {
|
||||||
Some(signal) => RunError::Signal{recipe: recipe, signal: signal},
|
Some(signal) => RunError::Signal{recipe: recipe, line_number: line_number, signal: signal},
|
||||||
None => RunError::UnknownFailure{recipe: recipe},
|
None => RunError::UnknownFailure{recipe: recipe, line_number: line_number},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn error_from_signal(recipe: &str, exit_status: process::ExitStatus) -> RunError {
|
fn error_from_signal(
|
||||||
RunError::UnknownFailure{recipe: recipe}
|
recipe: &str,
|
||||||
|
line_number: Option<usize>,
|
||||||
|
exit_status: process::ExitStatus
|
||||||
|
) -> RunError {
|
||||||
|
RunError::UnknownFailure{recipe: recipe, line_number: line_number}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
@ -362,16 +370,17 @@ impl<'a> Recipe<'a> {
|
|||||||
match command.status() {
|
match command.status() {
|
||||||
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
||||||
if code != 0 {
|
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 {
|
} 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{
|
Err(io_error) => return Err(RunError::TmpdirIoError{
|
||||||
recipe: self.name, io_error: io_error})
|
recipe: self.name, io_error: io_error})
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
let mut lines = self.lines.iter().peekable();
|
let mut lines = self.lines.iter().peekable();
|
||||||
|
let mut line_number = self.line_number + 1;
|
||||||
loop {
|
loop {
|
||||||
if lines.peek().is_none() {
|
if lines.peek().is_none() {
|
||||||
break;
|
break;
|
||||||
@ -382,6 +391,7 @@ impl<'a> Recipe<'a> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let line = lines.next().unwrap();
|
let line = lines.next().unwrap();
|
||||||
|
line_number += 1;
|
||||||
evaluated += &evaluator.evaluate_line(line, &argument_map)?;
|
evaluated += &evaluator.evaluate_line(line, &argument_map)?;
|
||||||
if line.last().map(Fragment::continuation).unwrap_or(false) {
|
if line.last().map(Fragment::continuation).unwrap_or(false) {
|
||||||
evaluated.pop();
|
evaluated.pop();
|
||||||
@ -422,10 +432,12 @@ impl<'a> Recipe<'a> {
|
|||||||
match cmd.status() {
|
match cmd.status() {
|
||||||
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
||||||
if code != 0 {
|
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 {
|
} 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{
|
Err(io_error) => return Err(RunError::IoError{
|
||||||
recipe: self.name, io_error: io_error}),
|
recipe: self.name, io_error: io_error}),
|
||||||
@ -1263,13 +1275,13 @@ impl<'a> Display for Justfile<'a> {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum RunError<'a> {
|
enum RunError<'a> {
|
||||||
ArgumentCountMismatch{recipe: &'a str, found: usize, min: usize, max: usize},
|
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},
|
InternalError{message: String},
|
||||||
IoError{recipe: &'a str, io_error: io::Error},
|
IoError{recipe: &'a str, io_error: io::Error},
|
||||||
NonLeadingRecipeWithParameters{recipe: &'a str},
|
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},
|
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>},
|
UnknownRecipes{recipes: Vec<&'a str>, suggestion: Option<&'a str>},
|
||||||
UnknownOverrides{overrides: Vec<&'a str>},
|
UnknownOverrides{overrides: Vec<&'a str>},
|
||||||
BacktickCode{token: Token<'a>, code: i32},
|
BacktickCode{token: Token<'a>, code: i32},
|
||||||
@ -1319,14 +1331,25 @@ impl<'a> Display for RunError<'a> {
|
|||||||
recipe, found, maybe_s(found), max)?;
|
recipe, found, maybe_s(found), max)?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Code{recipe, code} => {
|
Code{recipe, line_number, code} => {
|
||||||
write!(f, "Recipe `{}` failed with exit code {}", recipe, 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} => {
|
||||||
write!(f, "Recipe `{}` was terminated by signal {}", recipe, 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} => {
|
UnknownFailure{recipe, line_number} => {
|
||||||
write!(f, "Recipe `{}` failed for an unknown reason", recipe)?;
|
if let Some(n) = line_number {
|
||||||
|
write!(f, "Recipe `{}` failed on line {} for an unknown reason", recipe, n)?;
|
||||||
|
} else {
|
||||||
|
}
|
||||||
},
|
},
|
||||||
IoError{recipe, ref io_error} => {
|
IoError{recipe, ref io_error} => {
|
||||||
match io_error.kind() {
|
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() {
|
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!(recipe, "a");
|
||||||
assert_eq!(code, 200);
|
assert_eq!(code, 200);
|
||||||
|
assert_eq!(line_number, None);
|
||||||
},
|
},
|
||||||
other => panic!("expected an code run error, but got: {}", other),
|
other => panic!("expected an code run error, but got: {}", other),
|
||||||
}
|
}
|
||||||
@ -874,9 +875,10 @@ a:
|
|||||||
fn code_error() {
|
fn code_error() {
|
||||||
match parse_success("fail:\n @exit 100")
|
match parse_success("fail:\n @exit 100")
|
||||||
.run(&["fail"], &Default::default()).unwrap_err() {
|
.run(&["fail"], &Default::default()).unwrap_err() {
|
||||||
RunError::Code{recipe, code} => {
|
RunError::Code{recipe, line_number, code} => {
|
||||||
assert_eq!(recipe, "fail");
|
assert_eq!(recipe, "fail");
|
||||||
assert_eq!(code, 100);
|
assert_eq!(code, 100);
|
||||||
|
assert_eq!(line_number, Some(2));
|
||||||
},
|
},
|
||||||
other => panic!("expected a code run error, but got: {}", other),
|
other => panic!("expected a code run error, but got: {}", other),
|
||||||
}
|
}
|
||||||
@ -889,9 +891,10 @@ a return code:
|
|||||||
@x() { {{return}} {{code + "0"}}; }; x"#;
|
@x() { {{return}} {{code + "0"}}; }; x"#;
|
||||||
|
|
||||||
match parse_success(text).run(&["a", "return", "15"], &Default::default()).unwrap_err() {
|
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!(recipe, "a");
|
||||||
assert_eq!(code, 150);
|
assert_eq!(code, 150);
|
||||||
|
assert_eq!(line_number, Some(3));
|
||||||
},
|
},
|
||||||
other => panic!("expected an code run error, but got: {}", other),
|
other => panic!("expected an code run error, but got: {}", other),
|
||||||
}
|
}
|
||||||
@ -1017,8 +1020,9 @@ wut:
|
|||||||
};
|
};
|
||||||
|
|
||||||
match parse_success(text).run(&["wut"], &options).unwrap_err() {
|
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!(recipe, "wut");
|
||||||
|
assert_eq!(line_number, Some(8));
|
||||||
},
|
},
|
||||||
other => panic!("expected a recipe code errror, but got: {}", other),
|
other => panic!("expected a recipe code errror, but got: {}", other),
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user