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 named html exists). As the quick start example showed, it also means you can turn things into strings with precise formatting, as that used a transformer called money. 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

Source

Where to get it

The source can be found on BitBucket here.

Issues

Hit any problems? Create an issue here.

Other implementations

Decided to implement Snabela for your own favorite language? Just create an issue with the path to your implementation and I'll add it to the README.

Author: orbitz
Updated: 2017-05-15 Mon 07:40
Up