• Stars
    star
    14
  • Rank 1,438,076 (Top 29 %)
  • Language
    Erlang
  • License
    Apache License 2.0
  • Created over 8 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Dynamic template compiler for Erlang

Template Compiler for Erlang / Elixir

Test Join the chat at https://gitter.im/zotonic/zotonic

This is the template compiler used by The Erlang Content Management Framework and CMS Zotonic.

This compiler is a complete rewrite of the erlydtl fork used in Zotonic. In contrast with ErlyDTL the template_compiler generates small Erlang modules which are shared between different templates and sites.

Building

Run make. To test, run: make test

To use in your project with rebar3, use the Hex package:

{deps, [
    template_compiler
]}.

Or directly from git:

{deps, [
    {template_compiler, {git, "https://github.com/zotonic/template_compiler", {branch, "master"}}}.
]}.

Template Language

The template language is largely the same as Django Template Language with some additions:

  • Runtime includes, all templates are compiled to separate BEAM modules
  • overrules to override same-named templates
  • translation tags {_ my translatable text _} and {% include "a.tpl" arg=_"my translatable text" %}

Runtime Just in Time compilation

The template_compiler is a runtime compiler. It compiles templates from source files to in-memory BEAM modules. Templates are re-compiled if the source file changes or new translation strings are loaded.

Templates are compiled to functions containinge entry point to all blocks.

A template like:

Hello
{% block a %}this is block a{% endblock %}
World
{% block b %}this is block b{% endblock %}
{% optional include "foo.tpl" %}

Will be compiled to an Erlang module with the following structure:

-module(tpl_7bae8076a5771865123be7112468b79e9d78a640).
-export([
    render/3,
    render_block/4,
    timestamp/0,
    blocks/0,
    module/0,
    extends/0,
    includes/0,
    filename/0,
    mtime/0,
    is_autoid/0,
    runtime/0
]).

%% The main render function.
render(Args, BlockMap, Context) -> [ ... ].

%% Render functions per block, the 'Blocks' is a block trace used internally.
render_block(a, Vars, Blocks, Context) -> [ ... ];
render_block(b, Vars, Blocks, Context) -> [ ... ];
render_block(_, _Vars, _Blocks, _Context) -> <<>>.

%% Timestamp on module compilation (os:timestamp/0)
timestamp() -> {1574,685843,340548}.

%% Block defined in this template.
blocks() -> [ a, b ].

%% The module name
module() -> tpl_7bae8076a5771865123be7112468b79e9d78a640.

%% The template that this template extends on.
extends() -> undefined.

%% The templates that this template includes.
%% Includes which use variable names for the template name are not listed.
includes() -> [ 
        #{
            template => <<"foo.tpl">>,
            line => 5,
            column => 20,
            method => optional,   % normal | all | optional
            is_catinclude => false
        }
    ].

%% The filename of this template
filename() -> <<"foo/bar/a.tpl">>.

%% The modification time of the template file on compilation
mtime() - {{2023,9,28},{11,51,49}}.

%% Flag if the autoid ("#id") construct is used in this template.
is_autoid() -> false.

%% Runtime module this template is linked against.
runtime() -> template_compiler_runtime.

The module includes debug information so that stack traces show the correct template file and line number.

Runtime Module

The template_compiler uses a runtime module which implements template lookup and vatiable resolution methods. There is a default runtime module in template_compiler_runtime.

How to use

Render a template to an iolist:

Vars = #{ <<"a">> => 1 },                 % Template variables, use a map
Options = [],                       % Render and compilation options
Context = your_request_context,     % Context passed to the runtime module and filters
{ok, IOList} = template_compiler:render("hello.tpl", Vars, Options, Context).

The template_compiler:render/4 function looks up the template, ensures it is compiled, and then calls the compiled render function of the template or the templates it extends (which are also compiled etc.).

Use template_compiler:compile_file/3 and template_compiler:compile_binary/4 to compile a template file or in-memory binary to a module:

{ok, Module} = template_compiler:compile_binary(<<"...">>, Filename, Options, Context).

The Filename is used to reference the compiled binary.

Force recompilation

Sometimes (for example when template lookups or translations are changed) it is necessary to check all templates if they need to be recompiled.

For this are the flush functions:

  • template_compiler:flush() forces a check on all templates.
  • template_compiler:flush_file(TemplateFile) forces check on the given template.
  • template_compiler:flush_context(Context) forces check on templates compiled within the given request context.

The template_compiler parses the templates till their Form format and then calculates a checksum. If the checksum is not changed from the previous compilation then the Form is not compiled, sparing valuable processing time.

Template language and Tags

For more complete documentation see Zotonic: http://docs.zotonic.com/en/latest/developer-guide/templates.html

Below is a short list of tags and explanation of template include / extend.

The template_compiler package doesn't include filters.

Variables

Variables are surrounded by {{ and }} (double braces):

Hello, I’m {{ first_name }} {{ last_name }}.

When rendering this template, you need to pass the variables to it. If you pass β€œJames” for first_name and β€œBond” for last_name, the template renders to:

