Skip to main content

spar/
template.rs

1//! Filling prompt templates.
2
3/// Fill each placeholder in `template` from `values`, in one pass.
4///
5/// A value goes in as it is and is never scanned again, so an issue that
6/// mentions `{body}` reaches the agent saying `{body}`. Chained `str::replace`
7/// calls cannot promise that, because each one rescans what the calls before it
8/// inserted.
9///
10/// Names carry their braces, `("{title}", title)`, so searching for a
11/// placeholder finds both the template and the call that fills it. Anything in
12/// the template that no pair names is left alone.
13pub fn fill(template: &str, values: &[(&str, &str)]) -> String {
14    debug_assert!(
15        values
16            .iter()
17            .all(|(name, _)| name.len() > 2 && name.starts_with('{') && name.ends_with('}')),
18        "a placeholder name is written with its braces: {values:?}"
19    );
20    let mut out = String::with_capacity(template.len());
21    let mut rest = template;
22    while let Some(at) = rest.find('{') {
23        out.push_str(&rest[..at]);
24        rest = &rest[at..];
25        match values.iter().find(|(name, _)| rest.starts_with(name)) {
26            Some((name, value)) => {
27                out.push_str(value);
28                rest = &rest[name.len()..];
29            }
30            None => {
31                out.push('{');
32                rest = &rest[1..];
33            }
34        }
35    }
36    out.push_str(rest);
37    out
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[test]
45    fn a_value_naming_a_placeholder_is_not_filled_again() {
46        let out = fill("{a} and {b}", &[("{a}", "says {b}"), ("{b}", "says {a}")]);
47        assert_eq!("says {b} and says {a}", out);
48    }
49
50    #[test]
51    fn every_occurrence_in_the_template_is_filled() {
52        assert_eq!("1, 1", fill("{n}, {n}", &[("{n}", "1")]));
53    }
54
55    #[test]
56    fn braces_that_name_nothing_are_kept() {
57        assert_eq!(
58            "{ x } {other} {n",
59            fill("{ x } {other} {n", &[("{n}", "1")])
60        );
61        assert_eq!("{1}", fill("{{n}}", &[("{n}", "1")]));
62    }
63
64    #[test]
65    fn text_around_a_placeholder_survives_multibyte_characters() {
66        assert_eq!("é: ü.", fill("é: {v}.", &[("{v}", "ü")]));
67    }
68}