Earmark - Elixir's Markdown Converter

And I also want to take the time to thank the many contributors adding value and fixing bugs.

Not having much time for Earmark right now it is just so rewarding to get help from the community.

Check the Kudos for more information :wink:


Just release bugfix release for broken strike througs :wink:

EarmarkParser 1.4.29 2022-10-20

And here is the accompaning release for Earmark

Earmark 1.4.31 2022-10-20

  • Just using EarmarkParser 1.4.29
1 Like

As we all hate compiler warnings

Earmark 1.4.33 2022-11-02

Earmark 1.4.32 2022-10-31

  • catching some bad options values earlier (line and footnote_offset)
1 Like

Thanks for Earmark! Happy user :slight_smile:

In case anyone else has a need to walk and modify the AST (and if I’m reading the changelog so far correctly, that is not yet a built-in part of Earmark), I wrote a function for that which is public domain. Written for Earmark 1.4.19 and tested only with that version so far:

  @doc """
  Walks an AST and allows you to process it (storing details in acc) and/or
  modify it as it is walked.

  The process_item_fn function is required. It takes two parameters, the
  single item to process (which will either be a string or a 4-tuple) and
  the accumulator, and returns a tuple {processed_item, updated_acc}.
  Returning the empty list for processed_item will remove the item processed
  the AST.

  The process_list_fn function is optional and defaults to no modification of
  items or accumulator. It takes two parameters, the list of items that
  are the sub-items of a given element in the AST (or the top-level list of
  items), and the accumulator, and returns a tuple
  {processed_items_list, updated_acc}.

  This function ends up returning {ast, acc}.
  def walk_and_modify_ast(items, acc, process_item_fn, process_list_fn \\ &({&1, &2}))
  when is_list(items) and is_function(process_item_fn) and is_function(process_list_fn)
    {items, acc} = process_list_fn.(items, acc)
    {ast, acc} = Enum.map_reduce(items, acc, fn (item, acc) ->
      {_item, _acc} = walk_and_modify_ast_item(item, acc, process_item_fn, process_list_fn)
    {List.flatten(ast), acc}

  def walk_and_modify_ast_item(item, acc, process_item_fn, process_list_fn)
  when is_function(process_item_fn) and is_function(process_list_fn) do
    case process_item_fn.(item, acc) do
      {{type, attribs, items, annotations}, acc}
      when is_binary(type) and is_list(attribs) and is_list(items) and is_map(annotations) ->
        {items, acc} = walk_and_modify_ast(items, acc, process_item_fn, process_list_fn)
        {{type, attribs, List.flatten(items), annotations}, acc}
      {item_or_items, acc} when is_binary(item_or_items) or is_list(item_or_items) ->
        {item_or_items, acc}

You would use it something like follows, this example is from my own code where I’m fixing up a non-standard markdown format that uses * and / for strong and italic):

  def parse(some_markdown):
    {:ok, ast, _} = EarmarkParser.as_ast(some_markdown)
    |> handle_bold()
    |> handle_italics()

  # We process the ast to replace "em" with "strong" (because "Bear *boldly* goes
  # where no bear has gone before", and to find matching pairs of word-adjacent
  # slashes and change to italic (because "Bear has a certain /je ne c'est quoi/
  # to it").
  def handle_italics(ast) do
    |> walk_and_modify_ast("", &handle_italics_impl/2)
    |> elem(0)
  def handle_italics_impl(item, "a"), do: {item, ""}
  def handle_italics_impl(item, acc) when is_binary(item) do
    new_item = text_to_ast_list_splitting_regex(
      fn [_, content] ->
        {"em", [], [content], %{}}
    {new_item, acc}
  def handle_italics_impl({name, _, _, _} = item, _acc) do
    # Store the last seen element name so we can skip handling
    # italics within <a> elements.
    {item, name}

  def handle_bold(ast) do
    |> walk_and_modify_ast(0, &handle_bold_impl/2)
    |> elem(0)
  def handle_bold_impl({"em", attribs, items, annotations}, acc) do
    {{"strong", attribs, items, annotations}, acc}
  def handle_bold_impl(item, acc), do: {item, acc}

Thank you I will have a look at it (in the PR if you do not mind) when I find some time

1 Like

I just released a new version of Earmark

this one exposes a quite general AST transformation function allowing for structural modification. This is a contribution from Jói Sigurðsson

No updates on the parser version.

Earmark 1.4.34 2022-11-27


I just released new versions of EarmarkParser and Earmark

EarmarkParser 1.4.30 2023-01-27

Earmark 1.4.35 2023-01-27

Just updating to use EarmarkParser v1.4.30


I just release a new version of

Earmark 1.4.36 2023-02-11

1 Like

I just released a new version of

EarmarkParser 1.4.31 2023-03-03

and the accompagning

Earmark 1.4.37 2023-03-03

updating EarmarkParser to v1.4.31


I just released new versions

Earmark 1.4.38 2023-04-29

EarmarkParser 1.4.32 2023-04-29


4th of July and 14th of July releases

:us: :fr:

A little late annoncement for the last EarmarkParser release, but it only removed warnings in Elixir 1.16 :wink:

EarmarkParser 1.4.33 2023-07-04

PR Avoid warnings in String#slice/2 with Elixir 1.16
Kudos to José Valim

And …

Earmark 1.4.39 2023-07-14

updated EarmarkParser’s version to 1.4.33 to avoid warnings in Elixir 1.16


I just released

EarmarkParser 1.4.35 2023-09-12

  • Better error messages for bad data passed into EarmarkParser.as_ast

EarmarkParser 1.4.34 2023-09-11

and also

Earmark 1.4.40 2023-09-12

  • Exposing all relevant Earmark.Options to the command line

  • Updated EarmarkParser version to 1.4.35


I have just released, what I consider my last release of

Earmark 1.4.41 2023-09-19 Palindrome Edition

Isolate from EarmarkParser in order to prevent conflicts with indirect dependencies
on EarmarkParser via ExDoc.
This shall mark the end of my role as maintainer of Earmark.


Removing the dependency on EarmarkParser allows to

  • let the maintainer of Earmark decide how to parse markdown
  • avoid indirect version conflicts or problems for libs using EarmarkEarmarkParser and ex_docEarmarkParser as has occurred here

Just released a quickfix for Earmark 1.4.42 hopefully I can get ~> 1.4.42 stable soon, please stick to 1.4.40 for the time being in case of problems (but of course bug requests in GH are welcome)

1 Like

Just released

Earmark 1.4.43 2023-09-20

Added missing erlang sources into to mix package

@RobertDober thanks for all your work on Earmark, its a critical part of the ecosystem and you’ve made it better through your efforts.


Very minor changes here

EarmarkParser 1.4.36 2023-09-22

  • Correting deprection version for smarty_pants

  • Checking for result type EarmarkParser.t of EarmarkParser.as_ast

1 Like

In the case you use Earmark and EarmarkParser, I just released

Earmark 1.4.44 2023-09-26

Kudos to José Valim (do you really need a link here :wink: )for an important renaming task I had forgotten, this is only
important for users of both, Earmark and EarmarkParser


More work from José to cleanup my sloppy last releases. So greatful.
This one might be interesting for everyone

Earmark 1.4.45 2023-09-27

Two new releases

These are for people using Earmark and EarmarkParser. As they address a potential naming conflict concerning compiled yecc or leex file however, a version update might be useful for all users. All work was done again by José, thank you so much.

Here are the release notes:

EarmarkParser 1.4.37 2023-10-01

Earmark 1.4.46 2023-10-01