Backticks implemented. Error messages still suck though.

This commit is contained in:
Casey Rodarmor 2016-10-30 00:20:29 -07:00
parent 8b149b66fc
commit 980c5d0b33
3 changed files with 118 additions and 38 deletions

9
notes
View File

@ -1,14 +1,10 @@
notes notes
----- -----
- actually run backticks - unit tests for as many error types as possible
. test all error types, errors should underline backtick token - integration tests for as many error types as possible, errors should underline backtick token
. test success in assignment
. test success in recipe interpolations
- test shebang recipe `` evaluation order - test shebang recipe `` evaluation order
make sure that nothing happens if a `` fails on a later line make sure that nothing happens if a `` fails on a later line
- test command recipe `` evaluation order - test command recipe `` evaluation order
do some stuff, then have a line with a `` that fails do some stuff, then have a line with a `` that fails
make sure that stuff before that actually happened make sure that stuff before that actually happened
@ -57,6 +53,7 @@ notes
no longer accept a program or change its meaning no longer accept a program or change its meaning
. habit of using clever commands and writing little scripts . habit of using clever commands and writing little scripts
. debugging with --debug or --evaluate . debugging with --debug or --evaluate
. `` strips a single newline
. very low friction to write a script (no new file, chmod, add to rcs) . very low friction to write a script (no new file, chmod, add to rcs)
. make list of contributors, include travis . make list of contributors, include travis
. alias .j='just --justfile ~/.justfile --working-directory ~' . alias .j='just --justfile ~/.justfile --working-directory ~'

View File

@ -41,7 +41,7 @@ fn integration_test(
let stderr = super::std::str::from_utf8(&output.stderr).unwrap(); let stderr = super::std::str::from_utf8(&output.stderr).unwrap();
if stderr != expected_stderr { if stderr != expected_stderr {
println!("bad stdout:\ngot:\n{}\n\nexpected:\n{}", stderr, expected_stderr); println!("bad stderr:\ngot:\n{}\n\nexpected:\n{}", stderr, expected_stderr);
failure = true; failure = true;
} }
@ -141,6 +141,28 @@ c:
); );
} }
#[test]
fn print() {
let text =
"b:
echo b
a:
echo a
d:
echo d
c:
echo c";
integration_test(
"select",
&["d", "c"],
text,
0,
"d\nc\n",
"echo d\necho c\n",
);
}
#[test] #[test]
fn show() { fn show() {
let text = let text =
@ -215,3 +237,27 @@ fn error() {
", ",
); );
} }
#[test]
fn backtick_success() {
integration_test(
"backtick_success",
&[],
"a = `printf Hello,`\nbar:\n printf '{{a + `printf ' world!'`}}'",
0,
"Hello, world!",
"printf 'Hello, world!'\n",
);
}
#[test]
fn backtick_trimming() {
integration_test(
"backtick_trimming",
&[],
"a = `echo Hello,`\nbar:\n echo '{{a + `echo ' world!'`}}'",
0,
"Hello, world!\n",
"echo 'Hello, world!'\n",
);
}

View File

