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
|
### Added
|
||||||
- Align doc-comments in `--list` output (#273)
|
- Align doc-comments in `--list` output (#273)
|
||||||
- Add `arch()`, `os()`, and `os_family()` functions (#277)
|
- 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
|
## [0.3.4] - 2017-10-06
|
||||||
### Added
|
### 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()` – 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:
|
For example:
|
||||||
|
|
||||||
@ -273,6 +273,12 @@ $ just system-info
|
|||||||
This is an x86_64 machine
|
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
|
=== Command Evaluation Using Backticks
|
||||||
|
|
||||||
Backticks can be used to store the result of commands:
|
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::String{ref cooked_string} => Ok(cooked_string.cooked.clone()),
|
||||||
Expression::Backtick{raw, ref token} => {
|
Expression::Backtick{raw, ref token} => {
|
||||||
if self.dry_run {
|
if self.dry_run {
|
||||||
|
@ -75,7 +75,9 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
|
|||||||
return Err(token.error(UndefinedVariable{variable: name}));
|
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} => {
|
Expression::Concatination{ref lhs, ref rhs} => {
|
||||||
self.resolve_expression(lhs)?;
|
self.resolve_expression(lhs)?;
|
||||||
self.resolve_expression(rhs)?;
|
self.resolve_expression(rhs)?;
|
||||||
@ -89,17 +91,6 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
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! {
|
compilation_error_test! {
|
||||||
name: circular_variable_dependency,
|
name: circular_variable_dependency,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use common::*;
|
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>>;
|
pub type CompilationResult<'a, T> = Result<T, CompilationError<'a>>;
|
||||||
|
|
||||||
@ -24,6 +24,7 @@ pub enum CompilationErrorKind<'a> {
|
|||||||
DuplicateRecipe{recipe: &'a str, first: usize},
|
DuplicateRecipe{recipe: &'a str, first: usize},
|
||||||
DuplicateVariable{variable: &'a str},
|
DuplicateVariable{variable: &'a str},
|
||||||
ExtraLeadingWhitespace,
|
ExtraLeadingWhitespace,
|
||||||
|
FunctionArgumentCountMismatch{function: &'a str, found: usize, expected: usize},
|
||||||
InconsistentLeadingWhitespace{expected: &'a str, found: &'a str},
|
InconsistentLeadingWhitespace{expected: &'a str, found: &'a str},
|
||||||
Internal{message: String},
|
Internal{message: String},
|
||||||
InvalidEscapeSequence{character: char},
|
InvalidEscapeSequence{character: char},
|
||||||
@ -109,6 +110,13 @@ impl<'a> Display for CompilationError<'a> {
|
|||||||
ExtraLeadingWhitespace => {
|
ExtraLeadingWhitespace => {
|
||||||
writeln!(f, "Recipe line has extra leading whitespace")?;
|
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} => {
|
InconsistentLeadingWhitespace{expected, found} => {
|
||||||
writeln!(f,
|
writeln!(f,
|
||||||
"Recipe line has inconsistent leading whitespace. \
|
"Recipe line has inconsistent leading whitespace. \
|
||||||
|
@ -3,7 +3,7 @@ use common::*;
|
|||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum Expression<'a> {
|
pub enum Expression<'a> {
|
||||||
Backtick{raw: &'a str, token: Token<'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>>},
|
Concatination{lhs: Box<Expression<'a>>, rhs: Box<Expression<'a>>},
|
||||||
String{cooked_string: CookedString<'a>},
|
String{cooked_string: CookedString<'a>},
|
||||||
Variable{name: &'a str, token: Token<'a>},
|
Variable{name: &'a str, token: Token<'a>},
|
||||||
@ -26,11 +26,21 @@ impl<'a> Expression<'a> {
|
|||||||
impl<'a> Display for Expression<'a> {
|
impl<'a> Display for Expression<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
Expression::Backtick {raw, .. } => write!(f, "`{}`", raw)?,
|
Expression::Backtick {raw, .. } => write!(f, "`{}`", raw)?,
|
||||||
Expression::Call {name, .. } => write!(f, "{}()", name)?,
|
Expression::Concatination{ref lhs, ref rhs } => write!(f, "{} + {}", lhs, rhs)?,
|
||||||
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::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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -64,15 +74,15 @@ pub struct Functions<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for 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() {
|
match self.stack.pop() {
|
||||||
None
|
None
|
||||||
| Some(&Expression::String{..})
|
| Some(&Expression::String{..})
|
||||||
| Some(&Expression::Backtick{..})
|
| Some(&Expression::Backtick{..})
|
||||||
| Some(&Expression::Variable{..}) => None,
|
| 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}) => {
|
Some(&Expression::Concatination{ref lhs, ref rhs}) => {
|
||||||
self.stack.push(lhs);
|
self.stack.push(lhs);
|
||||||
self.stack.push(rhs);
|
self.stack.push(rhs);
|
||||||
|
105
src/functions.rs
105
src/functions.rs
@ -1,33 +1,104 @@
|
|||||||
use common::*;
|
use common::*;
|
||||||
use target;
|
use target;
|
||||||
|
|
||||||
pub fn resolve_function<'a>(token: &Token<'a>) -> CompilationResult<'a, ()> {
|
lazy_static! {
|
||||||
if !&["arch", "os", "os_family"].contains(&token.lexeme) {
|
static ref FUNCTIONS: Map<&'static str, Function> = vec![
|
||||||
Err(token.error(CompilationErrorKind::UnknownFunction{function: token.lexeme}))
|
("arch", Function::Nullary(arch )),
|
||||||
} else {
|
("os", Function::Nullary(os )),
|
||||||
Ok(())
|
("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> {
|
pub fn resolve_function<'a>(token: &Token<'a>, argc: usize) -> CompilationResult<'a, ()> {
|
||||||
match name {
|
let name = token.lexeme;
|
||||||
"arch" => Ok(arch().to_string()),
|
if let Some(function) = FUNCTIONS.get(&name) {
|
||||||
"os" => Ok(os().to_string()),
|
use self::Function::*;
|
||||||
"os_family" => Ok(os_family().to_string()),
|
match (function, argc) {
|
||||||
_ => Err(RuntimeError::Internal {
|
(&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)
|
message: format!("attempted to evaluate unknown function: `{}`", name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arch() -> &'static str {
|
pub fn arch() -> Result<String, String> {
|
||||||
target::arch()
|
Ok(target::arch().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn os() -> &'static str {
|
pub fn os() -> Result<String, String> {
|
||||||
target::os()
|
Ok(target::os().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn os_family() -> &'static str {
|
pub fn os_family() -> Result<String, String> {
|
||||||
target::os_family()
|
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_L: Regex = token(r"[(]" );
|
||||||
static ref PAREN_R: Regex = token(r"[)]" );
|
static ref PAREN_R: Regex = token(r"[)]" );
|
||||||
static ref AT: 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 COMMENT: Regex = token(r"#([^!\n\r].*)?$" );
|
||||||
static ref EOF: Regex = token(r"(?-m)$" );
|
static ref EOF: Regex = token(r"(?-m)$" );
|
||||||
static ref EOL: Regex = token(r"\n|\r\n" );
|
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)
|
(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Colon)
|
||||||
} else if let Some(captures) = AT.captures(self.rest) {
|
} else if let Some(captures) = AT.captures(self.rest) {
|
||||||
(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), At)
|
(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) {
|
} else if let Some(captures) = PAREN_L.captures(self.rest) {
|
||||||
(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), ParenL)
|
(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), ParenL)
|
||||||
} else if let Some(captures) = PAREN_R.captures(self.rest) {
|
} else if let Some(captures) = PAREN_R.captures(self.rest) {
|
||||||
@ -332,6 +335,7 @@ mod test {
|
|||||||
At => "@",
|
At => "@",
|
||||||
Backtick => "`",
|
Backtick => "`",
|
||||||
Colon => ":",
|
Colon => ":",
|
||||||
|
Comma => ",",
|
||||||
Comment{..} => "#",
|
Comment{..} => "#",
|
||||||
Dedent => "<",
|
Dedent => "<",
|
||||||
Eof => ".",
|
Eof => ".",
|
||||||
@ -420,8 +424,8 @@ mod test {
|
|||||||
|
|
||||||
summary_test! {
|
summary_test! {
|
||||||
tokenize_recipe_multiple_interpolations,
|
tokenize_recipe_multiple_interpolations,
|
||||||
"foo:#ok\n {{a}}0{{b}}1{{c}}",
|
"foo:,#ok\n {{a}}0{{b}}1{{c}}",
|
||||||
"N:#$>^{N}_{N}_{N}<.",
|
"N:,#$>^{N}_{N}_{N}<.",
|
||||||
}
|
}
|
||||||
|
|
||||||
summary_test! {
|
summary_test! {
|
||||||
|
@ -220,10 +220,11 @@ impl<'a> Parser<'a> {
|
|||||||
return Err(self.unexpected_token(&token, &[Text, InterpolationStart, Eol]));
|
return Err(self.unexpected_token(&token, &[Text, InterpolationStart, Eol]));
|
||||||
} else {
|
} else {
|
||||||
fragments.push(Fragment::Expression{
|
fragments.push(Fragment::Expression{
|
||||||
expression: self.expression(true)?
|
expression: self.expression()?
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(token) = self.expect(InterpolationEnd) {
|
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(())
|
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 first = self.tokens.next().unwrap();
|
||||||
let lhs = match first.kind {
|
let lhs = match first.kind {
|
||||||
Name => {
|
Name => {
|
||||||
@ -256,10 +257,11 @@ impl<'a> Parser<'a> {
|
|||||||
if let Some(token) = self.expect(ParenL) {
|
if let Some(token) = self.expect(ParenL) {
|
||||||
return Err(self.unexpected_token(&token, &[ParenL]));
|
return Err(self.unexpected_token(&token, &[ParenL]));
|
||||||
}
|
}
|
||||||
|
let arguments = self.arguments()?;
|
||||||
if let Some(token) = self.expect(ParenR) {
|
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 {
|
} else {
|
||||||
Expression::Variable {name: first.lexeme, token: first}
|
Expression::Variable {name: first.lexeme, token: first}
|
||||||
}
|
}
|
||||||
@ -275,21 +277,31 @@ impl<'a> Parser<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if self.accepted(Plus) {
|
if self.accepted(Plus) {
|
||||||
let rhs = self.expression(interpolation)?;
|
let rhs = self.expression()?;
|
||||||
Ok(Expression::Concatination{lhs: Box::new(lhs), rhs: Box::new(rhs)})
|
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 {
|
} else {
|
||||||
Ok(lhs)
|
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, ()> {
|
fn assignment(&mut self, name: Token<'a>, export: bool) -> CompilationResult<'a, ()> {
|
||||||
if self.assignments.contains_key(name.lexeme) {
|
if self.assignments.contains_key(name.lexeme) {
|
||||||
return Err(name.error(DuplicateVariable {variable: name.lexeme}));
|
return Err(name.error(DuplicateVariable {variable: name.lexeme}));
|
||||||
@ -297,7 +309,12 @@ impl<'a> Parser<'a> {
|
|||||||
if export {
|
if export {
|
||||||
self.exports.insert(name.lexeme);
|
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.assignments.insert(name.lexeme, expression);
|
||||||
self.assignment_tokens.insert(name.lexeme, name);
|
self.assignment_tokens.insert(name.lexeme, name);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -606,6 +623,32 @@ c = a + b + a + b",
|
|||||||
{{b}} {{c}}",
|
{{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! {
|
compilation_error_test! {
|
||||||
name: missing_colon,
|
name: missing_colon,
|
||||||
input: "a b c\nd e f",
|
input: "a b c\nd e f",
|
||||||
@ -773,7 +816,7 @@ c = a + b + a + b",
|
|||||||
line: 1,
|
line: 1,
|
||||||
column: 12,
|
column: 12,
|
||||||
width: Some(0),
|
width: Some(0),
|
||||||
kind: UnexpectedToken{expected: vec![Plus, Eol, InterpolationEnd], found: Dedent},
|
kind: UnexpectedToken{expected: vec![Plus, InterpolationEnd], found: Dedent},
|
||||||
}
|
}
|
||||||
|
|
||||||
compilation_error_test! {
|
compilation_error_test! {
|
||||||
@ -783,7 +826,7 @@ c = a + b + a + b",
|
|||||||
line: 0,
|
line: 0,
|
||||||
column: 8,
|
column: 8,
|
||||||
width: Some(0),
|
width: Some(0),
|
||||||
kind: UnexpectedToken{expected: vec![ParenR], found: Eof},
|
kind: UnexpectedToken{expected: vec![Name, StringToken, ParenR], found: Eof},
|
||||||
}
|
}
|
||||||
|
|
||||||
compilation_error_test! {
|
compilation_error_test! {
|
||||||
@ -793,7 +836,7 @@ c = a + b + a + b",
|
|||||||
line: 1,
|
line: 1,
|
||||||
column: 12,
|
column: 12,
|
||||||
width: Some(2),
|
width: Some(2),
|
||||||
kind: UnexpectedToken{expected: vec![ParenR], found: InterpolationEnd},
|
kind: UnexpectedToken{expected: vec![Name, StringToken, ParenR], found: InterpolationEnd},
|
||||||
}
|
}
|
||||||
|
|
||||||
compilation_error_test! {
|
compilation_error_test! {
|
||||||
|
@ -41,8 +41,8 @@ impl<'a, 'b> RecipeResolver<'a, 'b> {
|
|||||||
for line in &recipe.lines {
|
for line in &recipe.lines {
|
||||||
for fragment in line {
|
for fragment in line {
|
||||||
if let Fragment::Expression{ref expression, ..} = *fragment {
|
if let Fragment::Expression{ref expression, ..} = *fragment {
|
||||||
for function in expression.functions() {
|
for (function, argc) in expression.functions() {
|
||||||
if let Err(error) = ::functions::resolve_function(function) {
|
if let Err(error) = ::functions::resolve_function(function, argc) {
|
||||||
return Err(CompilationError {
|
return Err(CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: error.index,
|
index: error.index,
|
||||||
|
@ -25,14 +25,15 @@ pub enum RuntimeError<'a> {
|
|||||||
Backtick{token: Token<'a>, output_error: OutputError},
|
Backtick{token: Token<'a>, output_error: OutputError},
|
||||||
Code{recipe: &'a str, line_number: Option<usize>, code: i32},
|
Code{recipe: &'a str, line_number: Option<usize>, code: i32},
|
||||||
Cygpath{recipe: &'a str, output_error: OutputError},
|
Cygpath{recipe: &'a str, output_error: OutputError},
|
||||||
|
FunctionCall{token: Token<'a>, message: String},
|
||||||
Internal{message: String},
|
Internal{message: String},
|
||||||
IoError{recipe: &'a str, io_error: io::Error},
|
IoError{recipe: &'a str, io_error: io::Error},
|
||||||
Shebang{recipe: &'a str, command: String, argument: Option<String>, 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},
|
Signal{recipe: &'a str, line_number: Option<usize>, signal: i32},
|
||||||
TmpdirIoError{recipe: &'a str, io_error: io::Error},
|
TmpdirIoError{recipe: &'a str, io_error: io::Error},
|
||||||
Unknown{recipe: &'a str, line_number: Option<usize>},
|
|
||||||
UnknownOverrides{overrides: Vec<&'a str>},
|
UnknownOverrides{overrides: Vec<&'a str>},
|
||||||
UnknownRecipes{recipes: Vec<&'a str>, suggestion: Option<&'a str>},
|
UnknownRecipes{recipes: Vec<&'a str>, suggestion: Option<&'a str>},
|
||||||
|
Unknown{recipe: &'a str, line_number: Option<usize>},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RuntimeError<'a> {
|
impl<'a> RuntimeError<'a> {
|
||||||
@ -117,6 +118,10 @@ impl<'a> Display for RuntimeError<'a> {
|
|||||||
but output was not utf8: {}", recipe, utf8_error)?;
|
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} => {
|
Shebang{recipe, ref command, ref argument, ref io_error} => {
|
||||||
if let Some(ref argument) = *argument {
|
if let Some(ref argument) = *argument {
|
||||||
write!(f, "Recipe `{}` with shebang `#!{} {}` execution error: {}",
|
write!(f, "Recipe `{}` with shebang `#!{} {}` execution error: {}",
|
||||||
@ -161,11 +166,11 @@ impl<'a> Display for RuntimeError<'a> {
|
|||||||
error_token = Some(token);
|
error_token = Some(token);
|
||||||
}
|
}
|
||||||
OutputError::Signal(signal) => {
|
OutputError::Signal(signal) => {
|
||||||
write!(f, "Backtick was terminated by signal {}", signal)?;
|
write!(f, "Backtick was terminated by signal {}\n", signal)?;
|
||||||
error_token = Some(token);
|
error_token = Some(token);
|
||||||
}
|
}
|
||||||
OutputError::Unknown => {
|
OutputError::Unknown => {
|
||||||
write!(f, "Backtick failed for an unknown reason")?;
|
write!(f, "Backtick failed for an unknown reason\n")?;
|
||||||
error_token = Some(token);
|
error_token = Some(token);
|
||||||
}
|
}
|
||||||
OutputError::Io(ref io_error) => {
|
OutputError::Io(ref io_error) => {
|
||||||
@ -181,7 +186,7 @@ impl<'a> Display for RuntimeError<'a> {
|
|||||||
error_token = Some(token);
|
error_token = Some(token);
|
||||||
}
|
}
|
||||||
OutputError::Utf8(ref utf8_error) => {
|
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);
|
error_token = Some(token);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -29,6 +29,7 @@ pub enum TokenKind {
|
|||||||
At,
|
At,
|
||||||
Backtick,
|
Backtick,
|
||||||
Colon,
|
Colon,
|
||||||
|
Comma,
|
||||||
Comment,
|
Comment,
|
||||||
Dedent,
|
Dedent,
|
||||||
Eof,
|
Eof,
|
||||||
@ -53,6 +54,7 @@ impl Display for TokenKind {
|
|||||||
write!(f, "{}", match *self {
|
write!(f, "{}", match *self {
|
||||||
Backtick => "backtick",
|
Backtick => "backtick",
|
||||||
Colon => "':'",
|
Colon => "':'",
|
||||||
|
Comma => "','",
|
||||||
Comment => "comment",
|
Comment => "comment",
|
||||||
Dedent => "dedent",
|
Dedent => "dedent",
|
||||||
Eof => "end of file",
|
Eof => "end of file",
|
||||||
|
@ -1203,6 +1203,34 @@ foo:
|
|||||||
status: EXIT_SUCCESS,
|
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! {
|
integration_test! {
|
||||||
name: quiet_recipe,
|
name: quiet_recipe,
|
||||||
|
Loading…
Reference in New Issue
Block a user