Skip to content

Commit 2c1e1f1

Browse files
✨ Add the eval-rst directive (#226)
This directive parses its contents as ReStructuredText, which integrates back into the rest of the document, e.g. for cross-referencing. It provides a solution for both including rST files into Markdown, and utilising the sphinx autodoc directives (which have hard-coded rST aspects). Co-authored-by: Chris Sewell <chrisj_sewell@hotmail.com>
1 parent 32f65d5 commit 2c1e1f1

18 files changed

Lines changed: 273 additions & 29 deletions

File tree

docs/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
myst_admonition_enable = True
7070
myst_html_img_enable = True
7171
myst_dmath_enable = True
72+
myst_url_schemes = ("http", "https", "mailto")
7273

7374

7475
def run_apidoc(app):
@@ -127,6 +128,7 @@ def run_apidoc(app):
127128
("py:class", "docutils.nodes.Element"),
128129
("py:class", "docutils.parsers.rst.directives.misc.Include"),
129130
("py:class", "docutils.nodes.document"),
131+
("py:class", "docutils.parsers.rst.Parser"),
130132
]
131133

132134

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ and extensibility of Sphinx with the simplicity and readability of Markdown.
1515
MyST has the following main features:
1616

1717
* **[A markdown parser for Sphinx](parse-with-sphinx)**. You can write your entire
18-
{doc}`Sphinx documentation <sphinx:usage/quickstart>` in markdown.
18+
{doc}`Sphinx documentation <sphinx:usage/quickstart>` in Markdown.
1919
* **[Call Sphinx directives and roles from within Markdown](syntax/directives)**,
2020
allowing you to extend your document via Sphinx extensions.
2121
* **[Extended Markdown syntax for useful rST features](extended-block-tokens)**, such

docs/using/howto.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,49 @@
22

33
This page describes several common uses of MyST parser and how to accomplish them.
44

5+
(howto/include-rst)=
6+
## Include rST files into a Markdown file
7+
8+
As explained in [this section](syntax/directives/parsing), all MyST directives will parse their content as Markdown.
9+
Therefore, using the conventional `include` directive, will parse the file contents as Markdown:
10+
11+
````md
12+
```{include} snippets/include-md.md
13+
```
14+
````
15+
16+
```{include} snippets/include-md.md
17+
```
18+
19+
To include rST, we must first "wrap" the directive in the [eval-rst directive](syntax/directives/parsing):
20+
21+
````md
22+
```{eval-rst}
23+
.. include:: snippets/include-rst.rst
24+
```
25+
````
26+
27+
```{eval-rst}
28+
.. include:: snippets/include-rst.rst
29+
```
30+
31+
(howto/autodoc)=
32+
## Use `sphinx.ext.autodoc` in Markdown files
33+
34+
The [sphinx.ext.autodoc](sphinx:sphinx.ext.autodoc) is currently hard-coded to write rST, and so can not be used as a conventional MyST directive.
35+
Instead the special [eval-rst directive](syntax/directives/parsing) can be used to "wrap" the autodoc directives:
36+
37+
```{eval-rst}
38+
.. autoclass:: myst_parser.mocking.MockRSTParser
39+
:show-inheritance:
40+
:members: parse
41+
```
42+
43+
As with other objects in MyST, this can then be referenced:
44+
45+
- Using the role `` {py:class}`myst_parser.mocking.MockRSTParser` ``: {py:class}`myst_parser.mocking.MockRSTParser`
46+
- Using the Markdown syntax `[MockRSTParser](myst_parser.mocking.MockRSTParser)`: [MockRSTParser](myst_parser.mocking.MockRSTParser)
47+
548
## Show backticks inside raw markdown blocks
649

