Jennie 🩸

Logic-less templates inspired by Moustache.

Anthrodontics is the mobile operating system for dentists. Templates are the best and most predictable form of automation. We need something that it is
simple for users to understand, from beginners to experts. Curly brackets sit nicely in that spectrum - so there's currently Handlebars on the extreme end and Moustache on the other side. Jennie is a compromise that tries to achieve the best of both worlds

Installation

Add jennie to your list of dependencies in mix.exs:

def deps do
[
{:jennie, "~> 0.9"}
]
end

Usage

Jennie.render("Hello {{name}}!", %{"name" => "World"})
# Hello World!

Syntax

Jennie speaks the familiar Moustache tags:

TagMeaning
{{name}}Variable, HTML-escaped
{{{name}}} / {{& name}}Variable, raw (not escaped)
{{a.b.c}}Dotted name lookup
{{.}}Implicit iterator (the current context)
{{#name}}…{{/name}}Section — rendered when truthy, iterated over lists
{{^name}}…{{/name}}Inverted section — rendered when falsey/empty
{{! comment }}Comment (ignored)
{{> partial}}Partial

On top of these, Jennie adds two conveniences.

List formatters

A variable tag may name a formatter after a pipe. When the value is a list, the items are rendered (each escaped individually) and joined:

Jennie.render("{{list | and}}", %{"list" => ["mesial", "occlusal", "distal"]})
# "mesial, occlusal, and distal"
Jennie.render("{{steps | ol}}", %{"steps" => ["Etch", "Bond"]})
# "1. Etch\n2. Bond"

Available formatters:

FormatterResult
olNumbered lines (1. …, 2. …)
andOxford-comma list joined with "and"
orOxford-comma list joined with "or"
joinPlain join with ", "
join "sep"Plain join with a custom double-quoted separator

Only the list items are escaped — the separators and conjunctions are emitted raw. Use {{{list | and}}} or {{& list | and}} to skip escaping the items too. A non-list value renders as if no formatter were present, and formatters are only allowed on variable tags (not on {{#…}} / {{^…}} sections).

Collapsible lines

A line whose non-whitespace content is entirely enclosed in sections that open and close on that same line is dropped — trailing newline and leading indentation included — when those sections render blank, instead of leaving an empty line behind:

Jennie.render("{{#opt}}Step A.{{/opt}}\nStep B.", %{"opt" => false})
# "Step B."

Any literal (non-whitespace) text outside the sections keeps the line. This is enabled by default; pass collapse_empty_lines: false to turn it off.

Options

Jennie.render/3 and Jennie.compile/2 accept:

OptionDefaultMeaning
:escapeHTML escaperA (binary -> iodata) escaper
:partials%{}Map of partial name to template source
:raise_on_missing_partialfalseRaise instead of rendering ""
:engineJennie.EngineOutput engine module
:ignore_nilfalseLeave tags whose keys are absent in place
:collapse_empty_linestrueDrop lines whose same-line sections all render blank

You can run mix test to see the full code coverage.