Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ NEW: Add fieldlist extension #455

Merged
merged 1 commit into from
Dec 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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