Improve render()

Done

/home/devdi/devswarm/evals/tasks/hard_template_engine/repo · 2026-11-09 17:18:20 UTC · took 1m 06s

  1. Plan (done)
  2. Code (done)
  3. Test (done)
  4. Review (done)
  5. Finish (done)
Cost
$0.0089
Tests
Passed
Iterations
1
Re-plans
0
LLM calls
15
Prompt read from cache
76%

Usage by agent

Cost per agent
planner: $0.0022 planner planner, Cost: $0.0022 $0.0022 coder: $0.0024 coder coder, Cost: $0.0024 $0.0024 tester: $0.0030 tester tester, Cost: $0.0030 $0.0030 reviewer: $0.0013 reviewer reviewer, Cost: $0.0013 $0.0013
Tokens per agent
  • Cache read
  • Cache write
  • Input
  • Output
planner: 15.5K planner planner, Cache read: 8,839 planner, Cache write: 3,095 planner, Output: 3,506 15.5K coder: 35.6K coder coder, Cache read: 27.9K coder, Cache write: 4,663 coder, Output: 3,034 35.6K tester: 34.6K tester tester, Cache read: 23.3K tester, Cache write: 7,635 tester, Output: 3,613 34.6K reviewer: 7,439 reviewer reviewer, Cache read: 1,727 reviewer, Cache write: 4,137 reviewer, Output: 1,571 7,439
View as table
AgentCostCache readCache writeInputOutput
planner$0.00228,8393,095123,506
coder$0.002427,8994,663143,034
tester$0.003023,3297,635103,613
reviewer$0.00131,7274,13741,571

Timeline

  1. planner iteration 1 Finished 4 calls · 15.5K tokens · $0.0022 · 17.8s

    Loading…

  2. coder iteration 1 Finished 6 calls · 35.6K tokens · $0.0024 · 16.9s

    Loading…

  3. tester iteration 1 Finished 4 calls · 34.6K tokens · $0.0030 · 23.0s

    Loading…

  4. verify iteration 1 Passed 0.5s
    ........................                                                 [100%]
    24 passed in 0.04s
    
  5. reviewer iteration 1 Finished 1 calls · 7,439 tokens · $0.0013 · 8.0s

    Loading…

Final diff

diff --git a/template.py b/template.py
index 8cfdd25..b0c383f 100644
--- a/template.py
+++ b/template.py
@@ -1,5 +1,34 @@
 import re
 
 
+class MissingVariable(KeyError):
+    """Raised when a template refers to a name that cannot be found in the context."""
+
+    def __str__(self) -> str:
+        # KeyError.__str__ would repr() the message; show it plainly instead.
+        return str(self.args[0]) if self.args else ""
+
+
+# Alternative 1: \{{ ... }} is an escape and is emitted literally (minus the backslash).
+# Alternative 2: {{ dotted.name }} is a variable, with optional inner whitespace.
+_PATTERN = re.compile(r"\\\{\{.*?\}\}|\{\{\s*(\w+(?:\.\w+)*)\s*\}\}")
+
+
+def _lookup(context: dict, dotted: str):
+    current = context
+    for part in dotted.split("."):
+        if not isinstance(current, dict) or part not in current:
+            raise MissingVariable(dotted)
+        current = current[part]
+    return current
+
+
 def render(template: str, context: dict) -> str:
-    return re.sub(r"\{\{(\w+)\}\}", lambda match: str(context[match[1]]), template)
+    def replace(match: re.Match) -> str:
+        if match.group(0).startswith("\\"):
+            return match.group(0)[1:]
+        value = _lookup(context, match.group(1))
+        return "" if value is None else str(value)
+
+    # Single pass over the template: substituted values are never rescanned.
+    return _PATTERN.sub(replace, template)
diff --git a/test_template.py b/test_template.py
index 9cfb1dc..38a0744 100644
--- a/test_template.py
+++ b/test_template.py
@@ -1,5 +1,121 @@
-from template import render
+import pytest
+
+from template import MissingVariable, render
 
 
 def test_simple():
     assert render("Hi {{name}}!", {"name": "Ada"}) == "Hi Ada!"
