Introduction
This document describes the different ways mkdocstrings
can be implemented, their advantages and disadvantages.
The two main ways are:
- as an Mkdocs plugin only
- as an Mkdocs plugin plus a Markdown extension
What's a plugin?
An Mkdocs plugin is a set of methods called when certain events happen.
There are three kinds of events:
- global events, called once per build, at either the beginning or end of the build process;
- template events, called once for each non-page template (Jinja2/HTML files), all called after the
on_env
event and before any page event;
- page events, called once per Markdown page included in the site, all called after the
post_template
and before the post_build
event.
The events used in mkdocstrings
are:
on_config
: global event, used to prepare the configuration of the plugin
on_serve
: global event, used to add (source) directories to watched paths
on_nav
: global event, used to find "autodoc" instructions and pre-build documentation for every page
on_page_markdown
: page event, used to append Python objects references to allow cross-linking
on_page_contents
: page event, used to insert HTML divs to allow styling
When developing the plugin, we have to keep in mind that other plugins' hooks on events can be called before or after mkdocstrings
' own hooks.
Maybe you find it easier to understand a flow with pseudo-code? It should be something like this:
config = run_event_on_config(initial_config)
server = run_event_on_serve(initial_server, config)
nav = run_event_on_nav(initial_nav)
for page in nav.pages:
page.markdown = run_event_on_page_markdown(page.raw_text, page)
page.html = markdown.convert(page.markdown, config.extensions)
for page in nav.pages:
page.html = run_event_on_page_contents(page.html, page)
config.theme.render(nav)
What's a Markdown extension?
For our use-case, we can describe a Markdown extension as: "a piece of code that is triggered when a markdown block matches its regular expression, and that renders a Python object's documentation to HTML".
Typically, the goal here would be to match our "autodoc" instructions, whatever their format is, and render the corresponding object documentation instead of the instruction.
Example:
## My func
::: lib.mod.func
Our Markdown extension would match the ::: *
line, and render documentation for the Python object lib.mod.func
, directly to HTML.
A concrete implementation can be found in mkautodoc
on GitHub (mkdocstrings
was inspired from mkautodoc
, thank you @tomchristie!)
Working as a plugin only
When working as a plugin only, mkdocstrings
can (help) generate the final output in several ways.
"Normal-flow"
In the on_page_markdown
event, replace "autodoc" instructions with Markdown generated from the documentation.
Advantage: straight-forward, follow the logical flow of transformation.
Disadvantage: Markdown extensions required by mkdocstrings
must be registered by the user in mkdocs.yml
, or automatically inserted in the config from the on_config
event. These extensions are used globally, for every page, which might not be what the user wants.
"Discard-and-re-render"
For pages containing "autodoc" instructions, discard the rendered HTML in on_page_contents
, and use the original markdown again (from page.markdown
parameter) to convert it through a local Markdown converter instance.
Advantage: using a local Markdown converter allows us to use whatever extension we require, combined to the user-specified extensions, to render the markdown to HTML. User does not have to register these extensions in mkdocs.yml
anymore, and they do not affect other pages. Absolute URLs of cross-references can be appended only once, at the end of the Markdown text.
Disadvantage: mkdocstrings
can lose compatibility with other plugins, in certain conditions: the other plugins modify HTML in on_page_contents
before mkdocstrings
. In that case, mkdocstrings
will discard these modifications, and they will be lost.
"Direct-HTML"
In the on_page_contents
event, replace "autodoc" instructions (of the form <p>INSTRUCTION</p>
) with HTML generated from the documentation.
Advantage: generating HTML directly allows us to get rid of Markdown extensions requirements. HTML generation using an XML tree seems cleaner than concatenating lots of Markdown lines and then inserting divs in the output. It also allows more flexibility in the output (each element can have its own CSS classes), and therefore allows finer CSS styling by the user.
Disadvantage: the Table Of Contents must be updated manually, as it's computed while converting Markdown to HTML. We must imitate the HTML that Markdown extensions are generating, either manually or by importing some logic from their code. We also still need to convert docstrings (which are written in Markdown) to HTML using a Markdown converter. References to other Python objects must all be appended to each and every docstring to make sure links are correctly rendered.
Working as a plugin plus Markdown extension
We add a Markdown processor that is responsible for rendering "autodoc" instructions to HTML.
Advantages: we do not have to update the Table Of Contents manually. "Autodoc" instructions are rendered in complete isolation: the rest of the Markdown is not affected by mkdocstrings
. Complete flexibility, allowing CSS customization. We don't have to insert divs in the on_page_contents
even anymore.
Disadvantages: the extension must imitate the other extensions we get rid of, either manually or by importing some of their code, append all objects references to every docstring, and convert them from Markdown to HTML.
Final note
The "direct HTML" and "plugin+extension" methods are more complex to write and maintain, but allow more customization. They do not require the user to add extensions to the config, but would probably depend on them (Python imports). The extension is preferred to the "direct HTML" since they are implemented the same but the latter requires to update the TOC of each page manually.
The "discard" method is transparent for the user, and only modifies extensions for pages containing "autodoc" instructions. However, it can be incompatible with other plugins modifying HTML, and should therefore be put first in the list of plugins.
The "normal flow" either requires the user to add extensions in the config, or automatically adds them. These extensions are global and affect every page.
Overall, the best solution probably is the extension, as it allows complete customization of the output and works in isolation of the rest of the Markdown contents (no side-effects).
The second best solution is one of "normal flow" and "discard", depending on what users think is better: 100% compatibility with other plugins or partial isolation of the Markdown to HTML conversion (possible side-effects only in pages containing "autodoc" instructions).
The worst solution is the "direct HTML" rendering (plugin only) as it only has disadvantages over the "extension" one.