From d78ed0e1c7e9d536631f43cf889c3d2207eddac8 Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Wed, 24 Jun 2026 10:06:02 +0200 Subject: [PATCH 1/2] fix cross references in tutorials --- mkdocs.yml | 3 +++ scripts/docs/hooks.py | 19 +++++++++++++++++++ scripts/docs/macros.py | 35 ++++++++++++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 scripts/docs/hooks.py diff --git a/mkdocs.yml b/mkdocs.yml index 45195d7..044fe14 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -36,6 +36,9 @@ plugins: execute: true include_source: True +hooks: + - scripts/docs/hooks.py + extra: analytics: provider: google diff --git a/scripts/docs/hooks.py b/scripts/docs/hooks.py new file mode 100644 index 0000000..1b97abe --- /dev/null +++ b/scripts/docs/hooks.py @@ -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) diff --git a/scripts/docs/macros.py b/scripts/docs/macros.py index 329570e..1280aac 100644 --- a/scripts/docs/macros.py +++ b/scripts/docs/macros.py @@ -1,4 +1,5 @@ import json +import re import textwrap from pathlib import Path from typing import Any @@ -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 @@ -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") @@ -108,3 +110,34 @@ 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() + + 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}](/references/python_api/#{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) From 5bf21675a7d128fb479d4536013ab6de34c8f949 Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Wed, 24 Jun 2026 10:36:02 +0200 Subject: [PATCH 2/2] fix root path --- scripts/docs/macros.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/docs/macros.py b/scripts/docs/macros.py index 1280aac..a56dc49 100644 --- a/scripts/docs/macros.py +++ b/scripts/docs/macros.py @@ -126,12 +126,17 @@ def on_pre_page_macros(env: MacrosPlugin) -> None: 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}](/references/python_api/#{target})" + return f"[{display}]({python_api_reference}#{target})" processed = re.sub(r"\[([^]]+)\]\[([^]]*)\]", replace_crossref, original)