MyST-Parser icon indicating copy to clipboard operation
MyST-Parser copied to clipboard

"Empty" directives on 1 line?

Open paugier opened this issue 1 year ago • 4 comments

Context

In some documents I have to write a lot of

```{exercise-end}
```

(This directive comes from sphinx-exercise.)

I don't understand why it cannot be written on 1 line, i.e. just

```{exercise-end}```

Proposal

A line with

```{exercise-end}```

could be parsed as an exercise-end directive with no content.

Related question: What about ```{note} A very short note.``` on 1 line?

Tasks and updates

No response

paugier avatar Sep 14 '22 20:09 paugier

Thanks for opening your first issue here! Engagement like this is essential for open source projects! :hugs:
If you haven't done so already, check out EBP's Code of Conduct. Also, please try to follow the issue template as it helps other community members to contribute more effectively.
If your issue is a feature request, others may react to it, to raise its prominence (see Feature Voting).
Welcome to the EBP community! :tada:

welcome[bot] avatar Sep 14 '22 20:09 welcome[bot]

Heya thanks for the feedback.

The technical reason is that, in CommonMarkdown parsing (see https://markdown-it.github.io/)

```{exercise-end}
```

goes to

[
  {
    "type": "fence",
    "tag": "code",
    "attrs": null,
    "map": [
      0,
      2
    ],
    "nesting": 0,
    "level": 0,
    "children": null,
    "content": "",
    "markup": "```",
    "info": "{exercise-end}",
    "meta": null,
    "block": true,
    "hidden": false
  }
]

whereas

```{exercise-end} text```

goes to:

[
  {
    "type": "paragraph_open",
    "tag": "p",
    "attrs": null,
    "map": [
      0,
      1
    ],
    "nesting": 1,
    "level": 0,
    "children": null,
    "content": "",
    "markup": "",
    "info": "",
    "meta": null,
    "block": true,
    "hidden": false
  },
  {
    "type": "inline",
    "tag": "",
    "attrs": null,
    "map": [
      0,
      1
    ],
    "nesting": 0,
    "level": 1,
    "children": [
      {
        "type": "code_inline",
        "tag": "code",
        "attrs": null,
        "map": null,
        "nesting": 0,
        "level": 0,
        "children": null,
        "content": "{exercise-end} text",
        "markup": "```",
        "info": "",
        "meta": null,
        "block": false,
        "hidden": false
      }
    ],
    "content": "```{exercise-end} text```",
    "markup": "",
    "info": "",
    "meta": null,
    "block": true,
    "hidden": false
  },
  {
    "type": "paragraph_close",
    "tag": "p",
    "attrs": null,
    "map": null,
    "nesting": -1,
    "level": 0,
    "children": null,
    "content": "",
    "markup": "",
    "info": "",
    "meta": null,
    "block": true,
    "hidden": false
  }
]

so it is difficult to get these to act the same, and identify the one-line syntax.

This is not to say a one-line syntax would not be nice

chrisjsewell avatar Sep 14 '22 20:09 chrisjsewell

Thanks for the answer! https://markdown-it.github.io/ is very convenient! And by the way, the executablebooks / MyST project is really great!

I wonder if there is a strong reason for this difference. I guess the definition in markdown of a code fence. I'm going to ask in markdown-it repo, since it seems to me that it would be the right place to change this behavior.

paugier avatar Sep 15 '22 07:09 paugier

I got my answer, which is that indeed there is a strong reason for this difference for markdown-it. Any chance that myst-parser could support directive on 1 line with a simple Python filter? I mean something like this:


from pprint import pprint
import textwrap
from markdown_it import MarkdownIt


def filter_1line_fence(tokens):

    result = []
    t_first = None
    t_second = None
    t_third = None

    for t in tokens:
        t_first = t_second
        t_second = t_third
        t_third = t

        if t_first is None:
            continue
        else:
            if t_second is None:
                result.append(t_first)
                continue

        if not (
            t_first.type == "paragraph_open"
            and t_second.type == "inline"
            and t_third.type == "paragraph_close"
        ):
            result.append(t_first)
            continue

        t_inline = t_second
        if len(t_inline.children) > 1:
            result.append(t_first)
            continue

        child = t_inline.children[0]

        if child.type != "code_inline":
            result.append(t_first)
            continue

        new_token = t_inline.__class__(
            type="fence",
            tag=child.tag,
            nesting=0,
            map=t_inline.map,
            markup=child.markup,
            info=child.content,
            block=True,
        )
        result.append(new_token)
        t_first = t_second = t_third = None

    if t_second is not None:
        result.append(t_second)

    if t_third is not None:
        result.append(t_third)

    return result


def test_1line_fence():

    md = MarkdownIt()

    text = textwrap.dedent(
        """
    start
    ```{toto} hello
    ```
    stop

    start

    ```{toto} hello```

    stop

    [link](target)
    """
    )

    tokens = md.parse(text)
    result = filter_1line_fence(tokens)

    assert len(result) == len(tokens) - 2

    fences = [t for t in result if t.type == "fence"]
    pprint(fences)

    t_fence = fences[0]
    t_fence_created = fences[1]
    assert t_fence.map != t_fence_created.map
    t_fence_created.map = t_fence.map
    assert t_fence_created == t_fence


def test_no_1line_fence():
    md = MarkdownIt()

    text = textwrap.dedent(
        """
    start
    ```{toto} hello
    ```
    stop

    start ```{toto} hello``` stop

    A line with `name`.

    [link](target)

    """
    )

    tokens = md.parse(text)
    result = filter_1line_fence(tokens)
    assert tokens == result

paugier avatar Sep 20 '22 14:09 paugier