+
+
+def test_spaces_inside_braces():
+    assert render("{{ name }}", {"name": "Ada"}) == "Ada"
+
+
+def test_dotted_lookup():
+    assert render("{{ user.name }}", {"user": {"name": "Ada"}}) == "Ada"
+
+
+def test_backslash_escape():
+    assert render(r"\{{ name }}", {"name": "Ada"}) == "{{ name }}"
+
+
+def test_escape_stops_at_first_close_and_variable_still_renders():
+    assert render(r"\{{a}} {{b}}", {"b": "X"}) == "{{a}} X"
+
+
+def test_lone_backslash_unchanged():
+    assert render(r"C:\path {{ x }}", {"x": "1"}) == r"C:\path 1"
+
+
+def test_missing_dotted_name_raises_with_full_name():
+    with pytest.raises(MissingVariable) as info:
+        render("{{ user.age }}", {"user": {}})
+    assert isinstance(info.value, KeyError)
+    assert "user.age" in str(info.value)
+
+
+def test_dotted_path_through_string_raises():
+    with pytest.raises(MissingVariable) as info:
+        render("{{ user.name.first }}", {"user": {"name": "Ada"}})
+    assert "user.name.first" in str(info.value)
+
+
+def test_dotted_path_through_none_raises():
+    with pytest.raises(MissingVariable):
+        render("{{ user.name }}", {"user": None})
+
+
+def test_missing_simple_name_raises():
+    with pytest.raises(MissingVariable) as info:
+        render("{{ nope }}", {})
+    assert "nope" in str(info.value)
+
+
+def test_none_renders_empty():
+    assert render("[{{ x }}]", {"x": None}) == "[]"
+
+
+def test_substituted_values_are_not_expanded():
+    assert render("{{ a }}", {"a": "{{b}}", "b": "X"}) == "{{b}}"
+
+
+def test_escape_does_not_look_up_missing_name():
+    assert render(r"\{{ nope }}", {}) == "{{ nope }}"
+
+
+def test_escape_then_variable_with_other_name_on_same_line():
+    assert render(r"\{{ a }} and {{ b }}", {"a": "1", "b": "2"}) == "{{ a }} and 2"
+
+
+def test_double_backslash_before_braces_escapes_braces():
+    # The second backslash escapes the opening braces, so nothing is substituted.
+    assert render(r"\\{{ x }}", {"x": "1"}) == "\\{{ x }}"
+
+
+def test_escape_does_not_span_newline():
+    # An escape is a single-line construct: a backslash before a multi-line
+    # {{...}} is left as-is and the braces are treated as a variable.
+    template = "\\{{ a\n }}"
+    with pytest.raises(MissingVariable):
+        render(template, {})
+
+
+def test_hyphen_name_stays_literal():
+    assert render("{{ first-name }}", {"first-name": "Ada"}) == "{{ first-name }}"
+
+
+def test_name_with_inner_space_stays_literal():
+    assert render("{{ a b }}", {"a b": "x"}) == "{{ a b }}"
+
+
+def test_unicode_name():
+    assert render("{{ café }}", {"café": "ok"}) == "ok"
+
+
+def test_falsy_values_render_as_text():
+    assert render("{{ a }}|{{ b }}|{{ c }}", {"a": 0, "b": False, "c": ""}) == "0|False|"
+
+
+def test_missing_name_caught_as_keyerror():
+    try:
+        render("{{ zzz }}", {})
+    except KeyError as exc:
+        assert "zzz" in str(exc)
+    else:
+        raise AssertionError("expected KeyError")
+
+
+def test_intermediate_none_in_dotted_path_raises():
+    with pytest.raises(MissingVariable) as info:
+        render("{{ a.b.c }}", {"a": {"b": None}})
+    assert "a.b.c" in str(info.value)
+
+
+def test_final_none_in_dotted_path_renders_empty():
+    assert render("[{{ a.b }}]", {"a": {"b": None}}) == "[]"
+
+
+def test_str_is_not_repr_quoted():
+    with pytest.raises(MissingVariable) as info:
+        render("{{ user.age }}", {"user": {}})
+    assert str(info.value) == "user.age"