1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use std::env;
use std::ffi::{OsStr, OsString};

/// A rust lifetime scope for a set environment
/// variable. When an instance goes out of scope it will
/// automatically cleanup the environment
pub struct ScopedEnv<T>
where
    T: AsRef<OsStr>,
{
    name: T,
    old_value: Option<OsString>,
}

impl<T> ScopedEnv<T>
where
    T: AsRef<OsStr>,
{
    /// Sets the environment variable {name} to {value}. The
    /// returned instance should be assigned to a `_name`
    /// binding so that it lasts as long as the current
    /// block.
    ///
    /// ```rust
    /// use scoped_env::ScopedEnv;
    /// let c = ScopedEnv::set("HELLO", "WORLD");
    /// assert_eq!(std::env::var(c).unwrap().as_str(), "WORLD");
    /// ```
    pub fn set(name: T, value: T) -> Self {
        let old_value = env::var_os(name.as_ref());
        env::set_var(name.as_ref(), value);
        Self { name, old_value }
    }

    /// Removes the environment variable {name} from the
    /// environment of the currently running process.
    /// The returned instance should be assigned to a `_name`
    /// binding. The variable value will be restored when the
    /// handle goes out of scope.
    ///
    /// ```rust
    /// use scoped_env::ScopedEnv;
    /// std::env::set_var("HELLO", "WORLD");
    /// {
    ///     let c = ScopedEnv::remove("HELLO");
    ///     assert!(std::env::var(c).is_err());
    /// }
    /// assert_eq!(std::env::var("HELLO").unwrap().as_str(), "WORLD");
    /// ```
    pub fn remove(name: T) -> Self {
        let old_value = env::var_os(name.as_ref());
        env::remove_var(name.as_ref());
        Self { name, old_value }
    }
}

impl<T> AsRef<OsStr> for ScopedEnv<T>
where
    T: AsRef<OsStr>,
{
    fn as_ref(&self) -> &OsStr {
        self.name.as_ref()
    }
}

impl<T> Drop for ScopedEnv<T>
where
    T: AsRef<OsStr>,
{
    fn drop(&mut self) {
        match self.old_value {
            Some(ref old_value) => env::set_var(self.as_ref(), old_value),
            None => env::remove_var(self),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn does_set() {
        let c = ScopedEnv::set("FOOBAR", "hello");
        assert_eq!(env::var(c).unwrap(), "hello");
    }

    #[test]
    fn does_unset_at_end_of_block() {
        env::remove_var("FOOBAR1");
        {
            let c = ScopedEnv::set("FOOBAR1", "hello");
            assert_eq!(env::var(c).unwrap(), "hello");
        }

        assert_eq!(env::var_os("FOOBAR1"), None);
    }

    #[test]
    fn does_reset_at_end_of_block() {
        env::set_var("FOOBAR1", "OLD_VALUE");
        {
            let c = ScopedEnv::set("FOOBAR1", "hello");
            assert_eq!(env::var(c).unwrap(), "hello");
        }

        assert_eq!(env::var("FOOBAR1").unwrap(), "OLD_VALUE");
    }

    #[test]
    fn does_remove() {
        env::set_var("FOOBAR", "SOME_VALUE");
        {
            let c = ScopedEnv::remove("FOOBAR");
            assert_eq!(env::var_os(c), None);
        }

        assert_eq!(env::var_os("FOOBAR").unwrap(), "SOME_VALUE");
    }
}