Skip to content

williamthome/eel

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EEl

Much like Elixir has EEx, Erlang has EEl, or Embedded Erlang. With EEl we can embed and evaluate Erlang inside strings.

API

The Erlang code it's written between section punctuations called markers. In a nutshell, an IO data or a file can be evaluated

1> eel:eval(<<"Hello, <%= Name .%>!">>, #{'Name' => <<"World">>}).
["Hello, ",<<"World">>,"!"]

a file compiled to a module

1> eel:to_module(<<"Hello, <%= Name .%>!">>, foo).
{ok,foo}
2> foo:eval(#{'Name' => <<"World">>}).
["Hello, ",<<"World">>,"!"]

or a set of functions used in a module, for example, given this module

-module(foo).

-export([render/1, render/2]).

% Including the header will transform the `eel:compile` to AST
% by evaluating it in te compile time, boosting the performance.
-include("eel.hrl").

render(Bindings) ->
    {ok, Snapshot} = eel:compile(<<
        "<html>"
        "<head>"
            "<title><%= Title .%></title>"
        "</head>"
        "<body>"
            "Hello, <%= Name .%>!"
        "</body>"
        "</html>"
    >>),
    render(Bindings, Snapshot).

render(Bindings, Snapshot) ->
    {ok, RenderSnapshot} = eel_renderer:render(Bindings, Snapshot),
    {eel_evaluator:eval(RenderSnapshot), RenderSnapshot}.

now run

rebar3 shell

and type this in the Erlang shell

1> {_, Snapshot} = foo:render(#{'Title' => <<"Hey!">>, 'Name' => <<"World">>}).
{["<html><head><title>",<<"Hey!">>,
  "</title></head><body>Hello, ",<<"World">>,
  "!</body></html>"],
 {snapshot,[{1,{{1,1},"<html><head><title>"}},
            {3,{{1,33},"</title></head><body>Hello, "}},
            {5,{{1,73},"!</body></html>"}}],
           [{2,{{1,20},<<"Hey!">>}},{4,{{1,61},<<"World">>}}],
           [{2,
             {{1,20},
              [{call,1,
                     {remote,1,{atom,1,eel_converter},{atom,1,to_string}},
                     [{'fun',1,{clauses,[{clause,1,[],[],[{...}]}]}}]}]}},
            {4,
             {{1,61},
              [{call,1,
                     {remote,1,{atom,1,eel_converter},{atom,1,to_string}},
                     [{'fun',1,{clauses,[{clause,1,[],[],[...]}]}}]}]}}],
           #{'Name' => <<"World">>,'Title' => <<"Hey!">>},
           [{2,['Title']},{4,['Name']}],
           [{2,<<"Hey!">>},{4,<<"World">>}]}}
2> {IoData, _} = foo:render(#{'Name' => <<"Erlang">>}, Snapshot).
{["<html><head><title>",<<"Hey!">>,
  "</title></head><body>Hello, ",<<"Erlang">>,
  "!</body></html>"],
 {snapshot,[{1,{{1,1},"<html><head><title>"}},
            {3,{{1,33},"</title></head><body>Hello, "}},
            {5,{{1,73},"!</body></html>"}}],
           [{2,{{1,20},<<"Hey!">>}},{4,{{1,61},<<"Erlang">>}}],
           [{2,
             {{1,20},
              [{call,1,
                     {remote,1,{atom,1,eel_converter},{atom,1,to_string}},
                     [{'fun',1,{clauses,[{clause,1,[],[],[{...}]}]}}]}]}},
            {4,
             {{1,61},
              [{call,1,
                     {remote,1,{atom,1,eel_converter},{atom,1,to_string}},
                     [{'fun',1,{clauses,[{clause,1,[],[],[...]}]}}]}]}}],
           #{'Name' => <<"Erlang">>,'Title' => <<"Hey!">>},
           [{2,['Title']},{4,['Name']}],
           [{4,<<"Erlang">>}]}}
3> erlang:iolist_to_binary(IoData).
<<"<html><head><title>Hey!</title></head><body>Hello, Erlang!</body></html>">>

Looking at the pattern matched results, the first tuple element contains the evaluated value

["<html><head><title>",<<"Hey!">>,
 "</title></head><body>Hello, ",<<"World">>,
 "!</body></html>"],

and the second a metadata called snapshot

{snapshot,[{1,{{1,1},"<html><head><title>"}},
            {3,{{1,33},"</title></head><body>Hello, "}},
            {5,{{1,73},"!</body></html>"}}],
           [{2,{{1,20},<<"Hey!">>}},{4,{{1,61},<<"World">>}}],
           [{2,
             {{1,20},
              [{call,1,
                     {remote,1,{atom,1,eel_converter},{atom,1,to_string}},
                     [{'fun',1,{clauses,[{clause,1,[],[],[{...}]}]}}]}]}},
            {4,
             {{1,61},
              [{call,1,
                     {remote,1,{atom,1,eel_converter},{atom,1,to_string}},
                     [{'fun',1,{clauses,[{clause,1,[],[],[...]}]}}]}]}}],
           #{'Name' => <<"World">>,'Title' => <<"Hey!">>},
           [{2,['Title']},{4,['Name']}],
           [{2,<<"Hey!">>},{4,<<"World">>}]}

The line 1 will evaluate the bindings Title and Name, but the line 2 will only eval the Name variable, because it uses the snapshot of the previous render. It only eval the changes and does not need to compile the binary again, unless the expression contains the global Bindings variable (see below), because the snapshot includes the required information.

The var Bindings is a reserved one and can be used to get values in a conditional way, checking if the variable exists in the template, e.g.:

<%= maps:get('Foo', Bindings, bar) .%>

The Bindings should contains the unbound/required variables of the template. The syntax it's a map with keys as atoms starting with upper case, e.g:

#{'Foo' => <<"foo">>, 'FooBar' => bar}

or the same in lower case when passing the option #{snake_case => true} to the render function, e.g.:

eel_renderer:render(Bindings, Snapshot, #{snake_case => true})

Passing the snake_case option the Bindings above you must write the keys as

#{foo => <<"foo">>, foo_bar => bar}

Including Bindings to the expression makes it to be always evaluated by the render function.

Engine

The default engine is the eel_smart_engine.
You can implement your own engine using the behavior eel_engine.

eel_smart_engine

Markers

The eel_smart_engine markers are:

  • <%= starts an expression;
  • .%> indicates that the expression ends;
  • %> indicates that the expression continues;
  • <% continues the last expression if it ends with %>;
  • <%% starts a comment;
  • %%> ends a comment.

Template

The template should have the .eel extension and a structure like this:

Template

Highlight

If you use VSCode, you can get highlighting by installing the Embedded Erlang (EEl) extension.

Next steps

  • Explain how this lib works
  • Better explanation about the API
  • Improve the code
  • Functions documentations
  • Functions specs
  • Test everything

Sponsors

If you like this tool, please consider sponsoring me.
I'm thankful for your never-ending support ❤️

I also accept coffees ☕

"Buy Me A Coffee"

Contributing

Issues

Feels free to submit an issue on Github.

Installation

# Clone this repo
git clone git@github.com:williamthome/eel.git

# Navigate to the project root
cd eel

# Compile (ensure you have rebar3 installed)
rebar3 compile