pub fn render(template: &str, tokens: &[(&str, &str)]) -> String {
let mut out = String::with_capacity(template.len());
let mut rest = template;
while let Some(open) = rest.find('{') {
out.push_str(&rest[..open]);
let after = &rest[open + 1..];
match after.find('}') {
Some(close_rel) => {
let name = &after[..close_rel];
match tokens.iter().find(|(n, _)| *n == name) {
Some((_, value)) => out.push_str(value),
None => {
out.push('{');
out.push_str(name);
out.push('}');
}
}
rest = &after[close_rel + 1..];
}
None => {
out.push_str(&rest[open..]);
return out;
}
}
}
out.push_str(rest);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn expands_known_tokens() {
let out = render(
"{QUESTION} -> {RESULT}",
&[("QUESTION", "ok?"), ("RESULT", "SUCCESS")],
);
assert_eq!(out, "ok? -> SUCCESS");
}
#[test]
fn leaves_unknown_tokens_verbatim() {
let out = render("{RESULT} {MISSING}", &[("RESULT", "ERROR")]);
assert_eq!(out, "ERROR {MISSING}");
}
#[test]
fn does_not_rescan_substituted_values() {
let out = render(
"{STDOUT}|{CODE}",
&[("STDOUT", "see {CODE}"), ("CODE", "0")],
);
assert_eq!(out, "see {CODE}|0");
}
#[test]
fn copies_unbalanced_brace_through() {
let out = render("a {RESULT} b {dangling", &[("RESULT", "SUCCESS")]);
assert_eq!(out, "a SUCCESS b {dangling");
}
}