Skip to content

Commit 86bf84d

Browse files
authored
✨ NEW: Add fieldlist extension (#455)
Field lists are mappings from field names to field bodies, based on the [reStructureText syntax](https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#field-lists). A prominent use case of field lists is for use in API docstrings, as used in [Sphinx's docstring renderers](https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#the-python-domain). This should hopefully pave the way for use with `sphinx.ext.autodoc`
1 parent cf18116 commit 86bf84d

File tree

14 files changed

+622
-4
lines changed

14 files changed

+622
-4
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ repos:
5252
args: [--config-file=setup.cfg]
5353
additional_dependencies:
5454
- sphinx~=3.3
55-
- markdown-it-py>=1.0.0,<2.0.0
56-
- mdit-py-plugins~=0.2.8
55+
- markdown-it-py>=1.0.0,<3.0.0
56+
- mdit-py-plugins~=0.3.0
5757
files: >
5858
(?x)^(
5959
myst_parser/.*py|

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"dollarmath",
7676
"amsmath",
7777
"deflist",
78+
"fieldlist",
7879
"html_admonition",
7980
"html_image",
8081
"colon_fence",

docs/sphinx/reference.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ List of extensions:
4747

4848
- "amsmath": enable direct parsing of [amsmath](https://ctan.org/pkg/amsmath) LaTeX equations
4949
- "colon_fence": Enable code fences using `:::` delimiters, [see here](syntax/colon_fence) for details
50-
- "deflist"
50+
- "deflist": Enable definition lists, [see here](syntax/definition-lists) for details
5151
- "dollarmath": Enable parsing of dollar `$` and `$$` encapsulated math
5252
- "html_admonition": Convert `<div class="admonition">` elements to sphinx admonition nodes, see the [HTML admonition syntax](syntax/html-admonition) for details
53+
- "fieldlist": Enable field lists, [see here](syntax/fieldlists) for details
5354
- "html_image": Convert HTML `<img>` elements to sphinx image nodes, see the [image syntax](syntax/images) for details
5455
- "linkify": automatically identify "bare" web URLs and add hyperlinks
5556
- "replacements": automatically convert some common typographic texts

docs/syntax/optional.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ myst_enable_extensions = [
3030
"colon_fence",
3131
"deflist",
3232
"dollarmath",
33+
"fieldlist",
3334
"html_admonition",
3435
"html_image",
3536
"linkify",
@@ -589,6 +590,96 @@ and are applied to markdown list items starting with `[ ]` or `[x]`:
589590
- [ ] An item that needs doing
590591
- [x] An item that is complete
591592

593+
(syntax/fieldlists)=
594+
## Field Lists
595+
596+
Field lists are mappings from field names to field bodies,
597+
based on the [reStructureText syntax](https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#field-lists).
598+
599+
````md
600+
:name only:
601+
:name: body
602+
:*Nested syntax*: Both name and body may contain **nested syntax**.
603+
:Paragraphs: Since the field marker may be quite long, the second
604+
and subsequent lines of a paragraph do not have to line up
605+
with the first line.
606+
:Alignment 1: If the field body starts on the first line...
607+
608+
Then the entire field body must be indented the same.
609+
:Alignment 2:
610+
If the field body starts on a subsequent line...
611+
612+
Then the indentation is always two spaces.
613+
:Blocks:
614+
615+
As well as paragraphs, any block syntaxes may be used in a field body:
616+
617+
- Me
618+
- Myself
619+
- I
620+
621+
```python
622+
print("Hello, world!")
623+
```
624+
````
625+
626+
:name only:
627+
:name: body
628+
:*Nested syntax*: Both name and body may contain **nested syntax**.
629+
:Paragraphs: Since the field marker may be quite long, the second
630+
and subsequent lines of a paragraph do not have to line up
631+
with the first line.
632+
:Alignment 1: If the field body starts on the first line...
633+
634+
Then the entire field body must be indented the same.
635+
:Alignment 2:
636+
If the field body starts on a subsequent line...
637+
638+
Then the indentation is always two spaces.
639+
:Blocks:
640+
641+
As well as paragraphs, any block syntaxes may be used in a field body:
642+
643+
- Me
644+
- Myself
645+
- I
646+
647+
```python
648+
print("Hello, world!")
649+
```
650+
651+
A prominent use case of field lists is for use in API docstrings, as used in [Sphinx's docstring renderers](sphinx:python-domain):
652+
653+
````md
654+
```{py:function} send_message(sender, priority)
655+
656+
Send a message to a recipient
657+
658+
:param str sender: The person sending the message
659+
:param priority: The priority of the message, can be a number 1-5
660+
:type priority: int
661+
:return: the message id
662+
:rtype: int
663+
:raises ValueError: if the message_body exceeds 160 characters
664+
```
665+
````
666+
667+
```{py:function} send_message(sender, priority)
668+
669+
Send a message to a recipient
670+
671+
:param str sender: The person sending the message
672+
:param priority: The priority of the message, can be a number 1-5
673+
:type priority: int
674+
:return: the message id
675+
:rtype: int
676+
:raises ValueError: if the message_body exceeds 160 characters
677+
```
678+
679+
:::{note}
680+
Currently `sphinx.ext.autodoc` does not support MyST, see [](howto/autodoc).
681+
:::
682+
592683
(syntax/images)=
593684

594685
## Images

myst_parser/docutils_renderer.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,42 @@ def render_dl(self, token: SyntaxTreeNode) -> None:
942942
)
943943
self.current_node += [error_msg]
944944

945+
def render_field_list(self, token: SyntaxTreeNode) -> None:
946+
"""Render a field list."""
947+
field_list = nodes.field_list(classes=["myst"])
948+
self.add_line_and_source_path(field_list, token)
949+
with self.current_node_context(field_list, append=True):
950+
# raise ValueError(token.pretty(show_text=True))
951+
children = (token.children or [])[:]
952+
while children:
953+
child = children.pop(0)
954+
if not child.type == "fieldlist_name":
955+
error_msg = self.reporter.error(
956+
(
957+
"Expected a fieldlist_name as a child of a field_list"
958+
f", but found a: {child.type}"
959+
),
960+
# nodes.literal_block(content, content),
961+
line=token_line(child),
962+
)
963+
self.current_node += [error_msg]
964+
break
965+
field = nodes.field()
966+
self.add_line_and_source_path(field, child)
967+
field_list += field
968+
field_name = nodes.field_name()
969+
self.add_line_and_source_path(field_name, child)
970+
field += field_name
971+
with self.current_node_context(field_name):
972+
self.render_children(child)
973+
field_body = nodes.field_body()
974+
self.add_line_and_source_path(field_name, child)
975+
field += field_body
976+
if children and children[0].type == "fieldlist_body":
977+
child = children.pop(0)
978+
with self.current_node_context(field_body):
979+
self.render_children(child)
980+
945981
def render_directive(self, token: SyntaxTreeNode) -> None:
946982
"""Render special fenced code blocks as directives."""
947983
first_line = token.info.split(maxsplit=1)

myst_parser/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from mdit_py_plugins.colon_fence import colon_fence_plugin
1717
from mdit_py_plugins.deflist import deflist_plugin
1818
from mdit_py_plugins.dollarmath import dollarmath_plugin
19+
from mdit_py_plugins.field_list import fieldlist_plugin
1920
from mdit_py_plugins.footnote import footnote_plugin
2021
from mdit_py_plugins.front_matter import front_matter_plugin
2122
from mdit_py_plugins.myst_blocks import myst_block_plugin
@@ -61,6 +62,7 @@ def check_extensions(self, attribute, value):
6162
"dollarmath",
6263
"amsmath",
6364
"deflist",
65+
"fieldlist",
6466
"html_admonition",
6567
"html_image",
6668
"colon_fence",
@@ -193,6 +195,8 @@ def default_parser(config: MdParserConfig) -> MarkdownIt:
193195
md.use(amsmath_plugin)
194196
if "deflist" in config.enable_extensions:
195197
md.use(deflist_plugin)
198+
if "fieldlist" in config.enable_extensions:
199+
md.use(fieldlist_plugin)
196200
if "tasklist" in config.enable_extensions:
197201
md.use(tasklists_plugin)
198202
if "substitution" in config.enable_extensions:

myst_parser/mocking.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def __init__(
8787
self.document = renderer.document
8888
self.reporter = renderer.document.reporter
8989
self.state_machine = state_machine
90+
self.inliner = MockInliner(renderer, lineno)
9091

9192
class Struct:
9293
document = self.document
@@ -95,7 +96,7 @@ class Struct:
9596
title_styles: List[str] = []
9697
section_level = max(renderer._level_to_elem)
9798
section_bubble_up_kludge = False
98-
inliner = MockInliner(renderer, lineno)
99+
inliner = self.inliner
99100

100101
self.memo = Struct
101102

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
extensions = ["myst_parser"]
2+
exclude_patterns = ["_build"]
3+
4+
myst_enable_extensions = ["fieldlist"]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
:orphan:
2+
3+
# Test
4+
5+
:field:
6+
7+
:*field*: content
8+
9+
```{py:function} send_message(sender, priority)
10+
11+
Send a message to a recipient
12+
13+
:param str sender: The person sending the message
14+
:param priority: The priority of the message, can be a number 1-5
15+
:type priority: int
16+
:return: the message id
17+
:rtype: int
18+
:raises ValueError: if the message_body exceeds 160 characters
19+
```

tests/test_sphinx/test_sphinx_builds.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,3 +399,38 @@ def test_mathjax_warning(
399399
"overridden by myst-parser: 'other' -> 'tex2jax_process|mathjax_process|math|output_area'"
400400
in warnings
401401
)
402+
403+
404+
@pytest.mark.sphinx(
405+
buildername="html",
406+
srcdir=os.path.join(SOURCE_DIR, "fieldlist"),
407+
freshenv=True,
408+
)
409+
def test_fieldlist_extension(
410+
app,
411+
status,
412+
warning,
413+
get_sphinx_app_doctree,
414+
get_sphinx_app_output,
415+
remove_sphinx_builds,
416+
):
417+
"""test setting addition configuration values."""
418+
app.build()
419+
assert "build succeeded" in status.getvalue() # Build succeeded
420+
warnings = warning.getvalue().strip()
421+
assert warnings == ""
422+
423+
try:
424+
get_sphinx_app_doctree(
425+
app,
426+
docname="index",
427+
regress=True,
428+
regress_ext=f".sphinx{sphinx.version_info[0]}.xml",
429+
)
430+
finally:
431+
get_sphinx_app_output(
432+
app,
433+
filename="index.html",
434+
regress_html=True,
435+
regress_ext=f".sphinx{sphinx.version_info[0]}.html",
436+
)

0 commit comments

Comments
 (0)