Add env_var(key)
and env_var_or_default(key, default)
functions (#280)
`env_var(key)` looks up the value of the environment variable with name `key`, aborting execution if it is not found. `env_var_or_default(key, default)` looks up the value of the environment variable with name `key`, returning `default` if it is not found.
This commit is contained in:
parent
9a56e27e18
commit
79c0994387
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||
### Added
|
||||
- Align doc-comments in `--list` output (#273)
|
||||
- Add `arch()`, `os()`, and `os_family()` functions (#277)
|
||||
- Add `env_var(key)` and `env_var_or_default(key, default)` functions (#280)
|
||||
|
||||
## [0.3.4] - 2017-10-06
|
||||
### Added
|
||||
|
@ -259,7 +259,7 @@ Just provides a few built-in functions that might be useful when writing recipes
|
||||
|
||||
- `os()` – Operating system. Possible values are: `"android"`, `"bitrig"`, `"dragonfly"`, `"emscripten"`, `"freebsd"`, `"haiku"`, `"ios"`, `"linux"`, `"macos"`, `"netbsd"`, `"openbsd"`, `"solaris"`, and `"windows"`.
|
||||
|
||||
- `os_family()` - Operating system family; possible values are: `"unix"` and `"windows"`.
|
||||
- `os_family()` – Operating system family; possible values are: `"unix"` and `"windows"`.
|
||||
|
||||
For example:
|
||||
|
||||
@ -273,6 +273,12 @@ $ just system-info
|
||||
This is an x86_64 machine
|
||||
```
|
||||
|
||||
==== Environment Variables
|
||||
|
||||
- `env_var(key)` – Retrieves the environment variable with name `key`, aborting if it is not present.
|
||||
|
||||
- `env_var_or_default(key, default)` – Retrieves the environment variable with name `key`, returning `default` if it is not present.
|
||||
|
||||
=== Command Evaluation Using Backticks
|
||||
|
||||
Backticks can be used to store the result of commands:
|
||||
|
@ -99,7 +99,12 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> {
|
||||
})
|
||||
}
|
||||
}
|
||||
Expression::Call{name, ..} => ::functions::evaluate_function(name),
|
||||
Expression::Call{name, arguments: ref call_arguments, ref token} => {
|
||||
let call_arguments = call_arguments.iter().map(|argument| {
|
||||
self.evaluate_expression(argument, &arguments)
|
||||
}).collect::<Result<Vec<String>, RuntimeError>>()?;
|
||||
::functions::evaluate_function(&token, name, &call_arguments)
|
||||
}
|
||||
Expression::String{ref cooked_string} => Ok(cooked_string.cooked.clone()),
|
||||
Expression::Backtick{raw, ref token} => {
|
||||
if self.dry_run {
|
||||
|
@ -75,7 +75,9 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
|
||||
return Err(token.error(UndefinedVariable{variable: name}));
|
||||
}
|
||||
}
|
||||
Expression::Call{ref token, ..} => ::functions::resolve_function(token)?,
|
||||
Expression::Call{ref token, ref arguments, ..} => {
|
||||
::functions::resolve_function(token, arguments.len())?
|
||||
}
|
||||
Expression::Concatination{ref lhs, ref rhs} => {
|
||||
self.resolve_expression(lhs)?;
|
||||
self.resolve_expression(rhs)?;
|
||||
@ -89,17 +91,6 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use TokenKind::*;
|
||||
|
||||
compilation_error_test! {
|
||||
name: unclosed_interpolation_delimiter,
|
||||
input: "a:\n echo {{ foo",
|
||||
index: 15,
|
||||
line: 1,
|
||||
column: 12,
|
||||
width: Some(0),
|
||||
kind: UnexpectedToken{expected: vec![Plus, Eol, InterpolationEnd], found: Dedent},
|
||||
}
|
||||
|
||||
compilation_error_test! {
|
||||
name: circular_variable_dependency,
|
||||
|
@ -1,6 +1,6 @@
|
||||
use common::*;
|
||||
|
||||
use misc::{Or, write_error_context, show_whitespace};
|
||||
use misc::{Or, write_error_context, show_whitespace, maybe_s};
|
||||
|
||||
pub type CompilationResult<'a, T> = Result<T, CompilationError<'a>>;
|
||||
|
||||
@ -24,6 +24,7 @@ pub enum CompilationErrorKind<'a> {
|
||||
DuplicateRecipe{recipe: &'a str, first: usize},
|
||||
DuplicateVariable{variable: &'a str},
|
||||
ExtraLeadingWhitespace,
|
||||
FunctionArgumentCountMismatch{function: &'a str, found: usize, expected: usize},
|
||||
InconsistentLeadingWhitespace{expected: &'a str, found: &'a str},
|
||||
Internal{message: String},
|
||||
InvalidEscapeSequence{character: char},
|
||||
@ -109,6 +110,13 @@ impl<'a> Display for CompilationError<'a> {
|
||||
ExtraLeadingWhitespace => {
|
||||
writeln!(f, "Recipe line has extra leading whitespace")?;
|
||||
}
|
||||
FunctionArgumentCountMismatch{function, found, expected} => {
|
||||
writeln!(
|
||||
f,
|
||||
"Function `{}` called with {} argument{} but takes {}",
|
||||
function, found, maybe_s(found), expected
|
||||
)?;
|
||||
}
|
||||
InconsistentLeadingWhitespace{expected, found} => {
|
||||
writeln!(f,
|
||||
"Recipe line has inconsistent leading whitespace. \
|
||||
|
@ -3,7 +3,7 @@ use common::*;
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum Expression<'a> {
|
||||
Backtick{raw: &'a str, token: Token<'a>},
|
||||
Call{name: &'a str, token: Token<'a>},
|
||||
Call{name: &'a str, token: Token<'a>, arguments: Vec<Expression<'a>>},
|
||||
Concatination{lhs: Box<Expression<'a>>, rhs: Box<Expression<'a>>},
|
||||
String{cooked_string: CookedString<'a>},
|
||||
Variable{name: &'a str, token: Token<'a>},
|
||||
@ -27,10 +27,20 @@ impl<'a> Display for Expression<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
Expression::Backtick {raw, .. } => write!(f, "`{}`", raw)?,
|
||||
Expression::Call {name, .. } => write!(f, "{}()", name)?,
|
||||
Expression::Concatination{ref lhs, ref rhs } => write!(f, "{} + {}", lhs, rhs)?,
|
||||
Expression::String {ref cooked_string} => write!(f, "\"{}\"", cooked_string.raw)?,
|
||||
Expression::String {ref cooked_string } => write!(f, "\"{}\"", cooked_string.raw)?,
|
||||
Expression::Variable {name, .. } => write!(f, "{}", name)?,
|
||||
Expression::Call {name, ref arguments, ..} => {
|
||||
write!(f, "{}(", name)?;
|
||||
for (i, argument) in arguments.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, ", {}", argument)?;
|
||||
} else {
|
||||
write!(f, "{}", argument)?;
|
||||
}
|
||||
}
|
||||
write!(f, ")")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -64,15 +74,15 @@ pub struct Functions<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Functions<'a> {
|
||||
type Item = &'a Token<'a>;
|
||||
type Item = (&'a Token<'a>, usize);
|
||||
|
||||
fn next(&mut self) -> Option<&'a Token<'a>> {
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.stack.pop() {
|
||||
None
|
||||
| Some(&Expression::String{..})
|
||||
| Some(&Expression::Backtick{..})
|
||||
| Some(&Expression::Variable{..}) => None,
|
||||
Some(&Expression::Call{ref token, ..}) => Some(token),
|
||||
Some(&Expression::Call{ref token, ref arguments, ..}) => Some((token, arguments.len())),
|
||||
Some(&Expression::Concatination{ref lhs, ref rhs}) => {
|
||||
self.stack.push(lhs);
|
||||
self.stack.push(rhs);
|
||||
|
105
src/functions.rs
105
src/functions.rs
@ -1,33 +1,104 @@
|
||||
use common::*;
|
||||
use target;
|
||||
|
||||
pub fn resolve_function<'a>(token: &Token<'a>) -> CompilationResult<'a, ()> {
|
||||
if !&["arch", "os", "os_family"].contains(&token.lexeme) {
|
||||
Err(token.error(CompilationErrorKind::UnknownFunction{function: token.lexeme}))
|
||||
} else {
|
||||
Ok(())
|
||||
lazy_static! {
|
||||
static ref FUNCTIONS: Map<&'static str, Function> = vec![
|
||||
("arch", Function::Nullary(arch )),
|
||||
("os", Function::Nullary(os )),
|
||||
("os_family", Function::Nullary(os_family )),
|
||||
("env_var", Function::Unary (env_var )),
|
||||
("env_var_or_default", Function::Binary (env_var_or_default)),
|
||||
].into_iter().collect();
|
||||
}
|
||||
|
||||
enum Function {
|
||||
Nullary(fn( ) -> Result<String, String>),
|
||||
Unary (fn(&str ) -> Result<String, String>),
|
||||
Binary (fn(&str, &str) -> Result<String, String>),
|
||||
}
|
||||
|
||||
impl Function {
|
||||
fn argc(&self) -> usize {
|
||||
use self::Function::*;
|
||||
match *self {
|
||||
Nullary(_) => 0,
|
||||
Unary(_) => 1,
|
||||
Binary(_) => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluate_function<'a>(name: &'a str) -> RunResult<'a, String> {
|
||||
match name {
|
||||
"arch" => Ok(arch().to_string()),
|
||||
"os" => Ok(os().to_string()),
|
||||
"os_family" => Ok(os_family().to_string()),
|
||||
_ => Err(RuntimeError::Internal {
|
||||
pub fn resolve_function<'a>(token: &Token<'a>, argc: usize) -> CompilationResult<'a, ()> {
|
||||
let name = token.lexeme;
|
||||
if let Some(function) = FUNCTIONS.get(&name) {
|
||||
use self::Function::*;
|
||||
match (function, argc) {
|
||||
(&Nullary(_), 0) => Ok(()),
|
||||
(&Unary(_), 1) => Ok(()),
|
||||
(&Binary(_), 2) => Ok(()),
|
||||
_ => {
|
||||
Err(token.error(CompilationErrorKind::FunctionArgumentCountMismatch{
|
||||
function: name, found: argc, expected: function.argc(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(token.error(CompilationErrorKind::UnknownFunction{function: token.lexeme}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluate_function<'a>(token: &Token<'a>, name: &'a str, arguments: &[String]) -> RunResult<'a, String> {
|
||||
if let Some(function) = FUNCTIONS.get(name) {
|
||||
use self::Function::*;
|
||||
let argc = arguments.len();
|
||||
match (function, argc) {
|
||||
(&Nullary(f), 0) => f()
|
||||
.map_err(|message| RuntimeError::FunctionCall{token: token.clone(), message}),
|
||||
(&Unary(f), 1) => f(&arguments[0])
|
||||
.map_err(|message| RuntimeError::FunctionCall{token: token.clone(), message}),
|
||||
(&Binary(f), 2) => f(&arguments[0], &arguments[1])
|
||||
.map_err(|message| RuntimeError::FunctionCall{token: token.clone(), message}),
|
||||
_ => {
|
||||
Err(RuntimeError::Internal {
|
||||
message: format!("attempted to evaluate function `{}` with {} arguments", name, argc)
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(RuntimeError::Internal {
|
||||
message: format!("attempted to evaluate unknown function: `{}`", name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arch() -> &'static str {
|
||||
target::arch()
|
||||
pub fn arch() -> Result<String, String> {
|
||||
Ok(target::arch().to_string())
|
||||
}
|
||||
|
||||
pub fn os() -> &'static str {
|
||||
target::os()
|
||||
pub fn os() -> Result<String, String> {
|
||||
Ok(target::os().to_string())
|
||||
}
|
||||
|
||||
pub fn os_family() -> &'static str {
|
||||
target::os_family()
|
||||
pub fn os_family() -> Result<String, String> {
|
||||
Ok(target::os_family().to_string())
|
||||
}
|
||||
|
||||
pub fn env_var<'a>(key: &str) -> Result<String, String> {
|
||||
use std::env::VarError::*;
|
||||
match env::var(key) {
|
||||
Err(NotPresent) => Err(format!("environment variable `{}` not present", key)),
|
||||
Err(NotUnicode(os_string)) =>
|
||||
Err(format!("environment variable `{}` not unicode: {:?}", key, os_string)),
|
||||
Ok(value) => Ok(value),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn env_var_or_default<'a>(key: &str, default: &str) -> Result<String, String> {
|
||||
use std::env::VarError::*;
|
||||
match env::var(key) {
|
||||
Err(NotPresent) => Ok(default.to_string()),
|
||||
Err(NotUnicode(os_string)) =>
|
||||
Err(format!("environment variable `{}` not unicode: {:?}", key, os_string)),
|
||||
Ok(value) => Ok(value),
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +134,7 @@ impl<'a> Lexer<'a> {
|
||||
static ref PAREN_L: Regex = token(r"[(]" );
|
||||
static ref PAREN_R: Regex = token(r"[)]" );
|
||||
static ref AT: Regex = token(r"@" );
|
||||
static ref COMMA: Regex = token(r"," );
|
||||
static ref COMMENT: Regex = token(r"#([^!\n\r].*)?$" );
|
||||
static ref EOF: Regex = token(r"(?-m)$" );
|
||||
static ref EOL: Regex = token(r"\n|\r\n" );
|
||||
@ -209,6 +210,8 @@ impl<'a> Lexer<'a> {
|
||||
(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Colon)
|
||||
} else if let Some(captures) = AT.captures(self.rest) {
|
||||
(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), At)
|
||||
} else if let Some(captures) = COMMA.captures(self.rest) {
|
||||
(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Comma)
|
||||
} else if let Some(captures) = PAREN_L.captures(self.rest) {
|
||||
(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), ParenL)
|
||||
} else if let Some(captures) = PAREN_R.captures(self.rest) {
|
||||
@ -332,6 +335,7 @@ mod test {
|
||||
At => "@",
|
||||
Backtick => "`",
|
||||
Colon => ":",
|
||||
Comma => ",",
|
||||
Comment{..} => "#",
|
||||
Dedent => "<",
|
||||
Eof => ".",
|
||||
@ -420,8 +424,8 @@ mod test {
|
||||
|
||||
summary_test! {
|
||||
tokenize_recipe_multiple_interpolations,
|
||||
"foo:#ok\n {{a}}0{{b}}1{{c}}",
|
||||
"N:#$>^{N}_{N}_{N}<.",
|
||||
"foo:,#ok\n {{a}}0{{b}}1{{c}}",
|
||||
"N:,#$>^{N}_{N}_{N}<.",
|
||||
}
|
||||
|
||||
summary_test! {
|
||||
|
@ -220,10 +220,11 @@ impl<'a> Parser<'a> {
|
||||
return Err(self.unexpected_token(&token, &[Text, InterpolationStart, Eol]));
|
||||
} else {
|
||||
fragments.push(Fragment::Expression{
|
||||
expression: self.expression(true)?
|
||||
expression: self.expression()?
|
||||
});
|
||||
|
||||
if let Some(token) = self.expect(InterpolationEnd) {
|
||||
return Err(self.unexpected_token(&token, &[InterpolationEnd]));
|
||||
return Err(self.unexpected_token(&token, &[Plus, InterpolationEnd]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -248,7 +249,7 @@ impl<'a> Parser<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expression(&mut self, interpolation: bool) -> CompilationResult<'a, Expression<'a>> {
|
||||
fn expression(&mut self) -> CompilationResult<'a, Expression<'a>> {
|
||||
let first = self.tokens.next().unwrap();
|
||||
let lhs = match first.kind {
|
||||
Name => {
|
||||
@ -256,10 +257,11 @@ impl<'a> Parser<'a> {
|
||||
if let Some(token) = self.expect(ParenL) {
|
||||
return Err(self.unexpected_token(&token, &[ParenL]));
|
||||
}
|
||||
let arguments = self.arguments()?;
|
||||
if let Some(token) = self.expect(ParenR) {
|
||||
return Err(self.unexpected_token(&token, &[ParenR]));
|
||||
return Err(self.unexpected_token(&token, &[Name, StringToken, ParenR]));
|
||||
}
|
||||
Expression::Call {name: first.lexeme, token: first}
|
||||
Expression::Call {name: first.lexeme, token: first, arguments}
|
||||
} else {
|
||||
Expression::Variable {name: first.lexeme, token: first}
|
||||
}
|
||||
@ -275,21 +277,31 @@ impl<'a> Parser<'a> {
|
||||
};
|
||||
|
||||
if self.accepted(Plus) {
|
||||
let rhs = self.expression(interpolation)?;
|
||||
let rhs = self.expression()?;
|
||||
Ok(Expression::Concatination{lhs: Box::new(lhs), rhs: Box::new(rhs)})
|
||||
} else if interpolation && self.peek(InterpolationEnd) {
|
||||
Ok(lhs)
|
||||
} else if let Some(token) = self.expect_eol() {
|
||||
if interpolation {
|
||||
return Err(self.unexpected_token(&token, &[Plus, Eol, InterpolationEnd]))
|
||||
} else {
|
||||
Err(self.unexpected_token(&token, &[Plus, Eol]))
|
||||
}
|
||||
} else {
|
||||
Ok(lhs)
|
||||
}
|
||||
}
|
||||
|
||||
fn arguments(&mut self) -> CompilationResult<'a, Vec<Expression<'a>>> {
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
while !self.peek(ParenR) && !self.peek(Eof) && !self.peek(Eol) && !self.peek(InterpolationEnd) {
|
||||
arguments.push(self.expression()?);
|
||||
if !self.accepted(Comma) {
|
||||
if self.peek(ParenR) {
|
||||
break;
|
||||
} else {
|
||||
let next = self.tokens.next().unwrap();
|
||||
return Err(self.unexpected_token(&next, &[Comma, ParenR]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(arguments)
|
||||
}
|
||||
|
||||
fn assignment(&mut self, name: Token<'a>, export: bool) -> CompilationResult<'a, ()> {
|
||||
if self.assignments.contains_key(name.lexeme) {
|
||||
return Err(name.error(DuplicateVariable {variable: name.lexeme}));
|
||||
@ -297,7 +309,12 @@ impl<'a> Parser<'a> {
|
||||
if export {
|
||||
self.exports.insert(name.lexeme);
|
||||
}
|
||||
let expression = self.expression(false)?;
|
||||
|
||||
let expression = self.expression()?;
|
||||
if let Some(token) = self.expect_eol() {
|
||||
return Err(self.unexpected_token(&token, &[Plus, Eol]));
|
||||
}
|
||||
|
||||
self.assignments.insert(name.lexeme, expression);
|
||||
self.assignment_tokens.insert(name.lexeme, name);
|
||||
Ok(())
|
||||
@ -606,6 +623,32 @@ c = a + b + a + b",
|
||||
{{b}} {{c}}",
|
||||
}
|
||||
|
||||
summary_test! {
|
||||
unary_functions,
|
||||
"
|
||||
x = arch()
|
||||
|
||||
a:
|
||||
{{os()}} {{os_family()}}",
|
||||
"x = arch()
|
||||
|
||||
a:
|
||||
{{os()}} {{os_family()}}",
|
||||
}
|
||||
|
||||
summary_test! {
|
||||
env_functions,
|
||||
r#"
|
||||
x = env_var('foo',)
|
||||
|
||||
a:
|
||||
{{env_var_or_default('foo' + 'bar', 'baz',)}} {{env_var(env_var("baz"))}}"#,
|
||||
r#"x = env_var("foo")
|
||||
|
||||
a:
|
||||
{{env_var_or_default("foo" + "bar", "baz")}} {{env_var(env_var("baz"))}}"#,
|
||||
}
|
||||
|
||||
compilation_error_test! {
|
||||
name: missing_colon,
|
||||
input: "a b c\nd e f",
|
||||
@ -773,7 +816,7 @@ c = a + b + a + b",
|
||||
line: 1,
|
||||
column: 12,
|
||||
width: Some(0),
|
||||
kind: UnexpectedToken{expected: vec![Plus, Eol, InterpolationEnd], found: Dedent},
|
||||
kind: UnexpectedToken{expected: vec![Plus, InterpolationEnd], found: Dedent},
|
||||
}
|
||||
|
||||
compilation_error_test! {
|
||||
@ -783,7 +826,7 @@ c = a + b + a + b",
|
||||
line: 0,
|
||||
column: 8,
|
||||
width: Some(0),
|
||||
kind: UnexpectedToken{expected: vec![ParenR], found: Eof},
|
||||
kind: UnexpectedToken{expected: vec![Name, StringToken, ParenR], found: Eof},
|
||||
}
|
||||
|
||||
compilation_error_test! {
|
||||
@ -793,7 +836,7 @@ c = a + b + a + b",
|
||||
line: 1,
|
||||
column: 12,
|
||||
width: Some(2),
|
||||
kind: UnexpectedToken{expected: vec![ParenR], found: InterpolationEnd},
|
||||
kind: UnexpectedToken{expected: vec![Name, StringToken, ParenR], found: InterpolationEnd},
|
||||
}
|
||||
|
||||
compilation_error_test! {
|
||||
|
@ -41,8 +41,8 @@ impl<'a, 'b> RecipeResolver<'a, 'b> {
|
||||
for line in &recipe.lines {
|
||||
for fragment in line {
|
||||
if let Fragment::Expression{ref expression, ..} = *fragment {
|
||||
for function in expression.functions() {
|
||||
if let Err(error) = ::functions::resolve_function(function) {
|
||||
for (function, argc) in expression.functions() {
|
||||
if let Err(error) = ::functions::resolve_function(function, argc) {
|
||||
return Err(CompilationError {
|
||||
text: text,
|
||||
index: error.index,
|
||||
|
@ -25,14 +25,15 @@ pub enum RuntimeError<'a> {
|
||||
Backtick{token: Token<'a>, output_error: OutputError},
|
||||
Code{recipe: &'a str, line_number: Option<usize>, code: i32},
|
||||
Cygpath{recipe: &'a str, output_error: OutputError},
|
||||
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},
|
||||
Unknown{recipe: &'a str, line_number: Option<usize>},
|
||||
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> {
|
||||
@ -117,6 +118,10 @@ impl<'a> Display for RuntimeError<'a> {
|
||||
but output was not utf8: {}", recipe, utf8_error)?;
|
||||
}
|
||||
},
|
||||
FunctionCall{ref token, ref message} => {
|
||||
write!(f, "Call to function `{}` failed: {}\n", 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: {}",
|
||||
@ -161,11 +166,11 @@ impl<'a> Display for RuntimeError<'a> {
|
||||
error_token = Some(token);
|
||||
}
|
||||
OutputError::Signal(signal) => {
|
||||
write!(f, "Backtick was terminated by signal {}", signal)?;
|
||||
write!(f, "Backtick was terminated by signal {}\n", signal)?;
|
||||
error_token = Some(token);
|
||||
}
|
||||
OutputError::Unknown => {
|
||||
write!(f, "Backtick failed for an unknown reason")?;
|
||||
write!(f, "Backtick failed for an unknown reason\n")?;
|
||||
error_token = Some(token);
|
||||
}
|
||||
OutputError::Io(ref io_error) => {
|
||||
@ -181,7 +186,7 @@ impl<'a> Display for RuntimeError<'a> {
|
||||
error_token = Some(token);
|
||||
}
|
||||
OutputError::Utf8(ref utf8_error) => {
|
||||
write!(f, "Backtick succeeded but stdout was not utf8: {}", utf8_error)?;
|
||||
write!(f, "Backtick succeeded but stdout was not utf8: {}\n", utf8_error)?;
|
||||
error_token = Some(token);
|
||||
}
|
||||
},
|
||||
|
@ -29,6 +29,7 @@ pub enum TokenKind {
|
||||
At,
|
||||
Backtick,
|
||||
Colon,
|
||||
Comma,
|
||||
Comment,
|
||||
Dedent,
|
||||
Eof,
|
||||
@ -53,6 +54,7 @@ impl Display for TokenKind {
|
||||
write!(f, "{}", match *self {
|
||||
Backtick => "backtick",
|
||||
Colon => "':'",
|
||||
Comma => "','",
|
||||
Comment => "comment",
|
||||
Dedent => "dedent",
|
||||
Eof => "end of file",
|
||||
|
@ -1203,6 +1203,34 @@ foo:
|
||||
status: EXIT_SUCCESS,
|
||||
}
|
||||
|
||||
integration_test! {
|
||||
name: env_var_functions,
|
||||
justfile: r#"
|
||||
p = env_var('PATH')
|
||||
b = env_var_or_default('ZADDY', 'HTAP')
|
||||
x = env_var_or_default('XYZ', 'ABC')
|
||||
|
||||
foo:
|
||||
/bin/echo '{{p}}' '{{b}}' '{{x}}'
|
||||
"#,
|
||||
args: (),
|
||||
stdout: format!("{} HTAP ABC\n", env::var("PATH").unwrap()).as_str(),
|
||||
stderr: format!("/bin/echo '{}' 'HTAP' 'ABC'\n", env::var("PATH").unwrap()).as_str(),
|
||||
status: EXIT_SUCCESS,
|
||||
}
|
||||
|
||||
integration_test! {
|
||||
name: env_var_failure,
|
||||
justfile: "a:\n echo {{env_var('ZADDY')}}",
|
||||
args: ("a"),
|
||||
stdout: "",
|
||||
stderr: "error: Call to function `env_var` failed: environment variable `ZADDY` not present
|
||||
|
|
||||
2 | echo {{env_var('ZADDY')}}
|
||||
| ^^^^^^^
|
||||
",
|
||||
status: EXIT_FAILURE,
|
||||
}
|
||||
|
||||
integration_test! {
|
||||
name: quiet_recipe,
|
||||
|
Loading…
Reference in New Issue
Block a user