use super::*;
use yash_env::variable::Value;
impl From<Scope> for yash_env::variable::Scope {
fn from(value: Scope) -> Self {
match value {
Scope::Local => Self::Local,
Scope::Global => Self::Global,
}
}
}
impl SetVariables {
pub fn execute(self, env: &mut Env) -> Result<String, Vec<ExecuteError>> {
let mut errors = Vec::new();
'field: for mut field in self.variables {
let mut value_to_assign = None;
if let Some((name, value)) = field.value.split_once('=') {
value_to_assign = Some(Value::scalar(value));
field.value.truncate(name.len());
}
let mut variable = env.get_or_create_variable(&field.value, self.scope.into());
if let Some(value) = value_to_assign {
if let Err(error) = variable.assign(value, field.origin.clone()) {
errors.push(ExecuteError::AssignReadOnlyVariable(AssignReadOnlyError {
name: field.value,
new_value: error.new_value,
assigned_location: error.assigned_location.unwrap(),
read_only_location: error.read_only_location,
}));
continue;
}
}
for &(attr, state) in &self.attrs {
match (attr, state) {
(VariableAttr::ReadOnly, State::On) => {
variable.make_read_only(field.origin.clone())
}
(VariableAttr::ReadOnly, State::Off) => {
if let Some(read_only_location) = variable.read_only_location.clone() {
errors.push(ExecuteError::UndoReadOnlyVariable(UndoReadOnlyError {
name: field,
read_only_location,
}));
continue 'field;
}
}
(VariableAttr::Export, State::On) => variable.export(true),
(VariableAttr::Export, State::Off) => variable.export(false),
}
}
}
if errors.is_empty() {
Ok(String::new())
} else {
Err(errors)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use yash_env::option::Option::AllExport;
use yash_env::variable::{Context, Variable};
use yash_syntax::source::Location;
#[test]
fn setting_local_variables() {
let mut outer = Env::new_virtual();
let mut inner = outer.push_context(Context::default());
let baz_location = Location::dummy("baz assigned");
let mut baz = inner.get_or_create_variable("baz", Scope::Local.into());
baz.assign("BAZ", baz_location.clone()).unwrap();
let sv = SetVariables {
variables: Field::dummies(["foo=FOO", "bar", "baz"]),
attrs: vec![],
scope: Scope::Local,
};
let foo_location = sv.variables[0].origin.clone();
let result = sv.execute(&mut inner);
assert_eq!(result, Ok("".to_string()));
let foo = inner.variables.get("foo").unwrap();
assert_eq!(foo.value, Some(Value::scalar("FOO")));
assert_eq!(foo.last_assigned_location, Some(foo_location));
assert_eq!(foo.read_only_location, None);
let bar = inner.variables.get("bar").unwrap();
assert_eq!(bar.value, None);
assert_eq!(bar.last_assigned_location, None);
assert_eq!(bar.read_only_location, None);
let baz = inner.variables.get("baz").unwrap();
assert_eq!(baz.value, Some(Value::scalar("BAZ")));
assert_eq!(baz.last_assigned_location, Some(baz_location));
assert_eq!(baz.read_only_location, None);
Env::pop_context(inner);
assert_eq!(outer.variables.get("foo"), None);
assert_eq!(outer.variables.get("bar"), None);
assert_eq!(outer.variables.get("baz"), None);
}
#[test]
fn setting_global_variables() {
let mut outer = Env::new_virtual();
let baz_location = Location::dummy("assign");
let mut baz = outer.get_or_create_variable("baz", Scope::Global.into());
baz.assign("BAZ", baz_location.clone()).unwrap();
let mut inner = outer.push_context(Context::default());
let sv = SetVariables {
variables: Field::dummies(["foo=FOO", "bar", "baz"]),
attrs: vec![],
scope: Scope::Global,
};
let foo_location = sv.variables[0].origin.clone();
let result = sv.execute(&mut inner);
assert_eq!(result, Ok("".to_string()));
Env::pop_context(inner);
let foo = outer.variables.get("foo").unwrap();
assert_eq!(foo.value, Some(Value::scalar("FOO")));
assert_eq!(foo.last_assigned_location, Some(foo_location));
assert_eq!(foo.read_only_location, None);
let bar = outer.variables.get("bar").unwrap();
assert_eq!(bar.value, None);
assert_eq!(bar.last_assigned_location, None);
assert_eq!(bar.read_only_location, None);
let baz = outer.variables.get("baz").unwrap();
assert_eq!(baz.value, Some(Value::scalar("BAZ")));
assert_eq!(baz.last_assigned_location, Some(baz_location));
assert_eq!(baz.read_only_location, None);
}
#[test]
fn setting_variables_readonly() {
let mut env = Env::new_virtual();
let sv = SetVariables {
variables: Field::dummies(["foo", "bar=BAR"]),
attrs: vec![(VariableAttr::ReadOnly, State::On)],
scope: Scope::Local,
};
let foo_location = sv.variables[0].origin.clone();
let bar_location = sv.variables[1].origin.clone();
let result = sv.execute(&mut env);
assert_eq!(result, Ok("".to_string()));
let foo = env.variables.get("foo").unwrap();
assert_eq!(foo.value, None);
assert_eq!(foo.last_assigned_location.as_ref(), None);
assert_eq!(foo.read_only_location.as_ref(), Some(&foo_location));
let bar = env.variables.get("bar").unwrap();
assert_eq!(bar.value, Some(Value::scalar("BAR")));
assert_eq!(bar.last_assigned_location.as_ref(), Some(&bar_location));
assert_eq!(bar.read_only_location.as_ref(), Some(&bar_location));
}
#[test]
fn exporting_variables() {
let mut env = Env::new_virtual();
let sv = SetVariables {
variables: Field::dummies(["foo", "bar=BAR"]),
attrs: vec![(VariableAttr::Export, State::On)],
scope: Scope::Local,
};
let result = sv.execute(&mut env);
assert_eq!(result, Ok("".to_string()));
let foo = env.variables.get("foo").unwrap();
assert_eq!(foo.value, None);
assert!(foo.is_exported);
let bar = env.variables.get("bar").unwrap();
assert_eq!(bar.value, Some(Value::scalar("BAR")));
assert!(bar.is_exported);
}
#[test]
fn cancelling_exportation() {
let mut env = Env::new_virtual();
let mut var = env.get_or_create_variable("bar", Scope::Global.into());
var.assign("BAR", None).unwrap();
var.export(true);
let sv = SetVariables {
variables: Field::dummies(["foo", "bar=NEW_BAR"]),
attrs: vec![(VariableAttr::Export, State::Off)],
scope: Scope::Local,
};
let result = sv.execute(&mut env);
assert_eq!(result, Ok("".to_string()));
let foo = env.variables.get("foo").unwrap();
assert_eq!(foo.value, None);
assert!(!foo.is_exported);
let bar = env.variables.get("bar").unwrap();
assert_eq!(bar.value, Some(Value::scalar("NEW_BAR")));
assert!(!bar.is_exported);
}
#[test]
fn exportation_with_allexport_option() {
let mut env = Env::new_virtual();
env.options.set(AllExport, State::On);
let sv = SetVariables {
variables: Field::dummies(["foo=FOO"]),
attrs: vec![],
scope: Scope::Global,
};
let result = sv.execute(&mut env);
assert_eq!(result, Ok("".to_string()));
assert!(env.variables.get("foo").unwrap().is_exported);
let sv = SetVariables {
variables: Field::dummies(["foo=BAR"]),
attrs: vec![(VariableAttr::Export, State::Off)],
scope: Scope::Global,
};
let result = sv.execute(&mut env);
assert_eq!(result, Ok("".to_string()));
assert!(!env.variables.get("foo").unwrap().is_exported);
}
#[test]
fn unsetting_readonly_attribute() {
let mut env = Env::new_virtual();
let ro_location = Location::dummy("assign readonly");
let mut ro = env.get_or_create_variable("ro", Scope::Global.into());
ro.assign("readonly value", ro_location.clone()).unwrap();
ro.make_read_only(ro_location.clone());
let ro = ro.clone();
let w_location = Location::dummy("assign writable");
let mut w = env.get_or_create_variable("w", Scope::Global.into());
w.assign("writable value", w_location).unwrap();
let w = w.clone();
let sv = SetVariables {
variables: Field::dummies(["ro", "w=foo"]),
attrs: vec![(VariableAttr::ReadOnly, State::Off)],
scope: Scope::Global,
};
let ro_arg_location = sv.variables[0].origin.clone();
let w_location = sv.variables[1].origin.clone();
let errors = sv.execute(&mut env).unwrap_err();
assert_matches!(&errors[..], [ExecuteError::UndoReadOnlyVariable(error)] => {
assert_eq!(error.name.value, "ro");
assert_eq!(error.name.origin, ro_arg_location);
assert_eq!(error.read_only_location, ro_location);
});
assert_eq!(env.variables.get("ro"), Some(&ro));
assert_eq!(
env.variables.get("w"),
Some(&Variable {
value: Some(Value::scalar("foo")),
last_assigned_location: Some(w_location),
..w
})
);
}
#[test]
fn overwriting_readonly_variables() {
let mut env = Env::new_virtual();
let ro_location = Location::dummy("assign readonly");
let mut ro = env.get_or_create_variable("ro", Scope::Global.into());
ro.assign("readonly value", ro_location.clone()).unwrap();
ro.make_read_only(ro_location.clone());
let ro = ro.clone();
let sv = SetVariables {
variables: Field::dummies(["ro=foo"]),
attrs: vec![],
scope: Scope::Global,
};
let assigned_location = sv.variables[0].origin.clone();
let errors = sv.execute(&mut env).unwrap_err();
assert_matches!(&errors[..], [ExecuteError::AssignReadOnlyVariable(error)] => {
assert_eq!(error.new_value, Value::scalar("foo"));
assert_eq!(error.assigned_location, assigned_location);
assert_eq!(error.read_only_location, ro_location);
});
assert_eq!(env.variables.get("ro"), Some(&ro));
}
#[test]
fn hiding_readonly_variables() {
let mut outer = Env::new_virtual();
let assign_location = Location::dummy("assign");
let mut var = outer.get_or_create_variable("var", Scope::Global.into());
var.assign("VAR", assign_location.clone()).unwrap();
var.make_read_only(assign_location.clone());
let mut inner = outer.push_context(Context::default());
let sv = SetVariables {
variables: Field::dummies(["var=NEW"]),
attrs: vec![(VariableAttr::ReadOnly, State::Off)],
scope: Scope::Local,
};
let new_location = sv.variables[0].origin.clone();
let result = sv.execute(&mut inner);
assert_eq!(result, Ok("".to_string()));
let var = inner.variables.get("var").unwrap();
assert_eq!(var.value, Some(Value::scalar("NEW")));
assert_eq!(var.last_assigned_location, Some(new_location));
assert_eq!(var.read_only_location, None);
Env::pop_context(inner);
let var = outer.variables.get("var").unwrap();
assert_eq!(var.value, Some(Value::scalar("VAR")));
assert_eq!(var.last_assigned_location.as_ref(), Some(&assign_location));
assert_eq!(var.read_only_location.as_ref(), Some(&assign_location));
}
#[test]
fn combination_of_readonly_attributes() {
let mut env = Env::new_virtual();
let sv = SetVariables {
variables: Field::dummies(["foo=FOO"]),
attrs: vec![
(VariableAttr::ReadOnly, State::On),
(VariableAttr::ReadOnly, State::Off),
],
scope: Scope::Local,
};
let foo_location = sv.variables[0].origin.clone();
let errors = sv.execute(&mut env).unwrap_err();
assert_matches!(&errors[..], [ExecuteError::UndoReadOnlyVariable(error)] => {
assert_eq!(error.name.value, "foo");
assert_eq!(error.name.origin, foo_location);
assert_eq!(error.read_only_location, foo_location);
});
let var = env.variables.get("foo").unwrap();
assert_eq!(var.value, Some(Value::scalar("FOO")));
assert_eq!(var.last_assigned_location.as_ref(), Some(&foo_location));
assert_eq!(var.read_only_location.as_ref(), Some(&foo_location));
}
}