{"id":13509133,"url":"https://github.com/zambal/eml","last_synced_at":"2026-02-18T09:41:58.861Z","repository":{"id":12953406,"uuid":"15631593","full_name":"zambal/eml","owner":"zambal","description":"Library for writing and manipulating (html) markup in Elixir.","archived":false,"fork":false,"pushed_at":"2024-03-01T16:27:30.000Z","size":315,"stargazers_count":114,"open_issues_count":4,"forks_count":12,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-10-21T18:52:41.196Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zambal.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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,"publiccode":null,"codemeta":null}},"created_at":"2014-01-04T12:22:40.000Z","updated_at":"2025-07-07T22:36:48.000Z","dependencies_parsed_at":"2024-05-01T17:21:14.122Z","dependency_job_id":"d3c27ec8-45ef-43b8-8ed9-0903365dfaac","html_url":"https://github.com/zambal/eml","commit_stats":{"total_commits":183,"total_committers":6,"mean_commits":30.5,"dds":"0.47540983606557374","last_synced_commit":"932f10f2ecf29c02385c065515ac7012054ba78b"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zambal/eml","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zambal%2Feml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zambal%2Feml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zambal%2Feml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zambal%2Feml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zambal","download_url":"https://codeload.github.com/zambal/eml/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zambal%2Feml/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29575104,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T08:38:15.585Z","status":"ssl_error","status_checked_at":"2026-02-18T08:38:14.917Z","response_time":162,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2024-08-01T02:01:03.405Z","updated_at":"2026-02-18T09:41:53.851Z","avatar_url":"https://github.com/zambal.png","language":"Elixir","funding_links":[],"categories":["Templating"],"sub_categories":[],"readme":"[![Build Status](https://api.travis-ci.org/zambal/eml.svg?branch=master)](https://travis-ci.org/zambal/eml)\n\n# Eml\n\n## Markup for developers\n\n### What is it?\nEml makes markup a first class citizen in Elixir. It provides a flexible and\nmodular toolkit for generating, parsing and manipulating markup. It's main focus\nis html, but other markup languages could be implemented as well.\n\nTo start off:\n\nThis piece of code\n```elixir\nuse Eml.HTML\n\nname = \"Vincent\"\nage  = 36\n\ndiv class: \"person\" do\n  div do\n    span \"name: \"\n    span name\n  end\n  div do\n    span \"age: \"\n    span age\n  end\nend |\u003e Eml.compile\n```\n\nproduces\n```html\n\u003cdiv class='person'\u003e\n  \u003cdiv\u003e\n    \u003cspan\u003ename: \u003c/span\u003e\n    \u003cspan\u003eVincent\u003c/span\u003e\n  \u003c/div\u003e\n  \u003cdiv\u003e\n    \u003cspan\u003eage: \u003c/span\u003e\n    \u003cspan\u003e36\u003c/span\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n### Why?\nMost templating libraries are build around the idea of interpreting strings that\ncan contain embeded code. This code is mostly used for implementing view logic\nin the template. You could say that these libraries are making code a first\nclass citizen in template strings. As long as the view logic is simple this\nworks pretty well, but with more complex views this can become quite messy. Eml\ntakes this idea inside out and makes the markup that you normally would write as\na string a first class citizen of the programming language, allowing you to\ncompose and organize markup and view logic with all the power of Elixir.\n\nPlease read on for a walkthrough that tries to cover most of Eml's features.\n\n\n### Walkthrough\n\n- [Intro](#intro)\n- [Compiling](#compiling)\n- [Parsing](#parsing)\n- [Compiling and templates](#compiling-and-templates)\n- [Components and fragments](#components-and-fragments)\n- [Unpacking](#unpacking)\n- [Querying eml](#querying-eml)\n- [Transforming eml](#transforming-eml)\n- [Encoding data in Eml](#encoding-data-in-eml)\n- [Notes](#notes)\n\n#### Intro\n\n```elixir\niex\u003e use Eml\nnil\niex\u003e use Eml.HTML\nnil\n```\n\nBy invoking `use Eml`, some macro's are imported into the current scope and core\nAPI modules are aliased. `use Eml.HTML` imports all generated html element\nmacros from `Eml.HTML` into the current scope. The element macros just translate\nto a call to `%Eml.Element{...}`, a struct that is the actual representation of\nelements, so they can even be used inside a match.\n```elixir\niex\u003e div 42\n#div\u003c42\u003e\n```\nHere we created a `div` element with `42` as it contents.\n\nThe element macro's in Eml try to be clever about the type of arguments that get\npassed. For example, if the first argument is a Keyword list, it will be\ninterpreted as attributes, otherwise as content.\n```elixir\niex\u003e div id: \"some-id\"\n#div\u003c%{id: \"some-id\"}\u003e\n\niex\u003e div \"some content\"\n#div\u003c\"some content\"\u003e\n\niex\u003e div do\n...\u003e   \"some content\"\n...\u003e end\n#div\u003c[\"some content\"]\u003e\n\niex\u003e div [id: \"some-id\"], \"some content\"\n#div\u003c%{id: \"some-id\"} \"some content\"\u003e\n\niex\u003e div id: \"some-id\" do\n...\u003e   \"some content\"\n...\u003e end\n#div\u003c%{id: \"some-id\"} [\"some content\"]\u003e\n\niex\u003e %Element{tag: :div, attrs: %{id: \"some-id\"}, content: \"some content\"}\n#div\u003c%{id: \"some-id\"} \"some content\"\u003e\n```\n\nNote that attributes are stored internally as a map.\n\n\n#### Compiling\n\nContents can be compiled to a string by calling `Eml.compile`. Eml automatically\ninserts a doctype declaration when the html element is the root.\n```elixir\niex\u003e html(body(div(42))) |\u003e Eml.compile\n\"\u003c!doctype html\u003e\\n\u003chtml\u003e\u003cbody\u003e\u003cdiv\u003e42\u003c/div\u003e\u003c/body\u003e\\n\u003c/html\u003e\"\n\niex\u003e \"text \u0026 more\" |\u003e div |\u003e body |\u003e html |\u003e Eml.compile\n\"\u003c!doctype html\u003e\\n\u003chtml\u003e\u003cbody\u003e\u003cdiv\u003etext \u0026amp; more\u003c/div\u003e\u003c/body\u003e\u003c/html\u003e\"\n```\nAs you can see, you can also use Elixir's pipe operator for creating markup.\nHowever, using do blocks, as can be seen in the introductory example, is more\nconvenient most of the time.\n\n#### Parsing\n\nEml's parser by default converts a string with html content into Eml content.\n```elixir\niex\u003e Eml.parse \"\u003c!doctype html\u003e\\n\u003chtml\u003e\u003chead\u003e\u003cmeta charset='UTF-8'\u003e\u003c/head\u003e\u003cbody\u003e\u003cdiv\u003e42\u003c/div\u003e\u003c/body\u003e\u003c/html\u003e\"\n[#html\u003c[#head\u003c[#meta\u003c%{charset: \"UTF-8\"}\u003e]\u003e, #body\u003c[#div\u003c\"42\"\u003e]\u003e]\u003e]\n\niex\u003e Eml.parse \"\u003cdiv class=\\\"content article\\\"\u003e\u003ch1 class='title'\u003eTitle\u003ch1\u003e\u003cp class=\\\"paragraph\\\"\u003eblah \u0026amp; blah\u003c/p\u003e\u003c/div\u003e\"\n[#div\u003c%{class: \"content article\"}\n [#h1\u003c%{class: \"title\"}\n  [\"Title\", #h1\u003c[#p\u003c%{class: \"paragraph\"} \"blah \u0026 blah\"\u003e]\u003e]\u003e]\u003e]\n```\n\nThe html parser is primarily written to parse html compiled by Eml, but it's\nflexible enough to parse most html you throw at it. Most notable missing\nfeatures of the parser are attribute values without quotes and elements that are\nnot properly closed.\n\n\n#### Compiling and templates\n\nCompiling and templates can be used in situations where most content is static\nand performance is critical, since its contents gets precompiled during compiletime.\n\nEml uses the assigns extension from `EEx` for parameterizing templates. See\nthe `EEx` docs for more info about them. The function that the template macro\ndefines accepts optionally any Dict compatible dictionary as argument for\nbinding values to assigns.\n\n```elixir\niex\u003e defmodule MyTemplates1 do\n...\u003e   use Eml\n...\u003e   use Eml.HTML\n...\u003e\n...\u003e   template example do\n...\u003e     div id: \"example\" do\n...\u003e       span @text\n...\u003e     end\n...\u003e   end\n...\u003e end\niex\u003e MyTemplates.example text: \"Example text\"\n{:safe, \"\u003cdiv id='example'\u003e\u003cspan\u003eExample text\u003c/span\u003e\u003c/div\u003e\"}\n```\n\nEml templates provides two ways of executing logic during runtime. By\nproviding assigns handlers to the optional `funs` dictionary, or by calling\nexternal functions during runtime with the `\u0026` operator.\n\n```elixir\niex\u003e defmodule MyTemplates2 do\n...\u003e   use Eml\n...\u003e   use Eml.HTML\n...\u003e\n...\u003e   template assigns_handler,\n...\u003e   text: \u0026String.upcase/1 do\n...\u003e     div id: \"example1\" do\n...\u003e       span @text\n...\u003e     end\n...\u003e   end\n...\u003e\n...\u003e   template external_call do\n...\u003e     body \u0026assigns_handler(text: @example_text)\n...\u003e   end\n...\u003e end\niex\u003e MyTemplates.assigns_handler text: \"Example text\"\n{:safe, \"\u003cdiv id='example'\u003e\u003cspan\u003eEXAMPLE TEXT\u003c/span\u003e\u003c/div\u003e\"}\niex\u003e MyTemplates.exernal_call example_text: \"Example text\"\n{:safe, \"\u003cbody\u003e\u003cdiv id='example'\u003e\u003cspan\u003eEXAMPLE TEXT\u003c/span\u003e\u003c/div\u003e\u003c/body\u003e\"}\n```\n\nTemplates are composable, so they are allowed to call other templates. The\nonly catch is that it's not possible to pass an assign to another template\nduring precompilation. The reason for this is that the logic in a template is\nexecuted the moment the template is called, so if you would pass an assign\nduring precompilation, the logic in a template would receive this assign\ninstead of its result, which is only available during runtime. This all means\nthat when you for example want to pass an assign to a nested template, the\ntemplate should be prefixed with the `\u0026` operator, or in other words, executed\nduring runtime.\n\n```elixir\ntemplate templ1,\nnum: \u0026(\u00261 + \u00261) do\n  div @num\nend\n\ntemplate templ2 do\n h2 @title\n templ1(num: @number) # THIS GENERATES A COMPILE TIME ERROR\n \u0026templ1(num: @number) # THIS IS OK\nend\n```\n\nNote that because the body of a template is evaluated at compile time, it's\nnot possible to call other functions from the same module without using `\u0026`\noperator.\n\nInstead of defining a do block, you can also provide a path to a file with the\n`:file` option.\n\n```elixir\niex\u003e File.write! \"test.eml.exs\", \"div @number\"\niex\u003e defmodule MyTemplates3 do\n...\u003e   use Eml\n...\u003e   use Eml.HTML\n...\u003e\n...\u003e   template from_file, file: \"test.eml.exs\"\n...\u003e end\niex\u003e File.rm! \"test.eml.exs\"\niex\u003e MyTemplates3.from_file number: 42\n{:safe, \"\u003cdiv\u003e42\u003c/div\u003e\"}\n```\n\n#### Components and fragments\n\nEml also provides `component/3` and `fragment/3` macros for defining\ntemplate elements. They behave as normal elements, but they\naditionally contain a template function that gets called with the\nelement's attributes and content as arguments during compiling.\n\n```elixir\niex\u003e use Eml\niex\u003e use Eml.HTML\niex\u003e defmodule ElTest do\n...\u003e\n...\u003e   component my_list,\n...\u003e   __CONTENT__: fn content -\u003e\n...\u003e     for item \u003c- content do\n...\u003e       li do\n...\u003e         span \"* \"\n...\u003e         span item\n...\u003e         span \" *\"\n...\u003e       end\n...\u003e     end\n...\u003e   end do\n...\u003e     ul [class: @class], @__CONTENT__\n...\u003e   end\n...\u003e\n...\u003e end\niex\u003e import ElTest\niex\u003e el = my_list class: \"some-class\" do\n...\u003e   \"Item 1\"\n...\u003e   \"Item 2\"\n...\u003e end\n#my_list\u003c%{class: \"some-class\"} [\"Item 1\", \"Item 2\"]\u003e\niex\u003e Eml.compile(el)\n\"\u003cul class='some-class'\u003e\u003cli\u003e\u003cspan\u003e* \u003c/span\u003e\u003cspan\u003eItem 1\u003c/span\u003e\u003cspan\u003e *\u003c/span\u003e\u003c/li\u003e\u003cli\u003e\u003cspan\u003e* \u003c/span\u003e\u003cspan\u003eItem 2\u003c/span\u003e\u003cspan\u003e *\u003c/span\u003e\u003c/li\u003e\u003c/ul\u003e\"\n```\n\nJust like templates, its body gets precompiled and assigns, assign\nhandlers and function calls prefixed with the `operator` are evaluated\nat runtime. All attributes of the element can be accessed as assigns\nand the element contents is accessable as the assign `@__CONTENT__`.\n\nThe main difference between templates and components is their\ninterface. You can use components like normal elements, even within a\nmatch.\n\nIn addition to components, Eml also provides fragments. The difference\nbetween components and fragments is that fragments are without any\nlogic, so assign handlers or the `\u0026` capture operator are not allowed\nin fragment definitions.\n\nFragments can be used for better composability and performance,\nbecause unlike templates and components, assigns are allowed as\narguments during precompilation for fragments. This is possible\nbecause fragments don't contain any logic.\n\n\n#### Unpacking\n\nIn order to easily access the contents of elements, Eml provides `unpack/1`.\n```elixir\niex\u003e Eml.unpack div 42\n42\n\niex\u003e Eml.unpack div span(42)\n42\n```\n\n\n#### Querying eml\n\n`Eml.Element` implements the Elixir `Enumerable` protocol for traversing a tree of\nnodes. Let's start with creating something to query\n```elixir\niex\u003e e = html do\n...\u003e   head class: \"head\" do\n...\u003e     meta charset: \"UTF-8\"\n...\u003e   end\n...\u003e   body do\n...\u003e     article id: \"main-content\" do\n...\u003e       section class: \"article\" do\n...\u003e         h3 \"Hello world\"\n...\u003e       end\n...\u003e       section class: \"article\" do\n...\u003e         \"TODO\"\n...\u003e       end\n...\u003e     end\n...\u003e   end\n...\u003e end\n#html\u003c[#head\u003c%{class: \"head\"} [#meta\u003c%{charset: \"UTF-8\"}\u003e]\u003e,\n #body\u003c[#article\u003c%{id: \"main-content\"}\n  [#section\u003c%{class: \"article\"} [#h3\u003c\"Hello world\"\u003e]\u003e,\n   #section\u003c%{class: \"article\"} [\"TODO\"]\u003e]\u003e]\u003e]\u003e\n```\nTo get an idea how the tree is traversed, first just print all nodes\n```elixir\niex\u003e Enum.each(e, fn x -\u003e IO.puts(inspect x) end)\n#html\u003c[#head\u003c%{class: \"head\"} [#meta\u003c%{charset: \"UTF-8\"}\u003e]\u003e, #body\u003c[#article\u003c%{id: \"main-content\"} [#section\u003c%{class: \"article\"} [#h3\u003c\"Hello world\"\u003e]\u003e, #section\u003c%{class: \"article\"} [\"TODO\"]\u003e]\u003e]\u003e]\u003e\n#head\u003c%{class: \"head\"} [#meta\u003c%{charset: \"UTF-8\"}\u003e]\u003e\n#meta\u003c%{charset: \"UTF-8\"}\u003e\n#body\u003c[#article\u003c%{id: \"main-content\"} [#section\u003c%{class: \"article\"} [#h3\u003c\"Hello world\"\u003e]\u003e, #section\u003c%{class: \"article\"} [\"TODO\"]\u003e]\u003e]\u003e\n#article\u003c%{id: \"main-content\"} [#section\u003c%{class: \"article\"} [#h3\u003c\"Hello world\"\u003e]\u003e, #section\u003c%{class: \"article\"} [\"TODO\"]\u003e]\u003e\n#section\u003c%{class: \"article\"} [#h3\u003c\"Hello world\"\u003e]\u003e\n#h3\u003c\"Hello world\"\u003e\n\"Hello world\"\n#section\u003c%{class: \"article\"} [\"TODO\"]\u003e\n\"TODO\"\n:ok\n```\n\nAs you can see every node of the tree is passed to `Enum`. Let's continue with\nsome other examples.\n```elixir \niex\u003e Enum.member?(e, \"TODO\") true\n\niex\u003e Enum.filter(e, \u0026Eml.match?(\u00261, tag: :h3))\n[#h3\u003c\"Hello world\"\u003e]\n\niex\u003e Enum.filter(e, Eml.match?(\u00261, attrs: %{class: \"article\"}))\n[#section\u003c%{class: \"article\"} [#h3\u003c\"Hello world\"\u003e]\u003e,\n #section\u003c%{class: \"article\"} [\"TODO\"]\u003e]\n```\n\n\n#### Transforming eml\n\nEml also provides `Eml.transform/2`. `transform` mostly works like\nenumeration. The key difference is that `transform` returns a modified version\nof the eml tree that was passed as an argument, instead of collecting nodes in a\nlist.  `transform` passes any node it encounters to the provided transformation\nfunction. This transformer can return any data or `nil`, in which case the node\nis discarded, so it works a bit like a map and filter function in one pass.\n```elixir\niex\u003e Eml.transform(e, fn\n...\u003e   any(%{class: \"article\"}) = el -\u003e %{el|content: \"#\"}\n...\u003e   node -\u003e node\n...\u003e end)\n#html\u003c[#head\u003c%{class: \"head\"} [#meta\u003c%{charset: \"UTF-8\"}\u003e]\u003e,\n #body\u003c[#article\u003c%{id: \"main-content\"}\n  [#section\u003c%{class: \"article\"} \"#\"\u003e, #section\u003c%{class: \"article\"}\n   \"#\"\u003e]\u003e]\u003e]\u003e\n\niex\u003e Eml.transform(e, fn\n...\u003e   any(%{class: \"article\"}) = el -\u003e %{el|content: \"#\"}\n...\u003e   _ -\u003e nil\n...\u003e end)\nnil\n```\nThe last result may seem unexpected, but the result is `nil` because\n`Eml.transform` first evaluates a parent node, before continuing with its\nchildren. If the parent node gets removed, the children will be removed too and\nwon't get evaluated.\n\n\n#### Encoding data in Eml\n\nIn order to provide conversions from various data types, Eml provides the\n`Eml.Encoder` protocol. It is used internally by Eml's compiler. Eml provides a\nimplementation for strings, numbers, tuples and atoms, but you can provide a\nprotocol implementation for your own types by just implementing a `encode`\nfunction that converts your type to a valid `Eml.Compiler.node_primitive` type.\n\nSome examples using `Eml.encode`\n```elixir\n\niex\u003e Eml.Encoder.encode 1\n\"1\"\n\niex\u003e Eml.Encoder.encode %{div: 42, span: 12}\n** (Protocol.UndefinedError) protocol Eml.Encoder not implemented for %{div: 42, span: 12}\n\niex\u003e defimpl Eml.Encoder, for: Map\n...\u003e   use Eml.HTML\n...\u003e   def encode(data) do\n...\u003e     for { k, v } \u003c- data do\n...\u003e       %Eml.Element{tag: k, content: v}\n...\u003e     end\n...\u003e   end\n...\u003e end\niex\u003e Eml.Encoder.encode %{div: 42, span: 12}\n[#div\u003c42\u003e, #span\u003c12\u003e]\n```\n\n### Notes\n\nThe first thing to note is that this is still a work in progress.\nWhile it should already be pretty stable and has quite a rich API,\nexpect some raw edges here and there.\n\n#### Security\nObviously, as Eml has full access to the Elixir environment,\neml should only be written by developers that already have full access\nto the backend where Eml is used. Besides this, little thought has gone\ninto other potential security issues.\n\n#### Validation\nEml doesn't perform any validation on the produced output.\nYou can add any attribute name to any element and Eml won't\ncomplain, as it has no knowledge of the type of markup that\nis to be generated. If you want to make sure that your eml code\nwill be valid html, compile it to an html file and use this file with any\nexisting html validator. In this sense Eml is the same as hand\nwritten html.\n\n#### HTML Parser\nThe main purpose of the html parser is to parse back generated html\nfrom Eml. It's a custom parser written in about 500 LOC,\nso don't expect it to successfully parse every html in the wild.\n\nMost notably, it doesn't understand attribute values without quotes and arbitrary\nelements without proper closing, like `\u003cdiv\u003e`. An element should always be written\nas `\u003cdiv/\u003e`, or `\u003cdiv\u003e\u003c/div\u003e`. However, explicit exceptions are made for void\nelements that are expected to never have any child elements.\n\nThe bottom line is that whenever the parser fails to parse back generated\nhtml from Eml, it is a bug and please report it. Whenever it fails to\nparse some external html, I'm still interested to hear about it, but I\ncan't guarantee I can or will fix it.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzambal%2Feml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzambal%2Feml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzambal%2Feml/lists"}