Skip to main content

A syntax-example directive that shows markup as source and rendered result.

Project description

sphinx-syntax-example

PyPI Docs CI

A Sphinx extension adding the syntax-example directive, which shows a block of markup as both its raw source and its rendered result.

The directive renders its content twice, stacked inside one framed box: a syntax-highlighted source pane showing what to type, and a render pane showing what it produces. It is useful for documentation that teaches a markup syntax, where readers need to see the input alongside the output.

Installation

Requires Python 3.11+ and Sphinx 7.2+.

pip install sphinx-syntax-example

Then add it to the extensions list in your conf.py:

extensions = [
    # ...
    "sphinx_syntax_example",
]

The extension ships a small, theme-agnostic stylesheet that is registered automatically on HTML builds (it defines its own colours with fallbacks that pick up the active theme's variables when present, and looks right on Furo in both light and dark modes).

Usage

Write a syntax-example directive with the markup you want to demonstrate as its content. An optional argument sets the title shown above the block (defaulting to Example):

.. syntax-example:: An optional title

   Any **reStructuredText** or MyST markup here.

The block above produces a framed box titled An optional title, containing the highlighted source Any **reStructuredText** or MyST markup here. and, below it, that markup rendered (with reStructuredText in bold).

Options and arguments

Part Description
(argument) The title shown as a rubric above the block. Defaults to Example; inline markup is parsed.
:highlight: Override the source pane's highlight language (for example :highlight: python).

If :highlight: is omitted, the language is inferred from the document's format via the project's configured source-suffix mapping (sphinx.util.get_filetype over source_suffix — the same knowledge Sphinx's router uses to pick the parser). A Markdown document yields a myst lexer when one is registered (as myst-parser registers) else markdown, and everything else yields rst. A :highlight: value naming a lexer added with app.add_lexer — one that exists only for your project, and so is invisible to Pygments' global registry — is honoured too. An unknown value falls back to the inferred language (with a verbose-level note) rather than failing a strict -W build.

Numbering

Config value Default Description
syntax_example_numbering False Number the examples in each document.

Titles are unnumbered by default. Set syntax_example_numbering = True in conf.py to have the default title become a numbered label, with any argument carried as a subtitle:

.. syntax-example::          →  Example 1
.. syntax-example:: Custom   →  Example 2: Custom

The counter restarts at 1 in each document, and each registered directive name counts independently, so a downstream alias never shares the run. Numbering only decorates a non-empty default title: a subclass whose default_title is "" is unaffected by the config value.

Either way the extension keeps no cross-document state. The counter lives in env.temp_data, which Sphinx replaces for each source file it reads — and a document is exactly the unit of both parallel reading and incremental rebuilding, so re-reading one changed file reproduces the numbers of a full build and nothing crosses between parallel workers.

Subclassing

Downstream packages that want an aliased directive (for example .. need-example:: with its own label, a different class prefix, or no title at all) subclass SyntaxExampleDirective and register the subclass under the alias name in a setup(app) of their own. Behaviour is customised through small named seams and class attributes, never by re-implementing run:

  • the class attributes default_title, wrapper_classes, source_classes and render_classes — the single source of the CSS classes on the three parts, applied by run itself;
  • default_language — how the source language is inferred;
  • format_title — the title/numbering seam (return None to drop the title, as a numbering-free myst-example would, or ignore syntax_example_numbering to number on your own terms);
  • build_title_node — how the resolved title becomes a node (return None to suppress the rubric element entirely);
  • build_wrapper_node and build_render_node — the node type and any extra attributes on the two containers, for a subclass needing something other than a plain container;
  • source_text — what the source pane shows;
  • render_into — what is nested-parsed into the render pane.

Two helpers exist for those overrides to call: per_document_number() returns the next number in the current document (keyed by the registered directive name, so aliases count independently), and nested_parse_text(text, container) parses a string of markup into a node using the host document's own parser.

source_text and render_into together are the seam for a "shown source differs from rendered output" directive: override source_text to return the shown text, and render_into to render the alternative output.

from docutils.parsers.rst import directives

from sphinx_syntax_example import SyntaxExampleDirective


class NumberedExample(SyntaxExampleDirective):
    """Numbered unconditionally, regardless of syntax_example_numbering."""

    def format_title(self, raw_title):
        return f"{raw_title or 'Example'} {self.per_document_number()}"


class AltOutputExample(SyntaxExampleDirective):
    """Shows verbatim source, renders something else.

    Takes the alternative markup from an ``:alt-output:`` option.
    """

    option_spec = {
        **SyntaxExampleDirective.option_spec,
        "alt-output": directives.unchanged,
    }

    def render_into(self, container):
        alternative = self.options.get("alt-output")
        if alternative is None:
            super().render_into(container)
        else:
            self.nested_parse_text(alternative, container)


def setup(app):
    app.add_directive("numbered-example", NumberedExample)
    app.add_directive("alt-example", AltOutputExample)

build_wrapper_node and build_render_node exist for renderers that must not emit a docutils container. A myst-parser-style example returns a sphinx-design div (is_div=True, design_component="div") instead, so that extension's container visitor writes <div class="myst-example docutils"> without the container class — keeping Bootstrap- and pydata-theme .container layout rules off the frame:

from docutils import nodes

from sphinx_syntax_example import SyntaxExampleDirective


class DivExample(SyntaxExampleDirective):
    wrapper_classes = ("myst-example",)
    render_classes = ("myst-example-render",)

    def build_wrapper_node(self):
        return nodes.container(is_div=True, design_component="div")

    def build_render_node(self):
        return nodes.container(is_div=True, design_component="div")

Return the bare node: these seams choose the node type and its extra attributes, and run applies the class attributes to whatever comes back (merging with any class the factory set). So an override neither has to repeat the classes nor can accidentally drop them and render unstyled.

The full override surface is documented in the module docstring.

The bespoke "example" directives shipped by sphinx-needs and myst-parser (each rendering source next to output) can be replaced by a subclass of this directive.

Contributing

See CONTRIBUTING.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sphinx_syntax_example-0.2.0.tar.gz (28.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sphinx_syntax_example-0.2.0-py3-none-any.whl (16.0 kB view details)

Uploaded Python 3

File details

Details for the file sphinx_syntax_example-0.2.0.tar.gz.

File metadata

  • Download URL: sphinx_syntax_example-0.2.0.tar.gz
  • Upload date:
  • Size: 28.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for sphinx_syntax_example-0.2.0.tar.gz
Algorithm Hash digest
SHA256 616d8862aec6e7818efcb2fdbd3cd9bcd8e5be47cc3d21300f9557719f7b07a9
MD5 ca434b8932fb2dc5b5de75d7f1ef72b8
BLAKE2b-256 e2e4fe443fdf8a13e2a53daa309eaaa983996a614990b4d2ace37f6804ce928d

See more details on using hashes here.

Provenance

The following attestation bundles were made for sphinx_syntax_example-0.2.0.tar.gz:

Publisher: tests.yml on sphinx-extensions2/sphinx-syntax-example

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sphinx_syntax_example-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for sphinx_syntax_example-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e2ff213562e5c2148f2bd76a904dd90153329e83ef3883b2949bcdaaff9af4b4
MD5 88af62866dd64ca159939feb4dc3642e
BLAKE2b-256 86ffa6c6ab520fefd9f786b1c86480524929ed5a02077e4d2dc4a437a15a76ee

See more details on using hashes here.

Provenance

The following attestation bundles were made for sphinx_syntax_example-0.2.0-py3-none-any.whl:

Publisher: tests.yml on sphinx-extensions2/sphinx-syntax-example

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page