Mustache is
... a logic-less template syntax. It can be used for HTML,
config files, source code - anything. It works by expanding tags in a
template using values provided in a hash or object.
This package ports over the mustache.js implementation for use in Julia. All credit should go there. All bugs are my own.
Following the main documentation for Mustache.js
we have a "typical Mustache template" defined by:
using Mustache
tpl = mt"""
Hello {{name}}
You have just won {{value}} dollars!
{{#in_ca}}
Well, {{taxed_value}} dollars, after taxes.
{{/in_ca}}
"""
The values with braces (mustaches on their side) are looked up in a view, such as a dictionary or module. For example,
d = Dict(
"name" => "Chris",
"value" => 10000,
"taxed_value" => 10000 - (10000 * 0.4),
"in_ca" => true)
render(tpl, d)
Yielding
Hello Chris
You have just won 10000 dollars!
Well, 6000.0 dollars, after taxes.
The render
function pieces things together. Like print
, the first
argument is for an optional IO
instance. In the above example, where
one is not provided, a string is returned.
The flow is a template is parsed into tokens by Mustache.parse
. This can be called directly, indirectly through the non-standard string literal mt
, or when loading a file with Mustache.load
. The templates use tags comprised of matching mustaches ({}
), either two or three, to
indicate a value to be substituted for. These tags may be adjusted when parse
is called.
The render
function takes tokens as its second argument. If this argument is a string, parse
is called internally. The render
function than reassambles the template, substituting values, as appropriate, from the "view" passed to it and writes the output to the specified io
argument.
The view used to provide values to substitute into the template can be
specified in a variety of ways. The above example used a dictionary. A
Module may also be used, such as Main
:
name, value, taxed_value, in_ca = "Christine", 10000, 10000 - (10000 * 0.4), false
render(tpl, Main) |> print
Which yields:
Hello Christine
You have just won 10000 dollars!
Further, keyword arguments can be used when the variables in the templates are symbols:
goes_together = mt"{{{:x}}} and {{{:y}}}."
render(goes_together, x="Salt", y="pepper")
render(goes_together, x="Bread", y="butter")
Similarly, a named tuple may be used as a view. As well, one can use
Composite Kinds. This may make writing show
methods easier:
using Distributions
tpl = "Beta distribution with alpha={{α}}, beta={{β}}"
render(tpl, Beta(1, 2))
gives
"Beta distribution with alpha=1.0, beta=2.0"
Tags representing variables have the form {{varname}}
,
{{:symbol}}
, or their triple-braced versions {{{varname}}}
or
{{{:symbol}}}
. The triple brace prevents HTML substitution for
entities such as <
. The following are escaped when only double
braces are used: "&", "<", ">", "'", "", and "/".
If the variable refers to a function, the value will be the result of calling the function with no arguments passed in.
In the main example, the template included:
{{#in_ca}}
Well, {{taxed_value}} dollars, after taxes.
{{/in_ca}}
Tags beginning with #varname
and closed with /varname
create
sections. The part between them is used only if the variable is
defined.
If the section name refers to a function, that function will be passed the unevaluated string within the section, as expected by the Mustache specification. (If the tag "|" is used, the section value will be rendered first, an enhancement to the specification.)
Related, if the tag begins with ^varname
and ends with /varname
the text between these tags is included only if the variable is not
defined or is falsy
.
If the section variable, {{#VARNAME}}
, binds to an iterable
collection, then the text in the section is repeated for each item in
the collection with the view used for the context of the template
given by the item.
This is useful for collections of named objects, such as DataFrames
(where the collection is comprised of rows) or arrays of
dictionaries. For Tables.jl
objects the rows are iterated over.
For data frames, the variable names are specified as symbols or strings. Here is a template for making a web page:
tpl = mt"""
<html>
<head>
<title>{{:TITLE}}</title>
</head>
<body>
<table>
<tr><th>name</th><th>summary</th></tr>
{{#:D}}
<tr><td>{{:names}}</td><td>{{:summs}}</td></tr>
{{/:D}}
</body>
</html>
"""
This can be used to generate a web page for whos
-like values:
_names = String[]
_summaries = String[]
for s in sort(map(string, names(Main)))
v = Symbol(s)
if isdefined(Main,v)
push!(_names, s)
push!(_summaries, summary(eval(v)))
end
end
using DataFrames
d = DataFrame(names=_names, summs=_summaries)
out = render(tpl, TITLE="A quick table", D=d)
print(out)
This can be compared to using an array of Dict
s, convenient if you have data by the row:
A = [Dict("a" => "eh", "b" => "bee"),
Dict("a" => "ah", "b" => "buh")]
tpl = mt"{{#:A}}Pronounce a as {{a}} and b as {{b}}. {{/:A}}"
render(tpl, A=A) |> print
yielding
Pronounce a as eh and b as bee. Pronounce a as ah and b as buh.
The same approach can be made to make a LaTeX table from a data frame:
function df_to_table(df, label="label", caption="caption")
fmt = repeat("c", size(df,2))
header = join(string.(names(df)), " & ")
row = join(["{{:$x}}" for x in map(string, names(df))], " & ")
tpl="""
\\begin{table}
\\centering
\\begin{tabular}{$fmt}
$header\\\\
{{#:DF}} $row\\\\
{{/:DF}} \\end{tabular}
\\caption{$caption}
\\label{tab:$label}
\\end{table}
"""
Mustache.render(tpl, DF=df)
end
(A string is used above -- and not a mt
macro -- so that string
interpolation can happen.)
Though it isn't part of the Mustache specification, when iterating
over an unnamed vector, Mustache.jl uses {{.}}
to refer to the item:
tpl = "{{#:vec}}{{.}} {{/:vec}}"
render(tpl, vec = ["A1", "B2", "C3"]) # "A1 B2 C3 "
Note the extra space after C3
.
There is also limited support for indexing with the iteration of a vector that
allows one to treat the last element differently. The syntax .[ind]
refers to the value vec[ind]
. (There is no support for the usual
arithmetic on indices.)
To print commas one can use this pattern:
tpl = "{{#:vec}}{{.}}{{^.[end]}}, {{/.[end]}}{{/:vec}}"
render(tpl, vec = ["A1", "B2", "C3"]) # "A1, B2, C3"
To put the first value in bold, but no others, say:
tpl = """
{{#:vec}}
{{#.[1]}}<bold>{{.}}</bold>{{/.[1]}}
{{^.[1]}}{{.}}{{/.[1]}}
{{/:vec}}
"""
render(tpl, vec = ["A1", "B2", "C3"]) # basically "<bold>A1</bold>B2 C3"
This was inspired by
this
question, but the syntax chosen was more Julian. This syntax -- as
implemented for now -- does not allow for iteration. That is
constructs like {{#.[1]}}
don't introduce iteration, but only offer
a conditional check.
Partials are used to include partial templates into a template.
Partials begin with a greater than sign, like {{> box.tpl }}
. In this example, the file box.tpl
is opened and inserted into the template, then populated. A full path may be specified.
They also inherit the calling context.
In this way you may want to think of partials as includes, imports, template expansion, nested templates, or subtemplates, even though those aren't literally the case here.
The partial specified by {{< box.tpl }}
is not parsed, rather included as is into the file. This can be faster.
Julia
provides some alternatives to this package which are better
suited for many jobs:
-
For simple substitution inside a string there is string interpolation.
-
For piecing together pieces of text either the
string
function or string concatenation (the*
operator) are useful. (Also anIOBuffer
is useful for larger tasks of this type.) -
For formatting numbers and text, the Formatting.jl package, the Format package, and the StringLiterals package are available.
This project deviates from Mustache.js in a few significant ways:
-
Julia structures are used, not JavaScript objects. As illustrated, one can use Dicts, Modules, DataFrames, functions, ...
-
In the Mustache spec, when lambdas are used as section names, the function is passed the unevaluated section:
template = "<{{#lambda}}{{x}}{{/lambda}}>"
data = Dict("x" => "Error!", "lambda" => (txt) -> txt == "{{x}}" ? "yes" : "no")
Mustache.render(template, data) ## "<yes>", as txt == "{{x}}"
The tag "|" is similar to the section tag "#", but will receive the evaluated section:
template = "<{{|lambda}}{{x}}{{/lambda}}>"
data = Dict("x" => "Error!", "lambda" => (txt) -> txt == "{{x}}" ? "yes" : "no")
Mustache.render(template, data) ## "<no>", as "Error!" != "{{x}}"