750
If you'd like to show backticks inside of your markdown, you can do so by nesting them
@@ -28,7 +71,7 @@ hi
2871
```
2972
````
3073

31-
(autosectionlabel)=
74+
(howto/autosectionlabel)=
3275
## Automatically create targets for section headers
3376

3477
If you'd like to *automatically* generate targets for each of your section headers,

docs/using/intro.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ it in the Sphinx documentation engine.
1212
Installing the MyST parser provides access to two tools:
1313

1414
* A MyST-to-docutils parser and renderer.
15-
* A Sphinx parser that utilizes the above tool in building your documenation.
15+
* A Sphinx parser that utilizes the above tool in building your documentation.
1616

1717
To install the MyST parser, run the following in a
1818
[Conda environment](https://docs.conda.io) (recommended):
@@ -43,11 +43,11 @@ Naturally this site is generated with Sphinx and MyST!
4343

4444
:::{admonition,tip} You can use both MyST and reStructuredText
4545

46-
Activating the MyST parser will simply *enable* parsing markdown files with MyST, and the rST
47-
parser that ships with Sphinx by default will still work the same way. You can have
48-
combinations of both markdown and rST files in your documentation, and Sphinx will
49-
choose the right parser based on each file's extension. Sphinx features
50-
like cross-references will work just fine between the pages.
46+
Activating the MyST parser will simply *enable* parsing markdown files with MyST, and the rST parser that ships with Sphinx by default will still work the same way.
47+
You can have combinations of both markdown and rST files in your documentation, and Sphinx will choose the right parser based on each file's extension.
48+
Sphinx features like cross-references will work just fine between the pages.
49+
50+
You can even inject raw rST into Markdown files! (see [this explanation](syntax/directives/parsing))
5151
:::
5252

5353
:::{admonition,seealso} Want to add Jupyter Notebooks to your documentation?
@@ -146,9 +146,8 @@ For those who are familiar with reStructuredText, here is the equivalent in rST:
146146
```
147147
148148
Note that almost all documentation in the Sphinx ecosystem is written with
149-
reStructuredText (MyST is only a few months old). That means you'll likely see examples
150-
that have rST structure. You can modify any rST to work with MyST. Use this page,
151-
and [the syntax page](syntax) to help guide you.
149+
reStructuredText (MyST is only a few months old).
150+
That means you'll likely see examples that have rST structure. You can modify any rST to work with MyST. Use this page, and [the syntax page](syntax) to help guide you.
152151
````
153152

154153
As seen above, there are four main parts to consider when writing directives.

docs/using/snippets/include-md.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hallo I'm from a Markdown file, [with a reference](howto/autodoc).
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hallo I'm from an rST file, :ref:`with a reference <howto/autodoc>`.

docs/using/syntax.md

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@ and further details of a few major extensions from the CommonMark flavor of mark
1818
For an introduction to writing Directives and Roles with MyST markdown, see {ref}`intro/writing`.
1919
:::
2020

