knoebber

knoebber

How to extend Earmark

I’d like to extend Earmark to transform #hashtag into

<a href="/some/internal/route">hashtag</a>

Hashtags should only be transformed if they are inside of a string node, so I need to inspect the AST.

I’ve been looking through the Earmark docs and I don’t see a clear way to do this. I don’t think I can use map_ast as it says a string node must return a string.

I’ve found one example of something similar here:

It looks like they using Enum.map on the ast, but I don’t fully understand the transform_ast_node_text function, particularly the bits that pattern match on binary data. I hope I can do something simpler.

Any help is appreciated, thanks!

Marked As Solved

knoebber

knoebber

hey, thanks for the great work on Earmark!

I realized after posting this that I only want to transform text leafs that are descendants of certain tags. Otherwise I might inject an <a> tag inside of a <code> block for example. Here’s what I came up with if anyone is interested:

def parse_tags(text_leaf) when is_binary(text_leaf) do
  Regex.split(
    ~r/#([a-zA-Z0-9]{2,})/,
    text_leaf,
    include_captures: true
  )
  |> Enum.map(fn
    <<?#, tagname::binary>> ->
      {"a",
       [
         {"class", "tag"},
         {"href", "/board?tag=#{tagname}"}
       ], [tagname], %{}}

    text ->
      text
  end)
end

def map_ast(ast, text_is_eligible_for_hashtag \\ false) do
  Enum.map(ast, fn
    {tag, atts, children, m} ->
      {tag, atts, map_ast(children, tag in ["p", "li"]), m}

    text_leaf ->
      if text_is_eligible_for_hashtag, do: parse_tags(text_leaf), else: text_leaf
  end)
end

def markdown(md) do
  case EarmarkParser.as_ast(md) do
    {:ok, ast, _} -> map_ast(ast) |> Earmark.Transform.transform()
    _ -> md
  end
end

Also Liked

RobertDober

RobertDober

naturally :wink:

yeah I guess in these cases you have to do it yourself, would be an interesting idea to generalize,
many things come into mind, e.g. an option to parse with a callback function that adds things to the meta Map in the AST, or an AST walker that keeps access to the parent nodes, …

Unfortunately no time at all.

RobertDober

RobertDober

This should not be too difficult

import Earmark.Transform
iex(11)> {:ok, ast,_} = EarmarkParser.as_ast(" #x")
{:ok, [{"p", [], [" #x"], %{}}], []}
iex(12)> transform = fn
...(12)> x when is_tuple(x) -> x
...(12)> _ -> [ " ", {"a", [{"href", "whatever"}], ["x"], %{}}] end # need to scan the parameter for hashtags and create a structure like the one I just put here
#Function<44.65746770/1 in :erl_eval.expr/5>
iex(13)> ast1 =map_ast(ast, transform)
[{"p", [], [[" ", {"a", [{"href", "whatever"}], ["x"], %{}}]], %{}}]
iex(14)> transform(ast1, [])
"<p>\n <a href=\"whatever\">x</a></p>\n"

HTH

RobertDober

RobertDober

I just realized with the release of Earmark 1.4.25 there are two ways to achieve your goals with Earmarks tools at hand

even before 1.4.25 you could have used map_ast_with to keep track of the condition if to replace hashtags or not in the accumulator

and now from 1.4.25 you can also, which IMHO is more elegant, change the mapper function dynamically when traversing with map_ast by not returning a quadruple but a tuple with the new function and a quadruple

here
you can find an example of how to do that

Where Next?

Popular in Questions Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
LegitStack
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it. I’m very interested in Elixir,...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
Fl4m3Ph03n1x
Background Let’s assume I have a typical GenServer that receives messages as requests, does some operation in a DB and returns responses....
New
mathew4509
I have a list say x = ["23gh", "56kh", "97mh"] I would like to pass each element to Val in each iteration. Say, in iteration 1 -------...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
kostonstyle
Hi all I want to have a unix time, from the current time plus 1 hour. DateTime.now + 1 hour How to get it in elixir? Thanks
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

Other popular topics Top

yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement