I’m currently finally back on converting the OctoPrint docs to using Markdown and MkDocs.

Since I have some images in the docs that I want to be able to reference without having to use relative URLs (../../../../images/), especially since that would tie things in OctoPrint’s source tree structure too close to things in its documentation tree structure that might or might not end up being in a different repository in the future, I needed a way to use absolute URLs here (/images/). But since the docs will most likely also end up being hosted on a version specific subpath of docs.octoprint.org, just using (host) absolute URLs would not work either and break.

There’s several issues on the MkDocs issue tracker about workflow problems caused by this, but the suggested workarounds like using macros to prefix a variable’s contents to related URLs didn’t work for me due to me also heavily relying on mkdocstrings, and anything contained in docstrings is not processed by macros1.

So I got the idea to implement a minimal MkDocs plugin that would just turn all URLs contained in href and src attributes that are prefixed with a custom schema site: schema into site relative URLs, with this effect. Example:

URLsite_urlresulting URL
site:images/foo.pnghttps://docs.octoprint.org//images/foo.png
site:images/foo.pnghttps://docs.octoprint.org/1.9.x//1.9.x/images/foo.png

Using a hook I could register a callback for the on_page_content event that would then replace all URLs as needed in the generated page HTML.

And this is the resulting site_urls.py:

import logging
import urllib.parse
import re

import mkdocs.plugins

log = logging.getLogger("mkdocs")

SITE_URLS_REGEX = re.compile(r'(href|src)="site:([^"]+)"', re.IGNORECASE)

@mkdocs.plugins.event_priority(50)
def on_page_content(html, page, config, files):
    site_url = config["site_url"]
    path = urllib.parse.urlparse(site_url).path

    if not path:
        path = "/"
    if not path.endswith("/"):
        path += "/"

    def _replace(match):
        param = match.group(1)
        url = match.group(2)
        if url.startswith("/"):
            url = url[1:]

        log.info(f"Replacing site:{match.group(2)} with {path}{url}...")
        return f'{param}="{path}{url}"'

    return SITE_URLS_REGEX.sub(_replace, html)

that I’ve registered as a hook in my mkdocs.yaml like this:

hooks:
  - site_urls.py

Seems to work just fine, both for images and links! 😄

Update 2023-07-28: I’ve now published this as a proper plugin on PyPI, see mkdocs-site-urls. With that, all you need to do - given you are already on MkDocs 1.5 or newer - is installing the plugin via pip install mkdocs-site-urls and then adding this to your mkdocs.yaml:

plugins:
  - site-urls

  1. As I found out when I wanted to add a version_added macro, which simply didn’t render. ↩︎