← All tasks
javascriptcodex/javascript-t1 #35Not a task: already works

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 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

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();