yash_env/variable/
guard.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17use super::Context;
18use super::VariableSet;
19use crate::Env;
20use std::ops::Deref;
21use std::ops::DerefMut;
22
23/// RAII-style guard for temporarily retaining a variable context.
24///
25/// The guard object is created by [`VariableSet::push_context`].
26#[derive(Debug)]
27#[must_use = "You must retain ContextGuard to keep the context alive"]
28pub struct ContextGuard<'a> {
29    stack: &'a mut VariableSet,
30}
31
32impl VariableSet {
33    /// Pushes a new context to this variable set.
34    ///
35    /// This function returns a scope guard that will pop the context when dropped.
36    /// The guard provides a mutable reference to the variable set, allowing
37    /// variables to be added or modified within the context.
38    ///
39    /// Note that the guard does not provide access to the whole environment
40    /// that contains the variable set. If you need access to the environment,
41    /// use [`Env::push_context`] instead.
42    #[inline]
43    pub fn push_context(&mut self, context: Context) -> ContextGuard<'_> {
44        self.push_context_impl(context);
45        ContextGuard { stack: self }
46    }
47
48    /// Pops the topmost context from the variable set.
49    #[inline]
50    pub fn pop_context(guard: ContextGuard<'_>) {
51        drop(guard)
52    }
53}
54
55/// When the guard is dropped, the context that was pushed when creating the
56/// guard is [popped](VariableSet::pop_context).
57impl std::ops::Drop for ContextGuard<'_> {
58    #[inline]
59    fn drop(&mut self) {
60        self.stack.pop_context_impl()
61    }
62}
63
64impl std::ops::Deref for ContextGuard<'_> {
65    type Target = VariableSet;
66    #[inline]
67    fn deref(&self) -> &VariableSet {
68        self.stack
69    }
70}
71
72impl std::ops::DerefMut for ContextGuard<'_> {
73    #[inline]
74    fn deref_mut(&mut self) -> &mut VariableSet {
75        self.stack
76    }
77}
78
79/// RAII-style guard that makes sure a context is popped properly
80///
81/// The guard object is created by [`Env::push_context`].
82#[derive(Debug)]
83#[must_use = "The context is popped when the guard is dropped"]
84pub struct EnvContextGuard<'a> {
85    env: &'a mut Env,
86}
87
88impl Env {
89    /// Pushes a new context to the variable set.
90    ///
91    /// This function is equivalent to
92    /// `self.variables.push_context(context_type)`, but returns an
93    /// `EnvContextGuard` that allows re-borrowing the `Env`.
94    #[inline]
95    pub fn push_context(&mut self, context: Context) -> EnvContextGuard<'_> {
96        self.variables.push_context_impl(context);
97        EnvContextGuard { env: self }
98    }
99
100    /// Pops the topmost context from the variable set.
101    #[inline]
102    pub fn pop_context(guard: EnvContextGuard<'_>) {
103        drop(guard)
104    }
105}
106
107/// When the guard is dropped, the context that was pushed when creating the
108/// guard is [popped](VariableSet::pop_context).
109impl Drop for EnvContextGuard<'_> {
110    #[inline]
111    fn drop(&mut self) {
112        self.env.variables.pop_context_impl()
113    }
114}
115
116impl Deref for EnvContextGuard<'_> {
117    type Target = Env;
118    #[inline]
119    fn deref(&self) -> &Env {
120        self.env
121    }
122}
123
124impl DerefMut for EnvContextGuard<'_> {
125    #[inline]
126    fn deref_mut(&mut self) -> &mut Env {
127        self.env
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::super::Scope;
134    use super::super::Value;
135    use super::*;
136
137    #[test]
138    fn scope_guard() {
139        let mut env = Env::new_virtual();
140        let mut guard = env.variables.push_context(Context::default());
141        guard
142            .get_or_new("foo", Scope::Global)
143            .assign("", None)
144            .unwrap();
145        guard
146            .get_or_new("bar", Scope::Local)
147            .assign("", None)
148            .unwrap();
149        VariableSet::pop_context(guard);
150
151        let variable = env.variables.get("foo").unwrap();
152        assert_eq!(variable.value, Some(Value::scalar("")));
153        assert_eq!(env.variables.get("bar"), None);
154    }
155
156    #[test]
157    fn env_scope_guard() {
158        let mut env = Env::new_virtual();
159        let mut guard = env.push_context(Context::default());
160        guard
161            .variables
162            .get_or_new("foo", Scope::Global)
163            .assign("", None)
164            .unwrap();
165        guard
166            .variables
167            .get_or_new("bar", Scope::Local)
168            .assign("", None)
169            .unwrap();
170        Env::pop_context(guard);
171
172        let variable = env.variables.get("foo").unwrap();
173        assert_eq!(variable.value, Some(Value::scalar("")));
174        assert_eq!(env.variables.get("bar"), None);
175    }
176}