Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ plugins:
execute: true
include_source: True

hooks:
- scripts/docs/hooks.py

extra:
analytics:
provider: google
Expand Down
19 changes: 19 additions & 0 deletions scripts/docs/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""MkDocs lifecycle hooks."""

import shutil
import tempfile
from pathlib import Path

BUILD_TEMP_DIR_CONFIG_KEY = "build_temp_dir"


def on_pre_build(config):
config[BUILD_TEMP_DIR_CONFIG_KEY] = Path(tempfile.mkdtemp(prefix="ravnar_docs_"))


def on_post_build(config):
build_temp_dir: Path | None = config.pop(BUILD_TEMP_DIR_CONFIG_KEY, None)
if build_temp_dir is None or not build_temp_dir.exists():
return

shutil.rmtree(build_temp_dir, ignore_errors=True)
40 changes: 39 additions & 1 deletion scripts/docs/macros.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import re
import textwrap
from pathlib import Path
from typing import Any
Expand All @@ -8,6 +9,7 @@
import pygments.util
import yaml
from markupsafe import Markup
from mkdocs_macros.plugin import MacrosPlugin
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource

from _ravnar.config import Config
Expand Down Expand Up @@ -91,7 +93,7 @@ def render(self, attrs: list[str], *values: Any) -> str:
)


def define_env(env):
def define_env(env: MacrosPlugin):
config_options_renderer = ConfigOptionsRenderer()

env.macro(config_options_renderer.render, name="config_options")
Expand All @@ -108,3 +110,39 @@ def include_file(rel_path, language="") -> str:
language = lexer_cls.aliases[0]

return Markup(code(content, language=language))


def on_pre_page_macros(env: MacrosPlugin) -> None:
"""Preprocess tutorial notebook files to resolve cross-reference links.

mkdocs-jupyter converts notebooks directly to HTML via nbconvert, bypassing the markdown pipeline where
autorefs/mkdocstrings would normally resolve [target][] and [display][target] cross-references. This hook patches
the page to point at a temp copy of the notebook with those links already resolved, so mkdocs-jupyter picks up the
processed version without touching the original source files.
"""
src_path = env.page.file.src_path
if not (src_path.startswith("tutorials/") and src_path.endswith((".py", ".ipynb"))):
return

original = Path(env.page.file.abs_src_path).read_text()

# Compute a relative path to references/python_api/ so links work regardless of
# whether the site is hosted at the root or a sub-path (e.g. /latest/).
depth = len(env.page.url.rstrip("/").split("/"))
python_api_reference = "../" * depth + "references/python_api/"

def replace_crossref(match: re.Match) -> str:
display, target = match.group(1), match.group(2)
if not target:
display = f"`{display}`"
target = match.group(1)
return f"[{display}]({python_api_reference}#{target})"

processed = re.sub(r"\[([^]]+)\]\[([^]]*)\]", replace_crossref, original)

temp_dir: Path = env.conf["build_temp_dir"] / "mkdocs-jupyter-crossref"
temp_dir.mkdir(exist_ok=True)

temp_file = temp_dir / Path(src_path).name
temp_file.write_text(processed)
env.page.file.abs_src_path = str(temp_file)
Loading