use std::collections::{BTreeMap, HashSet};
use std::ffi::OsString;
use cageforge_command::{
CommandError, EnvironmentBase, EnvironmentInput, EnvironmentNameKey, EnvironmentSpec,
};
use crate::CompositionError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EffectiveEnvironment {
requested: EnvironmentSpec,
ceiling: EnvironmentSpec,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EffectiveEnvironmentRequirements {
base: EnvironmentBase,
filters: bool,
overrides: bool,
}
impl EffectiveEnvironmentRequirements {
pub const fn base(self) -> EnvironmentBase {
self.base
}
pub const fn filters(self) -> bool {
self.filters
}
pub const fn overrides(self) -> bool {
self.overrides
}
}
impl EffectiveEnvironment {
pub(crate) fn new(requested: EnvironmentSpec, ceiling: EnvironmentSpec) -> Self {
Self { requested, ceiling }
}
pub fn base(&self) -> EnvironmentBase {
least_permissive_base(self.requested.base(), self.ceiling.base())
}
pub fn requirements(&self) -> EffectiveEnvironmentRequirements {
EffectiveEnvironmentRequirements {
base: self.base(),
filters: !self.requested.filters().is_empty() || !self.ceiling.filters().is_empty(),
overrides: !self.requested.overrides().is_empty()
|| !self.ceiling.overrides().is_empty(),
}
}
pub fn requested_matches(&self, environment: &EnvironmentSpec) -> bool {
self.requested == *environment
}
pub fn apply_to(
&self,
input: EnvironmentInput,
) -> Result<BTreeMap<OsString, OsString>, CompositionError> {
if !base_is_at_most(input.base(), self.base()) {
return Err(CompositionError::EnvironmentBaseTooPermissive {
required: self.base(),
supplied: input.base(),
});
}
let requested = self
.requested
.apply_to(input)
.map_err(environment_application_error)?;
let requested_names: HashSet<_> = requested
.variables()
.keys()
.map(|name| EnvironmentNameKey::new(name))
.collect();
let mut effective = self
.ceiling
.apply_to(requested)
.map_err(environment_application_error)?
.into_variables();
effective.retain(|name, _| requested_names.contains(&EnvironmentNameKey::new(name)));
Ok(effective)
}
}
fn environment_application_error(error: CommandError) -> CompositionError {
match error {
CommandError::EnvironmentBaseTooPermissive { required, supplied } => {
CompositionError::EnvironmentBaseTooPermissive { required, supplied }
}
other => CompositionError::EnvironmentApplication { source: other },
}
}
fn least_permissive_base(left: EnvironmentBase, right: EnvironmentBase) -> EnvironmentBase {
match (left, right) {
(EnvironmentBase::None, _) | (_, EnvironmentBase::None) => EnvironmentBase::None,
(EnvironmentBase::Core, _) | (_, EnvironmentBase::Core) => EnvironmentBase::Core,
(EnvironmentBase::All, EnvironmentBase::All) => EnvironmentBase::All,
}
}
fn base_is_at_most(supplied: EnvironmentBase, required: EnvironmentBase) -> bool {
match required {
EnvironmentBase::None => supplied == EnvironmentBase::None,
EnvironmentBase::Core => supplied != EnvironmentBase::All,
EnvironmentBase::All => true,
}
}