[erlang-questions] [ANN] Brim - HTML templating library

Michael Turner michael.eugene.turner@REDACTED
Thu Mar 15 13:17:01 CET 2012


I think this sort of thing will always be ugly, no matter how you do
it. My suggestion is to hide the ugliness down in a smart algorithm
that figures out some of the more obvious things, and requires
relatively unobtrusive hints for the subtler and more error-prone
transformations.

The idea of a pure-vanilla-HTML template is really just one half of an
idea that used to be called "Programming by Example." )PBE) You
provide sample input and corresponding sample output. Then some
magical thingie infers what you (probably) want to do. It figures out
some rules or code or both, to make appropriate output happen more
routinely, for other input of the same form.

Dan Halbert pioneered PBE at Xerox PARC. It was later taken up as a
research project at at Apple. Then it was championed by none other
than Melinda Gates, at Microsoft.

It never took off.

Not for lack of trying: we hapless Windows users got Microsoft Bob,
then "Clippy," then that annoying dolphin swimming around on our
desktops. In each case, I think the idea came to grief because of Dan
Halbert's utopian ambition: to make Programming by Example
"programming for the masses" -- no code skills required.

But what if code skills *were* required?

Start simple: here's your HTML target.

  <html><head><title>The title</title></head><body>The body</body></html>

Here's an Erlang input term:

  { <<"The title">>, <<"The body">> }

How would you write code that took those two as input and figured out
how to convert all terms of that form to HTML like that target?

The matching problem might not be so hard for simple, constant-length
data structures. But more often, on the Web, we want to generate
content dynamically, not just hand a static data structure to a
translator. This is where PBE went astray: by making it an active
agent snooping in on the GUI, one that watches a user, and tries to
guess the next iteration of what seems to be a loop. Result: one
annoyed user. (In the case of programmers, one *really* annoyed user.)

What would a programmer prefer? Perhaps some sort of parse transform
that sees hints in the generator code about beginnings and endings,
and substitutes calls that generate part of the appropriate HTML. Or
maybe the API for the input-output matching algorithm might have
"hint" calls that you could sprinkle into your code until algorithm
"got it."

Anyway, I'm just hinting at a possible approach. I'm a little
concerned, because at this point it looks like you're starting to
implement a whole little language, with code passed in string
parameters  (e.g., "tr:not([id^=ker])". Erlang offers so much power as
a language itself, it seems there really should be some way to
leverage it, so that the user is required to learn only a small Erlang
API, not a whole embedded mini-language.

-michael turner



On Thu, Mar 15, 2012 at 7:40 PM, Per Melin <per.melin@REDACTED> wrote:
> Brim is inspired by Enlive for Clojure, meaning that the HTML templates are completely free of the embedded code, annotations, special attributes, etc that we usually see. It also uses CSS selectors to reference the parts of the HTML we want to transform with Erlang.
>
> https://github.com/permelin/brim
>
> It is version 0.0.1 and while usable it's too early to be very useful yet. I'm announcing it anyway because I really want input on the API, which I am struggling with.
>
> An example (since there is no documentation): A web page that shows a table with the applications running on our Erlang node. Here is the template, filled with some dummy text so that we can test what it will look like in a browser.
>
> <table id="apps">
>  <caption>node name will go here</caption>
>  <tbody>
>    <tr>
>      <td>application</td>
>      <td>yada yada yada</td>
>      <td>version</td>
>    </tr>
>  </tbody>
> </table>
>
>
> Then the code that will transform the above HTML and fill it with data.
>
> -module(running_apps).
>
> -export([html/0]).
>
> html() ->
>    % Read the template
>    Doc0 = brim:document("priv/apps.html"),
>
>    % Set the caption to the output from node()
>    Doc1 = brim:content(Doc0, "#apps > caption", node()),
>
>    % Fill the table tbody -- see rows/1 below
>    Doc2 = brim:content(Doc1, "#apps > tbody", rows(Doc1)),
>
>    % And to really show off the CSS selector support we'll add the
>    % class "pointless" on all <tr> whose id does not start with "ker"
>    Doc3 = brim:add_class(Doc2, "tr:not([id^=ker])", "pointless"),
>
>    % Print to screen -- use brim:render/1 instead to get io list
>    brim:print(Doc3).
>
> rows(Doc) ->
>    % Create one clone of the <tr> for each item returned from
>    % application:which_application() and transform them as specified
>    % by the fun.
>    brim:map(
>        Doc, "#apps tr", application:which_applications(),
>        fun(TR0, {App, Description, Version}) ->
>            TR1 = brim:content(TR0, "td:first-child", App),
>            TR2 = brim:content(TR1, "td:nth-child(2)", Description),
>            TR3 = brim:content(TR2, "td:last-child", Version),
>            % And finally set the id of each <tr> to the name of the
>            % corresponding application, for no good reason
>            brim:id(TR3, "tr", atom_to_list(App))
>        end).
>
>
> And here is the output (formatted for readability):
>
> <table id="apps">
>  <caption>nonode@REDACTED</caption>
>  <tbody>
>    <tr id="inets" class="pointless">
>      <td>inets</td>
>      <td>INETS  CXC 138 49</td>
>      <td>5.7.1</td>
>    </tr>
>    <tr id="stdlib" class="pointless">
>      <td>stdlib</td>
>      <td>ERTS  CXC 138 10</td>
>      <td>1.17.5</td>
>    </tr>
>    <tr id="kernel">
>      <td>kernel</td>
>      <td>ERTS  CXC 138 10</td>
>      <td>2.14.5</td>
>    </tr>
>  </tbody>
> </table>
>
>
> As you can see, there is a lot of explicit state threading (Doc0, Doc1, Doc2, Doc3, TR0, TR1, TR2, TR3) which I fear can get annoying when dealing with larger templates. I'm trying out different ways to minimize that (and verbosity in general). The most obvious (to me) is to move the state argument to last position to facilitate chaining.
>
> html() ->
>    Doc = brim:document("priv/apps.html"),
>    brim:print(
>      brim:add_class("tr:not([id^=ker])", "pointless",
>        brim:content("#apps > caption", node(),
>          brim:content("#apps > tbody", app_rows(Doc), Doc)))).
>
> Another alternative below. But I think it has too many limitations to be feasible.
>
> html() ->
>    Doc = brim:document("priv/apps.html"),
>    brim:do(Doc,
>        [brim:content("#apps > caption", node()),
>         brim:content("#apps > tbody", app_rows(Doc)),
>         brim:add_class("tr:not([id^=ker])", "pointless"),
>         brim:print()]).
>
> As I said, all input is appreciated.
>
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://erlang.org/mailman/listinfo/erlang-questions



More information about the erlang-questions mailing list