Introducing ErlTL: A Simple Erlang Template Language

Posted by Yariv on October 17, 2006

Have you ever wanted to build the kind of mail-merge program that never crashes, that runs on a scalable fault tolerant cluster, that you can easily scale by adding more boxes, that you can hot-swap its code without taking it offline, that runs on an optimized VM that can handle hundreds of thousands of simultaneous tasks at a time, and that’s built with the same technology that powers England’s phone network with a long string of nines after the dot?

If you do, you’re probably a dirty scumbag spammer, so go to hell :)

But assuming you’re not a spammer and/or you want an easy way of embedding small logic snippets in large swaths of Erlang binary data, you will probably enjoy ErlTL: A Simple Erlang Template Language.

ErlTL does not aim to be the most feature-rich template language. It may not have any time soon that feature that you really like in another template language and that makes your web designers oh so happy. (Of course, you’re always welcome to implement it yourself and share it with us. If it’s useful, I’ll add it to the standard distribution). Currently, ErlTL has 4 main objectives:

- Speed. ErlTL compiles template files into BEAM files prior to execution for top-notch performance.
- Simplicity. ErlTL has the smallest number of syntactic elements that are sufficient for it to be useful (at least, useful for me :) ).
- Reusability. ErlTL lets you reuse templates and template fragments, even from different template files.
- Good error reporting. When you write an invalid expression, the ErlTL compiler makes sure you know on which line the problem is.

Let’s take a look at an example. Below is a simple template file called song.et (ErlTL files have the ‘.et’ extension by convention).


<%! This template composes beautiful songs %>
<%? [What, Where, Objects, Action] = Data %>
<% What %> singing in <% Where %>,
Take these <% Objects %> and learn to <% Action %>

Now, let’s use this template. Fire up the shell and type:


erltl:compile("/path/to/song.et"),
song:render([<<"Blackbird">>,
  <<"the dead of the night">>, <<"broken wings">>,
  <<"fly">>]).

If you didn’t mess up, this is the output you’ll get:


[<<"\n\n">>,
<<"Blackbird">>,
<<" singing in ">>,
<<"the dead of the night">>,
<<",\nTake these ">>,
<<"broken wings">>,
<<" and learn to ">>,
<<"fly">>,
<<"\n">>]

If this isn’t exactly what you were expecting, don’t panic. For efficiency, ErlTL doesn’t try to concatenate all outputs. If you need to concatenate them, you can do it manually, e.g. with iolist_to_binary/1. However, doing so is often unnecessary when you want to send the result to an IO device such as a socket.

So what’s happening here? ErlTL creates a module called ’song’, with a function called ‘render’. This function takes one parameter called ‘Data’. (ErlTL also creates a zero-parameter function that calls the 1 parameter function with ‘undefined’ as the value for ‘Data’.) After you compile the template, you can call these functions from template code snippets as well as from any Erlang module.

The template syntax consists of the following elements:


<% [Erlang code block] %>

An Erlang code block is a sequence of Erlang expressions (separated by commas). The result of the expressions is included in the function’s output.


<%! [comment] %>

Comments are ignored by the compiler


<%? [top-level expressions] %>

The results of top-level expressions are excluded from the function’s output. They are used primary to bind variables to elements of the Data parameter. Top level expressions must go before all standard expressions in the same function.


<%@ [function declaration] %>

Function declarations tell the compiler that all the code following the declaration belongs to a new function with the given name. This is useful when you want to reuse a template snippet. Note that all functions are exported, so you can reuse code by calling functions from other templates. Let’s look at a simple example, called album.et:


<%! This template prints an album data in HTML %>
<%? {Title, Artist, Songs} = Data %>
<html>
<body>
Title: <b><% Title %></b><br>
Artist: <b><% Artist %></b><br>
Songs: <br>
<table>
<% [song(Song) || Song <- Songs] %>
</table>
</body>
</html>
 
<%@ song %>
<%? {Number, Name} = Data %>
<tr>
  <td><% integer_to_list(Number) %></td>
  <td><% Name %></td>
</tr>

In the shell, type the following:

erltl:compile("/path/to/album.et"),
album:render(
  {<<"Abbey Road">>, <<"The Beatles">>,
  [{1, <<"Come Together">>},
   {2, <<"Something">>},
   {3, <<"Maxwell's Silver Hammer">>},
   {4, <<"Oh! Darling">>},
   {5, <<"Octopus's Garden">>},
   {6, <<"I Want You (She's So Heavy)">>}]
}).

(Beatles fans, please forgive me for not typing all the song names! I got tired :) ) This will give you the following output:


