use core::borrow::BorrowMut;
use hcl::{
eval::{Context, Evaluate},
Value,
};
use itertools::Itertools;
type Res<T> = Result<T, crate::Error>;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum VarScope {
Var(String, Value),
Scope(String, VarScopes),
}
impl VarScope {
#[must_use]
#[inline]
pub fn get_scope_mut(&mut self, scope: &str) -> Option<&mut VarScopes> {
match self {
Self::Scope(key, varscopes) if key == scope => Some(varscopes),
_ => None,
}
}
#[must_use]
#[inline]
pub fn get_scope_ref(&self, scope: &str) -> Option<&VarScopes> {
match self {
Self::Scope(key, varscopes) if key == scope => Some(varscopes),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct VarScopes(Vec<VarScope>);
impl From<Vec<VarScope>> for VarScopes {
#[inline]
fn from(value: Vec<VarScope>) -> Self {
Self(value)
}
}
impl VarScopes {
#[must_use]
pub fn list_in_scope_mut<'a>(
&'a mut self,
scope: &'a [&str],
) -> Box<dyn Iterator<Item = &'_ mut VarScope> + 'a> {
let [first, remaining @ ..] = scope else {
return Box::new(self.0.iter_mut());
};
Box::new(
self.0
.iter_mut()
.filter_map(|varscope| varscope.get_scope_mut(first))
.flat_map(|varscopes| varscopes.list_in_scope_mut(remaining)),
)
}
#[must_use]
pub fn list_in_scope_ref<'a>(
&'a self,
scope: &'a [impl AsRef<str>],
) -> Box<dyn Iterator<Item = &'_ VarScope> + 'a> {
let [first, remaining @ ..] = scope else {
return Box::new(self.0.iter());
};
Box::new(
self.0
.iter()
.filter_map(|varscope| varscope.get_scope_ref(first.as_ref()))
.flat_map(|varscopes| varscopes.list_in_scope_ref(remaining)),
)
}
#[must_use]
pub fn to_hcl_value(&self) -> Value {
let mut indexmap = hcl::Map::new();
self.0.iter().for_each(|x| match x {
VarScope::Var(k, v) => _ = indexmap.insert(k.clone(), v.clone()),
VarScope::Scope(k, v) => _ = indexmap.insert(k.clone(), v.to_hcl_value()),
});
Value::Object(indexmap)
}
pub fn populate_hcl_ctx(&self, ctx: &mut Context, scope: &[impl AsRef<str>]) {
self.list_in_scope_ref(scope)
.for_each(|varscopes| match varscopes {
VarScope::Var(k, v) => ctx.declare_var(k.to_string(), v.to_owned()),
VarScope::Scope(k, v) => ctx.declare_var(k.to_string(), v.to_hcl_value()),
});
}
#[must_use]
pub fn to_hcl_ctx(&self, scope: &[impl AsRef<str>]) -> Context {
let mut ctx = Context::new();
self.populate_hcl_ctx(&mut ctx, scope);
ctx
}
pub fn set(&mut self, scope: &[String], key: String, value: Value) {
let [first, rest @ ..] = scope else {
self.0.push(VarScope::Var(key, value));
return;
};
if let Some(s) = self.0.iter_mut().find_map(|s| s.get_scope_mut(first)) {
s.set(rest, key, value);
} else {
let mut new = Self::default();
new.set(rest, key, value); self.0.push(VarScope::Scope(first.to_string(), new));
}
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct Engine<'a> {
pub ctx_init: Context<'a>,
pub scope: Vec<String>,
pub varlist: VarScopes,
}
impl Engine<'_> {
#[must_use]
pub fn new() -> Self {
let mut ctx_init = Context::new();
Self::init_ctx(&mut ctx_init);
Self {
ctx_init,
..Default::default()
}
}
pub fn clean_up(&mut self) -> &mut Self {
self.scope = vec![];
self.varlist = VarScopes::default();
self
}
fn init_ctx(ctx: &mut Context) {
crate::functions::ensan_builtin_fns(ctx);
crate::functions::string_manipulation(ctx);
crate::functions::encoding(ctx);
crate::functions::hashing(ctx);
}
fn parse_block(&mut self, block: &mut hcl::Block) -> Res<()> {
let old_scope_len = self.scope.len();
{
self.scope.reserve(1 + block.labels.len());
self.scope.push(block.identifier.to_string());
self.scope
.extend(block.labels.iter().map(|bl| bl.to_owned().into_inner()));
let mut ctx = self.ctx_init.clone();
self.varlist.populate_hcl_ctx(&mut ctx, &self.scope);
for structure in &mut block.body {
self.parse_struct(structure, &mut ctx)?;
}
}
self.scope.drain(old_scope_len..);
Ok(())
}
fn parse_struct(&mut self, structure: &mut hcl::Structure, ctx: &mut Context) -> Res<()> {
if let Some(attr) = structure.as_attribute_mut() {
let val = attr.expr.evaluate(ctx)?;
self.varlist
.set(&self.scope, attr.key.to_string(), val.clone());
ctx.declare_var(attr.key.clone(), val.clone());
*attr.expr.borrow_mut() = val.into(); } else if let Some(block) = structure.as_block_mut() {
self.parse_block(block)?;
let vs: VarScopes = self
.varlist
.list_in_scope_ref(&self.scope)
.filter(|v| matches!(v, VarScope::Scope(k, _) if k == block.identifier()))
.cloned()
.collect_vec()
.into();
vs.populate_hcl_ctx(ctx, &[] as &[&str]);
} else {
unreachable!()
};
Ok(())
}
#[must_use]
pub fn parse(&mut self, content: impl AsRef<str>) -> Res<hcl::Body> {
let mut body = hcl::parse(content.as_ref())?;
let mut ctx = self.ctx_init.clone();
self.varlist.populate_hcl_ctx(&mut ctx, &self.scope);
for structure in &mut body {
self.parse_struct(structure, &mut ctx)?;
}
Ok(body)
}
}