Mandira is a language agnostic logic-light templating system desigend to be usable on server and client side applications. Mandira strives to be easy to learn, easy to implement, efficient to render, comfortable for designers, tollerable for developers, suitable for non-HTML documents, as flexible as necessary (but no more so), and consistent in style and rendering.
Mandira templates should look familiar to users of other templates. It is called "logic-light" because it allows some forms of conditional logic while avoiding the complexity of more sophisticated features. It strives for both simplicity and ergonomics, but makes concessions to each where appropriate.
A simple Mandira template:
Hello {{name}} You have just won ${{value}}! {{?if in_monaco}} Well, ${{taxed_value}}, after taxes. {{/if}}
Given the following context:
{ "name": "Jason", "value": 10000, "taxed_value": 10000.0, "in_monaco": true }
Will produce the following:
Hello Jason You have just won $10000! Well, $10000, after taxes.
Tags in Mandira are in {{double-braces}}
, and the part inside the tag is called the tag key.
Variable tags look like {{name}}
. This will attempt to look up the name
key in the current context and render it. If name
isn't found, nothing is rendered. Variables always are HTML-escaped. To avoid escaping, use {{{name}}}
with three braces instead.
Template:
{{first}} {{last}} {{title}} {{{title}}}
Context:
{ "first": "Sübü'ätäi", "title": "<b>Örlög baghatur</b>" }
Output:
Sübü'ätäi <b>Örlög baghatur</b> <b>Örlög baghatur</b>
Variable tags can also take filters, which modify the output of variables. Filters are applied using the |
character (like unix pipes), can take additional arguments which must be in parentheses, and can be chained together from left to right. {{first|upper|js}}
will take the first
variable, upper case it, and then escape it for javascript. The actual first
variable in the context remains unchanged; it is only the output that changes.
Template:
{{first|upper}} {{last}} titles: {{titles|join(", ")}} family: {{family|join("|")}}
Context:
{ "first": "Gaius" "last": "Julius Caesar", "titles": ["Consul", "Imperator", "Pontifex Maximus", "Dictator"] }
Output:
GAIUS Julius Caesar titles: Consul, Imperator, Pontifex Maximus, Dictator family:
The following filters are provided by default:
upper
- variable in upper caselower
- variable in lower casetitle
- variable in title caselen
- length of the variableindex
- the numeric index of a list or stringslice
- a slice of a list or stringformat
- printf-style formatted stringdate
- PHP style date formatted stringjoin
- each element of the list joined with a string, ", "
by defaultdivisibleby
- true
if a number is divisible by an argumentMore detailed information on their behavior and return values is available in the implmentation specification.
Sections set off blocks of text to be rendered one or more times with an altered context, depending on the value of the key in the current context. {{#names}}
begins a "names" section, and {{/names}}
ends it.
If the key is a list, the block is rendered once per item in the list, with that item accessible by {{.}}
. If the list contains maps, the block rendered with each map's namespace as the context. If the key is a map, the block is rendered once with that map's namespace as the context. List sections additionally get the context variable .index
, which is the zero-indexed iteration of the loop.
Template:
{{#generals}} {{.index}}: {{.}} {{/generals}} {{#generallist}} {{last|upper}}, {{first}} {{/generallist}} {{#napoleon}} {{first}} {{last}} b. {{born}}, d. {{died}} {{/napoleon}}
Context:
{ "generals": ["Alexander The Great", "Hannibal Barca", "Gaius Julius Caesar"], "generallist": [ {"first": "Friedrich", "last": "der Große", {"first": "અર્જુન"} {"first": "家康", "last": "徳川"} ], "napoleon": { "first": "Napoléon", "last": "Bonaparte", "born": "15 August 1769", "died": "5 May 1821" } }
Output:
0: Alexander The Great 1: Hannibal Barca 2: Gaius Julius Caesar DER GROßE, Friedrich , અર્જુન 徳川, 家康 Napoléon Bonaparte b. 15 August 1769, d. 5 May 1821
Conditional logic is done via a section which looks like {{?if condition}}...{{?else}}...{{/if}}
, where the else
section is optional. The condition itself is standard boolean logic and allows filtered variables, with the operators:
==
, !=
, <
, >
, <=
, >=
not
, and
, or
Mandira also allows literals in expressions for use in conditional logic and as arguments to filters. The following shows examples of each data type, with the falsey value shown first:
0
, 1
, 213
0.0
, 1.4
, 3.14
""
, "hello, world!"
, "મંદિર"
Putting it all together:
Template:
{{#generals}} {{?if born|slice(-2) == "BC" }} <span class="ancient">{{name}}, b. {{born}}</span> {{?else}} <span class="modern">{{name}}, b. {{born}}</span> {{/if}} {{/generals}}
Context:
{ "generals": [ {"name": "Napoléon Bonaparte", "born": "15 August 1769"}, {"name": "Gaius Julius Casear", "born": "July 100 BC"} ] }
Output:
<span class="modern">Napoléon Bonaparte, b. 15 August 1769</span> <span class="ancient">Gaius Julius Caesar, b. July 100 BC</span>
Currently, Mandira has this loose specification and an alpha quality reference implementation in Go. It is currently in use as the main templating language on my personal website, where it replaced mustache. I intend to get feedback and live with it a while and see what works and what doesn't.
If you are interested in this language and the ideas behind it, read my Logic-less Template Redux blog post or ping @jmoiron on twitter. The name Mandira is sanskrit for "Temple", and the script used in the title is Mandira in Gujarati.