[<<"\n\n<html>\n<body>\nTitle: <b>">>,
<<"Abbey Road">>,
<<"</b><br>\nArtist: <b>">>,
<<"Beatles">>,
<<"</b><br>\nSongs: <br>\n<table>\n">>,
[[<<"\n\n<tr>\n  <td>">>,
   "1",
   <<"</td>\n  <td>">>,
   <<"Come Together">>,
   <<"</td>\n</tr>\n">>],
  [<<"\n\n<tr>\n  <td>">>,
   "2",
   <<"</td>\n  <td>">>,
   <<"Something">>,
   <<"</td>\n</tr>\n">>],
  [<<"\n\n<tr>\n  <td>">>,
   "3",
   <<"</td>\n  <td>">>,
   <<"Maxwell's Silver Hammer">>,
   <<"</td>\n</tr>\n">>],
  [<<"\n\n<tr>\n  <td>">>,
   "4",
   <<"</td>\n  <td>">>,
   <<"Oh! Darling">>,
   <<"</td>\n</tr>\n">>],
  [<<"\n\n<tr>\n  <td>">>,
   "5",
   <<"</td>\n  <td>">>,
   <<"Octopus's Garden">>,
   <<"</td>\n</tr>\n">>],
  [<<"\n\n<tr>\n  <td>">>,
   "6",
   <<"</td>\n  <td>">>,
   <<"I Want You (She's So Heavy)">>,
   <<"</td>\n</tr>\n">>]],
<<"\n</table>\n</body>\n</html>\n\n">>]

That’s pretty much all these is to ErlTL at the moment. At some point, it may be beneficial to add some iteration syntax, but I’m not planning on doing this in the near future. I hope you find ErlTL useful. As always, please let me know if you have any comments, suggestions or bug reports.

Enjoy!

Note: the current release is in the 0.9 branch in the repository.

Trackbacks

Use this link to trackback from your own site.

Comments

Leave a response

  1. Anon.Coward Tue, 17 Oct 2006 17:30:06 EDT

    Nice, but why not simple do it like this:

    table([[1,”Come together”],[2,”Something”],…etc…]).

    and the definition of table/1;

    table(Rows) ->
        {table, [],   td_rows(Rows)}.
     
    table(Header, Rows) ->
        table(Header, Rows, []).
     
    table([], Rows, Attrs) ->
        {table, lkup(table, Attrs, []),
         [td_rows(Rows)]};
    table(Header, Rows, Attrs) ->
        {table, lkup(table, Attrs, []),
         [{tr, [], map(fun(C) -> {th, [], [C]} end, Header)},
          td_rows(Rows)]}.
     
    td_rows(Rows) ->
         lists:map(fun(R) ->
                           {tr, [],
                            map(fun(C) -> {td, [], [C]} end, R)}
                   end, Rows).

    Note: you even get in headers and attributes in the above…

    Cheers!

  2. Yariv Tue, 17 Oct 2006 17:36:52 EDT

    I think the two approaches are complementary. When your content is largely programatically generated, it’s easier to work with functions. When you have a lot of static binary text and you want to embed some dynamic expressions inside it, it may be more readable to use a ErlTL template. I think in this example, a hybrid approach could work well:

    <code>&amp;lt;html&amp;gt;<br />
    &amp;lt;title&amp;gt;&#46;..&amp;lt;/title&amp;gt;<br />
    &amp;lt;body&amp;gt;&#46;..&amp;lt;% table(Data) %&amp;gt;&amp;lt;/body&amp;gt;<br />
    &amp;lt;/html&amp;gt;</code>

    In the end, it’s about readability. In addition, ErlTL helps newbies use binaries by default in places where they may mistakenly use strings.

  3. Yariv Tue, 17 Oct 2006 17:42:19 EDT

    (Another advantage to ErlTL is that with ErlTL, Yaws doesn’t have to spend time expanding the ehtml structure.)

  4. Dmitrii Dimandt aka Mamut Wed, 18 Oct 2006 01:31:05 EDT

    What about non-ASCII :)

    Will ErlT be able to compile templates with, say, Unicode data in them?

    The usual amount of drooling goes here :))

  5. Yariv Wed, 18 Oct 2006 13:07:12 EDT

    Hi Dmitirii, I think ErlTL will work at least with UTF8 encoding, but I haven’t tried it myself. Would you like to give it a test drive and tell me what happens. If there are problems, I may be able to find a solution to dealing with different encoding schemes.

  6. taide Mon, 23 Oct 2006 18:23:36 EDT

    Check this for inspiration, it’s very good:
    http://www.kieranholland.com/code/documentation/nevow-stan/

  7. taide Mon, 23 Oct 2006 18:23:45 EDT

    Check this for inspiration, it’s very good:
    http://www.kieranholland.com/code/documentation/nevow-stan/

  8. Dru Nelson Fri, 12 Jan 2007 02:31:29 EST

    just saw this on Reddit, I think this is a great tool for Erlang!

  9. Daniel Kwiecinski Fri, 16 Mar 2007 10:25:41 EDT

    If the *.et files contains top level expresions not in the first line (as in album.et) and are saved with Windows style line ending (CR+LF) instead of Unix one (LF), during the compilation I got the following error:

    {error,{misplaced_top_exprs,{{line,2},
    {chunk,” {Title, Artist, Songs} = Data %>\r”},
    {msg,”top expressions must appear before all other expressions in a function”}}}}

    Cheers,
    Daniel

Comments