Hello, I’m James Bond.

Instead of strings, variables can also be objects that contain attributes. To access the attributes, use dot notation:

{{ article.title }} was created by {{ article.author.last_name }}

The article could have been passed as a proplist or map in the Vars for the template render function:

#{
    <<"article">> => #{
        <<"title">> => <<"My title"/utf8>>,
        <<"author">> => #{
            <<"id">> => 1234,
            <<"last_name">> => <<"Janssen">>
        }
    }
}.

Values and expressions

Expressions can use different values types:

  • Variables (see above)
  • Number: 123
  • String: "hello" or 'hello'
  • Translatable string: _"Hello" (see below)
  • List: [ 1, 2, 3 ]
  • Map, using Elixir syntax: %{ a: 1, b: 2 } where the keys will become binary strings equivalent to the Erlang map: #{ <<"a">> => 1, <<"b">> => 2 }
  • Map, using strings as keys: %{ "foaf:name":"Hello" }
  • Erlang atom: `a` (quoted using backticks)
  • Tagged value list: {mytag a=1 b=2}, this translates to Erlang {mytag, [{a,1}, {b,2}]}
  • Unique generated id: #foo - an unique prefix is used for each template
  • Model calls m.rsc.foo
  • Model calls with an optional argument m.rsc.foo::bar

Translatable texts

There are three ways translatable texts can be used.

As a tag embedded in the text of the template:

{_ This text is translatable _}

As double quoted string prefixed with a '_', where you could also use a string:

{% include "_a.tpl" title=_"Translatable text" %}

As a text with arguments:

{% trans "Hello {foo}, Bye" foo=author.name_full %}

Note: to show a { or } in a trans tag text then double it to {{.

The trans tag is compiled to code so very efficient.

List translatable strings

To get a list of all translatable texts in a template use:

template_compiler:translations(TemplateFilename).

This returns a list of all strings, which could be used to generate a .po file:

[
    {<<"Hello {foo}, Bye">>, [], {<<"foo/bar.tpl">>, 1234, 5}}
].

Template include / extends

Templates can be combined to re-use parts and keep everything manageable.

Include

First a template can be included in another template:

Hello {% include "_name.tpl" id=foobar %}

The template (_name.tpl in this case) will be included at the called spot. All variables known at the spot of the include will be passed, with the addition of the arguments of the include tags.

Note: in ErlyDTL the included template is compiled inline into the surrounding template. In template_compiler the included template is compiled as a separate module.

Extends

You can also have a base template which can be used as the basis of other templates. The base template should define some blocks that can be changed in the template that extends the base template:

{% extends "base.tpl" %}
{% block name %}Piet!{% endblock %}

Where the base.tpl could be:

Hello {% block name %}...{% endblock %} world.

The {% extends "..." %} tag must be the first tag in the template. Also any text outside the block tags will be dropped. So be sure to surround replaceable parts in your base templates with block tags.

There is a variation on extends where the template extends on a same-named template of a lower priority. This is heavily used in Zotonic to extend templates in other modules.

{% overrules %}
{% block name %}...{% endblock %}

The overrules is used to make it explicit that this template overrules (or extends) another template with the same name.

Block

This defines a named portion of a template that can be replaced in a template that extends (or overrules) this template:

Some text
{% block myname %}
    Some text in the block that might be replaced
{% endblock %}
And text after the block

Blocks can be nested:

Some text
{% block myname %}
    Some text in the block that might be replaced
    {% block nestedblock %}
        Text in the nested block
    {% endblock %}
    And more text in the outer block
{% endblock %}
And text after the block

The blocks are compiled to separate functions. The template compiler uses the template extends chain to figure out which block-function to render from which extending template.

Inherit

The {% inherit %} tag van be used inside a block to render the same-named block in the extended (or overruled) template.

If base.tpl is like:

this is {% block a %}the base{% endblock %} template

And a.tpl is like:

{% extends "base.tpl" %}
{% block a %}hello {% inherit %} world{% endblock %}

Then a.tpl renders like:

this is hello the base world template

If tag

Conditionally show or hide parts of a template:

{% if somevar %}
    True
{% elif othervar %}
    Other var
{% else %}
    False
{% endif %}

The elif can also be written as elseif

With tag

Define a variable to be used within an enclosed part of the template:

{% with someexpr as v %}
    Here v can be used as any other variable
{% endwith %}

This is useful for using the result of a complicated expression multiple times or if a forloop (see below) iterator needs to be used in overruled blocks or included templates.

For tag

Loop over a list of values, printing a comma separated list:

{% for k in [ 1, 2, 3, 4 ] %}
    {{ k }}{% if nor forloop.last %},{% endif %}
{% endfor %}

Loop over a list of tuples (eg. [ {a, 1}, {b, 2} ]):

<table>
{% for key, value in someproplist %}
    <tr>
        <th>{{ key|escape }}</th>
        <td>{{ value|escape }}</td>
    </tr>
{% empty %}
    <tr>
        <td>{_ Sorry, no values _}</td>
    </tr>
{% endfor %}
</table>

Within a forloop there is an iterator called forloop with the following properties:

  • forloop.first true if this is the first iteration
  • forloop.last true if this is the last iteration
  • forloop.counter iteration counter, starts at 1
  • forloop.counter0 iteratiomn counter, starts at 0
  • forloop.revcounter iteration counter, starts at N, ends at 1
  • forloop.revcounter0 iteration counter, starts at N-1, ends at 0
  • forloop.parentloop the forloop parameter of the enclosing for loop (if any)

NOTE the forloop iterator is only available in the template that contains the for loop, it is not passed to any included or extending templates. If you want to use it in a block or included template then explicitly assign it to a variable using with or include arguments.

If the template compiler does sees that the forloop is not used inside the template where the forloop is defined then the code for tracking the forloop iterators is not generated.

Comment tag

Everything surrounded by the tag is excluded:

{% comment %}
    Some explanation that will not be part of the compiled template
{% endcomment %}

Useful to disable parts of a template or add some explanatory texts.

Raw tag

To echo some part without rendering it:

{% raw %}
    {{ hello }} {% this is }} %} echo'd as-is
{% endraw %}

