Make .env vars available in env_var functions (#310)

This commit is contained in:
Casey Rodarmor 2018-03-17 09:17:41 -07:00 committed by GitHub
parent 70e96d52eb
commit 68b343bc17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 105 additions and 56 deletions

View File

@ -106,7 +106,10 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> {
let call_arguments = call_arguments.iter().map(|argument| { let call_arguments = call_arguments.iter().map(|argument| {
self.evaluate_expression(argument, arguments) self.evaluate_expression(argument, arguments)
}).collect::<Result<Vec<String>, RuntimeError>>()?; }).collect::<Result<Vec<String>, 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::String{ref cooked_string} => Ok(cooked_string.cooked.clone()),
Expression::Backtick{raw, ref token} => { Expression::Backtick{raw, ref token} => {

View File

@ -76,7 +76,7 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
} }
} }
Expression::Call{ref token, ref arguments, ..} => { Expression::Call{ref token, ref arguments, ..} => {
::functions::resolve_function(token, arguments.len())? 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)?;

35
src/common.rs Normal file
View File

@ -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};

View File

@ -12,9 +12,9 @@ lazy_static! {
} }
enum Function { enum Function {
Nullary(fn( ) -> Result<String, String>), Nullary(fn(&FunctionContext, ) -> Result<String, String>),
Unary (fn(&str ) -> Result<String, String>), Unary (fn(&FunctionContext, &str ) -> Result<String, String>),
Binary (fn(&str, &str) -> Result<String, String>), Binary (fn(&FunctionContext, &str, &str) -> Result<String, String>),
} }
impl Function { impl Function {
@ -28,6 +28,10 @@ impl Function {
} }
} }
pub struct FunctionContext<'a> {
pub dotenv: &'a Map<String, String>,
}
pub fn resolve_function<'a>(token: &Token<'a>, argc: usize) -> CompilationResult<'a, ()> { pub fn resolve_function<'a>(token: &Token<'a>, argc: usize) -> CompilationResult<'a, ()> {
let name = token.lexeme; let name = token.lexeme;
if let Some(function) = FUNCTIONS.get(&name) { if let Some(function) = FUNCTIONS.get(&name) {
@ -50,17 +54,18 @@ pub fn resolve_function<'a>(token: &Token<'a>, argc: usize) -> CompilationResult
pub fn evaluate_function<'a>( pub fn evaluate_function<'a>(
token: &Token<'a>, token: &Token<'a>,
name: &'a str, name: &'a str,
context: &FunctionContext,
arguments: &[String] arguments: &[String]
) -> RunResult<'a, String> { ) -> RunResult<'a, String> {
if let Some(function) = FUNCTIONS.get(name) { if let Some(function) = FUNCTIONS.get(name) {
use self::Function::*; use self::Function::*;
let argc = arguments.len(); let argc = arguments.len();
match (function, argc) { match (function, argc) {
(&Nullary(f), 0) => f() (&Nullary(f), 0) => f(context)
.map_err(|message| RuntimeError::FunctionCall{token: token.clone(), message}), .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}), .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}), .map_err(|message| RuntimeError::FunctionCall{token: token.clone(), message}),
_ => { _ => {
Err(RuntimeError::Internal { Err(RuntimeError::Internal {
@ -75,20 +80,25 @@ pub fn evaluate_function<'a>(
} }
} }
pub fn arch() -> Result<String, String> { pub fn arch(_context: &FunctionContext) -> Result<String, String> {
Ok(target::arch().to_string()) Ok(target::arch().to_string())
} }
pub fn os() -> Result<String, String> { pub fn os(_context: &FunctionContext) -> Result<String, String> {
Ok(target::os().to_string()) Ok(target::os().to_string())
} }
pub fn os_family() -> Result<String, String> { pub fn os_family(_context: &FunctionContext) -> Result<String, String> {
Ok(target::os_family().to_string()) Ok(target::os_family().to_string())
} }
pub fn env_var(key: &str) -> Result<String, String> { pub fn env_var(context: &FunctionContext, key: &str) -> Result<String, String> {
use std::env::VarError::*; use std::env::VarError::*;
if let Some(value) = context.dotenv.get(key) {
return Ok(value.clone());
}
match env::var(key) { match env::var(key) {
Err(NotPresent) => Err(format!("environment variable `{}` not present", key)), Err(NotPresent) => Err(format!("environment variable `{}` not present", key)),
Err(NotUnicode(os_string)) => Err(NotUnicode(os_string)) =>
@ -97,7 +107,15 @@ pub fn env_var(key: &str) -> Result<String, String> {
} }
} }
pub fn env_var_or_default(key: &str, default: &str) -> Result<String, String> { pub fn env_var_or_default(
context: &FunctionContext,
key: &str,
default: &str,
) -> Result<String, String> {
if let Some(value) = context.dotenv.get(key) {
return Ok(value.clone());
}
use std::env::VarError::*; use std::env::VarError::*;
match env::var(key) { match env::var(key) {
Err(NotPresent) => Ok(default.to_string()), Err(NotPresent) => Ok(default.to_string()),

View File

@ -20,15 +20,16 @@ mod assignment_evaluator;
mod assignment_resolver; mod assignment_resolver;
mod color; mod color;
mod command_ext; mod command_ext;
mod common;
mod compilation_error; mod compilation_error;
mod configuration; mod configuration;
mod cooked_string; mod cooked_string;
mod load_dotenv;
mod expression; mod expression;
mod fragment; mod fragment;
mod functions; mod function;
mod justfile; mod justfile;
mod lexer; mod lexer;
mod load_dotenv;
mod misc; mod misc;
mod parameter; mod parameter;
mod parser; mod parser;
@ -41,43 +42,6 @@ mod runtime_error;
mod shebang; mod shebang;
mod token; 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::*; use common::*;
fn main() { fn main() {

View File

@ -42,7 +42,7 @@ impl<'a, 'b> RecipeResolver<'a, 'b> {
for fragment in line { for fragment in line {
if let Fragment::Expression{ref expression, ..} = *fragment { if let Fragment::Expression{ref expression, ..} = *fragment {
for (function, argc) in expression.functions() { 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 { return Err(CompilationError {
index: error.index, index: error.index,
line: error.line, line: error.line,

View File

@ -1771,3 +1771,32 @@ echo:
stderr: "echo dotenv-value\n", stderr: "echo dotenv-value\n",
status: EXIT_SUCCESS, 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,
}