environment/
lib.rs

1use std::ffi::OsString;
2
3/// Structure to deal with environment variables
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub struct Environment {
6    /// Customized environment variables
7    vars: Vec<(OsString, OsString)>,
8    /// Define if the structure must inherit
9    inherit: bool,
10}
11
12impl Default for Environment {
13    fn default() -> Self {
14        Self {
15            vars: vec![],
16            inherit: false,
17        }
18    }
19}
20
21impl Environment {
22    /// Create a new Environment that inherits this process' environment.
23    ///
24    /// # Examples
25    ///
26    /// ```rust
27    /// extern crate environment;
28    /// use std::ffi::OsString;
29    ///
30    /// let e = environment::Environment::inherit().compile();
31    /// let e_: Vec<(OsString, OsString)> = ::std::env::vars_os().collect();
32    ///
33    /// assert_eq!(e, e_);
34    /// ```
35    pub fn inherit() -> Self {
36        Self {
37            vars: vec![],
38            inherit: true,
39        }
40    }
41
42    /// Create a new Environment independent of the current process's Environment
43    ///
44    /// # Examples
45    ///
46    /// ```rust
47    /// extern crate environment;
48    ///
49    /// let e = environment::Environment::empty().compile();
50    /// assert_eq!(e, Vec::new());
51    /// ```
52    pub fn empty() -> Self {
53        Self::default()
54    }
55
56    /// Insert a new entry into the custom variables for this environment object
57    ///
58    /// # Examples
59    ///
60    /// ```rust
61    /// extern crate environment;
62    ///
63    /// use std::ffi::OsString;
64    ///
65    /// let e = environment::Environment::empty().insert("foo", "bar").compile();
66    /// assert_eq!(e, vec![(OsString::from("foo"), OsString::from("bar"))]);
67    /// ```
68    pub fn insert<S1: Into<OsString>, S2: Into<OsString>>(mut self, key: S1, val: S2) -> Self {
69        self.vars.push((key.into(), val.into()));
70        self
71    }
72
73    /// Compile Environment object
74    pub fn compile(self) -> Vec<(OsString, OsString)> {
75        if self.inherit {
76            ::std::env::vars_os().chain(self.vars).collect()
77        } else {
78            self.vars
79        }
80    }
81}
82
83/// Implicit clone for ergonomics
84impl<'a> From<&'a Environment> for Environment {
85    fn from(v: &'a Environment) -> Self {
86        v.clone()
87    }
88}
89
90pub trait EnvironmentItem {
91    fn to_environment_tuple(&self) -> (OsString, OsString);
92}
93
94impl<T: ToString, Z: ToString> EnvironmentItem for (T, Z) {
95    fn to_environment_tuple(&self) -> (OsString, OsString) {
96        (
97            OsString::from(self.0.to_string()),
98            OsString::from(self.1.to_string()),
99        )
100    }
101}
102
103impl<'s, T: ToString, Z: ToString> EnvironmentItem for &'s (T, Z) {
104    fn to_environment_tuple(&self) -> (OsString, OsString) {
105        (
106            OsString::from(self.0.to_string()),
107            OsString::from(self.1.to_string()),
108        )
109    }
110}
111
112impl<'s, T> From<T> for Environment
113where
114    T: IntoIterator,
115    T::Item: EnvironmentItem,
116{
117    fn from(v: T) -> Self {
118        Self {
119            vars: v.into_iter().map(|k| k.to_environment_tuple()).collect(),
120            inherit: false,
121        }
122    }
123}
124
125#[cfg(test)]
126mod test {
127    use super::*;
128    use std::process::Command;
129
130    #[test]
131    fn take_ownership() {
132        let x = Environment::inherit();
133
134        let mut c = Command::new("printenv");
135        c.env_clear().envs(x.clone().compile()).envs(x.compile());
136    }
137
138    #[test]
139    fn in_place_mod() {
140        let y = Environment::empty();
141
142        let y = y.insert("key", "value");
143
144        assert_eq!(
145            y.compile(),
146            vec![(OsString::from("key"), OsString::from("value"))]
147        );
148    }
149
150    #[test]
151    fn in_place_mod2() {
152        let x = Environment::inherit();
153
154        let mut c = Command::new("printenv");
155
156        let output = c.env_clear()
157            .envs(x.insert("key", "value").insert("key", "vv").compile())
158            .output()
159            .expect("failed to execute command");
160
161        let output = String::from_utf8_lossy(&output.stdout);
162
163        assert!(output.contains("key=vv"));
164        // Granted, `insert` moved `x`, so we can no longer reference it, even
165        // though only a reference was passed to `envs`
166    }
167
168    #[test]
169    fn in_place_mod3() {
170        // In-place modification while allowing later accesses to the `Environment`
171        let y = Environment::empty();
172
173        assert_eq!(
174            y.clone().insert("key", "value").compile(),
175            vec![(OsString::from("key"), OsString::from("value"))]
176        );
177
178        let mut c = Command::new("printenv");
179
180        let output = c.env_clear()
181            .envs(y.compile())
182            .output()
183            .expect("failed to execute command");
184
185        let output = String::from_utf8_lossy(&output.stdout);
186
187        assert_eq!(output, "");
188    }
189
190    #[test]
191    fn empty_env() {
192        // In-place modification while allowing later accesses to the `Environment`
193        let y = Environment::empty();
194
195        let mut c = Command::new("printenv");
196
197        let output = c.env_clear()
198            .envs(y.compile())
199            .output()
200            .expect("failed to execute command");
201
202        let output = String::from_utf8_lossy(&output.stdout);
203
204        assert!(output.is_empty());
205    }
206
207    #[test]
208    fn take_vec() {
209        let v = vec![("bar", "baz")];
210
211        let e: Environment = v.into();
212
213        let mut c = Command::new("printenv");
214
215        let output = c.env_clear()
216            .envs(e.clone().compile())
217            .output()
218            .expect("failed to execute command");
219
220        let output = String::from_utf8_lossy(&output.stdout);
221
222        assert!(output.contains("bar=baz"));
223
224        let mut c = Command::new("printenv");
225
226        let output = c.env_clear()
227            .envs(e.clone().insert("bar", "vv").compile())
228            .output()
229            .expect("failed to execute command");
230
231        let output = String::from_utf8_lossy(&output.stdout);
232
233        assert!(output.contains("bar=vv"));
234        assert!(!output.contains("bar=baz"));
235    }
236}