diff --git a/src/assignment_evaluator.rs b/src/assignment_evaluator.rs index 51ec54e..a439d2b 100644 --- a/src/assignment_evaluator.rs +++ b/src/assignment_evaluator.rs @@ -83,7 +83,7 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> { fn evaluate_expression( &mut self, expression: &Expression<'a>, - arguments: &Map<&str, Cow> + arguments: &Map<&str, Cow> ) -> RunResult<'a, String> { match *expression { Expression::Variable{name, ..} => { @@ -106,7 +106,10 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> { let call_arguments = call_arguments.iter().map(|argument| { self.evaluate_expression(argument, arguments) }).collect::, RuntimeError>>()?; - ::functions::evaluate_function(token, name, &call_arguments) + let context = FunctionContext { + dotenv: self.dotenv, + }; + evaluate_function(token, name, &context, &call_arguments) } Expression::String{ref cooked_string} => Ok(cooked_string.cooked.clone()), Expression::Backtick{raw, ref token} => { diff --git a/src/assignment_resolver.rs b/src/assignment_resolver.rs index 07abbbe..88c1c5b 100644 --- a/src/assignment_resolver.rs +++ b/src/assignment_resolver.rs @@ -76,7 +76,7 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> { } } Expression::Call{ref token, ref arguments, ..} => { - ::functions::resolve_function(token, arguments.len())? + resolve_function(token, arguments.len())? } Expression::Concatination{ref lhs, ref rhs} => { self.resolve_expression(lhs)?; diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 0000000..aca73f8 --- /dev/null +++ b/src/common.rs @@ -0,0 +1,35 @@ +pub use std::borrow::Cow; +pub use std::collections::{BTreeMap as Map, BTreeSet as Set}; +pub use std::fmt::Display; +pub use std::io::prelude::*; +pub use std::ops::Range; +pub use std::path::{Path, PathBuf}; +pub use std::process::Command; +pub use std::{cmp, env, fs, fmt, io, iter, process, vec, usize}; + +pub use color::Color; +pub use libc::{EXIT_FAILURE, EXIT_SUCCESS}; +pub use regex::Regex; +pub use tempdir::TempDir; + +pub use assignment_evaluator::AssignmentEvaluator; +pub use assignment_resolver::AssignmentResolver; +pub use command_ext::CommandExt; +pub use compilation_error::{CompilationError, CompilationErrorKind, CompilationResult}; +pub use configuration::Configuration; +pub use cooked_string::CookedString; +pub use expression::Expression; +pub use fragment::Fragment; +pub use function::{evaluate_function, resolve_function, FunctionContext}; +pub use justfile::Justfile; +pub use lexer::Lexer; +pub use load_dotenv::load_dotenv; +pub use misc::{default, empty}; +pub use parameter::Parameter; +pub use parser::Parser; +pub use range_ext::RangeExt; +pub use recipe::Recipe; +pub use recipe_resolver::RecipeResolver; +pub use runtime_error::{RuntimeError, RunResult}; +pub use shebang::Shebang; +pub use token::{Token, TokenKind}; diff --git a/src/functions.rs b/src/function.rs similarity index 71% rename from src/functions.rs rename to src/function.rs index 2678567..4e4ebc4 100644 --- a/src/functions.rs +++ b/src/function.rs @@ -12,9 +12,9 @@ lazy_static! { } enum Function { - Nullary(fn( ) -> Result), - Unary (fn(&str ) -> Result), - Binary (fn(&str, &str) -> Result), + Nullary(fn(&FunctionContext, ) -> Result), + Unary (fn(&FunctionContext, &str ) -> Result), + Binary (fn(&FunctionContext, &str, &str) -> Result), } impl Function { @@ -28,6 +28,10 @@ impl Function { } } +pub struct FunctionContext<'a> { + pub dotenv: &'a Map, +} + pub fn resolve_function<'a>(token: &Token<'a>, argc: usize) -> CompilationResult<'a, ()> { let name = token.lexeme; if let Some(function) = FUNCTIONS.get(&name) { @@ -48,19 +52,20 @@ pub fn resolve_function<'a>(token: &Token<'a>, argc: usize) -> CompilationResult } pub fn evaluate_function<'a>( - token: &Token<'a>, - name: &'a str, + token: &Token<'a>, + name: &'a str, + context: &FunctionContext, 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() + (&Nullary(f), 0) => f(context) .map_err(|message| RuntimeError::FunctionCall{token: token.clone(), message}), - (&Unary(f), 1) => f(&arguments[0]) + (&Unary(f), 1) => f(context, &arguments[0]) .map_err(|message| RuntimeError::FunctionCall{token: token.clone(), message}), - (&Binary(f), 2) => f(&arguments[0], &arguments[1]) + (&Binary(f), 2) => f(context, &arguments[0], &arguments[1]) .map_err(|message| RuntimeError::FunctionCall{token: token.clone(), message}), _ => { Err(RuntimeError::Internal { @@ -75,20 +80,25 @@ pub fn evaluate_function<'a>( } } -pub fn arch() -> Result { +pub fn arch(_context: &FunctionContext) -> Result { Ok(target::arch().to_string()) } -pub fn os() -> Result { +pub fn os(_context: &FunctionContext) -> Result { Ok(target::os().to_string()) } -pub fn os_family() -> Result { +pub fn os_family(_context: &FunctionContext) -> Result { Ok(target::os_family().to_string()) } -pub fn env_var(key: &str) -> Result { +pub fn env_var(context: &FunctionContext, key: &str) -> Result { use std::env::VarError::*; + + if let Some(value) = context.dotenv.get(key) { + return Ok(value.clone()); + } + match env::var(key) { Err(NotPresent) => Err(format!("environment variable `{}` not present", key)), Err(NotUnicode(os_string)) => @@ -97,7 +107,15 @@ pub fn env_var(key: &str) -> Result { } } -pub fn env_var_or_default(key: &str, default: &str) -> Result { +pub fn env_var_or_default( + context: &FunctionContext, + key: &str, + default: &str, +) -> Result { + if let Some(value) = context.dotenv.get(key) { + return Ok(value.clone()); + } + use std::env::VarError::*; match env::var(key) { Err(NotPresent) => Ok(default.to_string()), diff --git a/src/main.rs b/src/main.rs index 816e0fb..b511615 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,15 +20,16 @@ mod assignment_evaluator; mod assignment_resolver; mod color; mod command_ext; +mod common; mod compilation_error; mod configuration; mod cooked_string; -mod load_dotenv; mod expression; mod fragment; -mod functions; +mod function; mod justfile; mod lexer; +mod load_dotenv; mod misc; mod parameter; mod parser; @@ -41,43 +42,6 @@ mod runtime_error; mod shebang; mod token; -mod common { - pub use std::borrow::Cow; - pub use std::collections::{BTreeMap as Map, BTreeSet as Set}; - pub use std::fmt::Display; - pub use std::io::prelude::*; - pub use std::ops::Range; - pub use std::path::{Path, PathBuf}; - pub use std::process::Command; - pub use std::{cmp, env, fs, fmt, io, iter, process, vec, usize}; - - pub use color::Color; - pub use libc::{EXIT_FAILURE, EXIT_SUCCESS}; - pub use regex::Regex; - pub use tempdir::TempDir; - - pub use assignment_evaluator::AssignmentEvaluator; - pub use assignment_resolver::AssignmentResolver; - pub use command_ext::CommandExt; - pub use compilation_error::{CompilationError, CompilationErrorKind, CompilationResult}; - pub use configuration::Configuration; - pub use cooked_string::CookedString; - pub use expression::Expression; - pub use fragment::Fragment; - pub use justfile::Justfile; - pub use lexer::Lexer; - pub use load_dotenv::load_dotenv; - pub use misc::{default, empty}; - pub use parameter::Parameter; - pub use parser::Parser; - pub use range_ext::RangeExt; - pub use recipe::Recipe; - pub use recipe_resolver::RecipeResolver; - pub use runtime_error::{RuntimeError, RunResult}; - pub use shebang::Shebang; - pub use token::{Token, TokenKind}; -} - use common::*; fn main() { diff --git a/src/recipe_resolver.rs b/src/recipe_resolver.rs index 415eef6..1291780 100644 --- a/src/recipe_resolver.rs +++ b/src/recipe_resolver.rs @@ -42,7 +42,7 @@ impl<'a, 'b> RecipeResolver<'a, 'b> { for fragment in line { if let Fragment::Expression{ref expression, ..} = *fragment { for (function, argc) in expression.functions() { - if let Err(error) = ::functions::resolve_function(function, argc) { + if let Err(error) = resolve_function(function, argc) { return Err(CompilationError { index: error.index, line: error.line, diff --git a/tests/integration.rs b/tests/integration.rs index e98890e..cd99977 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1771,3 +1771,32 @@ echo: stderr: "echo dotenv-value\n", status: EXIT_SUCCESS, } +integration_test! { + name: dotenv_variable_in_function_in_recipe, + justfile: " +# +echo: + echo {{env_var_or_default('DOTENV_KEY', 'foo')}} + echo {{env_var('DOTENV_KEY')}} + ", + args: (), + stdout: "dotenv-value\ndotenv-value\n", + stderr: "echo dotenv-value\necho dotenv-value\n", + status: EXIT_SUCCESS, +} + +integration_test! { + name: dotenv_variable_in_function_in_backtick, + justfile: " +# +X=env_var_or_default('DOTENV_KEY', 'foo') +Y=env_var('DOTENV_KEY') +echo: + echo {{X}} + echo {{Y}} + ", + args: (), + stdout: "dotenv-value\ndotenv-value\n", + stderr: "echo dotenv-value\necho dotenv-value\n", + status: EXIT_SUCCESS, +}