Skip to content

Plugin API

Plugins bundle opinionated handler collections so you can enable MkDocs-specific behavior (Material tabs, cards, callouts) or ship your own HTML post-processors without patching the core engine.

texsmith.plugins exposes a namespace package populated by the MkDocs hook in docs/hooks/mkdocs_hooks.py. Importing a plugin module registers its handlers via the standard @renders decorator—no extra registry required.

Loading plugins

# ensure plugin handlers are registered
import texsmith.plugins.material  # noqa: F401

from texsmith import Document, convert_documents

bundle = convert_documents([Document.from_markdown(Path("intro.md"))])

When running texsmith, use the --enable-extension or MkDocs plugin configuration to import modules before TeXSmith executes. For example, add the following to your MkDocs hooks file:

def on_config(config):
    import texsmith.plugins.material  # registers Material handlers
    return config

Authoring your own plugin

  1. Create a module (e.g., texsmith.plugins.acme) and import any handlers that @renders functions depend on.
  2. Declare entry points or instruct consumers to import texsmith.plugins.acme before rendering.
  3. Optionally provide a MkDocs plugin/hook so documentation builds load your plugin automatically, mirroring what TeXSmith’s docs do with the Material helpers.

Keep plugin modules small and focused; most behaviour belongs in dedicated handler modules under texsmith.adapters.handlers.

Reference

Compatibility layer exposing plugin integrations to documentation.

TeXSmith consolidated its plugin utilities under texsmith.adapters.plugins. mkdocstrings still references the historical texsmith.plugins namespace, so we re-export the maintained modules here to keep the public import path available.

Optional handlers for MkDocs Material specific constructs.

register

register(renderer: Any) -> None

Register Material-specific exercise and epigraph handlers.

Source code in src/texsmith/adapters/plugins/material.py
162
163
164
165
166
def register(renderer: Any) -> None:
    """Register Material-specific exercise and epigraph handlers."""
    renderer.register(render_exercise_div)
    renderer.register(render_exercise_details)
    renderer.register(render_epigraph)

render_epigraph

render_epigraph(
    element: Tag, context: RenderContext
) -> None

Render Material epigraph blockquotes using the epigraph macro.

Source code in src/texsmith/adapters/plugins/material.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
@renders(
    "blockquote",
    phase=RenderPhase.POST,
    priority=10,
    name="material_epigraphs",
    auto_mark=False,
)
def render_epigraph(element: Tag, context: RenderContext) -> None:
    """Render Material epigraph blockquotes using the LaTeX epigraph macro."""
    classes = gather_classes(element.get("class"))
    if "epigraph" not in classes:
        return

    source = None
    if footer := element.find("footer"):
        source = footer.get_text(strip=True)
        footer.decompose()

    text = element.get_text(strip=False)
    latex = context.formatter.epigraph(text=text, source=source)
    node = mark_processed(NavigableString(latex))
    context.mark_processed(element)
    context.suppress_children(element)
    element.replace_with(node)

render_exercise_details

render_exercise_details(
    element: Tag, context: RenderContext
) -> None

Render exercise blocks authored using

markup.

Source code in src/texsmith/adapters/plugins/material.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@renders(
    "details",
    phase=RenderPhase.POST,
    priority=56,
    name="material_exercise_details",
    nestable=False,
)
def render_exercise_details(element: Tag, context: RenderContext) -> None:
    """Render exercise blocks authored using <details> markup."""
    classes = gather_classes(element.get("class"))
    if "exercise" not in classes:
        return

    title = ""
    if summary := element.find("summary"):
        title = summary.get_text(strip=True)
        summary.decompose()

    _render_exercise(element, context, classes=classes, title=title or "")

render_exercise_div

render_exercise_div(
    element: Tag, context: RenderContext
) -> None

Render Material exercise admonitions into callouts.

Source code in src/texsmith/adapters/plugins/material.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
@renders(
    "div",
    phase=RenderPhase.POST,
    priority=51,
    name="material_exercise_admonition",
    nestable=False,
)
def render_exercise_div(element: Tag, context: RenderContext) -> None:
    """Render Material exercise admonitions into LaTeX callouts."""
    classes = gather_classes(element.get("class"))
    if "admonition" not in classes or "exercise" not in classes:
        return

    title = base_admonitions._extract_title(  # noqa: SLF001
        element.find("p", class_="admonition-title")
    )
    _render_exercise(element, context, classes=classes, title=title)