use std::collections::HashMap;
use super::braces::find_matching_braces;
use super::{Expander, split_parts};
const MAX_PARAMETER_RECURSION: usize = 16;
#[derive(Debug)]
pub struct Template(Vec<TemplatePart>);
#[derive(Debug)]
enum TemplatePart {
Text(String),
Arg(TemplateArg),
}
#[derive(Debug)]
struct TemplateArg {
name: Template,
default: Option<Template>,
}
impl Template {
pub fn parse(body: &str) -> Self {
let mut parts = Vec::new();
let mut start = 0;
for (s, e) in find_matching_braces(body, 3) {
parts.push(TemplatePart::Text(body[start..s].to_string()));
let inner = body.get(s + 3..e.saturating_sub(3)).unwrap_or("");
parts.push(TemplatePart::Arg(TemplateArg::new(inner)));
start = e;
}
parts.push(TemplatePart::Text(body[start..].to_string()));
Self(parts)
}
pub fn subst(
&self,
params: &HashMap<String, String>,
expander: &mut Expander,
depth: usize,
) -> String {
if depth > MAX_PARAMETER_RECURSION {
log::debug!("max parameter recursion exceeded");
return String::new();
}
self.0
.iter()
.map(|part| match part {
TemplatePart::Text(text) => text.clone(),
TemplatePart::Arg(arg) => arg.subst(params, expander, depth),
})
.collect()
}
}
impl TemplateArg {
fn new(parameter: &str) -> Self {
let parts = split_parts(parameter);
Self {
name: Template::parse(&parts[0]),
default: parts.get(1).map(|default| Template::parse(default)),
}
}
fn subst(
&self,
params: &HashMap<String, String>,
expander: &mut Expander,
depth: usize,
) -> String {
let name = self.name.subst(params, expander, depth + 1);
let name = expander.expand(&name);
if let Some(value) = params.get(&name) {
value.clone()
} else if let Some(default) = &self.default {
let default = default.subst(params, expander, depth + 1);
expander.expand(&default)
} else {
String::new()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::expand::TemplateDb;
fn subst(body: &str, params: &[(&str, &str)]) -> String {
let params: HashMap<String, String> = params
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
let db = TemplateDb::default();
let mut expander = Expander::new("Test", &db);
Template::parse(body).subst(¶ms, &mut expander, 0)
}
#[test]
fn substitutes_positional_and_named_parameters() {
assert_eq!(
subst("Hello {{{1}}} and {{{who}}}!", &[("1", "a"), ("who", "b")]),
"Hello a and b!"
);
}
#[test]
fn missing_parameter_uses_default_or_empty() {
assert_eq!(subst("x{{{1|default}}}y", &[]), "xdefaulty");
assert_eq!(subst("x{{{1}}}y", &[]), "xy");
assert_eq!(subst("x{{{1|}}}y", &[]), "xy");
}
#[test]
fn nested_defaults_resolve() {
assert_eq!(subst("{{{a|{{{b|fallback}}}}}}", &[("b", "B")]), "B");
assert_eq!(subst("{{{a|{{{b|fallback}}}}}}", &[]), "fallback");
}
#[test]
fn computed_parameter_names_resolve() {
assert_eq!(subst("{{{{{{p}}}}}}", &[("p", "q"), ("q", "r")]), "r");
}
#[test]
fn extra_tplarg_parts_are_ignored() {
assert_eq!(subst("{{{1|a|b|c}}}", &[]), "a");
}
}