use crate::common::arrange_message_and_divert;
use thiserror::Error;
use yash_env::Env;
use yash_env::semantics::ExitStatus;
use yash_env::semantics::Field;
#[cfg(doc)]
use yash_env::system::SharedSystem;
use yash_env::variable::Scope::Global;
use yash_syntax::source::Location;
use yash_syntax::source::pretty::Annotation;
use yash_syntax::source::pretty::AnnotationType;
use yash_syntax::source::pretty::Message;
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub struct UnsetVariablesError<'a> {
pub name: &'a Field,
pub read_only_location: Location,
}
impl std::fmt::Display for UnsetVariablesError<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let error = yash_env::variable::UnsetError {
name: &self.name.value,
read_only_location: &self.read_only_location,
};
error.fmt(f)
}
}
pub fn unset_variables<'a>(
env: &mut Env,
names: &'a [Field],
) -> Result<(), Vec<UnsetVariablesError<'a>>> {
let mut errors = Vec::new();
for name in names {
match env.variables.unset(&name.value, Global) {
Ok(_) => (),
Err(error) => errors.push(UnsetVariablesError {
name,
read_only_location: error.read_only_location.clone(),
}),
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
#[must_use = "returned message should be printed"]
pub fn unset_variables_error_message(
env: &Env,
errors: &[UnsetVariablesError],
) -> (String, yash_env::semantics::Result) {
let annotations = errors
.iter()
.flat_map(|error| {
[
Annotation::new(
AnnotationType::Error,
error.to_string().into(),
&error.name.origin,
),
Annotation::new(
AnnotationType::Info,
format!("variable `{}` was made read-only here", error.name).into(),
&error.read_only_location,
),
]
})
.collect();
let message = Message {
r#type: AnnotationType::Error,
title: "cannot unset variable".into(),
annotations,
footers: vec![],
};
arrange_message_and_divert(env, message)
}
pub async fn report_variables_error(
env: &mut Env,
errors: &[UnsetVariablesError<'_>],
) -> crate::Result {
let (message, divert) = unset_variables_error_message(env, errors);
env.system.print_error(&message).await;
crate::Result::with_exit_status_and_divert(ExitStatus::FAILURE, divert)
}
#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[error("cannot unset read-only function `{name}`")]
pub struct UnsetFunctionsError<'a> {
pub name: &'a Field,
pub read_only_location: Location,
}
pub fn unset_functions<'a>(
env: &mut Env,
names: &'a [Field],
) -> Result<(), Vec<UnsetFunctionsError<'a>>> {
let mut errors = Vec::new();
for name in names {
match env.functions.unset(&name.value) {
Ok(_) => (),
Err(error) => errors.push(UnsetFunctionsError {
name,
read_only_location: error.existing.read_only_location.clone().unwrap(),
}),
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
#[must_use = "returned message should be printed"]
pub fn unset_functions_error_message(
env: &mut Env,
errors: &[UnsetFunctionsError<'_>],
) -> (String, yash_env::semantics::Result) {
let annotations = errors
.iter()
.flat_map(|error| {
[
Annotation::new(
AnnotationType::Error,
error.to_string().into(),
&error.name.origin,
),
Annotation::new(
AnnotationType::Info,
format!("function `{}` was made read-only here", error.name).into(),
&error.read_only_location,
),
]
})
.collect();
let message = Message {
r#type: AnnotationType::Error,
title: "cannot unset function".into(),
annotations,
footers: vec![],
};
arrange_message_and_divert(env, message)
}
pub async fn report_functions_error(
env: &mut Env,
errors: &[UnsetFunctionsError<'_>],
) -> crate::Result {
let (message, divert) = unset_functions_error_message(env, errors);
env.system.print_error(&message).await;
crate::Result::with_exit_status_and_divert(ExitStatus::FAILURE, divert)
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use yash_env::function::Function;
use yash_env::variable::Value;
use yash_syntax::source::Location;
use yash_syntax::syntax::FullCompoundCommand;
#[test]
fn unsetting_one_variable() {
let mut env = Env::new_virtual();
env.get_or_create_variable("foo", Global)
.assign("FOO", None)
.unwrap();
env.get_or_create_variable("bar", Global)
.assign("BAR", None)
.unwrap();
env.get_or_create_variable("baz", Global)
.assign("BAZ", None)
.unwrap();
unset_variables(&mut env, &Field::dummies(["bar"])).unwrap();
assert_eq!(
env.variables.get("foo").unwrap().value,
Some(Value::scalar("FOO")),
);
assert_eq!(env.variables.get("bar"), None);
assert_eq!(
env.variables.get("baz").unwrap().value,
Some(Value::scalar("BAZ")),
);
}
#[test]
fn unsetting_many_variables() {
let mut env = Env::new_virtual();
env.get_or_create_variable("foo", Global)
.assign("FOO", None)
.unwrap();
env.get_or_create_variable("bar", Global)
.assign("BAR", None)
.unwrap();
env.get_or_create_variable("baz", Global)
.assign("BAZ", None)
.unwrap();
unset_variables(&mut env, &Field::dummies(["bar", "foo", "baz"])).unwrap();
assert_eq!(env.variables.get("foo"), None);
assert_eq!(env.variables.get("bar"), None);
assert_eq!(env.variables.get("baz"), None);
}
#[test]
fn unsetting_readonly_variables() {
let mut env = Env::new_virtual();
let mut a = env.get_or_create_variable("a", Global);
a.assign("A", None).unwrap();
let mut b = env.get_or_create_variable("b", Global);
b.assign("B", None).unwrap();
let location_b = Location::dummy("readonly b");
b.make_read_only(location_b.clone());
let mut c = env.get_or_create_variable("c", Global);
c.assign("C", None).unwrap();
let location_c = Location::dummy("readonly c");
c.make_read_only(location_c.clone());
let mut d = env.get_or_create_variable("d", Global);
d.assign("D", None).unwrap();
let names = Field::dummies(["a", "b", "c", "d"]);
let errors = unset_variables(&mut env, &names).unwrap_err();
assert_matches!(&errors[..], [e1, e2] => {
assert_eq!(e1.name, &Field::dummy("b"));
assert_eq!(e1.read_only_location, location_b);
assert_eq!(e2.name, &Field::dummy("c"));
assert_eq!(e2.read_only_location, location_c);
});
assert_eq!(env.variables.get("a"), None);
assert_eq!(
env.variables.get("b").unwrap().value,
Some(Value::scalar("B")),
);
assert_eq!(
env.variables.get("c").unwrap().value,
Some(Value::scalar("C")),
);
assert_eq!(env.variables.get("d"), None);
}
fn dummy_function(name: &str) -> Function {
Function::new(
name,
"{ :; }".parse::<FullCompoundCommand>().unwrap(),
Location::dummy(name),
)
}
#[test]
fn unsetting_one_function() {
let mut env = Env::new_virtual();
env.functions.define(dummy_function("foo")).unwrap();
env.functions.define(dummy_function("bar")).unwrap();
env.functions.define(dummy_function("baz")).unwrap();
unset_functions(&mut env, &Field::dummies(["foo"])).unwrap();
assert_eq!(env.functions.get("foo"), None);
assert_eq!(env.functions.get("bar").unwrap().name, "bar");
assert_eq!(env.functions.get("baz").unwrap().name, "baz");
}
#[test]
fn unsetting_many_functions() {
let mut env = Env::new_virtual();
env.functions.define(dummy_function("foo")).unwrap();
env.functions.define(dummy_function("bar")).unwrap();
env.functions.define(dummy_function("baz")).unwrap();
unset_functions(&mut env, &Field::dummies(["bar", "foo", "baz"])).unwrap();
assert_eq!(env.functions.get("foo"), None);
assert_eq!(env.functions.get("bar"), None);
assert_eq!(env.functions.get("baz"), None);
}
#[test]
fn unsetting_readonly_function() {
let mut env = Env::new_virtual();
env.functions.define(dummy_function("a")).unwrap();
let location_b = Location::dummy("readonly b");
env.functions
.define(dummy_function("b").make_read_only(location_b.clone()))
.unwrap();
let location_c = Location::dummy("readonly c");
env.functions
.define(dummy_function("c").make_read_only(location_c.clone()))
.unwrap();
env.functions.define(dummy_function("d")).unwrap();
let names = Field::dummies(["a", "b", "c", "d"]);
let errors = unset_functions(&mut env, &names).unwrap_err();
assert_matches!(&errors[..], [e1, e2] => {
assert_eq!(e1.name, &Field::dummy("b"));
assert_eq!(e1.read_only_location, location_b);
assert_eq!(e2.name, &Field::dummy("c"));
assert_eq!(e2.read_only_location, location_c);
});
assert_eq!(env.functions.get("a"), None);
assert_eq!(env.functions.get("b").unwrap().name, "b");
assert_eq!(env.functions.get("c").unwrap().name, "c");
assert_eq!(env.functions.get("d"), None);
}
}