use std::fmt;
use crate::{Profile, Provider, Metadata};
use crate::coalesce::Coalescible;
use crate::value::{Map, Dict};
use crate::error::Error;
use crate::util::nest;
use uncased::{Uncased, UncasedStr};
crate::util::cloneable_fn_trait!(
FilterMap: for<'a> Fn(&'a UncasedStr) -> Option<Uncased<'a>> + 'static
);
#[derive(Clone)]
#[cfg_attr(nightly, doc(cfg(feature = "env")))]
pub struct Env {
filter_map: Box<dyn FilterMap>,
pub profile: Profile,
prefix: Option<String>,
lowercase: bool,
}
impl fmt::Debug for Env {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_set().entries(self.iter()).finish()
}
}
impl Env {
fn new() -> Self {
Env {
filter_map: Box::new(|key| Some(key.into())),
profile: Profile::Default,
prefix: None,
lowercase: true,
}
}
fn chain<F: Clone + 'static>(self, f: F) -> Self
where F: for<'a> Fn(Option<Uncased<'a>>) -> Option<Uncased<'a>>
{
let filter_map = self.filter_map;
Env {
filter_map: Box::new(move |key| f(filter_map(key))),
profile: self.profile,
prefix: self.prefix,
lowercase: true,
}
}
#[inline(always)]
pub fn raw() -> Self {
Env::new()
}
pub fn prefixed(prefix: &str) -> Self {
let owned_prefix = prefix.to_string();
let mut env = Env::new()
.filter_map(move |key| match key.starts_with(&owned_prefix) {
true => Some(key[owned_prefix.len()..].into()),
false => None
});
env.prefix = Some(prefix.into());
env
}
pub fn filter<F: Clone + 'static>(self, filter: F) -> Self
where F: Fn(&UncasedStr) -> bool
{
self.chain(move |prev| prev.filter(|v| filter(&v)))
}
pub fn map<F: Clone + 'static>(self, mapper: F) -> Self
where F: Fn(&UncasedStr) -> Uncased<'_>
{
self.chain(move |prev| prev.map(|v| mapper(&v).into_owned()))
}
pub fn filter_map<F: Clone + 'static>(self, f: F) -> Self
where F: Fn(&UncasedStr) -> Option<Uncased<'_>>
{
self.chain(move |prev| prev.and_then(|v| f(&v).map(|v| v.into_owned())))
}
pub fn lowercase(mut self, lowercase: bool) -> Self {
self.lowercase = lowercase;
self
}
pub fn split<P: Into<String>>(self, pattern: P) -> Self {
let pattern = pattern.into();
self.map(move |key| key.as_str().replace(&pattern, ".").into())
}
pub fn ignore(self, keys: &[&str]) -> Self {
let keys: Vec<String> = keys.iter().map(|s| s.to_string()).collect();
self.filter(move |key| !keys.iter().any(|k| k.as_str() == key))
}
pub fn only(self, keys: &[&str]) -> Self {
let keys: Vec<String> = keys.iter().map(|s| s.to_string()).collect();
self.filter(move |key| keys.iter().any(|k| k.as_str() == key))
}
pub fn iter<'a>(&'a self) -> impl Iterator<Item=(Uncased<'static>, String)> + 'a {
std::env::vars_os()
.filter(|(k, _)| !k.is_empty())
.filter_map(move |(k, v)| {
let key = k.to_string_lossy();
let key = (self.filter_map)(UncasedStr::new(key.trim()))?;
let key = key.as_str().trim();
if key.split('.').any(|s| s.is_empty()) { return None }
let key = match self.lowercase {
true => key.to_ascii_lowercase(),
false => key.to_owned(),
};
Some((key.into(), v.to_string_lossy().to_string()))
})
}
pub fn profile<P: Into<Profile>>(mut self, profile: P) -> Self {
self.profile = profile.into();
self
}
pub fn global(mut self) -> Self {
self.profile = Profile::Global;
self
}
pub fn var(name: &str) -> Option<String> {
for (env_key, val) in std::env::vars_os() {
let env_key = env_key.to_string_lossy();
if uncased::eq(env_key.trim(), name) {
return Some(val.to_string_lossy().trim().into());
}
}
None
}
pub fn var_or<S: Into<String>>(name: &str, default: S) -> String {
Self::var(name).unwrap_or_else(|| default.into())
}
}
impl Provider for Env {
fn metadata(&self) -> Metadata {
let mut md = Metadata::named("environment variable(s)")
.interpolater(move |_: &Profile, k: &[&str]| {
let keys: Vec<_> = k.iter()
.map(|k| k.to_ascii_uppercase())
.collect();
keys.join(".")
});
if let Some(prefix) = &self.prefix {
md.name = format!("`{}` {}", prefix.to_ascii_uppercase(), md.name).into();
}
md
}
fn data(&self) -> Result<Map<Profile, Dict>, Error> {
let mut dict = Dict::new();
for (k, v) in self.iter() {
let nested_dict = nest(k.as_str(), v.parse().expect("infallible"))
.into_dict()
.expect("key is non-empty: must have dict");
dict = dict.merge(nested_dict);
}
Ok(self.profile.collect(dict))
}
}