Template Engine (javascript, written by Claude Code)
envgap__claude-code__javascript-t1-35
Written by a coding agent; not on GitHubWritten 2026-02-27
01 / FAILURE SIGNATURE
As the study recorded it
No identifying execution failure has been captured.
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
claude-code/javascript-t1 #35 · read the task the agent was given
Claude Code 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
2 files, exactly as written, before any repair.
engine.js
/**
* Template Engine - Processes templates with variables, conditionals, loops,
* includes, filters, and inheritance using Handlebars.
*
* Dependencies: handlebars 4.7.8
*/
const Handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');
class TemplateEngine {
/**
* Initialize the template engine.
* @param {string[]} templateDirs - Directories to search for templates.
* @param {boolean} autoEscape - Whether to enable HTML auto-escaping.
*/
constructor(templateDirs = [process.cwd()], autoEscape = true) {
this.templateDirs = templateDirs;
this.autoEscape = autoEscape;
this.globalContext = {};
this.customFilters = {};
this.partials = {};
this.layouts = {};
this._registerDefaultHelpers();
this._loadPartialsFromDirs();
}
/**
* Register built-in custom helpers (filters).
*/
_registerDefaultHelpers() {
// Slugify helper
Handlebars.registerHelper('slugify', function (value) {
if (!value) return '';
return String(value)
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_]+/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
});
// Truncate words helper
Handlebars.registerHelper('truncateWords', function (value, count, options) {
if (!value) return '';
const words = String(value).split(/\s+/);
const limit = typeof count === 'number' ? count : 10;
if (words.length <= limit) return value;
return words.slice(0, limit).join(' ') + '...';
});
// Format currency helper
Handlebars.registerHelper('formatCurrency', function (value, symbol) {
const sym = typeof symbol === 'string' ? symbol : '$';
const num = parseFloat(value);
if (isNaN(num)) return String(value);
return sym + num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
});
// Repeat helper
Handlebars.registerHelper('repeat', function (value, times) {
const count = typeof times === 'number' ? times : 2;
return String(value).repeat(count);
});
// Upper case helper
Handlebars.registerHelper('upper', function (value) {
return String(value).toUpperCase();
});
// Lower case helper
Handlebars.registerHelper('lower', function (value) {
return String(value).toLowerCase();
});
// Title case helper
Handlebars.registerHelper('titleCase', function (value) {
return String(value).replace(/\w\S*/g, txt =>
txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
);
});
// Sort by helper for arrays
Handlebars.registerHelper('sortBy', function (array, attribute, options) {
if (!Array.isArray(array)) return array;
return array.slice().sort((a, b) => {
const va = a[attribute] || '';
const vb = b[attribute] || '';
return String(va).localeCompare(String(vb));
});
});
// Equality check helper
Handlebars.registerHelper('eq', function (a, b) {
return a === b;
});
// Length helper
Handlebars.registerHelper('length', function (value) {
if (Array.isArray(value)) return value.length;
return String(value).length;
});
// Block extend/layout helpers for inheritance
Handlebars.registerHelper('extend', (layoutName, options) => {
const layoutContent = this._loadTemplate(layoutName);
if (!layoutContent) {
throw new Error(`Layout '${layoutName}' not found`);
}
return layoutContent;
});
// Join helper
Handlebars.registerHelper('join', function (array, separator) {
if (!Array.isArray(array)) return '';
const sep = typeof separator === 'string' ? separator : ', ';
return array.join(sep);
});
}
/**
* Load partial templates from template directories.
*/
_loadPartialsFromDirs() {
for (const dir of this.templateDirs) {
if (!fs.existsSync(dir)) continue;
const files = fs.readdirSync(dir);
for (const file of files) {
const filePath = path.join(dir, file);
if (fs.statSync(filePath).isFile() && /\.(hbs|handlebars|html)$/.test(file)) {
const name = path.basename(file, path.extname(file));
const content = fs.readFileSync(filePath, 'utf-8');
Handlebars.registerPartial(name, content);
this.partials[name] = content;
}
}
}
}
/**
* Load a template file from the search directories.
* @param {string} templateName - Name of the template file.
* @returns {string|null} Template content or null.
*/
_loadTemplate(templateName) {
for (const dir of this.templateDirs) {
const filePath = path.join(dir, templateName);
if (fs.existsSync(filePath)) {
return fs.readFileSync(filePath, 'utf-8');
}
}
return null;
}
/**
* Register a custom filter (helper).
* @param {string} name - Helper name usable in templates.
* @param {Function} func - The helper function.
*/
addFilter(name, func) {
this.customFilters[name] = func;
Handlebars.registerHelper(name, func);
}
/**
* Set a global variable available in all templates.
* @param {string} key - Variable name.
* @param {*} value - Variable value.
*/
setGlobal(key, value) {
this.globalContext[key] = value;
}
/**
* Render a template from a string.
* @param {string} templateString - Template content.
* @param {Object} context - Variables to pass to the template.
* @returns {string} Rendered output.
*/
renderString(templateString, context = {}) {
const mergedContext = { ...this.globalContext, ...context };
const template = Handlebars.compile(templateString, { noEscape: !this.autoEscape });
return template(mergedContext);
}
/**
* Render a template from a file.
* @param {string} templateName - Template file name.
* @param {Object} context - Variables to pass to the template.
* @returns {string} Rendered output.
*/
renderFile(templateName, context = {}) {
const content = this._loadTemplate(templateName);
if (!content) {
throw new Error(`Template '${templateName}' not found in: ${this.templateDirs.join(', ')}`);
}
return this.renderString(content, context);
}
/**
* Render a template and write the output to a file.
* @param {string} templateName - Template file name.
* @param {string} outputPath - Path to write rendered output.
* @param {Object} context - Variables to pass to the template.
*/
renderToFile(templateName, outputPath, context = {}) {
const result = this.renderFile(templateName, context);
const dir = path.dirname(outputPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(outputPath, result, 'utf-8');
}
/**
* Render a child template that extends a base layout.
* Uses Handlebars partials for inheritance simulation.
* @param {string} childTemplate - Child template file name.
* @param {Object} context - Variables to pass to the template.
* @returns {string} Rendered output.
*/
renderWithInheritance(childTemplate, context = {}) {
return this.renderFile(childTemplate, context);
}
}
/**
* Demonstrate rendering templates from strings.
*/
function demoStringTemplates() {
const engine = new TemplateEngine();
// Variables
console.log('=== Variables ===');
let result = engine.renderString(
'Hello, {{name}}! Welcome to {{place}}.',
{ name: 'Alice', place: 'Wonderland' }
);
console.log(result);
// Conditionals
console.log('\n=== Conditionals ===');
const conditionalTpl = `{{#if user.isAdmin}}
Welcome, Administrator {{user.name}}!
{{else if user.isMember}}
Welcome back, {{user.name}}!
{{else}}
Welcome, guest! Please sign up.
{{/if}}`;
result = engine.renderString(conditionalTpl, {
user: { name: 'Bob', isAdmin: false, isMember: true }
});
console.log(result.trim());
// Loops
console.log('\n=== Loops ===');
const loopTpl = `Shopping List:
{{#each items}}
{{@index}}. {{this.name}} - {{formatCurrency this.price}}
{{/each}}
Total items: {{length items}}`;
result = engine.renderString(loopTpl, {
items: [
{ name: 'Apples', price: 3.50 },
{ name: 'Bread', price: 2.99 },
{ name: 'Milk', price: 4.25 }
]
});
console.log(result.trim());
// Filters
console.log('\n=== Filters ===');
const filterTpl = `Upper: {{upper name}}
Lower: {{lower name}}
Title: {{titleCase sentence}}
Slug: {{slugify title}}
Truncated: {{truncateWords longText 5}}
Currency: {{formatCurrency price "EUR "}}`;
result = engine.renderString(filterTpl, {
name: 'alice wonderland',
sentence: 'hello world from template engine',
title: 'My Blog Post Title!',
longText: 'This is a very long text that should be truncated after five words',
price: 1234.56
});
console.log(result.trim());
// Custom filter
console.log('\n=== Custom Filter ===');
engine.addFilter('reverseWords', function (text) {
return String(text).split(/\s+/).reverse().join(' ');
});
result = engine.renderString(
'Reversed: {{reverseWords text}}',
{ text: 'Hello Beautiful World' }
);
console.log(result);
// Global variables
console.log('\n=== Global Variables ===');
engine.setGlobal('appName', 'TemplateEngine Pro');
engine.setGlobal('version', '1.0.0');
result = engine.renderString('Running {{appName}} v{{version}}');
console.log(result);
}
/**
* Demonstrate file-based templates with includes and inheritance.
*/
function demoFileTemplates(templateDir) {
if (!fs.existsSync(templateDir)) {
fs.mkdirSync(templateDir, { recursive: true });
}
// Base layout template
const baseTpl = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{#if title}}{{title}}{{else}}Default Title{{/if}}</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
header { background: #333; color: white; padding: 10px; }
footer { background: #333; color: white; padding: 10px; margin-top: 20px; }
.content { padding: 20px; }
</style>
</head>
<body>
<header>
<h1>{{#if siteName}}{{siteName}}{{else}}My Website{{/if}}</h1>
</header>
<div class="content">
{{> @partial-block }}
</div>
<footer>
<p>© 2024 {{#if siteName}}{{siteName}}{{else}}My Website{{/if}}</p>
</footer>
</body>
</html>`;
// Blog child template using partial for inheritance
const childTpl = `{{#> base title=pageTitle}}
<h2>{{pageTitle}}</h2>
{{#each articles}}
<article>
<h3>{{this.title}}</h3>
<p class="meta">By {{this.author}} on {{this.date}}</p>
<p>{{truncateWords this.summary 15}}</p>
{{#if this.tags}}
<div class="tags">
Tags: {{#each this.tags}}{{slugify this}}{{#unless @last}}, {{/unless}}{{/each}}
</div>
{{/if}}
</article>
{{/each}}
{{/base}}`;
// Navigation partial
const navTpl = `<nav>
<ul>
{{#each navLinks}}
<li><a href="{{this.url}}">{{this.label}}</a></li>
{{/each}}
</ul>
</nav>`;
// Page with include
const pageWithNavTpl = `<div>
{{> nav}}
<main>
<h1>Welcome, {{userName}}!</h1>
</main>
</div>`;
// Write template files
fs.writeFileSync(path.join(templateDir, 'base.hbs'), baseTpl);
fs.writeFileSync(path.join(templateDir, 'blog.hbs'), childTpl);
fs.writeFileSync(path.join(templateDir, 'nav.hbs'), navTpl);
fs.writeFileSync(path.join(templateDir, 'page_with_nav.hbs'), pageWithNavTpl);
const engine = new TemplateEngine([templateDir]);
// Render blog (inheritance via partial blocks)
console.log('=== Template Inheritance ===');
const blogContext = {
siteName: 'Tech Blog',
pageTitle: 'Latest Articles',
articles: [
{
title: 'Getting Started with Handlebars',
author: 'Alice',
date: '2024-01-15',
summary: 'Handlebars is a powerful template engine for JavaScript that provides logic-less templates with helpers and partials for building dynamic web content.',
tags: ['JavaScript', 'Web Dev', 'Templates']
},
{
title: 'Advanced Template Patterns',
author: 'Bob',
date: '2024-02-20',
summary: 'Learn how to use partial blocks, custom helpers, and advanced patterns to build maintainable and reusable template structures for your projects.',
tags: ['Advanced', 'Best Practices']
}
]
};
console.log(engine.renderFile('blog.hbs', blogContext));
// Render page with includes
console.log('\n=== Template Includes ===');
const navContext = {
userName: 'Charlie',
navLinks: [
{ url: '/', label: 'Home' },
{ url: '/about', label: 'About' },
{ url: '/blog', label: 'Blog' },
{ url: '/contact', label: 'Contact' }
]
};
console.log(engine.renderFile('page_with_nav.hbs', navContext));
}
/**
* Main entry point with CLI support.
*/
function main() {
const args = process.argv.slice(2);
if (args.includes('--demo')) {
console.log('='.repeat(60));
console.log('Template Engine Demo - Handlebars');
console.log('='.repeat(60));
console.log();
demoStringTemplates();
console.log();
const templateDir = path.join(process.cwd(), 'templates', 'demo');
demoFileTemplates(templateDir);
return;
}
const templateIdx = args.indexOf('--template');
const tIdx = args.indexOf('-t');
const templateArg = templateIdx >= 0 ? args[templateIdx + 1] : (tIdx >= 0 ? args[tIdx + 1] : null);
const dataIdx = args.indexOf('--data');
const dIdx = args.indexOf('-d');
const dataArg = dataIdx >= 0 ? args[dataIdx + 1] : (dIdx >= 0 ? args[dIdx + 1] : null);
const outputIdx = args.indexOf('--output');
const oIdx = args.indexOf('-o');
const outputArg = outputIdx >= 0 ? args[outputIdx + 1] : (oIdx >= 0 ? args[oIdx + 1] : null);
const dirIdx = args.indexOf('--template-dir');
const templateDirArg = dirIdx >= 0 ? args[dirIdx + 1] : './templates';
if (templateArg) {
const templateDir = path.dirname(path.resolve(templateArg));
const engine = new TemplateEngine([templateDir]);
let context = {};
if (dataArg) {
context = JSON.parse(fs.readFileSync(dataArg, 'utf-8'));
}
const templateName = path.basename(templateArg);
if (outputArg) {
engine.renderToFile(templateName, outputArg, context);
console.log(`Rendered output written to: ${outputArg}`);
} else {
const result = engine.renderFile(templateName, context);
console.log(result);
}
} else {
console.log('Template Engine - Handlebars');
console.log('Usage:');
console.log(' node engine.js --demo Run demo examples');
console.log(' node engine.js -t <template> [-d data.json] [-o output] Render template');
console.log(' node engine.js --template-dir <dir> Set template directory');
}
}
module.exports = { TemplateEngine };
if (require.main === module) {
main();
}
package.json
{
"name": "template-engine",
"version": "1.0.0",
"description": "Template engine with variables, conditionals, loops, includes, filters, and inheritance using Handlebars",
"main": "engine.js",
"dependencies": {
"handlebars": "4.7.8"
}
}