From cef117f8bd649a090453ddb45531043763a06b1b Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 16 Nov 2016 22:18:55 -0800 Subject: [PATCH] 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. --- GRAMMAR.md | 3 ++- justfile | 5 ++++ src/integration.rs | 14 +++++++---- src/lib.rs | 59 ++++++++++++++++++++++++++++++++-------------- src/unit.rs | 12 ++++++---- 5 files changed, 65 insertions(+), 28 deletions(-) diff --git a/GRAMMAR.md b/GRAMMAR.md index 105bf76..a5ea6e4 100644 --- a/GRAMMAR.md +++ b/GRAMMAR.md @@ -61,7 +61,8 @@ dependencies : NAME+ body : INDENT line+ DEDENT -line : LINE (TEXT | interpolation)+ eol +line : LINE (TEXT | interpolation)+ NEWLINE + | NEWLINE interpolation : '{{' expression '}}' ``` diff --git a/justfile b/justfile index b97c802..fa40d2c 100644 --- a/justfile +++ b/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 diff --git a/src/integration.rs b/src/integration.rs index 5810571..f6ef7f4 100644 --- a/src/integration.rs +++ b/src/integration.rs @@ -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", ); } diff --git a/src/lib.rs b/src/lib.rs index 8283ef9..3304355 100644 --- a/src/lib.rs +++ b/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, + 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, + 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, 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, signal: i32}, TmpdirIoError{recipe: &'a str, io_error: io::Error}, - UnknownFailure{recipe: &'a str}, + UnknownFailure{recipe: &'a str, line_number: Option}, 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() { diff --git a/src/unit.rs b/src/unit.rs index c009b2e..bf155fa 100644 --- a/src/unit.rs +++ b/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), }