use std::fmt::Write as _;
use crate::{Guard, ValueKind};
use helm_schema_core::Predicate;
use super::domain::{AbstractFragment, AbstractString, EntryKey, Guarded, Splice, StringPart};
use super::eval::EvaluatedDocument;
#[must_use]
pub(crate) fn dump_document(document: &EvaluatedDocument) -> String {
let mut out = String::new();
dump_guarded(&document.root, 0, &mut out);
if !document.reads.is_empty() {
let _ = writeln!(out, "reads:");
for read in &document.reads {
let _ = writeln!(
out,
" {} [{}]",
read.values_path.encode(),
read.condition
.guard_conjunctions()
.iter()
.flatten()
.map(fmt_guard)
.collect::<Vec<_>>()
.join(", "),
);
}
}
out
}
fn dump_guarded(guarded: &Guarded<AbstractFragment>, depth: usize, out: &mut String) {
let pad = " ".repeat(depth);
for (condition, node) in &guarded.arms {
let _ = writeln!(out, "{pad}when {}:", fmt_condition(condition));
dump_node(node, depth + 1, out);
}
}
fn dump_node(node: &AbstractFragment, depth: usize, out: &mut String) {
let pad = " ".repeat(depth);
match node {
AbstractFragment::Mapping(mapping) => {
let _ = writeln!(out, "{pad}mapping:");
for entry in &mapping.entries {
match &entry.key {
EntryKey::Literal(key) => {
let _ = writeln!(out, "{pad} key {key:?}:");
}
EntryKey::Dynamic(key) => {
let _ = writeln!(out, "{pad} key dynamic {}:", fmt_string(key));
}
}
dump_guarded(&entry.value, depth + 2, out);
}
}
AbstractFragment::Sequence(sequence) => {
let _ = writeln!(out, "{pad}sequence:");
for item in &sequence.items {
let _ = writeln!(out, "{pad} item:");
dump_guarded(item, depth + 2, out);
}
}
AbstractFragment::Scalar(scalar) => {
let suppressed = if scalar.suppressed { " suppressed" } else { "" };
let _ = writeln!(out, "{pad}scalar{suppressed} {}", fmt_string(scalar));
}
AbstractFragment::Splice(splice) => {
let _ = writeln!(out, "{pad}{}", fmt_splice(splice));
}
AbstractFragment::Opaque(opaque) => {
if opaque.taint.is_empty() {
let _ = writeln!(out, "{pad}opaque");
} else {
let taint: Vec<String> = opaque
.taint
.iter()
.map(helm_schema_core::ValuesPath::encode)
.collect();
let _ = writeln!(out, "{pad}opaque taint={{{}}}", taint.join(", "));
}
}
}
}
fn fmt_string(string: &AbstractString) -> String {
let parts: Vec<String> = string
.parts
.iter()
.map(|part| match part {
StringPart::Text(alternatives) => {
let rendered: Vec<String> = alternatives
.iter()
.map(|text| format!("{text:?}"))
.collect();
format!("text{{{}}}", rendered.join("|"))
}
StringPart::Splice(splice) => fmt_splice(splice),
StringPart::Taint(taint) => {
let rendered: Vec<String> = taint
.paths
.iter()
.map(helm_schema_core::ValuesPath::encode)
.collect();
format!("taint{{{}}}", rendered.join(", "))
}
})
.collect();
format!("[{}]", parts.join(" "))
}
fn fmt_splice(splice: &Splice) -> String {
let kind = match splice.kind {
ValueKind::Scalar => "scalar",
ValueKind::PartialScalar => "partial",
ValueKind::Fragment => "fragment",
ValueKind::Serialized => "serialized",
ValueKind::YamlSerialized => "yaml-serialized",
ValueKind::TemplatedYamlSerialized => "templated-yaml-serialized",
ValueKind::WidenedDependency => "widened-dependency",
};
let mut rendered = format!("splice {} {kind}", splice.values_path.encode());
if splice.meta.defaulted {
rendered.push_str(" defaulted");
}
if splice.meta.encoded {
rendered.push_str(" encoded");
}
if splice.meta.range_key {
rendered.push_str(" range-key");
}
rendered
}
fn fmt_condition(condition: &Predicate) -> String {
match condition {
Predicate::True => "always".to_string(),
Predicate::False => "never".to_string(),
Predicate::Approximate { paths, .. } => {
format!(
"approximate({})",
paths
.iter()
.map(helm_schema_core::ValuesPath::encode)
.collect::<Vec<_>>()
.join(", ")
)
}
Predicate::Guard(guard) => fmt_guard(guard),
Predicate::Not(inner) => format!("!({})", fmt_condition(inner)),
Predicate::And(parts) => {
let rendered: Vec<String> = parts.iter().map(fmt_condition).collect();
format!("({})", rendered.join(" && "))
}
Predicate::Or(parts) => {
let rendered: Vec<String> = parts.iter().map(fmt_condition).collect();
format!("({})", rendered.join(" || "))
}
}
}
fn fmt_guard(guard: &Guard) -> String {
match guard {
Guard::Truthy { path } => format!("truthy({})", path.encode()),
Guard::Not { path } => format!("not({})", path.encode()),
Guard::Eq { path, value } => format!("eq({} == {value})", path.encode()),
Guard::NotEq { path, value } => format!("ne({} != {value})", path.encode()),
Guard::Absent { path } => format!("absent({})", path.encode()),
Guard::MatchesPattern {
path,
pattern,
templated,
} => {
let suffix = if *templated { " templated" } else { "" };
format!("matches({} ~ {pattern}{suffix})", path.encode())
}
Guard::NotMatchesPattern { path, pattern } => {
format!("notMatches({} ~ {pattern})", path.encode())
}
Guard::RangeKeyPrefix { path, prefix } => {
format!("rangeKeyPrefix({}: {prefix})", path.encode())
}
Guard::RangeKeyMatches { path, pattern } => {
format!("rangeKeyMatches({} ~ {pattern})", path.encode())
}
Guard::AtMostOneMember { path } => format!("atMostOneMember({})", path.encode()),
Guard::MinMembers { path, bound } => {
format!("minMembers({} >= {bound})", path.encode())
}
Guard::HasKey { path, key } => format!("hasKey({}: {key})", path.encode()),
Guard::NotHasKey { path, key } => format!("notHasKey({}: {key})", path.encode()),
Guard::ContainsEquals { path, value } => {
format!("containsEquals({} ∋ {value})", path.encode())
}
Guard::ContainsMemberEquals {
path,
member,
value,
} => format!(
"containsMemberEquals({}.*.{member} == {value})",
path.encode()
),
Guard::ContainsTruthyMember { path, member } => {
format!("containsTruthyMember({}.*.{member})", path.encode())
}
Guard::Or { paths } => format!(
"or({})",
paths
.iter()
.map(helm_schema_core::ValuesPath::encode)
.collect::<Vec<_>>()
.join(", ")
),
Guard::AnyOf { alternatives } => {
let rendered: Vec<String> = alternatives
.iter()
.map(|alternative| {
let guards: Vec<String> = alternative.iter().map(fmt_guard).collect();
format!("[{}]", guards.join(", "))
})
.collect();
format!("anyOf({})", rendered.join(" | "))
}
Guard::Range { path } => format!("range({})", path.encode()),
Guard::With { path } => format!("with({})", path.encode()),
Guard::Default { path } => format!("default({})", path.encode()),
Guard::TypeIs { path, schema_type } => {
format!("typeIs({}: {schema_type})", path.encode())
}
Guard::NotTypeIs { path, schema_type } => {
format!("notTypeIs({}: {schema_type})", path.encode())
}
Guard::IntGt { path, bound } => format!("intGt({} > {bound})", path.encode()),
Guard::IntLt { path, bound } => format!("intLt({} < {bound})", path.encode()),
Guard::RangeKeyEquals { path, key } => {
format!("rangeKeyEquals({}[{key}])", path.encode())
}
}
}