Mustache-style template engine for Eiffel with automatic HTML escaping and full section support.
- Mustache syntax - Familiar
{{variable}}placeholders - Auto HTML escaping - Prevents XSS by default
- Raw output - Triple braces
{{{raw}}}bypass escaping - Sections - Conditional blocks with
{{#section}}...{{/section}} - Inverted sections - Show when falsy with
{{^section}}...{{/section}} - List iteration - Repeat sections for arrays
- Comments -
{{! ignored }}for template documentation - Partials - Include sub-templates with
{{>partial}} - Missing variable policies - Empty, keep placeholder, or error
- Design by Contract - Full preconditions/postconditions
Add to your ECF:
<library name="simple_template" location="$SIMPLE_TEMPLATE\simple_template.ecf"/>Set environment variable:
SIMPLE_TEMPLATE=D:\prod\simple_template
local
tpl: SIMPLE_TEMPLATE
do
create tpl.make_from_string ("Hello, {{name}}!")
tpl.set_variable ("name", "World")
print (tpl.render) -- "Hello, World!"
endtpl.set_variable ("content", "<script>alert('xss')</script>")
print (tpl.render) -- "<script>alert('xss')</script>"create tpl.make_from_string ("{{{html}}}")
tpl.set_variable ("html", "<b>Bold</b>")
print (tpl.render) -- "<b>Bold</b>"create tpl.make_from_string ("{{#logged_in}}Welcome back!{{/logged_in}}")
tpl.set_section ("logged_in", True)
print (tpl.render) -- "Welcome back!"
tpl.set_section ("logged_in", False)
print (tpl.render) -- ""create tpl.make_from_string ("{{^has_items}}No items found{{/has_items}}")
tpl.set_section ("has_items", False)
print (tpl.render) -- "No items found"local
tpl: SIMPLE_TEMPLATE
items: ARRAYED_LIST [HASH_TABLE [STRING, STRING]]
item: HASH_TABLE [STRING, STRING]
do
create tpl.make_from_string ("{{#users}}{{name}} ({{email}})%N{{/users}}")
create items.make (2)
create item.make (2)
item.put ("Alice", "name")
item.put ("alice@example.com", "email")
items.extend (item)
create item.make (2)
item.put ("Bob", "name")
item.put ("bob@example.com", "email")
items.extend (item)
tpl.set_list ("users", items)
print (tpl.render)
-- Alice (alice@example.com)
-- Bob (bob@example.com)
endcreate tpl.make_from_string ("Hello{{! This is ignored }}World")
print (tpl.render) -- "HelloWorld"local
tpl, header: SIMPLE_TEMPLATE
do
create header.make_from_string ("<header>{{title}}</header>")
create tpl.make_from_string ("{{>header}}<main>Content</main>")
tpl.register_partial ("header", header)
tpl.set_variable ("title", "My Page")
print (tpl.render) -- "<header>My Page</header><main>Content</main>"
endlocal
vars: HASH_TABLE [STRING, STRING]
do
create vars.make (3)
vars.put ("John", "first_name")
vars.put ("Doe", "last_name")
vars.put ("john@example.com", "email")
tpl.set_variables (vars)
end-- Default: empty string
create tpl.make_from_string ("Hello, {{missing}}!")
print (tpl.render) -- "Hello, !"
-- Keep placeholder
tpl.set_missing_variable_policy (tpl.Policy_keep_placeholder)
print (tpl.render) -- "Hello, {{missing}}!"create tpl.make_from_file ("templates/email.mustache")
tpl.set_variable ("name", "Customer")
tpl.render_to_file ("output/welcome.html")tpl.set_escape_html (False) -- All output is rawlocal
vars: ARRAYED_LIST [STRING]
do
create tpl.make_from_string ("{{name}} lives in {{city}}")
vars := tpl.required_variables
-- vars contains: "name", "city"
end| Syntax | Description |
|---|---|
{{variable}} |
Output variable (HTML escaped) |
{{{variable}}} |
Output variable (raw, no escaping) |
{{#section}}...{{/section}} |
Conditional/loop section |
{{^section}}...{{/section}} |
Inverted section (show if false) |
{{! comment }} |
Comment (not rendered) |
{{>partial}} |
Include partial template |
| Feature | Description |
|---|---|
make |
Create empty template |
make_from_string (template) |
Create from string |
make_from_file (path) |
Create from file |
| Feature | Description |
|---|---|
set_escape_html (enabled) |
Enable/disable HTML escaping |
set_missing_variable_policy (policy) |
Set missing variable behavior |
register_partial (name, template) |
Register a partial template |
| Feature | Description |
|---|---|
set_variable (name, value) |
Set a variable |
set_variables (table) |
Set multiple variables |
set_section (name, visible) |
Set section visibility |
set_list (name, items) |
Set list for iteration |
clear_variables |
Clear all context |
| Feature | Description |
|---|---|
render: STRING |
Render template to string |
render_to_file (path) |
Render and write to file |
| Feature | Description |
|---|---|
has_variable (name): BOOLEAN |
Is variable defined? |
required_variables: LIST |
Extract variable names from template |
is_valid: BOOLEAN |
Is template syntactically valid? |
last_error: STRING |
Last error message |
template_source: STRING |
The template string |
escape_html_enabled: BOOLEAN |
Is escaping on? |
| Constant | Description |
|---|---|
Policy_empty_string |
Missing vars become "" (default) |
Policy_keep_placeholder |
Keep {{name}} in output |
Policy_raise_exception |
Set error on missing var |
local
tpl: SIMPLE_TEMPLATE
items: ARRAYED_LIST [HASH_TABLE [STRING, STRING]]
item: HASH_TABLE [STRING, STRING]
do
create tpl.make_from_string ("[
<h1>{{title}}</h1>
{{#has_items}}
<ul>
{{#items}}<li>{{name}} - ${{price}}</li>{{/items}}
</ul>
{{/has_items}}
{{^has_items}}
<p>Your cart is empty</p>
{{/has_items}}
]")
tpl.set_variable ("title", "Shopping Cart")
tpl.set_section ("has_items", True)
create items.make (2)
create item.make (2)
item.put ("Widget", "name")
item.put ("9.99", "price")
items.extend (item)
create item.make (2)
item.put ("Gadget", "name")
item.put ("19.99", "price")
items.extend (item)
tpl.set_list ("items", items)
print (tpl.render)
endThis library was designed after researching template engines and the Mustache specification:
Mustache Specification:
- Implements core Mustache syntax
- Logic-less design - no embedded code, just data binding
- Portable templates - same syntax works across languages
Competitor Analysis:
- ERB/EJS style - Too powerful, security risks with embedded code
- Handlebars - Good but helpers add complexity
- Jinja2 - Powerful but heavy for simple use cases
- Mustache - Perfect balance of features and simplicity
Common Pain Points Addressed:
- XSS vulnerabilities - HTML escaping ON by default
- Missing variable errors - Configurable policies
- Partial complexity - Simple registration API
- Testing difficulty -
required_variablesquery for validation
Key Design Choices:
- Auto-escaping -
{{var}}escapes,{{{var}}}for raw (opt-in unsafe) - Section truthiness - False, empty string, "0", "false" are all falsy
- List iteration - Same section syntax for both conditions and loops
- Context merging - List items inherit parent context
- Multiple policies - Choose empty string, keep placeholder, or error for missing vars
| Feature | Choice | Rationale |
|---|---|---|
| Delimiters | {{ }} |
Mustache standard, unlikely to conflict |
| Raw output | {{{ }}} |
Visual cue that it's "more open" |
| Sections | # and ^ |
Mustache standard |
| Comments | ! |
Mustache standard |
| Partials | > |
Mustache standard |
- Email templates - HTML and plain text emails
- HTML generation - Server-side page rendering
- Code generation - Generate source files from templates
- Reports - Fill in report templates with data
- Configuration - Template-based config file generation
- EiffelBase
MIT License - Copyright (c) 2024-2025, Larry Rix
