Skip to content
Open
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
94 changes: 94 additions & 0 deletions mdformat_myst/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,70 @@
from mdit_py_plugins.dollarmath import dollarmath_plugin
from mdit_py_plugins.myst_blocks import myst_block_plugin
from mdit_py_plugins.myst_role import myst_role_plugin
from mdit_py_plugins.container import container_plugin

from mdformat_myst._directives import fence, render_fence_html

_TARGET_PATTERN = re.compile(r"^\s*\(.+\)=\s*$")
_ROLE_NAME_PATTERN = re.compile(r"({[a-zA-Z0-9_\-+:]+})")
_YAML_HEADER_PATTERN = re.compile(r"(?m)(^:\w+: .*$\n?)+|^---$\n(?s:.).*\n---\n")

container_names = [
"admonition",
"attention",
"caution",
"danger",
"div",
"dropdown",
"embed",
"error",
"exercise",
"exercise-end",
"exercise-start",
"figure",
"glossary",
"grid",
"grid-item",
"grid-item-card",
"hint",
"image",
"important",
"include",
"index",
"literal-include",
"margin",
"math",
"note",
"prf:algorithm",
"prf:assumption",
"prf:axiom",
"prf:conjecture",
"prf:corollary",
"prf:criterion",
"prf:definition",
"prf:example",
"prf:lemma",
"prf:observation",
"prf:proof",
"prf:property",
"prf:proposition",
"prf:remark",
"prf:theorem",
"seealso",
"show-index",
"sidebar",
"solution",
"solution-end",
"solution-start",
"span",
"tab-item",
"tab-set",
"table",
"tip",
"todo",
"topics",
"warning",
]


def update_mdit(mdit: MarkdownIt) -> None:
Expand Down Expand Up @@ -52,6 +111,36 @@ def update_mdit(mdit: MarkdownIt) -> None:
mdit.add_render_rule("fence", render_fence_html)
mdit.add_render_rule("code_block", render_fence_html)

for name in container_names:
container_plugin(mdit, name="{" + name + "}", marker=":")
Comment on lines +114 to +115
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would want us to avoid the performance impact of creating each of these handlers

If we were to keep a hardcoded list, I would prefer to validate rather than search for specific matches (e.g. extract the directive name with regex like [\w\-:]+, then if present in the set of allowed names, proceed) - or use regex conditional to search all variations

But that's all blocked by the limitation of container_plugin being fixed strings only (no regex): https://github.com/executablebooks/mdit-py-plugins/blob/93fbf645108394bb10ecd392383e96c4e8902f30/mdit_py_plugins/container/index.py



def container_renderer(
node: RenderTreeNode, context: RenderContext, *args, **kwargs
) -> str:
children = node.children
paragraphs = []
if children:
# Look at the tokens forming the first paragraph and see if
# they form a YAML header. This could be stricter: there
# should be exactly three tokens: paragraph open, YAML
# header, paragraph end.
tokens = children[0].to_tokens()
if all(
token.type in {'paragraph_open', 'paragraph_close'} or
_YAML_HEADER_PATTERN.fullmatch(token.content)
for token in tokens
):
paragraphs.append('\n'.join(token.content.strip()
for token in tokens
if token.content))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please run tox locally? This looks like this should be formatted by black and part of the issue in CI. Let me know if you run into any trouble with your local setup

# and skip that first paragraph
children = children[1:]

paragraphs.extend(child.render(context) for child in children)

return node.markup + node.info + "\n" + "\n\n".join(paragraphs) + "\n" + node.markup
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have something like this working locally:

_YAML_METADATA_PATTERN = re.compile(r"^:\w[\w-]*:\s+.*$", re.MULTILINE)

def container_renderer(
    node: RenderTreeNode, context: RenderContext, *args, **kwargs
) -> str:
    children = node.children or []
    parts = []

    if children and children[0].type == "paragraph":
        tokens = children[0].to_tokens()
        yaml_lines = []

        for token in tokens:
            if token.type in {"paragraph_open", "paragraph_close"}:
                continue
            if token.content and _YAML_METADATA_PATTERN.match(token.content.strip()):
                yaml_lines.append(token.content.strip())
            elif yaml_lines:
                break  # Stop at first non-YAML line

        if yaml_lines:
            parts.append("\n".join(yaml_lines))
            children = children[1:]

    if children:
        rendered_children = "\n\n".join(child.render(context) for child in children)
        parts.append(rendered_children)

    if parts:
        content = "\n\n".join(parts)
        return f"{node.markup}{node.info}\n\n{content}\n\n{node.markup}"
    return f"{node.markup}{node.info}\n{node.markup}"

