use super::metadata::{ArgumentMetadata, FunctionMetadata, SyntaxVariants};
use super::traits::Function;
use minijinja::value::Kwargs;
use minijinja::{Error, ErrorKind, Value};
use std::collections::HashMap;
pub struct GetEnv;
impl Function for GetEnv {
const NAME: &'static str = "get_env";
const METADATA: FunctionMetadata = FunctionMetadata {
name: "get_env",
category: "environment",
description: "Get environment variable with optional default value",
arguments: &[
ArgumentMetadata {
name: "name",
arg_type: "string",
required: true,
default: None,
description: "Environment variable name",
},
ArgumentMetadata {
name: "default",
arg_type: "string",
required: false,
default: None,
description: "Default value if variable is not set",
},
],
return_type: "string",
examples: &[
"{{ get_env(name=\"HOME\") }}",
"{{ get_env(name=\"PORT\", default=\"8080\") }}",
],
syntax: SyntaxVariants::FUNCTION_ONLY,
};
fn call(kwargs: Kwargs) -> Result<Value, Error> {
let name: String = kwargs.get("name")?;
let default: Option<String> = kwargs.get("default").ok();
match std::env::var(&name) {
Ok(value) => Ok(Value::from(value)),
Err(_) => {
if let Some(def) = default {
Ok(Value::from(def))
} else {
Err(Error::new(
ErrorKind::UndefinedError,
format!(
"Environment variable '{}' is not set and no default provided",
name
),
))
}
}
}
}
}
pub struct FilterEnv;
impl Function for FilterEnv {
const NAME: &'static str = "filter_env";
const METADATA: FunctionMetadata = FunctionMetadata {
name: "filter_env",
category: "environment",
description: "Filter environment variables by glob pattern",
arguments: &[ArgumentMetadata {
name: "pattern",
arg_type: "string",
required: true,
default: None,
description: "Glob pattern to match variable names (e.g., \"SERVER_*\", \"DB_*\")",
}],
return_type: "array",
examples: &[
"{% for var in filter_env(pattern=\"SERVER_*\") %}{{ var.key }}={{ var.value }}{% endfor %}",
"{{ filter_env(pattern=\"DB_*\") }}",
],
syntax: SyntaxVariants::FUNCTION_ONLY,
};
fn call(kwargs: Kwargs) -> Result<Value, Error> {
let pattern: String = kwargs.get("pattern")?;
let regex_pattern = glob_to_regex(&pattern);
let re = regex::Regex::new(®ex_pattern).map_err(|e| {
Error::new(
ErrorKind::InvalidOperation,
format!("Invalid pattern: {}", e),
)
})?;
let mut results: Vec<HashMap<String, String>> = std::env::vars()
.filter(|(key, _)| re.is_match(key))
.map(|(key, value)| {
let mut map = HashMap::new();
map.insert("key".to_string(), key);
map.insert("value".to_string(), value);
map
})
.collect();
results.sort_by(|a, b| a.get("key").cmp(&b.get("key")));
Ok(Value::from_serialize(&results))
}
}
fn glob_to_regex(pattern: &str) -> String {
let mut regex = String::from("^");
for ch in pattern.chars() {
match ch {
'*' => regex.push_str(".*"),
'?' => regex.push('.'),
'.' | '+' | '^' | '$' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '\\' => {
regex.push('\\');
regex.push(ch);
}
_ => regex.push(ch),
}
}
regex.push('$');
regex
}