Skip to main content

yash_builtin/typeset/
set_variables.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2023 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::*;
18use yash_env::variable::Value;
19
20impl From<Scope> for yash_env::variable::Scope {
21    fn from(value: Scope) -> Self {
22        match value {
23            Scope::Local => Self::Local,
24            Scope::Global => Self::Global,
25        }
26    }
27}
28
29impl SetVariables {
30    /// Executes the command.
31    pub fn execute<S>(self, env: &mut Env<S>) -> Result<String, Vec<ExecuteError>> {
32        let mut errors = Vec::new();
33
34        'field: for mut field in self.variables {
35            // Split the field into the name and the value.
36            let mut value_to_assign = None;
37            if let Some((name, value)) = field.value.split_once('=') {
38                value_to_assign = Some(Value::scalar(value));
39
40                // Modify the field value so that it contains only the name.
41                field.value.truncate(name.len());
42            }
43
44            let mut variable = env.get_or_create_variable(&field.value, self.scope.into());
45
46            // Assign the value to the variable.
47            if let Some(value) = value_to_assign {
48                if let Err(error) = variable.assign(value, field.origin.clone()) {
49                    errors.push(ExecuteError::AssignReadOnlyVariable(AssignReadOnlyError {
50                        name: field.value,
51                        new_value: error.new_value,
52                        assigned_location: error.assigned_location.unwrap(),
53                        read_only_location: error.read_only_location,
54                    }));
55                    continue;
56                }
57            }
58
59            // Apply the attributes to the variable.
60            for &(attr, state) in &self.attrs {
61                match (attr, state) {
62                    (VariableAttr::ReadOnly, State::On) => {
63                        variable.make_read_only(field.origin.clone())
64                    }
65                    (VariableAttr::ReadOnly, State::Off) => {
66                        if let Some(read_only_location) = variable.read_only_location.clone() {
67                            errors.push(ExecuteError::UndoReadOnlyVariable(UndoReadOnlyError {
68                                name: field,
69                                read_only_location,
70                            }));
71                            continue 'field;
72                        }
73                    }
74                    (VariableAttr::Export, State::On) => variable.export(true),
75                    (VariableAttr::Export, State::Off) => variable.export(false),
76                }
77            }
78        }
79
80        if errors.is_empty() {
81            Ok(String::new())
82        } else {
83            Err(errors)
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use assert_matches::assert_matches;
92    use yash_env::option::Option::AllExport;
93    use yash_env::source::Location;
94    use yash_env::variable::{Context, Variable};
95
96    #[test]
97    fn setting_local_variables() {
98        let mut outer = Env::new_virtual();
99        let mut inner = outer.push_context(Context::default());
100        let baz_location = Location::dummy("baz assigned");
101        let mut baz = inner.get_or_create_variable("baz", Scope::Local.into());
102        baz.assign("BAZ", baz_location.clone()).unwrap();
103        let sv = SetVariables {
104            variables: Field::dummies(["foo=FOO", "bar", "baz"]),
105            attrs: vec![],
106            scope: Scope::Local,
107        };
108        let foo_location = sv.variables[0].origin.clone();
109
110        let result = sv.execute(&mut inner);
111
112        assert_eq!(result, Ok("".to_string()));
113        // $foo now has the value assigned by `execute`
114        let foo = inner.variables.get("foo").unwrap();
115        assert_eq!(foo.value, Some(Value::scalar("FOO")));
116        assert_eq!(foo.last_assigned_location, Some(foo_location));
117        assert_eq!(foo.read_only_location, None);
118        // $bar now exists but has no value
119        let bar = inner.variables.get("bar").unwrap();
120        assert_eq!(bar.value, None);
121        assert_eq!(bar.last_assigned_location, None);
122        assert_eq!(bar.read_only_location, None);
123        // $baz retains the previous value since `execute` assigned nothing
124        let baz = inner.variables.get("baz").unwrap();
125        assert_eq!(baz.value, Some(Value::scalar("BAZ")));
126        assert_eq!(baz.last_assigned_location, Some(baz_location));
127        assert_eq!(baz.read_only_location, None);
128        // All the variables are local, so they aren't visible from the outer context
129        Env::pop_context(inner);
130        assert_eq!(outer.variables.get("foo"), None);
131        assert_eq!(outer.variables.get("bar"), None);
132        assert_eq!(outer.variables.get("baz"), None);
133    }
134
135    #[test]
136    fn setting_global_variables() {
137        let mut outer = Env::new_virtual();
138        let baz_location = Location::dummy("assign");
139        let mut baz = outer.get_or_create_variable("baz", Scope::Global.into());
140        baz.assign("BAZ", baz_location.clone()).unwrap();
141        let mut inner = outer.push_context(Context::default());
142        let sv = SetVariables {
143            variables: Field::dummies(["foo=FOO", "bar", "baz"]),
144            attrs: vec![],
145            scope: Scope::Global,
146        };
147        let foo_location = sv.variables[0].origin.clone();
148
149        let result = sv.execute(&mut inner);
150
151        assert_eq!(result, Ok("".to_string()));
152        // All the variables are global, so they are visible from the outer context
153        Env::pop_context(inner);
154        // $foo now has the value assigned by `execute`
155        let foo = outer.variables.get("foo").unwrap();
156        assert_eq!(foo.value, Some(Value::scalar("FOO")));
157        assert_eq!(foo.last_assigned_location, Some(foo_location));
158        assert_eq!(foo.read_only_location, None);
159        // $bar now exists but has no value
160        let bar = outer.variables.get("bar").unwrap();
161        assert_eq!(bar.value, None);
162        assert_eq!(bar.last_assigned_location, None);
163        assert_eq!(bar.read_only_location, None);
164        // $baz retains the previous value since `execute` assigned nothing
165        let baz = outer.variables.get("baz").unwrap();
166        assert_eq!(baz.value, Some(Value::scalar("BAZ")));
167        assert_eq!(baz.last_assigned_location, Some(baz_location));
168        assert_eq!(baz.read_only_location, None);
169    }
170
171    #[test]
172    fn setting_variables_readonly() {
173        let mut env = Env::new_virtual();
174        let sv = SetVariables {
175            variables: Field::dummies(["foo", "bar=BAR"]),
176            attrs: vec![(VariableAttr::ReadOnly, State::On)],
177            scope: Scope::Local,
178        };
179        let foo_location = sv.variables[0].origin.clone();
180        let bar_location = sv.variables[1].origin.clone();
181
182        let result = sv.execute(&mut env);
183
184        assert_eq!(result, Ok("".to_string()));
185        let foo = env.variables.get("foo").unwrap();
186        assert_eq!(foo.value, None);
187        assert_eq!(foo.last_assigned_location.as_ref(), None);
188        assert_eq!(foo.read_only_location.as_ref(), Some(&foo_location));
189        let bar = env.variables.get("bar").unwrap();
190        assert_eq!(bar.value, Some(Value::scalar("BAR")));
191        assert_eq!(bar.last_assigned_location.as_ref(), Some(&bar_location));
192        assert_eq!(bar.read_only_location.as_ref(), Some(&bar_location));
193    }
194
195    #[test]
196    fn exporting_variables() {
197        let mut env = Env::new_virtual();
198        let sv = SetVariables {
199            variables: Field::dummies(["foo", "bar=BAR"]),
200            attrs: vec![(VariableAttr::Export, State::On)],
201            scope: Scope::Local,
202        };
203
204        let result = sv.execute(&mut env);
205
206        assert_eq!(result, Ok("".to_string()));
207        let foo = env.variables.get("foo").unwrap();
208        assert_eq!(foo.value, None);
209        assert!(foo.is_exported);
210        let bar = env.variables.get("bar").unwrap();
211        assert_eq!(bar.value, Some(Value::scalar("BAR")));
212        assert!(bar.is_exported);
213    }
214
215    #[test]
216    fn cancelling_exportation() {
217        let mut env = Env::new_virtual();
218        let mut var = env.get_or_create_variable("bar", Scope::Global.into());
219        var.assign("BAR", None).unwrap();
220        var.export(true);
221        let sv = SetVariables {
222            variables: Field::dummies(["foo", "bar=NEW_BAR"]),
223            attrs: vec![(VariableAttr::Export, State::Off)],
224            scope: Scope::Local,
225        };
226
227        let result = sv.execute(&mut env);
228
229        assert_eq!(result, Ok("".to_string()));
230        let foo = env.variables.get("foo").unwrap();
231        assert_eq!(foo.value, None);
232        assert!(!foo.is_exported);
233        let bar = env.variables.get("bar").unwrap();
234        assert_eq!(bar.value, Some(Value::scalar("NEW_BAR")));
235        assert!(!bar.is_exported);
236    }
237
238    #[test]
239    fn exportation_with_allexport_option() {
240        let mut env = Env::new_virtual();
241        env.options.set(AllExport, State::On);
242
243        let sv = SetVariables {
244            variables: Field::dummies(["foo=FOO"]),
245            attrs: vec![],
246            scope: Scope::Global,
247        };
248        let result = sv.execute(&mut env);
249        assert_eq!(result, Ok("".to_string()));
250        assert!(env.variables.get("foo").unwrap().is_exported);
251
252        let sv = SetVariables {
253            variables: Field::dummies(["foo=BAR"]),
254            attrs: vec![(VariableAttr::Export, State::Off)],
255            scope: Scope::Global,
256        };
257        let result = sv.execute(&mut env);
258        assert_eq!(result, Ok("".to_string()));
259        assert!(!env.variables.get("foo").unwrap().is_exported);
260    }
261
262    #[test]
263    fn unsetting_readonly_attribute() {
264        let mut env = Env::new_virtual();
265        let ro_location = Location::dummy("assign readonly");
266        let mut ro = env.get_or_create_variable("ro", Scope::Global.into());
267        ro.assign("readonly value", ro_location.clone()).unwrap();
268        ro.make_read_only(ro_location.clone());
269        let ro = ro.clone();
270        let w_location = Location::dummy("assign writable");
271        let mut w = env.get_or_create_variable("w", Scope::Global.into());
272        w.assign("writable value", w_location).unwrap();
273        let w = w.clone();
274        let sv = SetVariables {
275            variables: Field::dummies(["ro", "w=foo"]),
276            attrs: vec![(VariableAttr::ReadOnly, State::Off)],
277            scope: Scope::Global,
278        };
279        let ro_arg_location = sv.variables[0].origin.clone();
280        let w_location = sv.variables[1].origin.clone();
281
282        let errors = sv.execute(&mut env).unwrap_err();
283
284        assert_matches!(&errors[..], [ExecuteError::UndoReadOnlyVariable(error)] => {
285            assert_eq!(error.name.value, "ro");
286            assert_eq!(error.name.origin, ro_arg_location);
287            assert_eq!(error.read_only_location, ro_location);
288        });
289        assert_eq!(env.variables.get("ro"), Some(&ro));
290        // No error for variable `w`
291        assert_eq!(
292            env.variables.get("w"),
293            Some(&Variable {
294                value: Some(Value::scalar("foo")),
295                last_assigned_location: Some(w_location),
296                ..w
297            })
298        );
299    }
300
301    #[test]
302    fn overwriting_readonly_variables() {
303        let mut env = Env::new_virtual();
304        let ro_location = Location::dummy("assign readonly");
305        let mut ro = env.get_or_create_variable("ro", Scope::Global.into());
306        ro.assign("readonly value", ro_location.clone()).unwrap();
307        ro.make_read_only(ro_location.clone());
308        let ro = ro.clone();
309
310        let sv = SetVariables {
311            variables: Field::dummies(["ro=foo"]),
312            attrs: vec![],
313            scope: Scope::Global,
314        };
315        let assigned_location = sv.variables[0].origin.clone();
316
317        let errors = sv.execute(&mut env).unwrap_err();
318        assert_matches!(&errors[..], [ExecuteError::AssignReadOnlyVariable(error)] => {
319            assert_eq!(error.new_value, Value::scalar("foo"));
320            assert_eq!(error.assigned_location, assigned_location);
321            assert_eq!(error.read_only_location, ro_location);
322        });
323        assert_eq!(env.variables.get("ro"), Some(&ro));
324    }
325
326    #[test]
327    fn hiding_readonly_variables() {
328        let mut outer = Env::new_virtual();
329        let assign_location = Location::dummy("assign");
330        let mut var = outer.get_or_create_variable("var", Scope::Global.into());
331        var.assign("VAR", assign_location.clone()).unwrap();
332        var.make_read_only(assign_location.clone());
333        let mut inner = outer.push_context(Context::default());
334        let sv = SetVariables {
335            variables: Field::dummies(["var=NEW"]),
336            attrs: vec![(VariableAttr::ReadOnly, State::Off)],
337            scope: Scope::Local,
338        };
339        let new_location = sv.variables[0].origin.clone();
340
341        let result = sv.execute(&mut inner);
342
343        assert_eq!(result, Ok("".to_string()));
344        let var = inner.variables.get("var").unwrap();
345        assert_eq!(var.value, Some(Value::scalar("NEW")));
346        assert_eq!(var.last_assigned_location, Some(new_location));
347        assert_eq!(var.read_only_location, None);
348        Env::pop_context(inner);
349        let var = outer.variables.get("var").unwrap();
350        assert_eq!(var.value, Some(Value::scalar("VAR")));
351        assert_eq!(var.last_assigned_location.as_ref(), Some(&assign_location));
352        assert_eq!(var.read_only_location.as_ref(), Some(&assign_location));
353    }
354
355    #[test]
356    fn combination_of_readonly_attributes() {
357        let mut env = Env::new_virtual();
358        let sv = SetVariables {
359            variables: Field::dummies(["foo=FOO"]),
360            attrs: vec![
361                (VariableAttr::ReadOnly, State::On),
362                (VariableAttr::ReadOnly, State::Off),
363            ],
364            scope: Scope::Local,
365        };
366        let foo_location = sv.variables[0].origin.clone();
367
368        let errors = sv.execute(&mut env).unwrap_err();
369
370        assert_matches!(&errors[..], [ExecuteError::UndoReadOnlyVariable(error)] => {
371            assert_eq!(error.name.value, "foo");
372            assert_eq!(error.name.origin, foo_location);
373            assert_eq!(error.read_only_location, foo_location);
374        });
375        let var = env.variables.get("foo").unwrap();
376        assert_eq!(var.value, Some(Value::scalar("FOO")));
377        assert_eq!(var.last_assigned_location.as_ref(), Some(&foo_location));
378        assert_eq!(var.read_only_location.as_ref(), Some(&foo_location));
379    }
380}