...

for directive in CONTAINER_DIRECTIVES: # Just minor nits
    RENDERERS[f"container_{{{directive}}}"] = container_renderer



def _role_renderer(node: RenderTreeNode, context: RenderContext) -> str:
role_name = "{" + node.meta["name"] + "}"
Expand Down Expand Up @@ -155,4 +244,9 @@ def _escape_text(text: str, node: RenderTreeNode, context: RenderContext) -> str
"math_block": _math_block_renderer,
"fence": fence,
}


for name in container_names:
RENDERERS["container_{" + name + "}"] = container_renderer

POSTPROCESSORS = {"paragraph": _escape_paragraph, "text": _escape_text}
91 changes: 91 additions & 0 deletions tests/data/fixtures.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,3 +504,94 @@ MyST directive, no opts or content
```{some-directive} args
```
.

:::{admonition} MyST colon fenced directive with a title
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding tests!

It looks like you may have some missing formatting, the repeating pattern is:

<title>
.
<pre>
.
<post>
.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So something like:

MyST colon fenced directive with title
.
:::{admonition} MyST colon fenced directive with a title
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
:::
.
:::{admonition} MyST colon fenced directive with a title

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.

:::
.

MyST colon fenced directive with metadata
.
:::{admonition} MyST colon fenced directive with metadata
:class: foo
:truc: bla
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
:::
.
:::{admonition} MyST colon fenced directive with metadata

:class: foo
:truc: bla
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.

:::
.

MyST colon fenced directive with nested directive
.
::::{admonition} Parent directive
:::{image} foo.png
:class: foo
:alt: An image
:::
::::
.
::::{admonition} Parent directive

:::{image} foo.png

:class: foo
:alt: An image

:::

::::
.

MyST colon fenced directive with multiple nested admonitions
.
::::{admonition} Multiple nested admonitions
:::{admonition}
First nested admonition content.
:::
:::{admonition}
Second nested admonition content.
:::
:::{admonition}
Third nested admonition content.
:::
::::
.
::::{admonition} Multiple nested admonitions

:::{admonition}

First nested admonition content.

:::

:::{admonition}

Second nested admonition content.

:::

:::{admonition}

Third nested admonition content.

:::

::::
.

MyST colon fenced directive with mixed content and nested directives
.
::::{hint} A hint with nested tips and paragraphs
This is some introductory text.
:::{tip}
A nested tip with content.
:::
More text between directives.
:::{tip}
Another nested tip.
:::
Concluding text.
::::
.
::::{hint} A hint with nested tips and paragraphs

This is some introductory text.

:::{tip}

A nested tip with content.

:::

More text between directives.

:::{tip}

Another nested tip.

:::

Concluding text.

::::
.

MyST colon fenced directive nested in list
.
- Item with directive
  :::{tip} Nested tip in list item
  Tip content inside a list item.
  :::
.
- Item with directive
  :::{tip} Nested tip in list item

  Tip content inside a list item.

  :::
.

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
:::

.

:::{admonition} MyST colon fenced directive with simple metadata
:class: foo
:truc: bla

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
:::

::::{admonition} MyST colon fenced directive with nested directive with simple metadata
:::{image} foo.png
:class: foo
:truc: bla
:::
::::

% Admonitions with arbitrary yaml metadata are not yet supported.
% Issue: in a container, the `---` is interpreted as hrule by the parser
%
% :::{admonition} MyST colon fenced directive with arbitrary yaml metadata
% ---
% foo:
% bar: 1
% ---
%
% Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
% incididunt ut labore et dolore magna aliqua.
% :::

.

% Unknown colon-fenced directives are not yet implemented
% :::{exercise}
% This is an unknown admonition.
% :::

.

::::{admonition} MyST colon fenced directive with two nested admonitions
:::{admonition}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
:::

:::{admonition}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
:::

:::{admonition}
truc
:::
::::

.

::::{hint} A hint with alternating nested tips and texts
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.

:::{tip}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
:::

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.

:::{tip}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
:::

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
::::

.

- foo
:::{tip} A directive nested in bullet points
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
:::
Loading