use std::collections::HashMap;
use std::time::Duration;
pub const DEFAULT_PREFIX: &str = "EXPECT";
#[derive(Debug, Clone)]
pub struct EnvConfig {
prefix: String,
cache: HashMap<String, String>,
}
impl Default for EnvConfig {
fn default() -> Self {
Self::new(DEFAULT_PREFIX)
}
}
impl EnvConfig {
#[must_use]
pub fn new(prefix: impl Into<String>) -> Self {
Self {
prefix: prefix.into(),
cache: HashMap::new(),
}
}
#[must_use]
pub fn no_prefix() -> Self {
Self {
prefix: String::new(),
cache: HashMap::new(),
}
}
fn var_name(&self, name: &str) -> String {
if self.prefix.is_empty() {
name.to_uppercase()
} else {
format!("{}_{}", self.prefix, name.to_uppercase())
}
}
#[must_use]
pub fn get(&self, name: &str) -> Option<String> {
let var_name = self.var_name(name);
std::env::var(&var_name).ok()
}
#[must_use]
pub fn get_or(&self, name: &str, default: &str) -> String {
self.get(name).unwrap_or_else(|| default.to_string())
}
#[must_use]
pub fn parse<T: std::str::FromStr>(&self, name: &str) -> Option<T> {
self.get(name).and_then(|v| v.parse().ok())
}
#[must_use]
pub fn parse_or<T: std::str::FromStr>(&self, name: &str, default: T) -> T {
self.parse(name).unwrap_or(default)
}
#[must_use]
pub fn bool(&self, name: &str) -> Option<bool> {
self.get(name).map(|v| {
matches!(
v.to_lowercase().as_str(),
"1" | "true" | "yes" | "on" | "enabled"
)
})
}
#[must_use]
pub fn bool_or(&self, name: &str, default: bool) -> bool {
self.bool(name).unwrap_or(default)
}
#[must_use]
pub fn duration_secs(&self, name: &str) -> Option<Duration> {
self.parse::<u64>(name).map(Duration::from_secs)
}
#[must_use]
pub fn duration_millis(&self, name: &str) -> Option<Duration> {
self.parse::<u64>(name).map(Duration::from_millis)
}
#[must_use]
pub fn all(&self) -> HashMap<String, String> {
let mut result = HashMap::new();
let prefix_upper = format!("{}_", self.prefix.to_uppercase());
for (key, value) in std::env::vars() {
if self.prefix.is_empty() || key.starts_with(&prefix_upper) {
let key = if self.prefix.is_empty() {
key
} else {
key[prefix_upper.len()..].to_string()
};
result.insert(key, value);
}
}
result
}
#[must_use]
pub fn is_set(&self, name: &str) -> bool {
self.get(name).is_some()
}
pub fn set(&mut self, name: &str, value: impl Into<String>) {
let var_name = self.var_name(name);
std::env::set_var(&var_name, value.into());
}
pub fn unset(&mut self, name: &str) {
let var_name = self.var_name(name);
std::env::remove_var(&var_name);
}
}
pub mod vars {
pub const TIMEOUT: &str = "TIMEOUT";
pub const DEBUG: &str = "DEBUG";
pub const LOG_LEVEL: &str = "LOG_LEVEL";
pub const SHELL: &str = "SHELL";
pub const TERM: &str = "TERM";
pub const COLUMNS: &str = "COLUMNS";
pub const LINES: &str = "LINES";
pub const HOME: &str = "HOME";
pub const USER: &str = "USER";
pub const PATH: &str = "PATH";
}
#[must_use]
pub fn get_env(name: &str) -> Option<String> {
std::env::var(name).ok()
}
#[must_use]
pub fn current_user() -> Option<String> {
get_env("USER").or_else(|| get_env("USERNAME"))
}
#[must_use]
pub fn home_dir() -> Option<std::path::PathBuf> {
get_env("HOME")
.or_else(|| get_env("USERPROFILE"))
.map(std::path::PathBuf::from)
}
#[must_use]
pub fn current_shell() -> Option<String> {
get_env("SHELL")
}
#[must_use]
pub fn term_type() -> Option<String> {
get_env("TERM")
}
#[must_use]
pub fn subprocess_env() -> HashMap<String, String> {
let mut env = HashMap::new();
for var in ["PATH", "HOME", "USER", "TERM", "LANG", "LC_ALL", "SHELL"] {
if let Some(value) = get_env(var) {
env.insert(var.to_string(), value);
}
}
env
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn env_config_prefix() {
let config = EnvConfig::new("TEST");
assert_eq!(config.var_name("foo"), "TEST_FOO");
assert_eq!(config.var_name("bar_baz"), "TEST_BAR_BAZ");
}
#[test]
fn env_config_no_prefix() {
let config = EnvConfig::no_prefix();
assert_eq!(config.var_name("foo"), "FOO");
}
#[test]
fn env_bool_parsing() {
let mut config = EnvConfig::new("TEST");
config.set("ENABLED", "true");
config.set("DISABLED", "false");
assert_eq!(config.bool("ENABLED"), Some(true));
assert_eq!(config.bool("DISABLED"), Some(false));
config.unset("ENABLED");
config.unset("DISABLED");
}
#[test]
fn subprocess_env_has_path() {
let env = subprocess_env();
if std::env::var("PATH").is_ok() {
assert!(env.contains_key("PATH"));
}
}
}