From a8b25c2c8df6419e31b024a3bd581ced917c480a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20M=2E=20Thi=C3=A9ry?= Date: Thu, 24 Apr 2025 16:09:57 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20Support=20colon?= =?UTF-8?q?=20fenced=20directives?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mdformat_myst/plugin.py | 96 +++++++++++++++++++++++++++++++++++++++++ tests/data/fixtures.md | 83 +++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/mdformat_myst/plugin.py b/mdformat_myst/plugin.py index 13d796e..2b0c289 100644 --- a/mdformat_myst/plugin.py +++ b/mdformat_myst/plugin.py @@ -8,11 +8,69 @@ 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", + "topics", + "warning", +] def update_mdit(mdit: MarkdownIt) -> None: @@ -51,6 +109,39 @@ 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=":") + + +def container_renderer( + node: RenderTreeNode, context: RenderContext, *args, **kwargs +) -> str: + result = node.markup + node.info + "\n" + children = node.children + 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 start, YAML + # header, paragraph end. + tokens = children[0].to_tokens() + if all( + not token.content or _YAML_HEADER_PATTERN.fullmatch(token.content) + for token in tokens + ): + # If yes, render these tokens as is + for token in tokens: + if token.content: + result += token.content + "\n" + result += "\n" + # and skip that first paragraph + children = children[1:] + + result += ( + "\n\n".join(child.render(context) for child in children) + "\n" + node.markup + ) + + return result + def _role_renderer(node: RenderTreeNode, context: RenderContext) -> str: role_name = "{" + node.meta["name"] + "}" @@ -130,4 +221,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} diff --git a/tests/data/fixtures.md b/tests/data/fixtures.md index 5a62b17..4c226fd 100644 --- a/tests/data/fixtures.md +++ b/tests/data/fixtures.md @@ -406,3 +406,86 @@ MyST directive, no opts or content ```{some-directive} args ``` . + +:::{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 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. +::: + +% 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. + ::: From f7df460e5eb85a2cc0f343aa00308c204b6d7fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20M=2E=20Thi=C3=A9ry?= Date: Sat, 26 Apr 2025 14:08:15 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20Support=20colon?= =?UTF-8?q?=20fenced=20directives:=20fix=20corner=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mdformat_myst/plugin.py | 18 +++++++----------- tests/data/fixtures.md | 3 ++- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/mdformat_myst/plugin.py b/mdformat_myst/plugin.py index 2b0c289..8f8e860 100644 --- a/mdformat_myst/plugin.py +++ b/mdformat_myst/plugin.py @@ -14,7 +14,7 @@ _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") +_YAML_HEADER_PATTERN = re.compile(r"(?m)(^:\w+: .*$\n?)+|^---$\n(?s:.).*\n---\n") container_names = [ "admonition", @@ -116,8 +116,8 @@ def update_mdit(mdit: MarkdownIt) -> None: def container_renderer( node: RenderTreeNode, context: RenderContext, *args, **kwargs ) -> str: - result = node.markup + node.info + "\n" 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 @@ -128,19 +128,15 @@ def container_renderer( not token.content or _YAML_HEADER_PATTERN.fullmatch(token.content) for token in tokens ): - # If yes, render these tokens as is - for token in tokens: - if token.content: - result += token.content + "\n" - result += "\n" + paragraphs.append('\n'.join(token.content.strip() + for token in tokens + if token.content)) # and skip that first paragraph children = children[1:] - result += ( - "\n\n".join(child.render(context) for child in children) + "\n" + node.markup - ) + paragraphs.extend(child.render(context) for child in children) - return result + return node.markup + node.info + "\n" + "\n\n".join(paragraphs) + "\n" + node.markup def _role_renderer(node: RenderTreeNode, context: RenderContext) -> str: diff --git a/tests/data/fixtures.md b/tests/data/fixtures.md index 4c226fd..33d5190 100644 --- a/tests/data/fixtures.md +++ b/tests/data/fixtures.md @@ -415,7 +415,8 @@ incididunt ut labore et dolore magna aliqua. . :::{admonition} MyST colon fenced directive with simple metadata -:class: foo :truc: bla +:class: foo +:truc: bla Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. From 98df6a4eb0a5630c42770102c2a05cc8c5cb3555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20M=2E=20Thi=C3=A9ry?= Date: Sat, 26 Apr 2025 14:09:00 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20Support=20colon?= =?UTF-8?q?=20fenced=20directives:=20support=20todo=20directive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mdformat_myst/plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mdformat_myst/plugin.py b/mdformat_myst/plugin.py index 8f8e860..3b91c36 100644 --- a/mdformat_myst/plugin.py +++ b/mdformat_myst/plugin.py @@ -68,6 +68,7 @@ "tab-set", "table", "tip", + "todo", "topics", "warning", ] From 3ddf25d1c5c6a8ba0b56f0ca81291fe3d65fa353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20M=2E=20Thi=C3=A9ry?= Date: Sat, 26 Apr 2025 14:33:50 +0200 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20Support=20colon?= =?UTF-8?q?=20fenced=20directives:=20fix=20corner=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mdformat_myst/plugin.py | 5 +++-- tests/data/fixtures.md | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/mdformat_myst/plugin.py b/mdformat_myst/plugin.py index 3b91c36..dca6d9b 100644 --- a/mdformat_myst/plugin.py +++ b/mdformat_myst/plugin.py @@ -122,11 +122,12 @@ def container_renderer( 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 start, YAML + # should be exactly three tokens: paragraph open, YAML # header, paragraph end. tokens = children[0].to_tokens() if all( - not token.content or _YAML_HEADER_PATTERN.fullmatch(token.content) + 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() diff --git a/tests/data/fixtures.md b/tests/data/fixtures.md index 33d5190..77a34b3 100644 --- a/tests/data/fixtures.md +++ b/tests/data/fixtures.md @@ -422,6 +422,13 @@ 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 %