use helm_schema_ast::{TemplateExpr, parse_go_template};
use helm_schema_syntax::TemplatedDocument;
use crate::fragment_expr_eval::FragmentEvalContext;
use crate::static_file_template::{
StaticTemplateProgram, StaticTemplateSource, collect_template_requests_from_exprs,
collect_template_requests_from_helper, literal_helper_calls_from_exprs,
};
use helm_schema_core::{Guard, GuardValue, Predicate};
use super::domain::{AbstractFragment, Guarded};
use super::eval::{Interpreter, NodeView};
use crate::abstract_value::AbstractValue;
impl Interpreter<'_> {
pub(super) fn inline_static_file_fragments(
&mut self,
exprs: &[TemplateExpr],
) -> Guarded<AbstractFragment> {
self.inline_static_templates(exprs).0
}
pub(super) fn inline_static_template_value(
&mut self,
exprs: &[TemplateExpr],
) -> Option<AbstractValue> {
self.inline_static_templates(exprs).1
}
fn inline_static_templates(
&mut self,
exprs: &[TemplateExpr],
) -> (Guarded<AbstractFragment>, Option<AbstractValue>) {
let mut out = Guarded::empty();
let mut values = Vec::new();
let mut template_bindings = self.locals.range_member_values.clone();
template_bindings.extend(self.locals.fragment_values.clone());
let direct_requests = {
let context = FragmentEvalContext::new(self.db);
let current_dot = self.current_dot_fragment();
collect_template_requests_from_exprs(
exprs,
current_dot.as_ref(),
&template_bindings,
&self.locals.output_meta,
context,
)
};
for request in direct_requests {
let (fragment, value) = self.eval_static_template_program(&request);
out.extend(fragment);
values.extend(value);
}
for helper_call in literal_helper_calls_from_exprs(exprs) {
let requests = {
let context = FragmentEvalContext::new(self.db);
let current_dot = self.current_dot_fragment();
let mut seen = self.helper_seen.clone();
let helper_dot = helper_call.arg.as_ref().and_then(|arg| {
context.fragment_value_from_expr(
arg,
&template_bindings,
current_dot.as_ref(),
&mut seen,
)
});
collect_template_requests_from_helper(
&helper_call.name,
helper_dot.as_ref(),
context,
)
};
for request in requests {
let (fragment, value) = self.eval_static_template_program(&request);
out.extend(fragment);
values.extend(value);
}
}
(out, AbstractValue::choice(values))
}
fn eval_static_template_program(
&mut self,
request: &StaticTemplateProgram,
) -> (Guarded<AbstractFragment>, Option<AbstractValue>) {
let db = self.db;
let token: String;
let source_path: &str;
let source: &str;
let selection_predicate: Option<Predicate>;
let values_default_path: Option<&str>;
let textual_program: bool;
match &request.source {
StaticTemplateSource::File { path } => {
let Some(file_source) = db.file_source(path) else {
return (Guarded::empty(), None);
};
token = format!("file:{path}");
source_path = path;
source = file_source;
selection_predicate = None;
values_default_path = None;
textual_program = false;
}
StaticTemplateSource::ValuesDefault { path, program } => {
token = format!("values-default:{path}:{program}");
source_path = path;
source = program;
selection_predicate = Some(Predicate::from(Guard::Eq {
path: helm_schema_core::ValuesPath::parse(path),
value: GuardValue::string(program),
}));
values_default_path = Some(path);
textual_program = true;
}
StaticTemplateSource::Constructed { program } => {
token = format!("constructed:{program}");
source_path = "@tpl";
source = program;
selection_predicate = None;
values_default_path = None;
textual_program = true;
}
}
if self.inline_files.iter().any(|entry| entry == &token) {
return (Guarded::empty(), None);
}
let Some(tree) = parse_go_template(source) else {
return (Guarded::empty(), None);
};
let document = TemplatedDocument::parse_with_root(source, tree.root_node());
let mut nested = Interpreter::for_source(source, Some(source_path), db, &tree, &document);
nested.inline_files = self.inline_files.clone();
nested.inline_files.push(token);
nested.helper_scope = self.helper_scope || textual_program;
nested.helper_seen = self.helper_seen.clone();
nested
.locals
.set_chart_value_defaults(self.locals.chart_value_defaults.clone());
nested.dot_stack.push(request.dot.clone());
nested.active_predicates = self
.active_predicates
.iter()
.filter(|predicate| {
!values_default_path.is_some_and(|path| {
call_site_predicate_is_implied_by_selected_default(predicate, path)
})
})
.cloned()
.collect();
if let Some(predicate) = selection_predicate {
nested.active_predicates.push(predicate);
}
let roots: Vec<NodeView<'_>> = document.roots().iter().map(NodeView::plain).collect();
let contributions = nested.eval_node_list(&roots);
for read in nested.reads {
self.push_nested_read(read);
}
self.absorb_nested_observed_facts(&nested.observed_facts);
self.parsed_yaml_input_paths
.extend(nested.parsed_yaml_input_paths);
self.yaml_serialized_paths
.extend(nested.yaml_serialized_paths);
self.absorb_member_host_conversions(&nested.member_host_conversions);
self.apply_root_set_mutations(
&nested.root_set_mutations_observed,
&nested.root_set_predicates_observed,
&nested.root_value_dispatches_observed,
);
self.pre_rewrite_strict_paths
.extend(nested.pre_rewrite_strict_paths);
self.chart_defaults_observed
.extend(nested.chart_defaults_observed);
self.suppress_predicate_paths
.extend(nested.suppress_predicate_paths);
let fragment = contributions.assemble();
if textual_program {
let value = super::summary::projected_value(&fragment)
.map(AbstractValue::require_rendered_source_presence);
(Guarded::empty(), value)
} else {
(fragment, None)
}
}
}
fn call_site_predicate_is_implied_by_selected_default(predicate: &Predicate, path: &str) -> bool {
fn known_truth(
predicate: &Predicate,
implied_kind: &impl Fn(&str) -> Option<&'static str>,
) -> Option<bool> {
match predicate {
Predicate::True => Some(true),
Predicate::False => Some(false),
Predicate::Guard(Guard::Range { path } | Guard::Truthy { path }) => {
implied_kind(&path.encode()).map(|_| true)
}
Predicate::Guard(Guard::Absent { path }) => implied_kind(&path.encode()).map(|_| false),
Predicate::Guard(Guard::TypeIs { path, schema_type }) => {
implied_kind(&path.encode()).map(|kind| kind == schema_type)
}
Predicate::Guard(Guard::Eq {
path,
value: GuardValue::Null,
}) => implied_kind(&path.encode()).map(|_| false),
Predicate::Not(inner) => known_truth(inner, implied_kind).map(|value| !value),
Predicate::And(items) => {
let values = items
.iter()
.map(|item| known_truth(item, implied_kind))
.collect::<Option<Vec<_>>>()?;
Some(values.into_iter().all(|value| value))
}
Predicate::Or(items) => {
let values = items
.iter()
.map(|item| known_truth(item, implied_kind))
.collect::<Option<Vec<_>>>()?;
Some(values.into_iter().any(|value| value))
}
Predicate::Approximate { .. } | Predicate::Guard(_) => None,
}
}
let selected = helm_schema_core::split_value_path(path);
let implied_kind = |candidate: &str| {
let candidate = helm_schema_core::split_value_path(candidate);
let matches = candidate.len() <= selected.len()
&& candidate
.iter()
.zip(&selected)
.all(|(expected, actual)| expected == "*" || expected == actual);
matches.then_some({
if candidate.len() == selected.len() {
"string"
} else {
"object"
}
})
};
known_truth(predicate, &implied_kind) == Some(true)
}