Template Engine (java, written by Codex)
envgap__codex__java-t1-35
Written by a coding agent; not on GitHubWritten 2026-03-03
01 / FAILURE SIGNATURE
As the study recorded it
package com.mitchellbosecke.pebble does not exist - wrong package name for pebble 3.x
Not a benchmark task.
- Its repair changed source code, so it is not an environment task.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
pom.xml- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/java-t1 #35 · read the task the agent was given
Codex wrote this java project from the task below. It does not run on a clean Ubuntu 22.04 machine as written.
Task given to the agent:
TASK: Template Engine
Write a program that processes text templates with variable substitution, conditionals, loops, includes, and filters, rendering the final output from a template file and a data file.
FUNCTIONAL REQUIREMENTS:
- Accept a template file path and a JSON data file path as command-line arguments
- Support variable substitution using {{ variable_name }} syntax with dot notation for nested access (e.g., {{ user.name }})
- Support conditional blocks: {% if condition %} ... {% elif condition %} ... {% else %} ... {% endif %} with support for comparison operators (==, !=, <, >, <=, >=) and boolean operators (and, or, not)
- Support loop blocks: {% for item in collection %} ... {% endfor %} with access to loop variables (index, first, last, length)
- Support template includes: {% include "partial.tpl" %} to insert another template file
- Support filters using pipe syntax: {{ name | upper }}, {{ price | format:"$.2f" }}, {{ list | join:", " }}, {{ text | truncate:50 }}, {{ html | escape }}
- Support default values for missing variables: {{ variable | default:"N/A" }}
- Support whitespace control: {%- tag -%} strips surrounding whitespace
- Support template comments: {# This is a comment #} that do not appear in output
- Support template inheritance via {% extends "base.tpl" %} and {% block name %} ... {% endblock %} for defining overridable sections
- Print the rendered output to console by default
- Save rendered output to a file via --output flag
- Support batch rendering: apply the same template to multiple data files via --batch flag, generating one output file per data file
- If no input is given, generate a sample template (an HTML email template with header, body, footer, product list, conditional discount, and formatted prices) and a sample JSON data file, then render the template and display the result
- Handle errors: undefined variables (configurable: strict mode raises error, lenient mode outputs empty string via --strict flag), syntax errors in templates with line numbers, circular includes, and missing include files
Create a complete Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include:
- Source code
- pom.xml with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.tmlr</groupId>
<artifactId>template-engine</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.pebbletemplates</groupId>
<artifactId>pebble</artifactId>
<version>3.2.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.7.1</version>
<configuration>
<archive>
<manifest>
<mainClass>TemplateEngineApp</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>
README.md
# Template Engine (Java) ## Requirements - Ubuntu 22.04 - JDK 17+ - Maven 3.8+ ## Build ```bash mvn -q -DskipTests package assembly:single ``` ## Run ```bash java -cp target/template-engine-1.0.0-jar-with-dependencies.jar TemplateEngineApp template.tpl data.json java -cp target/template-engine-1.0.0-jar-with-dependencies.jar TemplateEngineApp template.tpl data.json --strict --output rendered.txt java -cp target/template-engine-1.0.0-jar-with-dependencies.jar TemplateEngineApp template.tpl --batch data_files.txt --output out_dir ``` If no arguments are provided, sample templates/data are generated and rendered. ## Dependencies - `io.pebbletemplates:pebble:3.2.2`
src/main/java/TemplateEngineApp.java
import com.mitchellbosecke.pebble.PebbleEngine;
import com.mitchellbosecke.pebble.error.PebbleException;
import com.mitchellbosecke.pebble.extension.AbstractExtension;
import com.mitchellbosecke.pebble.extension.Filter;
import com.mitchellbosecke.pebble.loader.FileLoader;
import com.mitchellbosecke.pebble.template.PebbleTemplate;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.*;
public class TemplateEngineApp {
private static class Config {
String template;
String data;
String output;
String batch;
boolean strict;
}
public static void main(String[] args) {
try {
Config cfg = parseArgs(args);
if (cfg.template == null || (cfg.data == null && cfg.batch == null)) {
runSample(cfg.strict);
return;
}
if (cfg.batch != null) {
runBatch(cfg);
return;
}
renderOne(Paths.get(cfg.template), Paths.get(cfg.data), cfg.output == null ? null : Paths.get(cfg.output), cfg.strict);
} catch (Exception ex) {
System.err.println("Error: " + ex.getMessage());
System.exit(1);
}
}
private static Config parseArgs(String[] args) {
Config cfg = new Config();
List<String> pos = new ArrayList<>();
for (int i = 0; i < args.length; i++) {
String a = args[i];
if (!a.startsWith("--")) {
pos.add(a);
continue;
}
switch (a) {
case "--output" -> cfg.output = args[++i];
case "--batch" -> cfg.batch = args[++i];
case "--strict" -> cfg.strict = true;
default -> throw new IllegalArgumentException("Unknown option: " + a);
}
}
if (!pos.isEmpty()) cfg.template = pos.get(0);
if (pos.size() > 1) cfg.data = pos.get(1);
return cfg;
}
private static PebbleEngine createEngine(Path templateDir, boolean strict) {
FileLoader loader = new FileLoader();
loader.setPrefix(templateDir.toAbsolutePath().toString());
loader.setSuffix("");
return new PebbleEngine.Builder()
.loader(loader)
.strictVariables(strict)
.autoEscaping(false)
.extension(new CustomExtension())
.build();
}
private static class CustomExtension extends AbstractExtension {
@Override
public Map<String, Filter> getFilters() {
Map<String, Filter> filters = new HashMap<>();
filters.put("upper", new Filter() {
@Override
public List<String> getArgumentNames() { return Collections.emptyList(); }
@Override
public Object apply(Object input, Map<String, Object> args, com.mitchellbosecke.pebble.template.PebbleTemplate self, com.mitchellbosecke.pebble.template.EvaluationContext context, int lineNumber) {
return input == null ? "" : input.toString().toUpperCase(Locale.ROOT);
}
});
filters.put("format", new Filter() {
@Override
public List<String> getArgumentNames() { return Collections.singletonList("pattern"); }
@Override
public Object apply(Object input, Map<String, Object> args, com.mitchellbosecke.pebble.template.PebbleTemplate self, com.mitchellbosecke.pebble.template.EvaluationContext context, int lineNumber) {
String pattern = String.valueOf(args.getOrDefault("pattern", "%s"));
return String.format(Locale.ROOT, pattern, input);
}
});
filters.put("truncate", new Filter() {
@Override
public List<String> getArgumentNames() { return Collections.singletonList("length"); }
@Override
public Object apply(Object input, Map<String, Object> args, com.mitchellbosecke.pebble.template.PebbleTemplate self, com.mitchellbosecke.pebble.template.EvaluationContext context, int lineNumber) {
String s = input == null ? "" : input.toString();
int n = Integer.parseInt(String.valueOf(args.getOrDefault("length", 50)));
if (s.length() <= n) return s;
return s.substring(0, Math.max(0, n - 3)) + "...";
}
});
return filters;
}
}
private static Map<String, Object> loadJson(Path path) throws IOException {
// minimal JSON loader using Java's built-in parser fallback via simple approach
String txt = Files.readString(path, StandardCharsets.UTF_8).trim();
return new JsonMiniParser(txt).parseObject();
}
private static void renderOne(Path templatePath, Path dataPath, Path outputPath, boolean strict) throws IOException, PebbleException {
PebbleEngine engine = createEngine(templatePath.toAbsolutePath().getParent(), strict);
PebbleTemplate template = engine.getTemplate(templatePath.getFileName().toString());
Map<String, Object> context = loadJson(dataPath);
StringWriter writer = new StringWriter();
template.evaluate(writer, context);
String rendered = writer.toString();
System.out.println(rendered);
if (outputPath != null) Files.writeString(outputPath, rendered, StandardCharsets.UTF_8);
}
private static void runBatch(Config cfg) throws IOException {
List<String> dataFiles = Files.readAllLines(Paths.get(cfg.batch), StandardCharsets.UTF_8).stream()
.map(String::trim)
.filter(s -> !s.isEmpty())
.toList();
if (dataFiles.isEmpty()) throw new IllegalArgumentException("Batch file is empty");
Path outDir = cfg.output == null ? Paths.get("batch_outputs") : Paths.get(cfg.output);
Files.createDirectories(outDir);
StringBuilder report = new StringBuilder();
report.append("{\n \"generatedAt\": \"").append(Instant.now()).append("\",\n \"records\": [\n");
boolean first = true;
for (String file : dataFiles) {
Path dataPath = Paths.get(file);
Path out = outDir.resolve(stripExt(dataPath.getFileName().toString()) + ".out.txt");
try {
renderOne(Paths.get(cfg.template), dataPath, out, cfg.strict);
} catch (Exception ex) {
System.err.println("Warning: " + file + ": " + ex.getMessage());
continue;
}
if (!first) report.append(",\n");
first = false;
report.append(" {\"dataFile\":\"").append(escapeJson(dataPath.toAbsolutePath().toString())).append("\",\"outputFile\":\"")
.append(escapeJson(out.toAbsolutePath().toString())).append("\"}");
}
report.append("\n ]\n}\n");
Files.writeString(Paths.get("template_batch_report.json"), report.toString(), StandardCharsets.UTF_8);
}
private static void runSample(boolean strict) throws IOException, PebbleException {
Path dir = Paths.get("sample_template");
Files.createDirectories(dir);
String base = """
<html>
<body>
<header><h1>{{ company.name | upper }}</h1></header>
{% block content %}{% endblock %}
<footer>{% include \"footer.tpl\" %}</footer>
</body>
</html>
""";
String email = """
{% extends \"base.tpl\" %}
{% block content %}
<h2>Hello {{ user.name | default(\"Customer\") }}</h2>
{% if discount > 0 %}
<p>Discount: {{ discount | format(pattern=\"$%.2f\") }}</p>
{% elif vip %}
<p>VIP pricing applies</p>
{% else %}
<p>No discount available</p>
{% endif %}
<ul>
{% for product in products %}
<li>{{ loop.index }}. {{ product.name }} - {{ product.price | format(pattern=\"$%.2f\") }}</li>
{% endfor %}
</ul>
<p>{{ notes | truncate(length=60) }}</p>
{% endblock %}
""";
String footer = "<small>{{ user.email }}</small>\n";
String data = """
{
"company": {"name": "Acme Retail"},
"user": {"name": "Alex", "email": "alex@example.com"},
"discount": 12.5,
"vip": false,
"products": [
{"name": "Keyboard", "price": 49.99},
{"name": "Mouse", "price": 29.5},
{"name": "Monitor", "price": 229.0}
],
"notes": "Thank you for your order. Please contact support if you need invoice changes."
}
""";
Files.writeString(dir.resolve("base.tpl"), base, StandardCharsets.UTF_8);
Files.writeString(dir.resolve("email.tpl"), email, StandardCharsets.UTF_8);
Files.writeString(dir.resolve("footer.tpl"), footer, StandardCharsets.UTF_8);
Files.writeString(dir.resolve("sample_data.json"), data, StandardCharsets.UTF_8);
renderOne(dir.resolve("email.tpl"), dir.resolve("sample_data.json"), Paths.get("sample_rendered.html"), strict);
}
private static String stripExt(String name) {
int i = name.lastIndexOf('.');
return i >= 0 ? name.substring(0, i) : name;
}
private static String escapeJson(String s) {
return s.replace("\\", "\\\\").replace("\"", "\\\"");
}
// Minimal JSON parser for object/array/string/number/bool/null used in sample-style inputs.
private static class JsonMiniParser {
private final String s;
private int i = 0;
JsonMiniParser(String s) { this.s = s; }
Map<String, Object> parseObject() {
Object v = parseValue();
if (!(v instanceof Map<?, ?> m)) throw new IllegalArgumentException("Top-level JSON must be object");
@SuppressWarnings("unchecked") Map<String, Object> cast = (Map<String, Object>) m;
return cast;
}
private Object parseValue() {
ws();
if (i >= s.length()) throw err("Unexpected end");
char c = s.charAt(i);
if (c == '{') return parseObj();
if (c == '[') return parseArr();
if (c == '"') return parseStr();
if (c == 't' && s.startsWith("true", i)) { i += 4; return true; }
if (c == 'f' && s.startsWith("false", i)) { i += 5; return false; }
if (c == 'n' && s.startsWith("null", i)) { i += 4; return null; }
return parseNum();
}
private Map<String, Object> parseObj() {
Map<String, Object> m = new LinkedHashMap<>();
expect('{');
ws();
if (peek('}')) { i++; return m; }
while (true) {
String k = parseStr();
ws(); expect(':');
Object v = parseValue();
m.put(k, v);
ws();
if (peek('}')) { i++; break; }
expect(',');
}
return m;
}
private List<Object> parseArr() {
List<Object> arr = new ArrayList<>();
expect('[');
ws();
if (peek(']')) { i++; return arr; }
while (true) {
arr.add(parseValue());
ws();
if (peek(']')) { i++; break; }
expect(',');
}
return arr;
}
private String parseStr() {
expect('"');
StringBuilder sb = new StringBuilder();
while (i < s.length()) {
char c = s.charAt(i++);
if (c == '"') break;
if (c == '\\') {
if (i >= s.length()) throw err("Bad escape");
char e = s.charAt(i++);
switch (e) {
case '"' -> sb.append('"');
case '\\' -> sb.append('\\');
case '/' -> sb.append('/');
case 'b' -> sb.append('\b');
case 'f' -> sb.append('\f');
case 'n' -> sb.append('\n');
case 'r' -> sb.append('\r');
case 't' -> sb.append('\t');
case 'u' -> {
String hex = s.substring(i, i + 4);
sb.append((char) Integer.parseInt(hex, 16));
i += 4;
}
default -> throw err("Invalid escape");
}
} else sb.append(c);
}
return sb.toString();
}
private Number parseNum() {
int st = i;
if (s.charAt(i) == '-') i++;
while (i < s.length() && Character.isDigit(s.charAt(i))) i++;
if (i < s.length() && s.charAt(i) == '.') { i++; while (i < s.length() && Character.isDigit(s.charAt(i))) i++; }
if (i < s.length() && (s.charAt(i) == 'e' || s.charAt(i) == 'E')) {
i++;
if (i < s.length() && (s.charAt(i) == '+' || s.charAt(i) == '-')) i++;
while (i < s.length() && Character.isDigit(s.charAt(i))) i++;
}
String n = s.substring(st, i);
return (n.contains(".") || n.contains("e") || n.contains("E")) ? Double.parseDouble(n) : Long.parseLong(n);
}
private void ws() { while (i < s.length() && Character.isWhitespace(s.charAt(i))) i++; }
private void expect(char c) { ws(); if (i >= s.length() || s.charAt(i) != c) throw err("Expected '" + c + "'"); i++; }
private boolean peek(char c) { ws(); return i < s.length() && s.charAt(i) == c; }
private IllegalArgumentException err(String m) { return new IllegalArgumentException(m + " at index " + i); }
}
}