use std::{io::Read, path::Path};
use hcl::{
eval::{Context, FuncArgs},
Value,
};
#[allow(clippy::module_name_repetitions)]
pub struct HclFormat;
impl figment::providers::Format for HclFormat {
type Error = hcl::Error;
const NAME: &'static str = "HCL";
fn from_str<T: serde::de::DeserializeOwned>(string: &str) -> Result<T, Self::Error> {
hcl::eval::from_str(string, &get_hcl_context())
}
}
#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
fn num_cpus(_: FuncArgs) -> Result<Value, String> {
let num_cpus = num_cpus::get();
Ok(Value::Number(num_cpus.into()))
}
#[allow(clippy::needless_pass_by_value)]
fn read_hcl_file(args: FuncArgs) -> Result<Value, String> {
let Some(path) = args.first().and_then(Value::as_str) else {
return Err("import() expects a file path".to_string());
};
let path = Path::new(path);
if !path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("hcl"))
{
return Err(format!(
"File must be a HCL file: {}",
path.to_string_lossy()
));
}
let Ok(path) = std::path::absolute(path) else {
return Err(format!("file not found: {}", path.to_string_lossy()));
};
let Ok(mut file) = std::fs::File::open(&path) else {
return Err(format!("file not found: {}", path.to_string_lossy()));
};
let mut contents = String::new();
if let Err(err) = file.read_to_string(&mut contents) {
return Err(format!(
"failed to read file {}: {err}",
path.to_string_lossy()
));
}
let value = match hcl::eval::from_str::<Value>(&contents, &get_hcl_context()) {
Ok(v) => v,
Err(e) => {
return Err(format!("failed to parse hcl file: {e}"));
}
};
Ok(value)
}
#[allow(clippy::needless_pass_by_value)]
fn get_env_var(args: FuncArgs) -> Result<Value, String> {
let Some(key) = args.first().and_then(Value::as_str) else {
return Err("env() expects a variable name".to_string());
};
let Ok(var) = std::env::var(key) else {
return Err(format!("Environment variable {key} not found"));
};
Ok(Value::String(var))
}
fn get_hcl_context<'a>() -> Context<'a> {
let env_func = hcl::eval::FuncDef::builder()
.param(hcl::eval::ParamType::String)
.build(get_env_var);
let read_file_func = hcl::eval::FuncDef::builder()
.param(hcl::eval::ParamType::String)
.build(read_hcl_file);
let num_cpus_func = hcl::eval::FuncDef::builder().build(num_cpus);
let mut context = hcl::eval::Context::new();
context.declare_func("env", env_func);
context.declare_func("import", read_file_func);
context.declare_func("num_cpus", num_cpus_func);
context
}
pub type Hcl = figment::providers::Data<HclFormat>;