use std::collections::{BTreeMap, BTreeSet};
use std::ffi::OsString;
use std::fmt;
#[derive(Clone, PartialEq, Eq, Default)]
pub struct Environment {
variables: BTreeMap<String, String>,
unusable: BTreeSet<String>,
}
impl Environment {
#[must_use]
pub fn from_process() -> Self {
Self::from_os_pairs(std::env::vars_os())
}
#[must_use]
pub fn from_os_pairs(pairs: impl IntoIterator<Item = (OsString, OsString)>) -> Self {
let mut variables = BTreeMap::new();
let mut unusable = BTreeSet::new();
for (name, value) in pairs {
let Ok(name) = name.into_string() else {
continue;
};
match value.into_string() {
Ok(value) => {
variables.insert(name, value);
}
Err(_) => {
unusable.insert(name);
}
}
}
Self {
variables,
unusable,
}
}
#[must_use]
pub fn from_pairs<K, V>(pairs: impl IntoIterator<Item = (K, V)>) -> Self
where
K: Into<String>,
V: Into<String>,
{
Self {
variables: pairs
.into_iter()
.map(|(name, value)| (name.into(), value.into()))
.collect(),
unusable: BTreeSet::new(),
}
}
pub fn unusable(&self) -> impl Iterator<Item = &str> {
self.unusable.iter().map(String::as_str)
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&str> {
self.variables.get(name).map(String::as_str)
}
#[must_use]
pub fn non_empty(&self, name: &str) -> Option<&str> {
self.get(name).filter(|value| !value.is_empty())
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.variables
.iter()
.map(|(name, value)| (name.as_str(), value.as_str()))
}
pub fn names(&self) -> impl Iterator<Item = &str> {
self.variables.keys().map(String::as_str)
}
}
impl fmt::Debug for Environment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Environment")
.field("variables", &self.names().collect::<Vec<_>>())
.field("unusable", &self.unusable().collect::<Vec<_>>())
.field("values", &"<redacted>")
.finish()
}
}