refactor: allow configuring mkdocstrings as a Markdown extension

Signed-off-by: Timothée Mazzucotelli <dev@pawamoy.fr>
This commit is contained in:
Timothée Mazzucotelli
2026-05-24 19:10:00 +02:00
parent cf0531028a
commit 71c976cd96
4 changed files with 102 additions and 19 deletions
+16 -10
View File
@@ -23,6 +23,7 @@
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING, Any
from zensical.extensions.autorefs import get_autorefs_store
@@ -60,8 +61,13 @@ class ToolConfig:
def get_mkdocstrings_extension(
handlers: dict[str, Any] | None = None,
*,
custom_templates: str | None = None,
enable_inventory: bool = True, # noqa: ARG001
default_handler: str = "python",
locale: str = "en",
config: dict[str, Any],
path: str,
) -> MkdocstringsExtension:
"""Create the mkdocstrings Markdown extension."""
from mkdocstrings import ( # noqa: PLC0415 # ty:ignore[unresolved-import]
@@ -73,19 +79,19 @@ def get_mkdocstrings_extension(
global HANDLERS # noqa: PLW0603
if HANDLERS is None:
mkdocstrings_config = config["plugins"]["mkdocstrings"]["config"]
tool_config = ToolConfig(config_file_path=path)
root_dir = Path(config["root_dir"])
config_file = root_dir / "zensical.toml"
tool_config = ToolConfig(config_file_path=str(config_file))
HANDLERS = Handlers(
theme="material",
default=mkdocstrings_config.get("default_handler") or "python",
inventory_project=mkdocstrings_config.get("inventory_project")
or config["site_name"],
inventory_version=mkdocstrings_config.get("inventory_version"),
handlers_config=mkdocstrings_config.get("handlers"),
custom_templates=mkdocstrings_config.get("custom_templates"),
default=default_handler,
inventory_project=config["site_name"],
inventory_version="0.0.0",
handlers_config=handlers if handlers is not None else {},
custom_templates=custom_templates,
mdx=config["markdown_extensions"],
mdx_config=config["mdx_configs"],
locale=mkdocstrings_config.get("locale"),
locale=locale,
tool_config=tool_config,
)
+7 -7
View File
@@ -41,11 +41,11 @@ from tomli import load as toml_load
from yaml import Loader, YAMLError
from yaml.constructor import ConstructorError
from zensical.compat.mkdocstrings import get_mkdocstrings_extension
from zensical.extensions.autorefs import AutorefsExtension
from zensical.extensions.emoji import to_svg, twemoji
from zensical.extensions.glightbox import GlightboxExtension
from zensical.extensions.macros import MacrosExtension
from zensical.extensions.mkdocstrings import MkdocstringsExtension
if TYPE_CHECKING:
from collections.abc import Iterator
@@ -679,7 +679,7 @@ def _apply_defaults(config: dict, path: str) -> dict:
# Map plugins configuration to Markdown extensions
_shim_autorefs(config)
_shim_markdown_exec(config)
_shim_mkdocstrings(config, path)
_shim_mkdocstrings(config)
_shim_glightbox(config)
_shim_macros(config)
@@ -858,19 +858,19 @@ def _shim_markdown_exec(config: dict[str, Any]) -> None:
)
def _shim_mkdocstrings(config: dict[str, Any], path: str) -> None:
def _shim_mkdocstrings(config: dict[str, Any]) -> None:
# Map mkdocstrings plugin configuration to the extension configuration
if "mkdocstrings" in config["plugins"]:
mkdocstrings_config = config["plugins"]["mkdocstrings"]["config"]
if mkdocstrings_config.get("enabled", True):
plugin = config["plugins"]["mkdocstrings"]["config"]
if plugin.get("enabled", True):
if not find_spec("mkdocstrings"):
raise ConfigurationError(
"mkdocstrings plugin is enabled, but mkdocstrings is not "
"installed. Please install mkdocstrings or disable the "
"plugin."
)
mkdocstrings = get_mkdocstrings_extension(config, path)
config["markdown_extensions"].append(mkdocstrings)
config["markdown_extensions"].append(MkdocstringsExtension.name)
config["mdx_configs"][MkdocstringsExtension.name] = plugin
def _shim_glightbox(config: dict[str, Any]) -> None:
@@ -0,0 +1,76 @@
# Copyright (c) 2025-2026 Zensical and contributors
# SPDX-License-Identifier: MIT
# All contributions are certified under the DCO
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from markdown import Extension, Markdown
from zensical.compat.mkdocstrings import get_mkdocstrings_extension
from zensical.extensions.context import ContextPreprocessor
if TYPE_CHECKING:
from markdown import Markdown
# -----------------------------------------------------------------------------
# Classes
# -----------------------------------------------------------------------------
class MkdocstringsExtension(Extension):
"""Markdown extension that renders Python API documentation."""
name = "zensical.extensions.mkdocstrings"
def __init__(self, **kwargs: Any) -> None:
"""Initialize the extension."""
self._enabled: bool = kwargs.pop("enabled", True)
self._kwargs = kwargs
def extendMarkdown(self, md: Markdown) -> None:
"""Register Markdown extension."""
if not self._enabled:
return
# The context extension must have registered its dummy processor
# by then. We ensure this in `render()` by inserting it first
# in the list of extension so that Python-Markdown calls its
# `extendMarkdown()` method first.
if context := ContextPreprocessor.from_markdown(md):
config = context.config
# We need the following indirection because we have
# to pass the configured Markdown extensions again
# to the actual extension.
ext = get_mkdocstrings_extension(**self._kwargs, config=config)
ext.extendMarkdown(md)
# -----------------------------------------------------------------------------
# Functions
# -----------------------------------------------------------------------------
def makeExtension(**kwargs: Any) -> MkdocstringsExtension:
"""Register Markdown extension."""
return MkdocstringsExtension(**kwargs)
+3 -2
View File
@@ -96,11 +96,12 @@ def render(content: str, path: str, url: str) -> dict:
extension._kwargs["page"] = page
break
else:
config["markdown_extensions"].append(
config["markdown_extensions"].insert(
0,
ContextExtension(
page=page,
config=config,
)
),
)
# Initialize Markdown parser