{"id":22235310,"url":"https://github.com/williamthome/eel","last_synced_at":"2025-07-27T21:32:42.843Z","repository":{"id":59509939,"uuid":"485120444","full_name":"williamthome/eel","owner":"williamthome","description":"Embedded Erlang (EEl)","archived":false,"fork":false,"pushed_at":"2024-02-21T06:06:31.000Z","size":880,"stargazers_count":30,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-05-01T17:21:41.594Z","etag":null,"topics":["compiler","eel","embedded-erlang","erlang","template-engine"],"latest_commit_sha":null,"homepage":"","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/williamthome.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null},"funding":{"github":["williamthome"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":["https://www.buymeacoffee.com/williamthome"]}},"created_at":"2022-04-24T19:27:18.000Z","updated_at":"2024-04-18T03:32:12.000Z","dependencies_parsed_at":"2024-02-18T17:37:22.199Z","dependency_job_id":null,"html_url":"https://github.com/williamthome/eel","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/williamthome%2Feel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/williamthome%2Feel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/williamthome%2Feel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/williamthome%2Feel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/williamthome","download_url":"https://codeload.github.com/williamthome/eel/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227836979,"owners_count":17827066,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compiler","eel","embedded-erlang","erlang","template-engine"],"created_at":"2024-12-03T02:12:47.045Z","updated_at":"2024-12-03T02:12:47.733Z","avatar_url":"https://github.com/williamthome.png","language":"Erlang","funding_links":["https://github.com/sponsors/williamthome","https://www.buymeacoffee.com/williamthome"],"categories":[],"sub_categories":[],"readme":"# EEl\n\nMuch like Elixir has EEx, Erlang has EEl, or Embedded Erlang. With EEl we can embed and evaluate Erlang inside strings.\n\n## API\n\nThe Erlang code it's written between section punctuations called `markers`.\nIn a nutshell, an IO data or a file can be evaluated\n\n```erlang\n1\u003e eel:eval(\u003c\u003c\"Hello, \u003c%= Name .%\u003e!\"\u003e\u003e, #{'Name' =\u003e \u003c\u003c\"World\"\u003e\u003e}).\n[\"Hello, \",\u003c\u003c\"World\"\u003e\u003e,\"!\"]\n```\n\na file compiled to a module\n\n```erlang\n1\u003e eel:to_module(\u003c\u003c\"Hello, \u003c%= Name .%\u003e!\"\u003e\u003e, foo).\n{ok,foo}\n2\u003e foo:eval(#{'Name' =\u003e \u003c\u003c\"World\"\u003e\u003e}).\n[\"Hello, \",\u003c\u003c\"World\"\u003e\u003e,\"!\"]\n```\n\nor a set of functions used in a module, for example, given this module\n\n```erlang\n-module(foo).\n\n-export([render/1, render/2]).\n\n% Including the header will transform the `eel:compile` to AST\n% by evaluating it in te compile time, boosting the performance.\n-include(\"eel.hrl\").\n\nrender(Bindings) -\u003e\n    {ok, Snapshot} = eel:compile(\u003c\u003c\n        \"\u003chtml\u003e\"\n        \"\u003chead\u003e\"\n            \"\u003ctitle\u003e\u003c%= Title .%\u003e\u003c/title\u003e\"\n        \"\u003c/head\u003e\"\n        \"\u003cbody\u003e\"\n            \"Hello, \u003c%= Name .%\u003e!\"\n        \"\u003c/body\u003e\"\n        \"\u003c/html\u003e\"\n    \u003e\u003e),\n    render(Bindings, Snapshot).\n\nrender(Bindings, Snapshot) -\u003e\n    {ok, RenderSnapshot} = eel_renderer:render(Bindings, Snapshot),\n    {eel_evaluator:eval(RenderSnapshot), RenderSnapshot}.\n```\n\nnow run\n\n```shell\nrebar3 shell\n```\n\nand type this in the Erlang shell\n\n```erlang\n1\u003e {_, Snapshot} = foo:render(#{'Title' =\u003e \u003c\u003c\"Hey!\"\u003e\u003e, 'Name' =\u003e \u003c\u003c\"World\"\u003e\u003e}).\n{[\"\u003chtml\u003e\u003chead\u003e\u003ctitle\u003e\",\u003c\u003c\"Hey!\"\u003e\u003e,\n  \"\u003c/title\u003e\u003c/head\u003e\u003cbody\u003eHello, \",\u003c\u003c\"World\"\u003e\u003e,\n  \"!\u003c/body\u003e\u003c/html\u003e\"],\n {snapshot,[{1,{{1,1},\"\u003chtml\u003e\u003chead\u003e\u003ctitle\u003e\"}},\n            {3,{{1,33},\"\u003c/title\u003e\u003c/head\u003e\u003cbody\u003eHello, \"}},\n            {5,{{1,73},\"!\u003c/body\u003e\u003c/html\u003e\"}}],\n           [{2,{{1,20},\u003c\u003c\"Hey!\"\u003e\u003e}},{4,{{1,61},\u003c\u003c\"World\"\u003e\u003e}}],\n           [{2,\n             {{1,20},\n              [{call,1,\n                     {remote,1,{atom,1,eel_converter},{atom,1,to_string}},\n                     [{'fun',1,{clauses,[{clause,1,[],[],[{...}]}]}}]}]}},\n            {4,\n             {{1,61},\n              [{call,1,\n                     {remote,1,{atom,1,eel_converter},{atom,1,to_string}},\n                     [{'fun',1,{clauses,[{clause,1,[],[],[...]}]}}]}]}}],\n           #{'Name' =\u003e \u003c\u003c\"World\"\u003e\u003e,'Title' =\u003e \u003c\u003c\"Hey!\"\u003e\u003e},\n           [{2,['Title']},{4,['Name']}],\n           [{2,\u003c\u003c\"Hey!\"\u003e\u003e},{4,\u003c\u003c\"World\"\u003e\u003e}]}}\n2\u003e {IoData, _} = foo:render(#{'Name' =\u003e \u003c\u003c\"Erlang\"\u003e\u003e}, Snapshot).\n{[\"\u003chtml\u003e\u003chead\u003e\u003ctitle\u003e\",\u003c\u003c\"Hey!\"\u003e\u003e,\n  \"\u003c/title\u003e\u003c/head\u003e\u003cbody\u003eHello, \",\u003c\u003c\"Erlang\"\u003e\u003e,\n  \"!\u003c/body\u003e\u003c/html\u003e\"],\n {snapshot,[{1,{{1,1},\"\u003chtml\u003e\u003chead\u003e\u003ctitle\u003e\"}},\n            {3,{{1,33},\"\u003c/title\u003e\u003c/head\u003e\u003cbody\u003eHello, \"}},\n            {5,{{1,73},\"!\u003c/body\u003e\u003c/html\u003e\"}}],\n           [{2,{{1,20},\u003c\u003c\"Hey!\"\u003e\u003e}},{4,{{1,61},\u003c\u003c\"Erlang\"\u003e\u003e}}],\n           [{2,\n             {{1,20},\n              [{call,1,\n                     {remote,1,{atom,1,eel_converter},{atom,1,to_string}},\n                     [{'fun',1,{clauses,[{clause,1,[],[],[{...}]}]}}]}]}},\n            {4,\n             {{1,61},\n              [{call,1,\n                     {remote,1,{atom,1,eel_converter},{atom,1,to_string}},\n                     [{'fun',1,{clauses,[{clause,1,[],[],[...]}]}}]}]}}],\n           #{'Name' =\u003e \u003c\u003c\"Erlang\"\u003e\u003e,'Title' =\u003e \u003c\u003c\"Hey!\"\u003e\u003e},\n           [{2,['Title']},{4,['Name']}],\n           [{4,\u003c\u003c\"Erlang\"\u003e\u003e}]}}\n3\u003e erlang:iolist_to_binary(IoData).\n\u003c\u003c\"\u003chtml\u003e\u003chead\u003e\u003ctitle\u003eHey!\u003c/title\u003e\u003c/head\u003e\u003cbody\u003eHello, Erlang!\u003c/body\u003e\u003c/html\u003e\"\u003e\u003e\n```\n\nLooking at the pattern matched results, the first tuple element contains the evaluated value\n```erlang\n[\"\u003chtml\u003e\u003chead\u003e\u003ctitle\u003e\",\u003c\u003c\"Hey!\"\u003e\u003e,\n \"\u003c/title\u003e\u003c/head\u003e\u003cbody\u003eHello, \",\u003c\u003c\"World\"\u003e\u003e,\n \"!\u003c/body\u003e\u003c/html\u003e\"],\n```\nand the second a metadata called `snapshot`\n```erlang\n{snapshot,[{1,{{1,1},\"\u003chtml\u003e\u003chead\u003e\u003ctitle\u003e\"}},\n            {3,{{1,33},\"\u003c/title\u003e\u003c/head\u003e\u003cbody\u003eHello, \"}},\n            {5,{{1,73},\"!\u003c/body\u003e\u003c/html\u003e\"}}],\n           [{2,{{1,20},\u003c\u003c\"Hey!\"\u003e\u003e}},{4,{{1,61},\u003c\u003c\"World\"\u003e\u003e}}],\n           [{2,\n             {{1,20},\n              [{call,1,\n                     {remote,1,{atom,1,eel_converter},{atom,1,to_string}},\n                     [{'fun',1,{clauses,[{clause,1,[],[],[{...}]}]}}]}]}},\n            {4,\n             {{1,61},\n              [{call,1,\n                     {remote,1,{atom,1,eel_converter},{atom,1,to_string}},\n                     [{'fun',1,{clauses,[{clause,1,[],[],[...]}]}}]}]}}],\n           #{'Name' =\u003e \u003c\u003c\"World\"\u003e\u003e,'Title' =\u003e \u003c\u003c\"Hey!\"\u003e\u003e},\n           [{2,['Title']},{4,['Name']}],\n           [{2,\u003c\u003c\"Hey!\"\u003e\u003e},{4,\u003c\u003c\"World\"\u003e\u003e}]}\n```\n\nThe line `1` will evaluate the bindings `Title` and `Name`, but the line `2`\nwill only eval the `Name` variable, because it uses the snapshot of the previous\nrender. It only eval the changes and does not need to compile the binary again, unless the expression contains the global `Bindings` variable (see below),\nbecause the `snapshot` includes the required information.\n\nThe 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.:\n\n```\n\u003c%= maps:get('Foo', Bindings, bar) .%\u003e\n```\n\nThe `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:\n\n```erlang\n#{'Foo' =\u003e \u003c\u003c\"foo\"\u003e\u003e, 'FooBar' =\u003e bar}\n```\n\nor the same in lower case when passing the option #{snake_case =\u003e true} to the render function, e.g.:\n\n```erlang\neel_renderer:render(Bindings, Snapshot, #{snake_case =\u003e true})\n```\n\nPassing the snake_case option the `Bindings` above you must write the keys as\n\n```erlang\n#{foo =\u003e \u003c\u003c\"foo\"\u003e\u003e, foo_bar =\u003e bar}\n```\n\nIncluding `Bindings` to the expression makes it to be always evaluated by the render function.\n\n## Engine\n\nThe default engine is the `eel_smart_engine`.\\\nYou can implement your own engine using the behavior `eel_engine`.\n\n### eel_smart_engine\n\n#### Markers\n\nThe `eel_smart_engine` markers are:\n- `\u003c%=` starts an expression;\n- `.%\u003e` indicates that the expression ends;\n- `%\u003e`  indicates that the expression continues;\n- `\u003c%`  continues the last expression if it ends with `%\u003e`;\n- `\u003c%%` starts a comment;\n- `%%\u003e` ends a comment.\n\n## Template\n\nThe template should have the `.eel` extension and a structure like this:\n\n![Template](images/template.png)\n\n## Highlight\n\nIf you use VSCode, you can get highlighting by installing the [Embedded Erlang (EEl)](https://github.com/williamthome/vscode_eel) extension.\n\n## Next steps\n\n- Explain how this lib works\n- Better explanation about the API\n- Improve the code\n- Functions documentations\n- Functions specs\n- Test everything\n\n## Sponsors\n\nIf you like this tool, please consider [sponsoring me](https://github.com/sponsors/williamthome).\\\nI'm thankful for your never-ending support :heart:\n\nI also accept coffees :coffee:\n\n[![\"Buy Me A Coffee\"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/williamthome)\n\n## Contributing\n\n### Issues\n\nFeels free to [submit an issue on Github](https://github.com/williamthome/eel/issues/new).\n\n### Installation\n\n```shell\n# Clone this repo\ngit clone git@github.com:williamthome/eel.git\n\n# Navigate to the project root\ncd eel\n\n# Compile (ensure you have rebar3 installed)\nrebar3 compile\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwilliamthome%2Feel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwilliamthome%2Feel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwilliamthome%2Feel/lists"}