♻️ REFACTOR: docs example directive via sphinx-syntax-example - #1181
Merged
Conversation
Replace the bespoke `myst-example` docs directive with a thin subclass of
the published `sphinx_syntax_example.SyntaxExampleDirective`, and adopt the
canonical `syntax-example` name used across the ecosystem.
The old implementation built a synthetic MyST string wrapping the content in
sphinx-design `{div}` fences and re-parsed it, growing its backtick fence to
survive nested code fences. The base class builds the nodes directly, so the
re-serialisation is gone and content is attributed to its real source lines.
The subclass keeps the previous behaviour exactly: no title/rubric, no
arguments, the same `highlight` and `alt-output` option names, and the
sphinx-design `is_div` flag on the containers so the emitted HTML keeps its
`<div class="... docutils">` form without the `container` class that would
attract the theme's Bootstrap layout rules.
The wrapper/source/render CSS classes are renamed to the base class defaults
(`syntax-example*`), with `docs/_static/local.css` and the `myst-to-html`
directive's shared frame updated to match. The packaged stylesheet is not
loaded — the extension is deliberately not in `extensions`, only the class is
imported — so `local.css` remains the single source of styling.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Replaces the bespoke
myst-exampledocs directive (myst_parser/_docs.py) with athin subclass of the published
sphinx-syntax-example0.2.0,and renames the directive — and its CSS classes — to the canonical
syntax-example.This is a docs-tooling change only. Nothing in the shipped parser changes: the
only touched runtime module is
myst_parser/_docs.py, which is imported solelyby
docs/conf.py(it is excluded from the autodoc2 API docs and omitted fromcoverage).
Why
Reuse over bespoke. The "show this markup as source and as rendered output"
directive is generic; it is now maintained as a standalone package rather than as
~35 lines of string templating inside this repo.
It removes a re-serialisation hack. The old implementation did not build
nodes — it built a synthetic MyST document that wrapped the content in
sphinx-design
{div}fences and re-parsed it, growing its own backtick fence(
```→````→ …) until it was longer than any fence in the content, justso nested code fences would survive the round trip:
The base class builds the container / literal-block / container subtree directly,
so that whole class of quoting problem disappears. As a side effect source-line
attribution improves: content is now nested-parsed at its real offset instead of
inside a re-generated string, so warnings inside an example point at the line the
author actually wrote.
One canonical directive name across the ecosystem.
syntax-exampleis thename used by the published extension, by sphinx-needs, and by the ubCode docs; the
ubCode Rust engine recognises
syntax-examplenatively. Aligning here means thesame markup works in all of them, and a reader who learns the directive in one
project already knows it in the next.
Exact parity
The subclass pins every behaviour the old directive had:
default_title = ""(the base suppresses the rubric element entirely, rather than emitting an empty one)optional_arguments = 0— the base allows an optional title argument, which would otherwise let a stray argument silently create a rubric. With it set to 0, MyST folds any opening-line text into the content instead:highlight:optionoption_spec:alt-output:optionoption_spec;render_into()hands that value to the base class'nested_parse_text()helper, which parses it with the host document's parser — MyST here (used once,docs/syntax/typography.md)mystMystLexeris registered viaapp.add_lexer(), and the base class' lexer probe consults Sphinx'sadd_lexerregistries as well as Pygments', so a Markdown document resolves tomyston its owncontainerclassbuild_wrapper_node()andbuild_render_node()returnnodes.container(is_div=True, design_component="div").is_divis the flag sphinx-design's overridden container visitor keys on; without it the divs would render asclass="docutils container"and pick up the theme's Bootstrap.containerlayout rules.design_componentis inert here — read only by sphinx-design'sis_component()for tab-set/grid/card children — and is set to match what{div}producedMystLexer,MystToHTMLDirectiveand every other_docs.pymember are otherwiseuntouched.
One deliberate behaviour change: a bodyless
syntax-examplenow hard-errors(the base class calls
assert_has_content()), where the old directive rendered anempty frame. No current usage is bodyless.
The subclass is only four members: two one-line node factories,
render_into()for
:alt-output:, and the class attributes. It re-implements nothing — per thepackage's contract the node factories return bare nodes and
run()applieswrapper_classes/render_classesto them, so the CSS classes cannot drift outof sync with the stylesheet.
The rename
myst-example→syntax-example, applied consistently in three places that mustmove together, because
MystToHTMLDirectiveshares the frame styling via the CSSclass:
docs/conf.pyregisters the subclass assyntax-example.docs/**/*.md(124 active + the 2 commented-out ones, sore-enabling them later still works).
syntax-example,syntax-example-source,syntax-example-render), with the selectors indocs/_static/local.cssrenamed to match and themyst-to-htmldirective nowemitting
::::syntax-example.The packaged extension's own stylesheet is not loaded:
sphinx_syntax_exampleis deliberately kept out of the
extensionslist and only the class is imported,so
local.cssremains the single source of styling and there is nodouble-styling despite the shared class names.
local.cssrule bodies are unchanged, with one necessary exception. The baseclass puts the source class directly on the
literal_block, so the source paneis the
highlight-<lang>div rather than a wrapper around it, and the oldchild-combinator selector no longer matches:
Same declaration, re-pointed at the new DOM, keeping the source pane flush inside
the frame (
local.cssloads last).Verification
Built from a clean worktree with
pip install -e .[linkify,rtd]:sphinx-build -nW --keep-goingsucceeds for html, text, man and latex (thefour strict builders between CI and RTD), with zero warnings.
ruff check,ruff formatandmypyclean.sphinx-syntax-example~=0.2.0isadded to the
rtdextra and to the mypy pre-commit hook'sadditional_dependencies(the package shipspy.typed).Output compared against a build of unmodified
master, in an environment pinnedto the same docutils and Sphinx:
class rename and the one collapsed wrapper level — 124 from the directive plus
3 from
myst-to-html, with the same per-file distribution.highlight-myst, 6highlight-latex,5
highlight-html.containerclass on anysyntax-examplediv.:alt-output:example still shows* * *as its source and an<hr class="docutils">as its rendered output.intro.html'smyst-to-htmlblocks are unchanged and keep their frame.blank line the old redundant source-pane wrapper used to produce.
Note for reviewers
Because
syntax-exampleis now registered directly bydocs/conf.py, addingsphinx_syntax_exampleto theextensionslist later would register thedirective twice. If we ever want the packaged stylesheet, the local registration
should be dropped at the same time.