conch_runtime_pshaw/env/
var.rs

1use crate::env::SubEnvironment;
2use std::borrow::{Borrow, Cow};
3use std::collections::HashMap;
4use std::fmt;
5use std::hash::Hash;
6use std::sync::Arc;
7
8/// An interface for setting and getting shell and environment variables.
9pub trait VariableEnvironment {
10    /// The type of the name this environment associates for a variable.
11    type VarName: Eq + Hash;
12    /// The type of variables this environment holds.
13    type Var;
14    /// Get the value of some variable. The values of both shell-only
15    /// and environment variables will be looked up and returned.
16    fn var<Q: ?Sized>(&self, name: &Q) -> Option<&Self::Var>
17    where
18        Self::VarName: Borrow<Q>,
19        Q: Hash + Eq;
20    /// Set the value of some variable, maintaining its status as an
21    /// environment variable if previously set as such.
22    fn set_var(&mut self, name: Self::VarName, val: Self::Var);
23    /// Unset the value of some variable (including environment variables).
24    /// Get all current pairs of environment variables and their values.
25    fn env_vars(&self) -> Cow<'_, [(&Self::VarName, &Self::Var)]>;
26}
27
28impl<'a, T: ?Sized + VariableEnvironment> VariableEnvironment for &'a mut T {
29    type VarName = T::VarName;
30    type Var = T::Var;
31
32    fn var<Q: ?Sized>(&self, name: &Q) -> Option<&Self::Var>
33    where
34        Self::VarName: Borrow<Q>,
35        Q: Hash + Eq,
36    {
37        (**self).var(name)
38    }
39
40    fn set_var(&mut self, name: Self::VarName, val: Self::Var) {
41        (**self).set_var(name, val);
42    }
43
44    fn env_vars(&self) -> Cow<'_, [(&Self::VarName, &Self::Var)]> {
45        (**self).env_vars()
46    }
47}
48
49/// An interface for setting and getting shell and environment variables and
50/// controlling whether or not they can appear as environment variables to
51/// subprocesses.
52pub trait ExportedVariableEnvironment: VariableEnvironment {
53    /// Get the value of some variable and whether it is exported.
54    fn exported_var(&self, name: &Self::VarName) -> Option<(&Self::Var, bool)>;
55    /// Set the value of some variable, and set it's exported status as specified.
56    fn set_exported_var(&mut self, name: Self::VarName, val: Self::Var, exported: bool);
57}
58
59impl<'a, T: ?Sized + ExportedVariableEnvironment> ExportedVariableEnvironment for &'a mut T {
60    fn exported_var(&self, name: &Self::VarName) -> Option<(&Self::Var, bool)> {
61        (**self).exported_var(name)
62    }
63
64    fn set_exported_var(&mut self, name: Self::VarName, val: Self::Var, exported: bool) {
65        (**self).set_exported_var(name, val, exported)
66    }
67}
68
69/// An interface for unsetting shell and envrironment variables.
70pub trait UnsetVariableEnvironment: VariableEnvironment {
71    /// Unset the value of some variable (including environment variables).
72    fn unset_var(&mut self, name: &Self::VarName);
73}
74
75impl<'a, T: ?Sized + UnsetVariableEnvironment> UnsetVariableEnvironment for &'a mut T {
76    fn unset_var(&mut self, name: &T::VarName) {
77        (**self).unset_var(name);
78    }
79}
80
81/// An environment module for setting, getting, and exporting shell variables.
82#[derive(PartialEq, Eq)]
83pub struct VarEnv<N: Eq + Hash, V> {
84    /// A mapping of variable names to their values.
85    ///
86    /// The tupled boolean indicates if a variable should be exported to other commands.
87    vars: Arc<HashMap<N, (V, bool)>>,
88}
89
90impl<N, V> VarEnv<N, V>
91where
92    N: Eq + Hash,
93{
94    /// Constructs a new environment with no environment variables.
95    pub fn new() -> Self {
96        Self {
97            vars: Arc::new(HashMap::new()),
98        }
99    }
100
101    /// Constructs a new environment and initializes it with the environment
102    /// variables of the current process.
103    pub fn with_process_env_vars() -> Self
104    where
105        N: From<String>,
106        V: From<String>,
107    {
108        Self::with_env_vars(::std::env::vars().map(|(k, v)| (k.into(), v.into())))
109    }
110
111    /// Constructs a new environment with a provided collection of `(key, value)`
112    /// environment variable pairs. These variables (if any) will be inherited by
113    /// all commands.
114    pub fn with_env_vars<I: IntoIterator<Item = (N, V)>>(iter: I) -> Self {
115        Self {
116            vars: Arc::new(
117                iter.into_iter()
118                    .map(|(k, v)| (k, (v, true)))
119                    .collect::<HashMap<_, _>>(),
120            ),
121        }
122    }
123}
124
125impl<N, V> VariableEnvironment for VarEnv<N, V>
126where
127    N: Eq + Clone + Hash,
128    V: Eq + Clone,
129{
130    type VarName = N;
131    type Var = V;
132
133    fn var<Q: ?Sized>(&self, name: &Q) -> Option<&Self::Var>
134    where
135        Self::VarName: Borrow<Q>,
136        Q: Hash + Eq,
137    {
138        self.vars.get(name).map(|&(ref val, _)| val)
139    }
140
141    fn set_var(&mut self, name: Self::VarName, val: Self::Var) {
142        let (needs_insert, exported) = match self.vars.get(&name) {
143            Some(&(ref existing_val, exported)) => (&val != existing_val, exported),
144            None => (true, false),
145        };
146
147        if needs_insert {
148            Arc::make_mut(&mut self.vars).insert(name, (val, exported));
149        }
150    }
151
152    fn env_vars(&self) -> Cow<'_, [(&Self::VarName, &Self::Var)]> {
153        let ret: Vec<_> = self
154            .vars
155            .iter()
156            .filter_map(|(k, &(ref v, exported))| if exported { Some((k, v)) } else { None })
157            .collect();
158
159        Cow::Owned(ret)
160    }
161}
162
163impl<N, V> ExportedVariableEnvironment for VarEnv<N, V>
164where
165    N: Eq + Clone + Hash,
166    V: Eq + Clone,
167{
168    fn exported_var(&self, name: &Self::VarName) -> Option<(&Self::Var, bool)> {
169        self.vars
170            .get(name)
171            .map(|&(ref val, exported)| (val, exported))
172    }
173
174    fn set_exported_var(&mut self, name: Self::VarName, val: Self::Var, exported: bool) {
175        let needs_insert = match self.vars.get(&name) {
176            Some(&(ref existing_val, _)) => val != *existing_val,
177            None => true,
178        };
179
180        if needs_insert {
181            Arc::make_mut(&mut self.vars).insert(name, (val, exported));
182        }
183    }
184}
185
186impl<N, V> UnsetVariableEnvironment for VarEnv<N, V>
187where
188    N: Eq + Clone + Hash,
189    V: Eq + Clone,
190{
191    fn unset_var(&mut self, name: &N) {
192        if self.vars.contains_key(name) {
193            Arc::make_mut(&mut self.vars).remove(name);
194        }
195    }
196}
197
198impl<N, V> fmt::Debug for VarEnv<N, V>
199where
200    N: Eq + Ord + Hash + fmt::Debug,
201    V: fmt::Debug,
202{
203    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
204        use std::collections::BTreeMap;
205
206        let mut vars = BTreeMap::new();
207        let mut env_vars = BTreeMap::new();
208
209        for (name, &(ref val, is_env)) in &*self.vars {
210            if is_env {
211                env_vars.insert(name, val);
212            } else {
213                vars.insert(name, val);
214            }
215        }
216
217        fmt.debug_struct(stringify!(VarEnv))
218            .field("env_vars", &env_vars)
219            .field("vars", &vars)
220            .finish()
221    }
222}
223
224impl<N, V> Default for VarEnv<N, V>
225where
226    N: Eq + Hash,
227{
228    fn default() -> Self {
229        Self::new()
230    }
231}
232
233impl<N, V> Clone for VarEnv<N, V>
234where
235    N: Eq + Hash,
236{
237    fn clone(&self) -> Self {
238        Self {
239            vars: self.vars.clone(),
240        }
241    }
242}
243
244impl<N, V> SubEnvironment for VarEnv<N, V>
245where
246    N: Eq + Hash,
247{
248    fn sub_env(&self) -> Self {
249        self.clone()
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256    use crate::env::SubEnvironment;
257
258    #[test]
259    fn test_set_get_unset_var() {
260        let name = "var";
261        let value = "value";
262        let mut env = VarEnv::new();
263        assert_eq!(env.var(name), None);
264        env.set_var(name, value);
265        assert_eq!(env.var(name), Some(&value));
266        env.unset_var(&name);
267        assert_eq!(env.var(name), None);
268    }
269
270    #[test]
271    fn test_set_get_unset_exported_var() {
272        let exported = "exported";
273        let exported_value = "exported_value";
274        let name = "var";
275        let value = "value";
276
277        let mut env = VarEnv::with_env_vars(vec![(exported, exported_value)]);
278        assert_eq!(env.exported_var(&exported), Some((&exported_value, true)));
279
280        assert_eq!(env.var(&name), None);
281        env.set_exported_var(name, value, false);
282        assert_eq!(env.exported_var(&name), Some((&value, false)));
283
284        // Regular insert maintains exported value
285        let new_value = "new_value";
286        env.set_var(exported, new_value);
287        assert_eq!(env.exported_var(&exported), Some((&new_value, true)));
288        env.set_var(name, new_value);
289        assert_eq!(env.exported_var(&name), Some((&new_value, false)));
290    }
291
292    #[test]
293    fn test_sub_env_no_needless_clone() {
294        let not_set = "not set";
295        let name = "var";
296        let value = "value";
297        let mut env = VarEnv::new();
298        env.set_var(name, value);
299
300        let mut env = env.sub_env();
301        env.set_var(name, value);
302        if Arc::get_mut(&mut env.vars).is_some() {
303            panic!("needles clone!");
304        }
305
306        env.unset_var(&not_set);
307        if Arc::get_mut(&mut env.vars).is_some() {
308            panic!("needles clone!");
309        }
310    }
311
312    #[test]
313    fn test_env_vars() {
314        use std::collections::HashSet;
315        use std::iter::FromIterator;
316
317        let env_name1 = "env_name1";
318        let env_name2 = "env_name2";
319        let env_val1 = "env_val1";
320        let env_val2 = "env_val2";
321        let name = "name";
322        let val = "value";
323
324        let mut env = VarEnv::with_env_vars(vec![(env_name1, env_val1), (env_name2, env_val2)]);
325        env.set_var(name, val);
326
327        let correct = vec![(&env_name1, &env_val1), (&env_name2, &env_val2)];
328
329        let vars: HashSet<(_, _)> = HashSet::from_iter(env.env_vars().into_owned());
330        assert_eq!(vars, HashSet::from_iter(correct));
331    }
332
333    #[test]
334    fn test_set_var_in_child_env_should_not_affect_parent() {
335        let parent_name = "parent-var";
336        let parent_value = "parent-value";
337        let child_name = "child-var";
338        let child_value = "child-value";
339
340        let mut parent = VarEnv::new();
341        parent.set_var(parent_name, parent_value);
342
343        {
344            let mut child = parent.sub_env();
345            assert_eq!(child.var(parent_name), Some(&parent_value));
346
347            child.set_var(parent_name, child_value);
348            child.set_var(child_name, child_value);
349            assert_eq!(child.var(parent_name), Some(&child_value));
350            assert_eq!(child.var(child_name), Some(&child_value));
351
352            assert_eq!(parent.var(parent_name), Some(&parent_value));
353            assert_eq!(parent.var(child_name), None);
354        }
355
356        assert_eq!(parent.var(parent_name), Some(&parent_value));
357        assert_eq!(parent.var(child_name), None);
358    }
359
360    #[test]
361    fn test_get_env_vars_visible_in_parent_and_child() {
362        use std::collections::HashSet;
363        use std::iter::FromIterator;
364
365        let name1 = "var1";
366        let value1 = "value1";
367        let name2 = "var2";
368        let value2 = "value2";
369
370        let env = VarEnv::with_env_vars(vec![(name1, value1), (name2, value2)]);
371
372        let env_vars = HashSet::from_iter(vec![(&name1, &value1), (&name2, &value2)]);
373
374        let vars: HashSet<(_, _)> = HashSet::from_iter(env.env_vars().into_owned());
375        assert_eq!(vars, env_vars);
376
377        let child = env.sub_env();
378        let vars: HashSet<(_, _)> = HashSet::from_iter(child.env_vars().into_owned());
379        assert_eq!(vars, env_vars);
380    }
381}