← All tasks
javacodex/java-t1 #35Not a task: repair changed code

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 output

04 / 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); }
    }
}