@ -134,7 +134,21 @@ fn error_from_signal(recipe: &str, exit_status: process::ExitStatus) -> RunError
RunError::UnknownFailure{recipe: recipe} RunError::UnknownFailure{recipe: recipe}
} }
fn run_backtick<'a>(raw: &'a str, _token: &Token) -> Result<String, RunError<'a>> { #[cfg(unix)]
fn backtick_error_from_signal(exit_status: process::ExitStatus) -> RunError<'static> {
use std::os::unix::process::ExitStatusExt;
match exit_status.signal() {
Some(signal) => RunError::BacktickSignal{signal: signal},
None => RunError::BacktickUnknownFailure,
}
}
#[cfg(windows)]
fn backtick_error_from_signal(exit_status: process::ExitStatus) -> RunError<'static> {
RunError::BacktickUnknownFailure
}
fn run_backtick<'a>(raw: &str, _token: &Token<'a>) -> Result<String, RunError<'a>> {
let output = process::Command::new("sh") let output = process::Command::new("sh")
.arg("-cu") .arg("-cu")
.arg(raw) .arg(raw)
@ -142,33 +156,36 @@ fn run_backtick<'a>(raw: &'a str, _token: &Token) -> Result<String, RunError<'a>
.output(); .output();
match output { match output {
Ok(output) => if let Some(code) = output.status.code() { Ok(output) => {
if let Some(code) = output.status.code() {
if code != 0 { if code != 0 {
return Err(RunError::BacktickCode { return Err(RunError::BacktickCode {
raw: raw,
code: code, code: code,
}); });
} }
}, } else {
_ => {} return Err(backtick_error_from_signal(output.status));
}
match std::str::from_utf8(&output.stdout) {
Err(error) => return Err(RunError::BacktickUtf8Error{utf8_error: error}),
Ok(utf8) => {
Ok(if utf8.ends_with('\n') {
&utf8[0..utf8.len()-1]
} else if utf8.ends_with("\r\n") {
&utf8[0..utf8.len()-2]
} else {
utf8
}.to_string())
}
}
}
Err(error) => Err(RunError::BacktickIoError{io_error: error}),
} }
// if !output.status.success() {
// panic!("backtick evaluation failed");
// }
// warn!("{}",
// status
// stdout
// stderr
Ok("".into())
} }
impl<'a> Recipe<'a> { impl<'a> Recipe<'a> {
fn run( fn run(
&'a self, &self,
arguments: &[&'a str], arguments: &[&'a str],
scope: &BTreeMap<&'a str, String> scope: &BTreeMap<&'a str, String>
) -> Result<(), RunError<'a>> { ) -> Result<(), RunError<'a>> {
@ -475,7 +492,7 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
} }
fn evaluate_assignments<'a>( fn evaluate_assignments<'a>(
assignments: &'a BTreeMap<&'a str, Expression<'a>>, assignments: &BTreeMap<&'a str, Expression<'a>>,
) -> Result<BTreeMap<&'a str, String>, RunError<'a>> { ) -> Result<BTreeMap<&'a str, String>, RunError<'a>> {
let mut evaluator = Evaluator { let mut evaluator = Evaluator {
evaluated: BTreeMap::new(), evaluated: BTreeMap::new(),
@ -499,7 +516,7 @@ struct Evaluator<'a: 'b, 'b> {
impl<'a, 'b> Evaluator<'a, 'b> { impl<'a, 'b> Evaluator<'a, 'b> {
fn evaluate_line( fn evaluate_line(
&mut self, &mut self,
line: &'a [Fragment<'a>], line: &[Fragment<'a>],
arguments: &BTreeMap<&str, &str> arguments: &BTreeMap<&str, &str>
) -> Result<String, RunError<'a>> { ) -> Result<String, RunError<'a>> {
let mut evaluated = String::new(); let mut evaluated = String::new();
@ -533,7 +550,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
fn evaluate_expression( fn evaluate_expression(
&mut self, &mut self,
expression: &'a Expression<'a>, expression: &Expression<'a>,
arguments: &BTreeMap<&str, &str> arguments: &BTreeMap<&str, &str>
) -> Result<String, RunError<'a>> { ) -> Result<String, RunError<'a>> {
Ok(match *expression { Ok(match *expression {
@ -808,7 +825,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
fn run_recipe<'c>( fn run_recipe<'c>(
&'c self, &'c self,
recipe: &'c Recipe<'a>, recipe: &Recipe<'a>,
arguments: &[&'a str], arguments: &[&'a str],
scope: &BTreeMap<&'c str, String>, scope: &BTreeMap<&'c str, String>,
ran: &mut HashSet<&'a str> ran: &mut HashSet<&'a str>
@ -860,10 +877,11 @@ enum RunError<'a> {
TmpdirIoError{recipe: &'a str, io_error: io::Error}, TmpdirIoError{recipe: &'a str, io_error: io::Error},
UnknownFailure{recipe: &'a str}, UnknownFailure{recipe: &'a str},
UnknownRecipes{recipes: Vec<&'a str>}, UnknownRecipes{recipes: Vec<&'a str>},
BacktickCode{raw: &'a str, code: i32}, BacktickCode{code: i32},
// BacktickSignal{backtick: Token<'a>, code: i32}, BacktickIoError{io_error: io::Error},
// BacktickIoError{backtick: Token<'a>, io_error: io::Error}, BacktickSignal{signal: i32},
// BacktickUTF8Error BacktickUtf8Error{utf8_error: std::str::Utf8Error},
BacktickUnknownFailure,
} }
impl<'a> Display for RunError<'a> { impl<'a> Display for RunError<'a> {
@ -897,11 +915,30 @@ impl<'a> Display for RunError<'a> {
try!(match io_error.kind() { try!(match io_error.kind() {
io::ErrorKind::NotFound => write!(f, "Recipe \"{}\" could not be run because j could not find `sh` the command:\n{}", recipe, io_error), io::ErrorKind::NotFound => write!(f, "Recipe \"{}\" could not be run because j could not find `sh` the command:\n{}", recipe, io_error),
io::ErrorKind::PermissionDenied => write!(f, "Recipe \"{}\" could not be run because j could not run `sh`:\n{}", recipe, io_error), io::ErrorKind::PermissionDenied => write!(f, "Recipe \"{}\" could not be run because j could not run `sh`:\n{}", recipe, io_error),
_ => write!(f, "Recipe \"{}\" could not be run because of an IO error while launching the `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} => {
try!(writeln!(f, "backtick failed with exit code {}", code));
}
RunError::BacktickSignal{signal} => {
try!(writeln!(f, "backtick was terminated by signal {}", signal));
}
RunError::BacktickUnknownFailure => {
try!(writeln!(f, "backtick failed for an uknown reason"));
}
RunError::BacktickIoError{ref io_error} => {
try!(match io_error.kind() {
io::ErrorKind::NotFound => write!(f, "backtick could not be run because j could not find `sh` the command:\n{}", io_error),
io::ErrorKind::PermissionDenied => write!(f, "backtick could not be run because j 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),
});
}
RunError::BacktickUtf8Error{ref utf8_error} => {
try!(write!(f, "backtick succeeded but stdout was not utf8: {}", utf8_error));
}
RunError::InternalError{ref message} => { RunError::InternalError{ref message} => {
try!(writeln!(f, "internal error, this may indicate a bug in j: {}\n consider filing an issue: https://github.com/casey/j/issues/new", message)); try!(writeln!(f, "internal error, this may indicate a bug in j: {}\n consider filing an issue: https://github.com/casey/j/issues/new", message));
} }