Template Engine (javascript, written by Codex)
envgap__codex__javascript-t1-35
Written by a coding agent; not on GitHubWritten 2026-03-03
01 / FAILURE SIGNATURE
As the study recorded it
None
Not a benchmark task.
- The project already builds and runs before the fix, so there is nothing to repair.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
package.json- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/javascript-t1 #35 · read the task the agent was given
Codex wrote this javascript project from the task below. It installed and ran 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 JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include:
- Source code
- package.json 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
4 files, exactly as written, before any repair.
package-lock.json
{
"name": "template-engine",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "template-engine",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"nunjucks": "3.2.4"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/a-sync-waterfall": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
"integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==",
"license": "MIT"
},
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"license": "MIT"
},
"node_modules/commander": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/nunjucks": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz",
"integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==",
"license": "BSD-2-Clause",
"dependencies": {
"a-sync-waterfall": "^1.0.0",
"asap": "^2.0.3",
"commander": "^5.1.0"
},
"bin": {
"nunjucks-precompile": "bin/precompile"
},
"engines": {
"node": ">= 6.9.0"
},
"peerDependencies": {
"chokidar": "^3.3.0"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
}
}
}
package.json
{
"name": "template-engine",
"version": "1.0.0",
"description": "Template rendering CLI with conditionals, loops, includes, inheritance and filters",
"type": "module",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js"
},
"engines": {
"node": ">=20.0.0"
},
"dependencies": {
"nunjucks": "3.2.4"
},
"license": "MIT"
}
README.md
# Template Engine (JavaScript) ## Requirements - Ubuntu 22.04 - Node.js 20+ ## Install ```bash npm install ``` ## Dependencies (Pinned) - `nunjucks@3.2.4` - Transitive dependencies are pinned in `package-lock.json` ## Run ```bash node src/index.js template.tpl data.json node src/index.js template.tpl data.json --strict --output rendered.txt node src/index.js template.tpl --batch data_files.txt --output out_dir ``` `data_files.txt` should contain one JSON data file path per line. ## Demo Mode If inputs are omitted, sample templates/data are generated in `sample_template/` and rendered. ## Features - Variable substitution, dot notation - Conditionals and loops (engine-native) - Includes, inheritance, blocks - Filters (`upper`, `format`, `join`, `truncate`, `escape`, `default`) - Strict mode for undefined variables - Batch rendering with JSON report (`template_batch_report.json`)
src/index.js
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import nunjucks from "nunjucks";
function parseArgs(argv) {
const cfg = {
template: null,
data: null,
output: null,
batch: null,
strict: false
};
const positional = [];
for (let i = 0; i < argv.length; i += 1) {
const a = argv[i];
if (!a.startsWith("--")) {
positional.push(a);
continue;
}
if (a === "--output") cfg.output = argv[++i];
else if (a === "--batch") cfg.batch = argv[++i];
else if (a === "--strict") cfg.strict = true;
else throw new Error(`Unknown option: ${a}`);
}
if (positional.length >= 1) cfg.template = positional[0];
if (positional.length >= 2) cfg.data = positional[1];
return cfg;
}
function ensureSampleFiles(baseDir) {
fs.mkdirSync(baseDir, { recursive: true });
const baseTpl = `{# base template #}
<html>
<body>
<header>
<h1>{{ company.name | upper }}</h1>
</header>
{% block content %}{% endblock %}
<footer>
{% include "footer.tpl" %}
</footer>
</body>
</html>
`;
const emailTpl = `{% extends "base.tpl" %}
{% block content %}
<h2>Hello {{ user.name | default("Customer") }}</h2>
{% if discount > 0 %}
<p>Discount: {{ discount | format("$%.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("$%.2f") }}</li>
{% endfor %}
</ul>
<p>Notes: {{ notes | truncate(60) }}</p>
{% endblock %}
`;
const footerTpl = `<small>Generated for {{ user.email | escape }}</small>`;
const 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 adjustments or expedited shipping details."
};
fs.writeFileSync(path.join(baseDir, "base.tpl"), baseTpl, "utf8");
fs.writeFileSync(path.join(baseDir, "email.tpl"), emailTpl, "utf8");
fs.writeFileSync(path.join(baseDir, "footer.tpl"), footerTpl, "utf8");
fs.writeFileSync(path.join(baseDir, "sample_data.json"), JSON.stringify(data, null, 2), "utf8");
}
function loadData(dataPath) {
try {
return JSON.parse(fs.readFileSync(dataPath, "utf8"));
} catch (err) {
throw new Error(`Failed to parse JSON data file ${dataPath}: ${err.message}`);
}
}
function buildEnv(templatesDir, strict) {
const env = new nunjucks.Environment(new nunjucks.FileSystemLoader(templatesDir, { noCache: true }), {
autoescape: false,
throwOnUndefined: strict,
trimBlocks: true,
lstripBlocks: true
});
env.addFilter("upper", (v) => String(v ?? "").toUpperCase());
env.addFilter("join", (arr, sep = ", ") => (Array.isArray(arr) ? arr.join(sep) : ""));
env.addFilter("truncate", (value, n = 50) => {
const s = String(value ?? "");
if (s.length <= n) return s;
return `${s.slice(0, Math.max(0, n - 3))}...`;
});
env.addFilter("format", (value, fmt = "%s") => {
if (typeof value !== "number") return String(value ?? "");
const match = fmt.match(/^([^%]*)%\.([0-9]+)f$/);
if (match) {
const [, prefix, prec] = match;
return `${prefix}${value.toFixed(Number.parseInt(prec, 10))}`;
}
return String(value);
});
return env;
}
function renderOne(templatePath, dataPath, outputPath, strictMode) {
const templateAbs = path.resolve(templatePath);
const templatesDir = path.dirname(templateAbs);
const env = buildEnv(templatesDir, strictMode);
const data = loadData(dataPath);
let rendered;
try {
rendered = env.render(path.basename(templateAbs), data);
} catch (err) {
throw new Error(`Template rendering failed: ${err.message}`);
}
process.stdout.write(`${rendered}\n`);
if (outputPath) {
fs.writeFileSync(path.resolve(outputPath), rendered, "utf8");
}
return rendered;
}
function renderBatch(templatePath, batchFile, strictMode, outputDir) {
const rows = fs
.readFileSync(batchFile, "utf8")
.split(/\r?\n/)
.map((x) => x.trim())
.filter(Boolean);
if (!rows.length) throw new Error("Batch file is empty.");
const outDir = outputDir ? path.resolve(outputDir) : path.resolve("batch_outputs");
fs.mkdirSync(outDir, { recursive: true });
const records = [];
for (const dataPath of rows) {
const base = path.parse(dataPath).name;
const out = path.join(outDir, `${base}.out.txt`);
renderOne(templatePath, dataPath, out, strictMode);
records.push({ dataFile: path.resolve(dataPath), outputFile: out });
}
return records;
}
function runSample(strictMode) {
const sampleDir = path.resolve("sample_template");
ensureSampleFiles(sampleDir);
const rendered = renderOne(path.join(sampleDir, "email.tpl"), path.join(sampleDir, "sample_data.json"), path.join(sampleDir, "sample_rendered.html"), strictMode);
process.stdout.write("\n===== SAMPLE TEMPLATE RENDERED OUTPUT =====\n");
process.stdout.write(`${rendered}\n`);
}
function main() {
try {
const cfg = parseArgs(process.argv.slice(2));
if (!cfg.template || (!cfg.data && !cfg.batch)) {
runSample(cfg.strict);
return;
}
if (cfg.batch) {
const records = renderBatch(cfg.template, cfg.batch, cfg.strict, cfg.output);
fs.writeFileSync(
"template_batch_report.json",
JSON.stringify({ generatedAt: new Date().toISOString(), records }, null, 2),
"utf8"
);
return;
}
renderOne(cfg.template, cfg.data, cfg.output, cfg.strict);
} catch (err) {
process.stderr.write(`Error: ${err.message}\n`);
process.exit(1);
}
}
main();