Snabela 1.0: Logic-less @templates@
Quick start
Snabela is a BSD licensed template specification, library implemented in Ocaml, and command line tool that uses the library to apply a template.
To install Snabela via opam do:
opam install snabela
To use Snabela, take a template called foo.tmpl:
@#parties-@
@name@ has a minimum age of @min_age-@
@% This comment is between two lines that will be turned
into one line because of the whitespace trimming with -
Notice the indent in the line after after this, otherwise
the min_age and the word "and" would be right next to each
other -@
and has a $ @-cost | money@ cover charge.
@#?guest_list-@
Guest list:
@-#guest_list-@
@name@
@-/guest_list-@
@/guest_list-@
@#!guest_list-@
No guests have signed up.
@/guest_list-@
@/parties-@
Email joe.blow@@parties.com
A TOML file called foo.toml containing values:
min_age = 18 guest_list = [] [[parties]] name = "End of the world party" cost = 20.0 [[parties.guest_list]] name = "me" [[parties.guest_list]] name = "myself" [[parties.guest_list]] name = "i" [[parties]] name = "End of the world party party" min_age = 21 cost = 40.0
A file with the path transformers/money:
#! /bin/sh read line printf "%0.2f" $line
Then run the command:
snabela --kv foo.toml --td transformers < foo.tmpl
The expected output is:
End of the world party has a minimum age of 18 and has a $20.00 cover charge.
Guest list:
me
myself
i
End of the world party party has a minimum age of 21 and has a $40.00 cover charge.
No guests have signed up.
Email joe.blow@parties.com
Snabela: Logic-less templates
Snabela is a BSD licensed spec, library, and program that implements logic-less templates. The idea is not new and much of Snabela is copied from Mustache however Snabela attempts to fix the issues the author found with Mustache. Snabela was greatly affected by an existing template system created by Samuel Rivas for Erlang and he provided feedback during its creation.
What Snabela does differently
Before creating Snabela, I looked at different template engines including Golang's, Puppet's, Jinja, and finally Mustache. I wanted something that would work in Ocaml and after looking at most of those they either required a lot of reflection in order to apply the template at run-time or had very expressive template languages, which was too much complexity for me. Mustache seemed like the best fit and had an Ocaml implementation. After playing with it for a bit, though, I decide it did a few things that I'd like to do differently. Specifically:
- Snabela uses the
@symbol to indicate a replacement and@@to escape a@. Mustache has no direct way to escape a replacement other than actually changing the syntax of a replacement, which seemed a bit obtuse to me. Snabela also uses the same symbol for opening a replacement and closing one. Given that a replacement cannot be nested, this seems perfectly fine. - Snabela supports arbitrary transformations on a replacement via the pipe (
|) notation. This means you can use it for HTML if that suits your needs by doing something like@foo | html@(assuming a transformation namedhtmlexists). As the quick start example showed, it also means you can turn things into strings with precise formatting, as that used a transformer calledmoney. Snabela also supports appending a transformer to all replacements, for example if you want to make sure that a replacement is always HTML escaped rather than relying on the author of the template to do it right. In contrast, Mustache HTML escapes everything by default and uses a special form of replacement to not escape. This means the author of a template could make an unsafe template by accident or on purpose if the template comes from the outside world. It also does not support arbitrary transformations so making money look like money has to be done in the object to which the template is applied. - Snabela is stricter. Missing keys are a failure and testing a key of the
wrong type is a failure. Basic tests (
@?and@!) must be done on a key with a boolean type and list tests (@#?and@#!) must be done on a key with a list type.
That being said, Snabela steals many good concepts from Mustache.
The Snabela Template Language
Snabela templates aim to find a reasonable balance between expressiveness and complexity. While "logic-less templates" sounds nice, it's not an entirely accurate statement as one can do conditionals in the template. However there is no arbitrary code execution in the template itself like many other template languages.
Key Replacement
The basic use of a template is replacing a key with its value. This is done
with @key@.
Trimming
Often times, in order to make a template readable white space and new lines will
be added to the template that should not be in the output. White space can be
trimmed on the left side with @-, which removes any white space up to the
first non-white space but not including the new line. White space can be
trimmed on the right with -@ which will remove any white space up to and
including the new line.
The following template:
Your email is @first-@ . @-last-@ @@ @-domain@.
Assuming some values for the keys will output something like:
Your email is joe.blow@parties.com.
Testing Booleans
Snabela has a sections which are portions of the template that are dynamic based on the values of the keys. Whole portions of a template may or may not show up in the output. The basic section is a boolean test.
@? parties_tonight -@ There are parties tonight. @/parties_tonight-@ @!parties_tonight-@ There are no parties tonight. @/parties_tonight@
If parties_tonight is true, the output is:
There are parties tonight.
Otherwise:
There are no parties tonight.
Iterating lists
Another section type is iterating lists.
@#parties-@ @name@ @/parties@
If parties is the list ["one", "two", "three"] the output is:
one two three
Testing emptiness of a list
Testing the emptiness of a list is like boolean tests. Often it's useful to give a special message if a list is empty.
@#?parties-@
@-# parties -@
@-name@
@/parties@
@/parties-@
@#! parties -@
There are no parties.
@/parties-@
Will output something like:
one two three
Or if the list is empty:
There are no parties.
Comments
Templates are like code and often need some commenting.
@% This is a comment -@
Will output nothing.
Transformers
Transformers are where Snabela diverges from Mustache quite a bit. Transformers apply only to key replacements, not sections or comments. A transformer, as the name applies, transforms the value of a replacement. They can be used from anything to making sure titles are capitalized, or money is always in double decimal digits, to escaping unsafe characters. Multiple transformers can be applied to a replacement.
In the CLI tool, a transformer is the name of a program in the transformer directory which reads a line and writes a line. In the library a transformer is an Ocaml function.
The template:
Warning, @ warning_message | uppercase @.
With the a script in the transformer directory called uppercase:
#! /bin/sh tr 'a-z' 'A-Z'
And a TOML file with:
warning_msg = "bad things"
Will output
Warning, BAD THINGS.
Snabela Library Example
The library takes a template and a list of transformers and compiles them to a value which a key-value can be applied.
let template = "Hello, @name | capitalize@" in let kv = Snabela.Kv.(Map.of_list [("name", string "foo")]) in let t = CCResult.get_exn (Snabela.Template.of_utf8_string template) in let capitalize = function | Snabela.Kv.S s -> Snabela.Kv.S (CCString.capitalize_ascii s) | _ -> raise (Invalid_argument "not a string") in let compile = Snabela.of_template t [("capitalize", capitalize)] in CCResult.get_exn (Snabela.apply compile kv)
Will evaluate to a string with the contents:
Hello, Foo
