just/src/runtime_error.rs
2018-08-27 18:36:40 -07:00

215 lines
8.4 KiB
Rust

use common::*;
use dotenv;
use brev::OutputError;
use misc::{And, Or, maybe_s, Tick, ticks, write_error_context};
use self::RuntimeError::*;
pub type RunResult<'a, T> = Result<T, RuntimeError<'a>>;
fn write_token_error_context(f: &mut fmt::Formatter, token: &Token) -> Result<(), fmt::Error> {
write_error_context(
f,
token.text,
token.index,
token.line,
token.column + token.prefix.len(),
Some(token.lexeme.len())
)
}
#[derive(Debug)]
pub enum RuntimeError<'a> {
ArgumentCountMismatch{recipe: &'a str, found: usize, min: usize, max: usize},
Backtick{token: Token<'a>, output_error: OutputError},
Code{recipe: &'a str, line_number: Option<usize>, code: i32},
Cygpath{recipe: &'a str, output_error: OutputError},
Dotenv{dotenv_error: dotenv::Error},
FunctionCall{token: Token<'a>, message: String},
Internal{message: String},
IoError{recipe: &'a str, io_error: io::Error},
Shebang{recipe: &'a str, command: String, argument: Option<String>, io_error: io::Error},
Signal{recipe: &'a str, line_number: Option<usize>, signal: i32},
TmpdirIoError{recipe: &'a str, io_error: io::Error},
UnknownOverrides{overrides: Vec<&'a str>},
UnknownRecipes{recipes: Vec<&'a str>, suggestion: Option<&'a str>},
Unknown{recipe: &'a str, line_number: Option<usize>},
}
impl<'a> RuntimeError<'a> {
pub fn code(&self) -> Option<i32> {
match *self {
Code{code, ..} | Backtick{output_error: OutputError::Code(code), ..} => Some(code),
_ => None,
}
}
}
impl<'a> Display for RuntimeError<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use RuntimeError::*;
let color = if f.alternate() { Color::always() } else { Color::never() };
let error = color.error();
let message = color.message();
write!(f, "{} {}", error.paint("error:"), message.prefix())?;
let mut error_token = None;
match *self {
UnknownRecipes{ref recipes, ref suggestion} => {
write!(f, "Justfile does not contain recipe{} {}.",
maybe_s(recipes.len()), Or(&ticks(recipes)))?;
if let Some(suggestion) = *suggestion {
write!(f, "\nDid you mean `{}`?", suggestion)?;
}
},
UnknownOverrides{ref overrides} => {
write!(f, "Variable{} {} overridden on the command line but not present in justfile",
maybe_s(overrides.len()),
And(&overrides.iter().map(Tick).collect::<Vec<_>>()))?;
},
ArgumentCountMismatch{recipe, found, min, max} => {
if min == max {
let expected = min;
write!(f, "Recipe `{}` got {} argument{} but {}takes {}",
recipe, found, maybe_s(found),
if expected < found { "only " } else { "" }, expected)?;
} else if found < min {
write!(f, "Recipe `{}` got {} argument{} but takes at least {}",
recipe, found, maybe_s(found), min)?;
} else if found > max {
write!(f, "Recipe `{}` got {} argument{} but takes at most {}",
recipe, found, maybe_s(found), max)?;
}
},
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)?;
}
},
Cygpath{recipe, ref output_error} => match *output_error {
OutputError::Code(code) => {
write!(f, "Cygpath failed with exit code {} while translating recipe `{}` \
shebang interpreter path", code, recipe)?;
}
OutputError::Signal(signal) => {
write!(f, "Cygpath terminated by signal {} while translating recipe `{}` \
shebang interpreter path", signal, recipe)?;
}
OutputError::Unknown => {
write!(f, "Cygpath experienced an unknown failure while translating recipe `{}` \
shebang interpreter path", recipe)?;
}
OutputError::Io(ref io_error) => {
match io_error.kind() {
io::ErrorKind::NotFound => write!(
f, "Could not find `cygpath` executable to translate recipe `{}` \
shebang interpreter path:\n{}", recipe, io_error),
io::ErrorKind::PermissionDenied => write!(
f, "Could not run `cygpath` executable to translate recipe `{}` \
shebang interpreter path:\n{}", recipe, io_error),
_ => write!(f, "Could not run `cygpath` executable:\n{}", io_error),
}?;
}
OutputError::Utf8(ref utf8_error) => {
write!(f, "Cygpath successfully translated recipe `{}` shebang interpreter path, \
but output was not utf8: {}", recipe, utf8_error)?;
}
},
Dotenv{ref dotenv_error} => {
writeln!(f, "Failed to load .env: {}", dotenv_error)?;
}
FunctionCall{ref token, ref message} => {
writeln!(f, "Call to function `{}` failed: {}", token.lexeme, message)?;
error_token = Some(token);
}
Shebang{recipe, ref command, ref argument, ref io_error} => {
if let Some(ref argument) = *argument {
write!(f, "Recipe `{}` with shebang `#!{} {}` execution error: {}",
recipe, command, argument, io_error)?;
} else {
write!(f, "Recipe `{}` with shebang `#!{}` execution error: {}",
recipe, command, io_error)?;
}
}
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)?;
}
}
Unknown{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() {
io::ErrorKind::NotFound => writeln!(f,
"Recipe `{}` could not be run because just could not find `sh`:{}",
recipe, io_error),
io::ErrorKind::PermissionDenied => writeln!(
f, "Recipe `{}` could not be run because just could not run `sh`:{}",
recipe, io_error),
_ => writeln!(f, "Recipe `{}` could not be run because of an IO error while \
launching `sh`:{}", recipe, io_error),
}?;
},
TmpdirIoError{recipe, ref io_error} =>
writeln!(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`:{}",
recipe, io_error)?,
Backtick{ref token, ref output_error} => match *output_error {
OutputError::Code(code) => {
writeln!(f, "Backtick failed with exit code {}", code)?;
error_token = Some(token);
}
OutputError::Signal(signal) => {
writeln!(f, "Backtick was terminated by signal {}", signal)?;
error_token = Some(token);
}
OutputError::Unknown => {
writeln!(f, "Backtick failed for an unknown reason")?;
error_token = Some(token);
}
OutputError::Io(ref io_error) => {
match io_error.kind() {
io::ErrorKind::NotFound => write!(
f, "Backtick could not be run because just could not find `sh`:\n{}",
io_error),
io::ErrorKind::PermissionDenied => write!(
f, "Backtick could not be run because just 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),
}?;
error_token = Some(token);
}
OutputError::Utf8(ref utf8_error) => {
writeln!(f, "Backtick succeeded but stdout was not utf8: {}", utf8_error)?;
error_token = Some(token);
}
},
Internal{ref message} => {
write!(f, "Internal error, this may indicate a bug in just: {} \
consider filing an issue: https://github.com/casey/just/issues/new",
message)?;
}
}
write!(f, "{}", message.suffix())?;
if let Some(token) = error_token {
write_token_error_context(f, token)?;
}
Ok(())
}
}