1use 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 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 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 field.value.truncate(name.len());
42 }
43
44 let mut variable = env.get_or_create_variable(&field.value, self.scope.into());
45
46 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 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 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 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 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 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 Env::pop_context(inner);
154 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 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 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 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}