Spaceless tag

Remove spaces between HTML tags:

a-{% spaceless %} <a> x<span>xxx </span> </a> {% endspaceless %}-b

Renders as:

a-<a> x<span>xxx </span></a>-b

Print tag

Print a value in <pre> tags, used to inspect variables or dump some value when you are still writing the template:

{% print somexpr %}

The expression value will be printed using io_lib:format("~p", [ SomExpr ]), then escaped and surrounded with <pre>...</pre>.

Whitespace Handling

  • A single trailing newline is stripped, from the template, if present.
  • Other whitespace, like tabs, newlines and spaces are returned unchanged.

License

The template_compiler is released under the APLv2 license. See the file LICENSE for the exact terms.

More Repositories

1

zotonic

Zotonic - The Erlang Web Framework & CMS
Erlang
815
star
2

z_stdlib

Zotonic standard function library
Erlang
19
star
3

depcache

An in-memory caching server for Erlang
Erlang
15
star
4

cowmachine

Webmachine for Zotonic and Cowboy
Erlang
14
star
5

router

In-memory trie based router for fast parallel path lookups with wildcards.
Erlang
11
star
6

webzmachine

Zotonic's fork of Basho's webmachine
Erlang
11
star
7

logstasher

Erlang Logger formatter for logstash
Erlang
10
star
8

eblurhash

Blurhash in Erlang
C
10
star
9

buffalo

πŸƒ Deduplicating delayed / debounced erlang apply
Erlang
8
star
10

hexpub

Automatically publish Erlang packages to Hex.pm
Shell
7
star
11

dispatch_compiler

Compiles dispatch rules to an Erlang module for quick dispatch matching.
Erlang
6
star
12

mod_vault

Encrypts data using self generated RSA keys.
Erlang
6
star
13

ua_classifier

User-Agent Classifier: Erlang version of WeatherChannel's dClass
C
6
star
14

ringbuffer

πŸ”„ Ring buffer implementation for Erlang using ETS tables.
Erlang
5
star
15

mqtt_packet_map

Encoder and decoder for MQTT v5 and earlier.
Erlang
5
star
16

zotonic_example

Example website for Zotonic - Clone this repo to start with a new project
Erlang
5
star
17

zotonicwww

The source code for http://zotonic.com
CSS
4
star
18

mod_admin_multiupload

Batch upload of media files in the Zotonic admin
Smarty
3
star
19

zotonic_ssl

Useful SSL routines for Erlang projects
Erlang
3
star
20

zotonicdemo

demo.zotonic.com
Erlang
3
star
21

mod_import_anymeta

Import an Anymeta site into Zotonic
Erlang
3
star
22

mod_zmr

Zotonic Module Repository site as a module.
Erlang
3
star
23

mqtt_sessions

MQTT session and topic routing - embeddable in Erlang projects.
Erlang
3
star
24

mochiweb

Zotonic's version of mochiweb.
Erlang
2
star
25

zotonic_mod_demo

Module for the Zotonic demo site.
Erlang
2
star
26

jsxrecord

JSON encoding with records and 'null'/'undefined' mapping for Erlang
Erlang
2
star
27

zotonicwww2

The second incarnation of the Zotonic web site - also used as an example site
Erlang
2
star
28

zutils

Zotonic maintainance utilities
Python
2
star
29

mod_geomap

Mapping and geographical functions for Zotonic resources
JavaScript
2
star
30

zotonic_mod_paysub

Zotonic module for paid subscriptions and other payments
Erlang
1
star
31

zotonic_rdf

RDF triples handling - map triples to resources - RDF support routines.
Erlang
1
star
32

statz

Statistics for Zotonic
Erlang
1
star
33

mod_logging_elastic

Log events in Elastic Search for analysis with Kibana
Erlang
1
star
34

notifier

The notifier system makes it possible to create modular components with pluggable interfaces.
Erlang
1
star
35

docker-erlang

Alpine-based Erlang Docker image
Dockerfile
1
star