use crate::common::*; use target; use crate::platform::{Platform, PlatformInterface}; lazy_static! { static ref FUNCTIONS: BTreeMap<&'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)), ( "invocation_directory", Function::Nullary(invocation_directory) ), ] .into_iter() .collect(); } enum Function { Nullary(fn(&FunctionContext) -> Result), Unary(fn(&FunctionContext, &str) -> Result), Binary(fn(&FunctionContext, &str, &str) -> Result), } impl Function { fn argc(&self) -> usize { use self::Function::*; match *self { Nullary(_) => 0, Unary(_) => 1, Binary(_) => 2, } } } pub struct FunctionContext<'a> { pub invocation_directory: &'a Result, pub dotenv: &'a BTreeMap, } 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) | (&Unary(_), 1) | (&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, 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(context).map_err(|message| RuntimeError::FunctionCall { token: token.clone(), message, }), (&Unary(f), 1) => f(context, &arguments[0]).map_err(|message| RuntimeError::FunctionCall { token: token.clone(), message, }), (&Binary(f), 2) => { f(context, &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(_context: &FunctionContext) -> Result { Ok(target::arch().to_string()) } pub fn os(_context: &FunctionContext) -> Result { Ok(target::os().to_string()) } pub fn os_family(_context: &FunctionContext) -> Result { Ok(target::os_family().to_string()) } pub fn invocation_directory(context: &FunctionContext) -> Result { context.invocation_directory.clone().and_then(|s| { Platform::to_shell_path(&s).map_err(|e| format!("Error getting shell path: {}", e)) }) } 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)) => Err(format!( "environment variable `{}` not unicode: {:?}", key, os_string )), Ok(value) => Ok(value), } } 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()), Err(NotUnicode(os_string)) => Err(format!( "environment variable `{}` not unicode: {:?}", key, os_string )), Ok(value) => Ok(value), } }