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
-----
- actually run backticks
. test all error types, errors should underline backtick token
. test success in assignment
. test success in recipe interpolations
- unit tests for as many error types as possible
- integration tests for as many error types as possible, errors should underline backtick token
- test shebang recipe `` evaluation order
make sure that nothing happens if a `` fails on a later line
- test command recipe `` evaluation order
do some stuff, then have a line with a `` that fails
make sure that stuff before that actually happened
@ -57,6 +53,7 @@ notes
no longer accept a program or change its meaning
. habit of using clever commands and writing little scripts
. debugging with --debug or --evaluate
. `` strips a single newline
. very low friction to write a script (no new file, chmod, add to rcs)
. make list of contributors, include travis
. 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();
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;
}
@ -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]
fn show() {
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}
}
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")
.arg("-cu")
.arg(raw)
@ -142,33 +156,36 @@ fn run_backtick<'a>(raw: &'a str, _token: &Token) -> Result<String, RunError<'a>
.output();
match output {
Ok(output) => if let Some(code) = output.status.code() {
if code != 0 {
return Err(RunError::BacktickCode {
raw: raw,
code: code,
});
Ok(output) => {
if let Some(code) = output.status.code() {
if code != 0 {
return Err(RunError::BacktickCode {
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> {
fn run(
&'a self,
&self,
arguments: &[&'a str],
scope: &BTreeMap<&'a str, String>
) -> Result<(), RunError<'a>> {
@ -475,7 +492,7 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
}
fn evaluate_assignments<'a>(
assignments: &'a BTreeMap<&'a str, Expression<'a>>,
assignments: &BTreeMap<&'a str, Expression<'a>>,
) -> Result<BTreeMap<&'a str, String>, RunError<'a>> {
let mut evaluator = Evaluator {
evaluated: BTreeMap::new(),
@ -499,7 +516,7 @@ struct Evaluator<'a: 'b, 'b> {
impl<'a, 'b> Evaluator<'a, 'b> {
fn evaluate_line(
&mut self,
line: &'a [Fragment<'a>],
line: &[Fragment<'a>],
arguments: &BTreeMap<&str, &str>
) -> Result<String, RunError<'a>> {
let mut evaluated = String::new();
@ -533,7 +550,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
fn evaluate_expression(
&mut self,
expression: &'a Expression<'a>,
expression: &Expression<'a>,
arguments: &BTreeMap<&str, &str>
) -> Result<String, RunError<'a>> {
Ok(match *expression {
@ -808,7 +825,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
fn run_recipe<'c>(
&'c self,
recipe: &'c Recipe<'a>,
recipe: &Recipe<'a>,
arguments: &[&'a str],
scope: &BTreeMap<&'c str, String>,
ran: &mut HashSet<&'a str>
@ -860,10 +877,11 @@ enum RunError<'a> {
TmpdirIoError{recipe: &'a str, io_error: io::Error},
UnknownFailure{recipe: &'a str},
UnknownRecipes{recipes: Vec<&'a str>},
BacktickCode{raw: &'a str, code: i32},
// BacktickSignal{backtick: Token<'a>, code: i32},
// BacktickIoError{backtick: Token<'a>, io_error: io::Error},
// BacktickUTF8Error
BacktickCode{code: i32},
BacktickIoError{io_error: io::Error},
BacktickSignal{signal: i32},
BacktickUtf8Error{utf8_error: std::str::Utf8Error},
BacktickUnknownFailure,
}
impl<'a> Display for RunError<'a> {
@ -897,11 +915,30 @@ impl<'a> Display for RunError<'a> {
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::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} =>
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} => {
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));
}