1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
use std::fmt::{Display, Write};
use serde::Deserialize;
#[cfg(test)]
#[path = "template_test.rs"]
mod template_test;
/// A simple positional template with `{}` placeholders.
#[derive(Debug, Deserialize)]
pub struct Template(pub String);
impl Template {
/// Creates a new `Template` from any type implementing `ToString`.
pub fn new<T: ToString>(s: T) -> Self {
Template(s.to_string())
}
/// Renders the template by substituting `{}` placeholders with the provided args. Panics if the
/// number of `{}` in the template doesn't match the number of args provided.
pub fn format(&self, args: &[&dyn Display]) -> String {
// Count how many `{}` placeholders are in the template string, and ensure the number of
// args matches the number of placeholders.
let placeholder_count = self.0.matches("{}").count();
assert_eq!(
placeholder_count,
args.len(),
"Template {} expects {} placeholders, but got {} args",
self.0,
placeholder_count,
args.len()
);
// Allocate the output buffer once, with some extra capacity for each argument. This avoids
// reallocations as we append to the string. In case of insufficient capacity, the string
// will indeed reallocate, but this is a trade-off for performance in the common case.
const SIZE_PER_ARG: usize = 16; // Estimated size for each argument, for initial allocation.
let mut out = String::with_capacity(self.0.len() + SIZE_PER_ARG * args.len());
// Walk through the template, streaming chunks + args into `out`
let mut rest = self.0.as_str();
for value in args {
if let Some(i) = rest.find("{}") {
// Write the prefix before the placeholder
out.push_str(&rest[..i]);
write!(out, "{value}").unwrap();
rest = &rest[i + 2..];
}
}
// Append whatever is left after the last placeholder
out.push_str(rest);
out
}
}