Why?
The first question you should ask yourself when seeing this title is “why?”. Why would we want to add wikilink1 support to a Markdown formatting library?
Well, recently I started using Obsidian (a Markdown-based personal wiki). Since my writing is mostly code-related, it includes a lot of code-snippets. As I like having my code neatly formatted, and hate formatting it by hand, I wanted a tool to do that for me. The best tool I found was mdformat. It’s Python based, formats Python, Rust, and Go code snippets, and even formats the Markdown itself.
The only problem being: it formats a lovely wiki [[link]]
as \[\[link\]\]
, breaking it in the process.
To mitigate that, I had to add wikilink support2.
If you just want to see the code, go to mdformat-wikilink.
Mdformat Plugins
mdformat is a markdown formatting tool written in Python. It is based on the wonderful markdown-it-py library, and has plugin support.
There are 2 types of mdformat plugins:
- Code formatter plugins, used for formatting the code inside fenced blocks;
- Parser extension plugins, used to add support for new nodes.
Since we’re adding support for a new type of syntax, a wikilink, we’ll be writing a parser extension plugin. To do so, we’ll follow the mdformat guide for developing plugins.
That said, our mdformat plugin will only do the rendering. For parsing, we need to write a markdown-it-py plugin.
Markdown-it-py Plugins
Luckily for us, markdown-it-py has very good support for plugins as well. Documentation includes design principles (which make for a good architecture overview), API documentation, and existing plugins. You can also check the markdown-it live demo (using the Javascript library that was later ported to Python) to interactively see a token stream.
Actual Code
After a bit of reading, experimenting, and finding out - it seems that we only need very little code to make things work. We can do something more complex, but for our needs (ensuring mdformat doesn’t modify wikilinks) we can hack something quick. We’re going to create a new parser token for wikilinks, and make mdformat render it as-is.
The code will consist of 3 parts:
- markdown-it-py plugin, to parse the wikilinks as a new token
- mdformat plugin, to use the previous plugin, and render the new token as-is
- A bit of
pyproject.toml
config to makemdformat
recognize the plugin.
Parsing Wikilinks
Since we’re only parsing wikilinks to keep them unmodified, we’re not going to break them up into parts. Instead, we’ll just keep them as a block of text. This means that we can write a simplistic parser that does the following:
- Using regex, we check whether the string currently fed to the parser is a link
- If it isn’t, we do nothing and report that it isn’t.
- If it is, we push a
wikilink
token with the entire link as its content, and increment the parser position part the link.
The last (and most important) part of the code is registering the parser we just wrote. We register it as an “inline” rule (as it is an inline element, not a block), and we register it last, as it doesn’t replace any other elements (well, plain text…).
|
|
Formatting Wikilinks
Our mdformat plugin is even more simplistic.
- The
update_mdit
function is used to load our wikilink-parsing plugin into the current instance of markdown-it-py - The
_render_wikilink
function returns the content of the wikilink token (or node, in mdformat terminology) that we pushed
That’s it.
|
|
A Tiny Bit of Config
The last thing we need to do is register the right entry-point for our plugin, so that mdformat will know to load and use it.
We can do it in our pyproject.toml
file (I’m using Poetry, other tools have similar options).
|
|
And with that, we’re done.
You can see the whole project at mdformat-wikilink.