use std::collections::BTreeMap;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
#[derive(Clone, PartialEq, Eq)]
pub struct Environment {
home: PathBuf,
vars: BTreeMap<String, OsString>,
}
impl std::fmt::Debug for Environment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Environment")
.field("home", &self.home)
.field("vars", &self.vars.keys().collect::<Vec<_>>())
.finish()
}
}
impl Environment {
#[must_use]
pub fn new(home: impl Into<PathBuf>) -> Self {
Self {
home: home.into(),
vars: BTreeMap::new(),
}
}
#[must_use]
pub fn with_var(mut self, name: impl Into<String>, value: impl Into<OsString>) -> Self {
self.vars.insert(name.into(), value.into());
self
}
#[must_use]
pub fn from_process() -> Option<Self> {
let home = std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.filter(|value| !value.is_empty())?;
let vars = std::env::vars_os()
.filter_map(|(name, value)| Some((name.into_string().ok()?, value)))
.collect();
Some(Self {
home: PathBuf::from(home),
vars,
})
}
#[must_use]
pub fn home(&self) -> &Path {
&self.home
}
#[must_use]
pub fn var(&self, name: &str) -> Option<&OsString> {
self.vars.get(name).filter(|value| !value.is_empty())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn debug_shows_variable_names_not_values() {
let env = Environment::new("/home/x").with_var("API_TOKEN", "s3cr3t-value");
let shown = format!("{env:?}");
assert!(shown.contains("API_TOKEN"), "{shown}");
assert!(!shown.contains("s3cr3t-value"), "{shown}");
}
}