21-
% ```{seealso}
22-
% {ref}`MyST Extended AST Tokens API <api/tokens>`
23-
% ```
24-
2521
MyST builds on the tokens defined by markdown-it, to extend the syntax
2622
described in the [CommonMark Spec](https://spec.commonmark.org/0.29/), which the parser is tested against.
2723

@@ -389,11 +385,13 @@ print(f'my {a}nd line')
389385
```
390386
````
391387

388+
(syntax/directives/parsing)=
392389
### How directives parse content
393390

394-
Some directives parse the content that is in their content block. This
395-
means that MyST markdown can be written in the content areas of any directives written
396-
in MyST markdown. For example:
391+
Some directives parse the content that is in their content block.
392+
MyST parses this content **as Markdown**.
393+
394+
This means that MyST markdown can be written in the content areas of any directives written in MyST markdown. For example:
397395

398396
````md
399397
```{admonition} My markdown link
@@ -405,7 +403,7 @@ Here is [markdown link syntax](https://jupyter.org)
405403
Here is [markdown link syntax](https://jupyter.org)
406404
```
407405

408-
As a short-hand for directives that require no arguments, and when no paramter options are used (see below),
406+
As a short-hand for directives that require no arguments, and when no parameter options are used (see below),
409407
you may start the content directly after the directive name.
410408

411409
````md
@@ -416,10 +414,41 @@ you may start the content directly after the directive name.
416414
```{note} Notes require **no** arguments, so content can start here.
417415
```
418416

417+
For special cases, MySt also offers the `eval-rst` directive.
418+
This will parse the content **as ReStructuredText**:
419+
420+
````md
421+
```{eval-rst}
422+
.. figure:: img/fun-fish.png
423+
:width: 100px
424+
:name: rst-fun-fish
425+
426+
Party time!
427+
428+
A reference from inside: ref:`rst-fun-fish`
429+
430+
A reference from outside: :ref:`syntax/directives/parsing`
431+
```
432+
````
433+
434+
```{eval-rst}
435+
.. figure:: img/fun-fish.png
436+
:width: 100px
437+
:name: rst-fun-fish
438+
439+
Party time!
440+
441+
A reference from inside: ref:`rst-fun-fish`
442+
443+
A reference from outside: :ref:`syntax/directives/parsing`
444+
```
445+
446+
Note how the text is integrated into the rest of the document, so we can also reference [party fish](rst-fun-fish) anywhere else in the documentation.
447+
419448
### Nesting directives
420449

421-
You can nest directives by ensuring that the ticklines corresponding to the
422-
outermost directive are longer than the ticklines for the inner directives.
450+
You can nest directives by ensuring that the tick-lines corresponding to the
451+
outermost directive are longer than the tick-lines for the inner directives.
423452
For example, nest a warning inside a note block like so:
424453

425454
`````md
@@ -912,7 +941,7 @@ to them.
912941
```{tip}
913942
If you'd like to *automatically* generate targets for each of your section headers,
914943
check out the [`autosectionlabel`](https://www.sphinx-doc.org/en/master/usage/extensions/autosectionlabel.html)
915-
sphinx feature. See {ref}`autosectionlabel` for more details.
944+
sphinx feature. See {ref}`howto/autosectionlabel` for more details.
916945
```
917946

918947
Target headers are defined with this syntax:

myst_parser/docutils_renderer.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
MockStateMachine,
3232
MockingError,
3333
MockIncludeDirective,
34+
MockRSTParser,
3435
)
3536
from .parse_directives import parse_directive_text, DirectiveParsingError
3637
from .parse_html import HTMLImgParser
@@ -363,7 +364,22 @@ def render_fence(self, token):
363364
token.info = token.info.strip()
364365
language = token.info.split()[0] if token.info else ""
365366

366-
if (
367+
if not self.config.get("commonmark_only", False) and language == "{eval-rst}":
368+
# copy necessary elements (source, line no, env, reporter)
369+
newdoc = make_document()
370+
newdoc["source"] = self.document["source"]
371+
newdoc.settings = self.document.settings
372+
newdoc.reporter = self.reporter
373+
# pad the line numbers artificially so they offset with the fence block
374+
pseudosource = ("\n" * token.map[0]) + token.content
375+
# actually parse the rst into our document
376+
MockRSTParser().parse(pseudosource, newdoc)
377+
for node in newdoc:
378+
if node["names"]:
379+
self.document.note_explicit_target(node, node)
380+
self.current_node.extend(newdoc[:])
381+
return
382+
elif (
367383
not self.config.get("commonmark_only", False)
368384
and language.startswith("{")
369385
and language.endswith("}")

myst_parser/mocking.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import List, Optional, Tuple, Type
55

66
from docutils import nodes
7-
from docutils.parsers.rst import Directive, DirectiveError
7+
from docutils.parsers.rst import Directive, DirectiveError, Parser as RSTParser
88
from docutils.parsers.rst.directives.misc import Include
99
from docutils.parsers.rst.languages import get_language
1010
from docutils.parsers.rst.states import Inliner, RSTStateMachine, Body
@@ -438,3 +438,21 @@ def add_name(self, node):
438438
del node["name"]
439439
node["names"].append(name)
440440
self.renderer.document.note_explicit_target(node, node)
441+
442+
443+
class MockRSTParser(RSTParser):
444+
"""RSTParser which avoids a negative side effect."""
445+
446+
def parse(self, inputstring: str, document: nodes.document):
447+
"""Parse the input to populate the document AST."""
448+
from docutils.parsers.rst import roles
449+
450+
should_restore = False
451+
if "" in roles._roles:
452+
should_restore = True
453+
blankrole = roles._roles[""]
454+
455+
super().parse(inputstring, document)
456+
457+
if should_restore:
458+
roles._roles[""] = blankrole

setup.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,12 @@
5353
"beautifulsoup4",
5454
],
5555
# Note: This is only required for internal use
56-
"rtd": ["sphinxcontrib-bibtex", "ipython", "sphinx-book-theme", "sphinx_tabs"],
56+
"rtd": [
57+
"sphinxcontrib-bibtex",
58+
"ipython",
59+
"sphinx-book-theme>=0.0.36",
60+
"sphinx_tabs",
61+
],
5762
},
5863
zip_safe=True,
5964
)

0 commit comments

Comments
 (0)