pub fn tf(template: &str, args: &[(&str, &dyn std::fmt::Display)]) -> String {
if args.is_empty() {
return template.to_string();
}
const ESCAPED_OPEN: &str = "__ESC_OPEN_7F3A__";
const ESCAPED_CLOSE: &str = "__ESC_CLOSE_7F3A__";
let mut result = template.replace("\\{", ESCAPED_OPEN);
result = result.replace("\\}", ESCAPED_CLOSE);
let mut sorted: Vec<_> = args.iter().map(|(k, v)| (k, v)).collect();
sorted.sort_by(|(a, _), (b, _)| b.len().cmp(&a.len()));
for (key, val) in &sorted {
result = result.replace(&format!("{{{}}}", key), &val.to_string());
}
let result = result.replace(ESCAPED_OPEN, "{");
result.replace(ESCAPED_CLOSE, "}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tf_no_args_returns_template() {
assert_eq!(tf("Hello", &[]), "Hello");
}
#[test]
fn tf_single_placeholder() {
assert_eq!(tf("Hello {name}!", &[("name", &"World")]), "Hello World!");
}
#[test]
fn tf_multiple_placeholders() {
assert_eq!(
tf(
"{total} users, page {page} / {total_pages}",
&[("total", &"10"), ("page", &"1"), ("total_pages", &"5")]
),
"10 users, page 1 / 5"
);
}
#[test]
fn tf_longer_placeholder_before_shorter() {
assert_eq!(
tf(
"{total} / {total_pages}",
&[("total", &"5"), ("total_pages", &"10")]
),
"5 / 10"
);
}
#[test]
fn tf_escaped_braces() {
assert_eq!(
tf("Status {status}: \\{200, 404\\}", &[("status", &"code")]),
"Status code: {200, 404}"
);
}
#[test]
fn tf_missing_key_keeps_placeholder() {
assert_eq!(tf("Hello {name}!", &[("other", &"value")]), "Hello {name}!");
}
#[test]
fn tf_empty_args_is_zero_alloc() {
let t = tf("static text", &[]);
assert_eq!(t, "static text");
}
#[test]
fn tf_display_trait_works() {
assert_eq!(tf("Count: {n}", &[("n", &42)]), "Count: 42");
}
}