Exadra37
I am trying to use this library to parse my custom markdown where I want to find calls to HEEX and then handle them but cannot figure out how to make the custom parser to be invoked.
The markdown:
## TEST
Some text before a card.
<.card image_path="/images/awesome.svg">Some nice card with an image on the left.</.card>
Continuing after the card.
My parser:
defmodule MasWeb.MdParser do
use Md.Parser
alias Md.Parser.Syntax.Void
@default_syntax Map.put(Void.syntax(), :settings, Void.settings())
@syntax @default_syntax
@impl true
def parse(input, state) do
# copied from the Md.Parser source code:
%State{ast: ast, path: []} = state = do_parse(input, state)
{"", %State{state | ast: Enum.reverse(ast)}}
end
end
The docs for Md.Parser say this:
Custom parsers might be used in syntax declaration when the generic functionality
is not enough.Let’s consider one needs a specific handling of links with titles.
The generic engine does not support it, so one would need to implement a custom parser
and instructMd.Parserto use it with:# config/prod.exs config :md, syntax: %{ custom: %{ {"![", MyApp.Parsers.Img}, ... } }Once the original parser would meet the
"!["binary, it’d callMyApp.Parsers.Img.parse/2.
The latter must proceed until the tag is closed and return the remainder and the updated state
as a tuple.
Adding the configuration to config/prod.exs doesn’t seem to make sense to me, thus I added it to config.exs :
config :md, syntax: %{
custom: [
{"<.", MasWeb.MdParser},
]
}
But then I get this error:
Erlang/OTP 25 [erts-13.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
ERROR! the application :md has a different value set for key :syntax during runtime compared to compile time. Since this application environment entry was marked as compile time, this difference can lead to different behaviour than expected:
* Compile time value was not set
* Runtime value was set to: %{custom: [{"<.", MasWeb.MdParser}]}
To fix this error, you might:
* Make the runtime value match the compile time one
* Recompile your project. If the misconfigured application is a dependency, you may need to run "mix deps.compile md --force"
* Alternatively, you can disable this check. If you are using releases, you can set :validate_compile_env to false in your release configuration. If you are using Mix to start your system, you can pass the --no-validate-compile-env flag
10:57:41.583 [error] Task #PID<0.252.0> started from #PID<0.107.0> terminating
** (stop) "aborting boot"
(elixir 1.14.3) Config.Provider.boot/2
Function: &:erlang.apply/2
Args: [#Function<1.104735216/1 in Mix.Tasks.Compile.All.load_apps/3>, [md: "/home/developer/workspace/_build/dev/lib"]]
** (EXIT from #PID<0.107.0>) an exception was raised:
** (ErlangError) Erlang error: "aborting boot"
(elixir 1.14.3) Config.Provider.boot/2
If I try to recompile it as suggested in the output:
mix deps.compile md 1 ↵
==> md
Compiling 13 files (.ex)
== Compilation error in file lib/md/parser/default.ex ==
** (FunctionClauseError) no function clause matching in :erl_eval."-inside-an-interpreted-fun-"/1
The following arguments were given to :erl_eval."-inside-an-interpreted-fun-"/1:
# 1
{"<.", MasWeb.MdParser}
(stdlib 4.3) :erl_eval."-inside-an-interpreted-fun-"/1
(stdlib 4.3) erl_eval.erl:898: :erl_eval.eval_fun/8
/home/developer/workspace/deps/md/lib/md/parser/default.ex:1: (file)
/home/developer/workspace/deps/md/lib/md/parser/default.ex:1: (file)
(stdlib 4.3) erl_eval.erl:748: :erl_eval.do_apply/7
(stdlib 4.3) erl_eval.erl:136: :erl_eval.exprs/6
/home/developer/workspace/deps/md/lib/md/parser/default.ex:1: Md.Engine.__before_compile__/1
could not compile dependency :md, "mix compile" failed. Errors may have been logged above. You can recompile this dependency with "mix deps.compile md", update it with "mix deps.update md" or clean it with "mix deps.clean md"
I have no idea how to recover form this error, thus I commented out the MD entry from my config.exs and tried instead to configure it through my custom parser:
defmodule MasWeb.MdParser do
use Md.Parser
alias Md.Parser.Syntax.Void
@default_syntax Map.put(Void.syntax(), :settings, Void.settings())
@syntax @default_syntax |> Map.merge(%{
# I think I am doing something wrong here
common: {"<.", MasWeb.MdParser}, # example from Md.Parser
})
@impl true
def parse(input, state) do
IO.inspect(input, label: "MD PARSER INPUT")
IO.inspect(state, label: "MD PARSER STATE")
%State{ast: ast, path: []} = state = do_parse(input, state)
{"", %State{state | ast: Enum.reverse(ast)}}
end
end
I can now compile but I never see the IO.inspect output, thus my custom parser is not being invoked, which I kind of expected to happen, but i needed to give it a try.
Any guidance how to proceed to get a custom parser configured in my Phoenix app?
Trending in Questions
Other Trending Topics
Latest Phoenix Threads
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #ai
- #phoenix_html
- #elixirconf-us
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming











Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
mudasobwa
Guilty.
The docs of
mdmust be updated and extended widely.In the first place, you’ve found a bug in the custom parsers implementation, thanks for that. Fixed in
v0.9.7.Second, you kinda mix up custom parsers and syntax. The very correct change in
config.exswould now be handled properly, the correct syntax is:The custom parser is an implementation of
Md.Parserbehaviour, so copy-pasting from the library source wouldn’t help much. Instead, you are supposed to implement the whole parser. The very naïve implementation would look like this:Once we use
<.as a tag,<.would not be passed to the handler itself. Hence we gotcardin the first split and the inner tag content in the second split. We end up withrest, which must be passed back to the “main” parser as a continuation, andcontentwhich you should translate to AST yourself (because it’s a custom parser.) Here we simply return back a text node.Sidenote:
customname is essential,commonwould not work, it’s used as a tag type.Thanks for giving the library a try, please, don’t hesitate to ask if anything. I’d love to make some progress with its docs and tests, but unfortunately, it serves our current needs and I am like, ok, later, then
Exadra37
Wow. I didn’t expect this first class support

Tonight I will try your latest changes and then I will provide some feedback.
Afterwards I will try to add at least a simple quickstart example to your home page in the docs.
mudasobwa
I was under impression that is the Elixir community standard.
mudasobwa
I gave a thought to your usage example and now I have a question.
mdalready supportstags viatag:syntax, but the support is very limited (no attributes whatsoever,) I am having plans to extend it, and that’s why I’d love to see a real example of how it could be used.My proposal would be for attributes to go to resulting attributes as is, the default parser would do all the work, produce an AST, and the tag itself then be passed to the (newly introduced, optional) transformer.
Before I am to file an issue and start working on it, I’d love to know what do you expect to get back as the result.
Exadra37
To answer this directly: I don’t know yet.
What I am trying to achieve it to embed any HEEX template in a markdown document. This would allow me to reuse the same components used elsewhere in other HEEX templates,
For example:
All of this HEEX components would be customisable in order to pass classes and any other html attributes, etc.
The trigger was to be able to write my website pages in markdonw and have them with text and images alternating:
This current webpage is written in markdonw and content aligned with a lot of css trickery and repetition of images declaration:
Writing markdonw like this becomes tedious and time consuming. Earmark is being used to parse it.
Then I also came to realise that I could render HEEX templates with Earmark, but not pass custom content to the HEEX templates, and this was when I started to look into your library.
That renders to this:
The HEEX template that I am currently trying to use from the markdown to include the card with text and image:
The Card HEEX template it’s only a draft. I still need add variables to customise the HTML attributes.
Let me know if you have further questions.
mudasobwa
Thank you, that helped a lot.
Now I understand it’s surely not about tags per se.
The main problem to figure out would be who leads the parsing.
EExallows custom engine implementation and one might call a markdown parser from a customEEx.Engine.handle_text/3callback.We can support
eex/heexas is, or introducedelegatewhich would be likecustombut instead of the implementation ofMd.Parserit would rather parse it until it’s closed, maybe apply its own format, and then delegate the whole to the external engine.The question who leads the process remains though. Consider
<%= cta_newsletter(assigns) %>wherecta_newsletter/1returns"Subscribe to **the newsletter**"string, or<.card image=...>My _fancy_ text with **markup**</.card>.Anyway, it needs some time to twiddle the puzzle in hands, but meanwhile, I would suggest you try the vice-versa approach with a
HEExengine ruling the process andmdbeing called on text nodes (no idea of how to achieve that, though,HEExdoes not seem to be friendly to external parsers.Exadra37
Thank you very much for the options you gave me to think about. I will tak a look to them for sure.
Yesterday night I made it work and it seems that it matches the approach you suggest here:
My code:
The custom Phoenix Engine:
The
config.exs:Add
mdextension to Phoenix live reload inconfig/dev.exsThe markdown file
test.html.md:The result:
At the moment my Phoenix 1.7 app isn’t using LiveView but I plan to do so, therefore I need to wait until I can be sure that this also works properly with LiveView tracking.
Would this be something you would consider to add support for in your Lib? If yes I can make the PR.
mudasobwa
Sidenote: Handling a tag with and without attributes by the same code should be as easy as swapping splits
What worries me, is generating of the HTML where it should not technically be generated.
I am not sure it would work properly with nested tags, although it seems it would, still it looks like a kludge.
Md.Parsersupports attaching a listener, which might modify the result, and we should at least re-pass it to the underlyinggenerate/2. Also, it’d fail on creating deferred links ([foo][1]followed by[1] link,) and lose the context in general, when not surrounded by\n\n.mdis a streaming parser, unlike[H]EEx, which makes it less trivial to interoperate with other engines.Anyway, I will think about how can it be done without leaving AST representation and come back to you.
zenw0lf
I’m really interested in rendering function components inside markdown files.
Any advances in this front?
mudasobwa
Honestly, there is not much demand and I did not come up with a generic solution.
If you could share an example of what you need, I could give it another spin.