globalenv/
lib.rs

1//! Globally set or unset environment variables (and not just for the current process).
2//! Support for Windows, zsh and bash (MacOS and most Linux distros).
3//! Example:
4//! ```rust
5//! use globalenv::{set_var, unset_var};
6//! set_var("ENVTEST", "TESTVALUE").unwrap();
7//! unset_var("ENVTEST").unwrap();
8//! ```
9
10use std::{env, fmt, error};
11#[cfg(target_os = "windows")]
12use winreg::{ enums::*, RegKey };
13
14#[cfg(target_family = "unix")]
15use std::{ fs, io::prelude::*, path::PathBuf, fs::OpenOptions };
16
17#[derive(Debug, PartialEq, Eq, Clone)]
18pub enum EnvError {
19    /// Unsupported shell
20    UnsupportedShell,
21    /// IO Error (file or registry operation)
22    IOError,
23    /// ENV error (can't get or set variable)
24    VarError
25}
26
27impl error::Error for EnvError {}
28
29impl fmt::Display for EnvError {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        f.write_str("ENV operation error : ")?;
32        f.write_str(match self {
33            EnvError::UnsupportedShell => "Unsupported shell",
34            EnvError::IOError => "I/O error",
35            EnvError::VarError => "error while getting or setting env",
36        })
37    }
38}
39
40impl From<std::io::Error> for EnvError {
41    fn from(_e: std::io::Error) -> EnvError {
42        EnvError::IOError
43    }
44}
45
46impl From<std::env::VarError> for EnvError {
47    fn from(_e: std::env::VarError) -> EnvError {
48        EnvError::VarError
49    }
50}
51
52#[cfg(target_os = "windows")]
53/// Sets a global environment variable, usable also in current process without reload.
54pub fn set_var(var: &str, value: &str) -> Result<(), EnvError> {
55    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
56    let key = hkcu.open_subkey_with_flags("Environment", KEY_SET_VALUE)?;
57    // Setting the variable globally
58    key.set_value(var, &value)?;
59    // Additionnaly, we set the env for current shell
60    env::set_var(var, value);
61    Ok(())
62}
63
64#[cfg(target_family = "unix")]
65/// Sets a global environment variable, usable also in current process without reload.
66pub fn set_var(var: &str, value: &str) -> Result<(), EnvError> {
67    // Getting env and building env file path
68    let homedir = env::var("HOME")?;
69    let shell = env::var("SHELL")?;
70    let envfile = match shell.as_str() {
71        "/usr/bin/zsh" => ".zshenv",
72        "/bin/zsh" => ".zshenv",
73        "/bin/bash" => ".bashrc",
74        _ => return Err(EnvError::UnsupportedShell)
75    };
76
77    let mut envfilepath = PathBuf::from(homedir);
78    envfilepath.push(envfile);
79
80    // Reading the env file
81    let env = fs::read_to_string(&envfilepath)?;
82
83    // Building the "export" line according to requested parameters
84    let mut export = String::from("export ");
85    export.push_str(var);
86    export.push_str("=");
87    export.push_str(value);
88    export.push_str("\n");
89
90    // Already present ? we just set the variable for current process
91    if env.contains(&export) { env::set_var(var, value); return Ok(()); }
92
93    // Not present ? we append the env file to set it globally
94    let mut env_file = OpenOptions::new()
95        .append(true)
96        .create(true)
97        .open(envfilepath)?;
98    env_file.write(export.as_bytes())?;
99
100    // Additionnaly, we set the env for current process
101    env::set_var(var, value);
102            
103    Ok(())
104}
105
106#[cfg(target_os = "windows")]
107/// Unsets both global and local (process) environment variable.
108pub fn unset_var(var: &str) -> Result<(), EnvError> {
109    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
110    let key = hkcu.open_subkey_with_flags("Environment", KEY_SET_VALUE)?;
111    key.delete_value(var)?;
112    env::remove_var(var);
113    Ok(())
114}
115
116#[cfg(target_family = "unix")]
117/// Unsets both global and local (process) environment variable.
118pub fn unset_var(var: &str) -> Result<(), EnvError> {
119    // Getting env and building env file path
120    let homedir = env::var("HOME")?;
121    let shell = env::var("SHELL")?;
122    let envfile = match shell.as_str() {
123        "/usr/bin/zsh" => ".zshenv",
124        "/bin/zsh" => ".zshenv",
125        "/bin/bash" => ".bashrc",
126        _ => return Err(EnvError::UnsupportedShell)
127    };
128
129    let mut envfilepath = PathBuf::from(homedir);
130    envfilepath.push(envfile);
131
132    // Reading the env file
133    let env = fs::read_to_string(&envfilepath)?;
134
135    // Building the "export" line according to requested parameters
136    let mut export = String::from("export ");
137    export.push_str(var);
138    export.push_str("=");
139
140    // Variable not present in env file ? we just unset the variable for current process
141    if !env.contains(&export) { env::remove_var(var); return Ok(()); }
142
143    // Present ? we remove it from the env file to unset it globally
144    let mut updated_env = String::new();
145    for l in env.lines() { if !l.contains(var) { updated_env.push_str(l); updated_env.push_str("\n") } }
146    fs::write(envfilepath, updated_env)?;
147
148    // Additionnaly, we unset the env for current process
149    env::remove_var(var);
150            
151    Ok(())
152}
153
154/* Run the tests in a single thread context !
155$env:RUST_TEST_THREADS=1; cargo test
156RUST_TEST_THREADS=1 cargo test */
157
158#[cfg(target_os = "windows")]
159#[cfg(test)]
160mod tests {
161    use winreg::enums::*;
162    use winreg::RegKey;
163    use std::env;
164    #[test]
165    fn is_set_globally() {
166        crate::set_var("ENVTEST", "TESTVALUE").unwrap();
167        let hkcu = RegKey::predef(HKEY_CURRENT_USER);
168        let key = hkcu
169            .open_subkey_with_flags("Environment", KEY_READ)
170            .unwrap();
171        let var: String = key.get_value("ENVTEST").unwrap();
172        assert_eq!(String::from("TESTVALUE"), var);
173    }
174
175    #[test]
176    fn is_set_locally() {
177        assert_eq!(String::from("TESTVALUE"), env::var("ENVTEST").unwrap());
178    }
179
180    #[test]
181    #[should_panic]
182    fn is_unset_globally() {
183        crate::unset_var("ENVTEST").unwrap();
184        let hkcu = RegKey::predef(HKEY_CURRENT_USER);
185        let key = hkcu
186            .open_subkey_with_flags("Environment", KEY_READ)
187            .unwrap();
188        let _: String = key.get_value("ENVTEST").unwrap();
189    }
190
191    #[test]
192    #[should_panic]
193    fn is_unset_locally() {
194        env::var("ENVTEST").unwrap();
195    }
196}
197
198#[cfg(target_family = "unix")]
199mod tests {
200    #[test]
201    fn is_set_globally() {
202        crate::set_var("ENVTEST", "TESTVALUE").unwrap();
203        // Getting env and building env file path
204        let homedir = crate::env::var("HOME").unwrap();
205        let shell = crate::env::var("SHELL").unwrap();
206        let envfile = match shell.as_str() {
207            "/usr/bin/zsh" => ".zshenv",
208            "/bin/zsh" => ".zshenv",
209            "/bin/bash" => ".bashrc",
210            _ => panic!("Unsupported shell")
211        };
212
213        let mut envfilepath = crate::PathBuf::from(homedir);
214        envfilepath.push(envfile);
215
216        // Reading the env file
217        let env = crate::fs::read_to_string(&envfilepath).unwrap();
218
219        assert_eq!(env.contains("export ENVTEST=TESTVALUE\n"), true);
220    }
221
222    #[test]
223    fn is_set_locally() {
224        assert_eq!(String::from("TESTVALUE"), crate::env::var("ENVTEST").unwrap());
225    }
226
227    #[test]
228    fn is_unset_globally() {
229        crate::unset_var("ENVTEST").unwrap();
230        // Getting env and building env file path
231        let homedir = crate::env::var("HOME").unwrap();
232        let shell = crate::env::var("SHELL").unwrap();
233        let envfile = match shell.as_str() {
234            "/usr/bin/zsh" => ".zshenv",
235            "/bin/zsh" => ".zshenv",
236            "/bin/bash" => ".bashrc",
237            _ => panic!("Unsupported shell")
238        };
239
240        let mut envfilepath = crate::PathBuf::from(homedir);
241        envfilepath.push(envfile);
242
243        // Reading the env file
244        let env = crate::fs::read_to_string(&envfilepath).unwrap();
245
246        assert_eq!(env.contains("export ENVTEST=TESTVALUE\n"), false);
247    }
248
249    #[test]
250    #[should_panic]
251    fn is_unset_locally() {
252        crate::env::var("ENVTEST").unwrap();
253    }
254}
255
256