Skip to content

Commit

Permalink
✨ NEW: Add fieldlist extension (#455)
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
chrisjsewell committed Dec 6, 2021
1 parent cf18116 commit 86bf84d
Show file tree
Hide file tree
Showing 14 changed files with 622 additions and 4 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ repos:
args: [--config-file=setup.cfg]
additional_dependencies:
- sphinx~=3.3
- markdown-it-py>=1.0.0,<2.0.0
- mdit-py-plugins~=0.2.8
- markdown-it-py>=1.0.0,<3.0.0
- mdit-py-plugins~=0.3.0
files: >
(?x)^(
myst_parser/.*py|
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"dollarmath",
"amsmath",
"deflist",
"fieldlist",
"html_admonition",
"html_image",
"colon_fence",
Expand Down
3 changes: 2 additions & 1 deletion docs/sphinx/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ List of extensions:

- "amsmath": enable direct parsing of [amsmath](https://ctan.org/pkg/amsmath) LaTeX equations
- "colon_fence": Enable code fences using `:::` delimiters, [see here](syntax/colon_fence) for details
- "deflist"
- "deflist": Enable definition lists, [see here](syntax/definition-lists) for details
- "dollarmath": Enable parsing of dollar `$` and `$$` encapsulated math
- "html_admonition": Convert `<div class="admonition">` elements to sphinx admonition nodes, see the [HTML admonition syntax](syntax/html-admonition) for details
- "fieldlist": Enable field lists, [see here](syntax/fieldlists) for details
- "html_image": Convert HTML `<img>` elements to sphinx image nodes, see the [image syntax](syntax/images) for details
- "linkify": automatically identify "bare" web URLs and add hyperlinks
- "replacements": automatically convert some common typographic texts
Expand Down
91 changes: 91 additions & 0 deletions docs/syntax/optional.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ myst_enable_extensions = [
"colon_fence",
"deflist",
"dollarmath",
"fieldlist",
"html_admonition",
"html_image",
"linkify",
Expand Down Expand Up @@ -589,6 +590,96 @@ and are applied to markdown list items starting with `[ ]` or `[x]`:
- [ ] An item that needs doing
- [x] An item that is complete

(syntax/fieldlists)=
## Field Lists

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).

````md
:name only:
:name: body
:*Nested syntax*: Both name and body may contain **nested syntax**.
:Paragraphs: Since the field marker may be quite long, the second
and subsequent lines of a paragraph do not have to line up
with the first line.
:Alignment 1: If the field body starts on the first line...

Then the entire field body must be indented the same.
:Alignment 2:
If the field body starts on a subsequent line...

Then the indentation is always two spaces.
:Blocks:

As well as paragraphs, any block syntaxes may be used in a field body:

- Me
- Myself
- I

```python
print("Hello, world!")
```
````

:name only:
:name: body
:*Nested syntax*: Both name and body may contain **nested syntax**.
:Paragraphs: Since the field marker may be quite long, the second
and subsequent lines of a paragraph do not have to line up
with the first line.
:Alignment 1: If the field body starts on the first line...

Then the entire field body must be indented the same.
:Alignment 2:
If the field body starts on a subsequent line...

Then the indentation is always two spaces.
:Blocks:

As well as paragraphs, any block syntaxes may be used in a field body:

- Me
- Myself
- I

```python
print("Hello, world!")
```

A prominent use case of field lists is for use in API docstrings, as used in [Sphinx's docstring renderers](sphinx:python-domain):

````md
```{py:function} send_message(sender, priority)

Send a message to a recipient

:param str sender: The person sending the message
:param priority: The priority of the message, can be a number 1-5
:type priority: int
:return: the message id
:rtype: int
:raises ValueError: if the message_body exceeds 160 characters
```
````

```{py:function} send_message(sender, priority)

Send a message to a recipient

:param str sender: The person sending the message
:param priority: The priority of the message, can be a number 1-5
:type priority: int
:return: the message id
:rtype: int
:raises ValueError: if the message_body exceeds 160 characters
```

:::{note}
Currently `sphinx.ext.autodoc` does not support MyST, see [](howto/autodoc).
:::

(syntax/images)=

## Images
Expand Down
36 changes: 36 additions & 0 deletions myst_parser/docutils_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,42 @@ def render_dl(self, token: SyntaxTreeNode) -> None:
)
self.current_node += [error_msg]

def render_field_list(self, token: SyntaxTreeNode) -> None:
"""Render a field list."""
field_list = nodes.field_list(classes=["myst"])
self.add_line_and_source_path(field_list, token)
with self.current_node_context(field_list, append=True):
# raise ValueError(token.pretty(show_text=True))
children = (token.children or [])[:]
while children:
child = children.pop(0)
if not child.type == "fieldlist_name":
error_msg = self.reporter.error(
(
"Expected a fieldlist_name as a child of a field_list"
f", but found a: {child.type}"
),
# nodes.literal_block(content, content),
line=token_line(child),
)
self.current_node += [error_msg]
break
field = nodes.field()
self.add_line_and_source_path(field, child)
field_list += field
field_name = nodes.field_name()
self.add_line_and_source_path(field_name, child)
field += field_name
with self.current_node_context(field_name):
self.render_children(child)
field_body = nodes.field_body()
self.add_line_and_source_path(field_name, child)
field += field_body
if children and children[0].type == "fieldlist_body":
child = children.pop(0)
with self.current_node_context(field_body):
self.render_children(child)

def render_directive(self, token: SyntaxTreeNode) -> None:
"""Render special fenced code blocks as directives."""
first_line = token.info.split(maxsplit=1)
Expand Down
4 changes: 4 additions & 0 deletions myst_parser/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from mdit_py_plugins.colon_fence import colon_fence_plugin
from mdit_py_plugins.deflist import deflist_plugin
from mdit_py_plugins.dollarmath import dollarmath_plugin
from mdit_py_plugins.field_list import fieldlist_plugin
from mdit_py_plugins.footnote import footnote_plugin
from mdit_py_plugins.front_matter import front_matter_plugin
from mdit_py_plugins.myst_blocks import myst_block_plugin
Expand Down Expand Up @@ -61,6 +62,7 @@ def check_extensions(self, attribute, value):
"dollarmath",
"amsmath",
"deflist",
"fieldlist",
"html_admonition",
"html_image",
"colon_fence",
Expand Down Expand Up @@ -193,6 +195,8 @@ def default_parser(config: MdParserConfig) -> MarkdownIt:
md.use(amsmath_plugin)
if "deflist" in config.enable_extensions:
md.use(deflist_plugin)
if "fieldlist" in config.enable_extensions:
md.use(fieldlist_plugin)
if "tasklist" in config.enable_extensions:
md.use(tasklists_plugin)
if "substitution" in config.enable_extensions:
Expand Down
3 changes: 2 additions & 1 deletion myst_parser/mocking.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def __init__(
self.document = renderer.document
self.reporter = renderer.document.reporter
self.state_machine = state_machine
self.inliner = MockInliner(renderer, lineno)

class Struct:
document = self.document
Expand All @@ -95,7 +96,7 @@ class Struct:
title_styles: List[str] = []
section_level = max(renderer._level_to_elem)
section_bubble_up_kludge = False
inliner = MockInliner(renderer, lineno)
inliner = self.inliner

self.memo = Struct

Expand Down
4 changes: 4 additions & 0 deletions tests/test_sphinx/sourcedirs/fieldlist/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
extensions = ["myst_parser"]
exclude_patterns = ["_build"]

myst_enable_extensions = ["fieldlist"]
19 changes: 19 additions & 0 deletions tests/test_sphinx/sourcedirs/fieldlist/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
:orphan:

# Test

:field:

:*field*: content

```{py:function} send_message(sender, priority)
Send a message to a recipient
:param str sender: The person sending the message
:param priority: The priority of the message, can be a number 1-5
:type priority: int
:return: the message id
:rtype: int
:raises ValueError: if the message_body exceeds 160 characters
```
35 changes: 35 additions & 0 deletions tests/test_sphinx/test_sphinx_builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,38 @@ def test_mathjax_warning(
"overridden by myst-parser: 'other' -> 'tex2jax_process|mathjax_process|math|output_area'"
in warnings
)


@pytest.mark.sphinx(
buildername="html",
srcdir=os.path.join(SOURCE_DIR, "fieldlist"),
freshenv=True,
)
def test_fieldlist_extension(
app,
status,
warning,
get_sphinx_app_doctree,
get_sphinx_app_output,
remove_sphinx_builds,
):
"""test setting addition configuration values."""
app.build()
assert "build succeeded" in status.getvalue() # Build succeeded
warnings = warning.getvalue().strip()
assert warnings == ""

try:
get_sphinx_app_doctree(
app,
docname="index",
regress=True,
regress_ext=f".sphinx{sphinx.version_info[0]}.xml",
)
finally:
get_sphinx_app_output(
app,
filename="index.html",
regress_html=True,
regress_ext=f".sphinx{sphinx.version_info[0]}.html",
)
Loading

0 comments on commit 86bf84d

Please sign in to comment.