use serde_json::Value;
use std::borrow::Cow;
use crate::funnel::{self, TemplatePlaceholder, TemplateToken};
use crate::parse::{Element, Node};
pub fn fill_attr_templates_in_nodes(nodes: &mut [Node], ctx: &Value) {
for node in nodes {
if let Node::Element(el) = node {
fill_attrs(el, ctx);
fill_attr_templates_in_nodes(&mut el.children, ctx);
}
}
}
fn fill_attrs(el: &mut Element, ctx: &Value) {
if el.is_script() || el.is_style() {
return;
}
for (name, v) in &mut el.attrs {
if !Element::is_translation_attr(name) && funnel::has_template_tokens(v) {
*v = expand_template(v, ctx);
}
}
}
pub(crate) fn expand_template(raw: &str, ctx: &Value) -> String {
funnel::template_tokens(raw)
.into_iter()
.map(|token| match token {
TemplateToken::Text(text) => Cow::Borrowed(text),
TemplateToken::Placeholder(TemplatePlaceholder::Path(path)) => {
Cow::Owned(funnel::path_as_str(ctx, path.as_str()))
}
TemplateToken::Placeholder(TemplatePlaceholder::Expression(expr)) => {
Cow::Owned(funnel::path_as_str(ctx, &expr))
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::funnel::{bind_context, BindDecl};
use serde_json::json;
#[test]
fn expands_slug_in_href() {
let ctx = json!({"slug": "hello-world"});
assert_eq!(
expand_template("/posts/${slug}/", &ctx),
"/posts/hello-world/"
);
}
#[test]
fn named_prop_no_magic_flatten() {
let button = json!({"variant": "ghost", "href": "/x"});
let ctx = bind_context(&BindDecl::Named("button".into()), &button);
assert_eq!(expand_template("${button.href}", &ctx), "/x");
assert_eq!(expand_template("${variant}", &ctx), ""); }
#[test]
fn destructure_exposes_listed_fields() {
let ctx = bind_context(
&BindDecl::destructure_flat(["variant", "href"]),
&json!({"variant": "ghost", "href": "/x"}),
);
assert_eq!(
expand_template(r#"class="button ${variant}" href="${href}""#, &ctx),
r#"class="button ghost" href="/x""#
);
}
#[test]
fn template_expressions_are_not_evaluated() {
let ctx = json!({"a": 1, "b": 2});
assert_eq!(expand_template("${a + b}", &ctx), "");
}
}