use RefCounted;
use env::SubEnvironment;
use std::borrow::{Borrow, Cow};
use std::collections::HashMap;
use std::fmt;
use std::hash::Hash;
use std::rc::Rc;
use std::sync::Arc;
pub trait VariableEnvironment {
type VarName: Eq + Hash;
type Var;
fn var<Q: ?Sized>(&self, name: &Q) -> Option<&Self::Var>
where Self::VarName: Borrow<Q>, Q: Hash + Eq;
fn set_var(&mut self, name: Self::VarName, val: Self::Var);
fn env_vars(&self) -> Cow<[(&Self::VarName, &Self::Var)]>;
}
impl<'a, T: ?Sized + VariableEnvironment> VariableEnvironment for &'a mut T {
type VarName = T::VarName;
type Var = T::Var;
fn var<Q: ?Sized>(&self, name: &Q) -> Option<&Self::Var>
where Self::VarName: Borrow<Q>, Q: Hash + Eq,
{
(**self).var(name)
}
fn set_var(&mut self, name: Self::VarName, val: Self::Var) {
(**self).set_var(name, val);
}
fn env_vars(&self) -> Cow<[(&Self::VarName, &Self::Var)]> {
(**self).env_vars()
}
}
pub trait ExportedVariableEnvironment: VariableEnvironment {
fn exported_var(&self, name: &Self::VarName) -> Option<(&Self::Var, bool)>;
fn set_exported_var(&mut self, name: Self::VarName, val: Self::Var, exported: bool);
}
impl<'a, T: ?Sized + ExportedVariableEnvironment> ExportedVariableEnvironment for &'a mut T {
fn exported_var(&self, name: &Self::VarName) -> Option<(&Self::Var, bool)> {
(**self).exported_var(name)
}
fn set_exported_var(&mut self, name: Self::VarName, val: Self::Var, exported: bool) {
(**self).set_exported_var(name, val, exported)
}
}
pub trait UnsetVariableEnvironment: VariableEnvironment {
fn unset_var<Q: ?Sized>(&mut self, name: &Q)
where Self::VarName: Borrow<Q>, Q: Hash + Eq;
}
impl<'a, T: ?Sized + UnsetVariableEnvironment> UnsetVariableEnvironment for &'a mut T {
fn unset_var<Q: ?Sized>(&mut self, name: &Q)
where Self::VarName: Borrow<Q>, Q: Hash + Eq,
{
(**self).unset_var(name);
}
}
macro_rules! impl_env {
($(#[$attr:meta])* pub struct $Env:ident, $Rc:ident) => {
$(#[$attr])*
#[derive(PartialEq, Eq)]
pub struct $Env<N: Eq + Hash, V> {
vars: $Rc<HashMap<N, (V, bool)>>,
}
impl<N: Eq + Hash, V> $Env<N, V> {
pub fn new() -> Self {
$Env {
vars: HashMap::new().into(),
}
}
pub fn with_process_env_vars() -> Self where N: From<String>, V: From<String> {
Self::with_env_vars(::std::env::vars().into_iter()
.map(|(k, v)| (k.into(), v.into())))
}
pub fn with_env_vars<I: IntoIterator<Item = (N, V)>>(iter: I) -> Self {
$Env {
vars: iter.into_iter()
.map(|(k, v)| (k, (v, true)))
.collect::<HashMap<_, _>>()
.into(),
}
}
}
impl<N: Eq + Hash + Clone, V: Clone + Eq> VariableEnvironment for $Env<N, V> {
type VarName = N;
type Var = V;
fn var<Q: ?Sized>(&self, name: &Q) -> Option<&Self::Var>
where Self::VarName: Borrow<Q>, Q: Hash + Eq,
{
self.vars.get(name).map(|&(ref val, _)| val)
}
fn set_var(&mut self, name: Self::VarName, val: Self::Var) {
let (needs_insert, exported) = match self.vars.get(&name) {
Some(&(ref existing_val, exported)) => (&val != existing_val, exported),
None => (true, false),
};
if needs_insert {
self.vars.make_mut().insert(name, (val, exported));
}
}
fn env_vars(&self) -> Cow<[(&Self::VarName, &Self::Var)]> {
let ret: Vec<_> = self.vars.iter()
.filter_map(|(k, &(ref v, exported))| if exported {
Some((k, v))
} else {
None
})
.collect();
Cow::Owned(ret)
}
}
impl<N: Eq + Hash + Clone, V: Clone + Eq> ExportedVariableEnvironment for $Env<N, V> {
fn exported_var(&self, name: &Self::VarName) -> Option<(&Self::Var, bool)> {
self.vars.get(name).map(|&(ref val, exported)| (val, exported))
}
fn set_exported_var(&mut self, name: Self::VarName, val: Self::Var, exported: bool) {
let needs_insert = match self.vars.get(&name) {
Some(&(ref existing_val, _)) => val != *existing_val,
None => true,
};
if needs_insert {
self.vars.make_mut().insert(name, (val, exported));
}
}
}
impl<N: Eq + Hash + Clone, V: Eq + Clone> UnsetVariableEnvironment for $Env<N, V> {
fn unset_var<Q: ?Sized>(&mut self, name: &Q)
where Self::VarName: Borrow<Q>, Q: Hash + Eq,
{
if self.vars.contains_key(name) {
self.vars.make_mut().remove(name);
}
}
}
impl<N: Eq + Ord + Hash + fmt::Debug, V: fmt::Debug> fmt::Debug for $Env<N, V> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use std::collections::BTreeMap;
let mut vars = BTreeMap::new();
let mut env_vars = BTreeMap::new();
for (name, &(ref val, is_env)) in &*self.vars {
if is_env {
env_vars.insert(name, val);
} else {
vars.insert(name, val);
}
}
fmt.debug_struct(stringify!($Env))
.field("env_vars", &env_vars)
.field("vars", &vars)
.finish()
}
}
impl<N: Eq + Hash, V> Default for $Env<N, V> {
fn default() -> Self {
Self::new()
}
}
impl<N: Eq + Hash, V> Clone for $Env<N, V> {
fn clone(&self) -> Self {
$Env {
vars: self.vars.clone(),
}
}
}
impl<N: Eq + Hash, V> SubEnvironment for $Env<N, V> {
fn sub_env(&self) -> Self {
self.clone()
}
}
};
}
impl_env!(
pub struct VarEnv,
Rc
);
impl_env!(
pub struct AtomicVarEnv,
Arc
);
#[cfg(test)]
mod tests {
use RefCounted;
use env::SubEnvironment;
use super::*;
#[test]
fn test_set_get_unset_var() {
let name = "var";
let value = "value".to_owned();
let mut env = VarEnv::new();
assert_eq!(env.var(name), None);
env.set_var(name.to_owned(), value.clone());
assert_eq!(env.var(name), Some(&value));
env.unset_var(name);
assert_eq!(env.var(name), None);
}
#[test]
fn test_set_get_unset_exported_var() {
let exported = "exported";
let exported_value = "exported_value";
let name = "var";
let value = "value";
let mut env = VarEnv::with_env_vars(vec!((exported, exported_value)));
assert_eq!(env.exported_var(&exported), Some((&exported_value, true)));
assert_eq!(env.var(&name), None);
env.set_exported_var(name, value, false);
assert_eq!(env.exported_var(&name), Some((&value, false)));
let new_value = "new_value";
env.set_var(exported, new_value);
assert_eq!(env.exported_var(&exported), Some((&new_value, true)));
env.set_var(name, new_value);
assert_eq!(env.exported_var(&name), Some((&new_value, false)));
}
#[test]
fn test_sub_env_no_needless_clone() {
let not_set = "not set";
let name = "var";
let value = "value";
let mut env = VarEnv::new();
env.set_var(name.to_owned(), value.to_owned());
let mut env = env.sub_env();
env.set_var(name.to_owned(), value.to_owned());
if env.vars.get_mut().is_some() {
panic!("needles clone!");
}
env.unset_var(not_set);
if env.vars.get_mut().is_some() {
panic!("needles clone!");
}
}
#[test]
fn test_env_vars() {
use std::collections::HashSet;
use std::iter::FromIterator;
let env_name1 = "env_name1".to_owned();
let env_name2 = "env_name2".to_owned();
let env_val1 = "env_val1".to_owned();
let env_val2 = "env_val2".to_owned();
let name = "name".to_owned();
let val = "value".to_owned();
let mut env = VarEnv::with_env_vars(vec!(
(env_name1.clone(), env_val1.clone()),
(env_name2.clone(), env_val2.clone()),
));
env.set_var(name, val);
let correct = vec!(
(&env_name1, &env_val1),
(&env_name2, &env_val2),
);
let vars: HashSet<(&String, &String)> = HashSet::from_iter(env.env_vars().into_owned());
assert_eq!(vars, HashSet::from_iter(correct));
}
#[test]
fn test_set_var_in_child_env_should_not_affect_parent() {
let parent_name = "parent-var";
let parent_value = "parent-value";
let child_name = "child-var";
let child_value = "child-value";
let mut parent = VarEnv::new();
parent.set_var(parent_name.to_owned(), parent_value);
{
let mut child = parent.sub_env();
assert_eq!(child.var(parent_name), Some(&parent_value));
child.set_var(parent_name.to_owned(), child_value);
child.set_var(child_name.to_owned(), child_value);
assert_eq!(child.var(parent_name), Some(&child_value));
assert_eq!(child.var(child_name), Some(&child_value));
assert_eq!(parent.var(parent_name), Some(&parent_value));
assert_eq!(parent.var(child_name), None);
}
assert_eq!(parent.var(parent_name), Some(&parent_value));
assert_eq!(parent.var(child_name), None);
}
#[test]
fn test_get_env_vars_visible_in_parent_and_child() {
use std::collections::HashSet;
use std::iter::FromIterator;
let name1 = "var1".to_owned();
let value1 = "value1".to_owned();
let name2 = "var2".to_owned();
let value2 = "value2".to_owned();
let env = VarEnv::with_env_vars(vec!(
(name1.clone(), value1.clone()),
(name2.clone(), value2.clone()),
));
let env_vars = HashSet::from_iter(vec!(
(&name1, &value1),
(&name2, &value2),
));
let vars: HashSet<(&String, &String)> = HashSet::from_iter(env.env_vars().into_owned());
assert_eq!(vars, env_vars);
let child = env.sub_env();
let vars: HashSet<(&String, &String)> = HashSet::from_iter(child.env_vars().into_owned());
assert_eq!(vars, env_vars);
}
}