From f6a71eb896c2ed173cbb9aead9f5f92cc5630027 Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Sun, 29 Sep 2024 14:34:13 +0530 Subject: [PATCH 1/2] :sparkles: NEW: Add plugin & tests to render subscripts --- docs/index.md | 6 ++ mdit_py_plugins/subscript/__init__.py | 117 +++++++++++++++++++++++ mdit_py_plugins/subscript/port.yaml | 8 ++ tests/fixtures/subscript.md | 130 ++++++++++++++++++++++++++ tests/test_subscript.py | 23 +++++ 5 files changed, 284 insertions(+) create mode 100644 mdit_py_plugins/subscript/__init__.py create mode 100644 mdit_py_plugins/subscript/port.yaml create mode 100644 tests/fixtures/subscript.md create mode 100644 tests/test_subscript.py diff --git a/docs/index.md b/docs/index.md index 71846de..ea43f45 100644 --- a/docs/index.md +++ b/docs/index.md @@ -113,6 +113,12 @@ html_string = md.render("some *Markdown*") .. autofunction:: mdit_py_plugins.amsmath.amsmath_plugin ``` +## Subscripts + +```{eval-rst} +.. autofunction:: mdit_py_plugins.subscript.sub_plugin +``` + ## MyST plugins `myst_blocks` and `myst_role` plugins are also available, for utilisation by the [MyST renderer](https://myst-parser.readthedocs.io/en/latest/using/syntax.html) diff --git a/mdit_py_plugins/subscript/__init__.py b/mdit_py_plugins/subscript/__init__.py new file mode 100644 index 0000000..cc41fa3 --- /dev/null +++ b/mdit_py_plugins/subscript/__init__.py @@ -0,0 +1,117 @@ +""" +Markdown-it-py plugin to introduce markup using ~subscript~. + +Ported from +https://github.com/markdown-it/markdown-it-sub/blob/master/index.mjs + +Originally ported during implementation of https://github.com/hasgeek/funnel/blob/main/funnel/utils/markdown/mdit_plugins/sub_tag.py +""" + +from __future__ import annotations + +from collections.abc import Sequence +import re + +from markdown_it import MarkdownIt +from markdown_it.renderer import RendererHTML +from markdown_it.rules_inline import StateInline +from markdown_it.token import Token +from markdown_it.utils import EnvType, OptionsDict + +__all__ = ["sub_plugin"] + +TILDE_CHAR = "~" + +WHITESPACE_RE = re.compile(r"(^|[^\\])(\\\\)*\s") +UNESCAPE_RE = re.compile(r'\\([ \\!"#$%&\'()*+,.\/:;<=>?@[\]^_`{|}~-])') + + +def tokenize(state: StateInline, silent: bool) -> bool: + """Parse a ~subscript~ token.""" + start = state.pos + ch = state.src[start] + maximum = state.posMax + found = False + + # Don't run any pairs in validation mode + if silent: + return False + + if ch != TILDE_CHAR: + return False + + if start + 2 >= maximum: + return False + + state.pos = start + 1 + + while state.pos < maximum: + if state.src[state.pos] == TILDE_CHAR: + found = True + break + state.md.inline.skipToken(state) + + if not found or start + 1 == state.pos: + state.pos = start + return False + + content = state.src[start + 1 : state.pos] + + # Don't allow unescaped spaces/newlines inside + if WHITESPACE_RE.search(content) is not None: + state.pos = start + return False + + # Found a valid pair, so update posMax and pos + state.posMax = state.pos + state.pos = start + 1 + + # Earlier we checked "not silent", but this implementation does not need it + token = state.push("sub_open", "sub", 1) + token.markup = TILDE_CHAR + + token = state.push("text", "", 0) + token.content = UNESCAPE_RE.sub(r"\1", content) + + token = state.push("sub_close", "sub", -1) + token.markup = TILDE_CHAR + + state.pos = state.posMax + 1 + state.posMax = maximum + return True + + +def sub_open( + renderer: RendererHTML, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, +) -> str: + """Render the opening tag for a ~subscript~ token.""" + return "" + + +def sub_close( + renderer: RendererHTML, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, +) -> str: + """Render the closing tag for a ~subscript~ token.""" + return "" + + +def sub_plugin(md: MarkdownIt) -> None: + """ + Markdown-it-py plugin to introduce markup using ~subscript~. + + Ported from + https://github.com/markdown-it/markdown-it-sub/blob/master/index.mjs + + Originally ported during implementation of https://github.com/hasgeek/funnel/blob/main/funnel/utils/markdown/mdit_plugins/sub_tag.py + """ + md.inline.ruler.after("emphasis", "sub", tokenize) + md.add_render_rule("sub_open", sub_open) + md.add_render_rule("sub_close", sub_close) diff --git a/mdit_py_plugins/subscript/port.yaml b/mdit_py_plugins/subscript/port.yaml new file mode 100644 index 0000000..04cbd3f --- /dev/null +++ b/mdit_py_plugins/subscript/port.yaml @@ -0,0 +1,8 @@ +- package: markdown-it-sub + commit: 422e93885b3c611234d602aa795f3d75a62cc93e + date: 5 Dec 2023 + version: 3.0.0 + changes: + - TODO - Strikethroughs within a subscript are not rendered correctly in + markdown-it either, but that can be fixed at a later stage, perhaps + in both markdown-it and markdown-it-py diff --git a/tests/fixtures/subscript.md b/tests/fixtures/subscript.md new file mode 100644 index 0000000..14e00db --- /dev/null +++ b/tests/fixtures/subscript.md @@ -0,0 +1,130 @@ +. +~foo\~ +. +

~foo~

+. + +. +~foo bar~ +. +

~foo bar~

+. + +. +~foo\ bar\ baz~ +. +

foo bar baz

+. + +. +~\ foo\ ~ +. +

foo

+. + +. +~foo\\\\\\\ bar~ +. +

foo\\\ bar

+. + +. +~foo\\\\\\ bar~ +. +

~foo\\\ bar~

+. + +. +**~foo~ bar** +. +

foo bar

+. + + +coverage +. +*~f +. +

*~f

+. + +Basic: +. +H~2~O +. +

H2O

+. + +Spaces: +. +H~2 O~2 +. +

H~2 O~2

+. + +Escaped: +. +H\~2\~O +. +

H~2~O

+. + +Nested: +. +a~b~c~d~e +. +

abcde

+. + +Strikethrough versus subscript: +. +~~strikethrough~~ versus ~subscript~ +. +

strikethrough versus subscript

+. + +Subscript in strikethrough (beginning): +. +~~~subscript~ in the beginning within a strikethrough is perceived as first line of a code block and hence ignored~~ +. +
+. + +Strikethrough in subscript (beginning): +. +~~~strikethrough~ in the beginning within a subscript is perceived as first line of a code block and hence ignored~~ +. +
+. + +Subscript in strikethrough (end): +. +~~strikethrough contains ~subscript~~~ +. +

strikethrough contains subscript

+. + +Strikethrough in subscript (end): +. +~subscript contains ~~strikethrough~~~ +TODO: This is not rendered correctly in markdown-it either, but can be fixed +. +

~subscript contains strikethrough~ +TODO: This is not rendered correctly in markdown-it either, but can be fixed

+. + +Subscript in strikethrough: +. +~~strikethrough with ~subscript~ text~~ +. +

strikethrough with subscript text

+. + +Strikethrough in subscript: +. +~subscript contains ~~strikethrough~~ text~ +TODO: This is not rendered correctly in markdown-it either, but can be fixed +. +

~subscript contains strikethrough text~ +TODO: This is not rendered correctly in markdown-it either, but can be fixed

+. \ No newline at end of file diff --git a/tests/test_subscript.py b/tests/test_subscript.py new file mode 100644 index 0000000..699beaf --- /dev/null +++ b/tests/test_subscript.py @@ -0,0 +1,23 @@ +"""Tests for subscript plugin.""" + +from pathlib import Path + +from markdown_it import MarkdownIt +from markdown_it.utils import read_fixture_file +import pytest + +from mdit_py_plugins.subscript import sub_plugin + +FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "subscript.md") + + +@pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH)) +def test_all(line, title, input, expected): + """Tests for subscript plugin.""" + md = MarkdownIt("commonmark").enable("strikethrough").use(sub_plugin) + text = md.render(input) + try: + assert text.rstrip() == expected.rstrip() + except AssertionError: + print(text) + raise From 7f86d1d1a1beb60b9050bd791a4d9e195c42e0e0 Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Wed, 2 Oct 2024 11:40:05 +0530 Subject: [PATCH 2/2] :test_tube: TEST: Update tests for subscripts Better handle the cases combining subscript and strikethrough markdown --- mdit_py_plugins/subscript/port.yaml | 7 ++- tests/fixtures/subscript.md | 53 ------------------ tests/fixtures/subscript_strikethrough.md | 68 +++++++++++++++++++++++ tests/test_subscript.py | 17 ++++++ 4 files changed, 89 insertions(+), 56 deletions(-) create mode 100644 tests/fixtures/subscript_strikethrough.md diff --git a/mdit_py_plugins/subscript/port.yaml b/mdit_py_plugins/subscript/port.yaml index 04cbd3f..896872b 100644 --- a/mdit_py_plugins/subscript/port.yaml +++ b/mdit_py_plugins/subscript/port.yaml @@ -3,6 +3,7 @@ date: 5 Dec 2023 version: 3.0.0 changes: - - TODO - Strikethroughs within a subscript are not rendered correctly in - markdown-it either, but that can be fixed at a later stage, perhaps - in both markdown-it and markdown-it-py + - TODO - Some strikethrough and subscript combinations are not rendered + correctly in markdown-it either, but that can be fixed at a later stage, + perhaps in both markdown-it and markdown-it-py. + See `tests/fixtures/subscript_strikethrough.md` for examples. diff --git a/tests/fixtures/subscript.md b/tests/fixtures/subscript.md index 14e00db..92ea312 100644 --- a/tests/fixtures/subscript.md +++ b/tests/fixtures/subscript.md @@ -74,57 +74,4 @@ Nested: a~b~c~d~e .

abcde

-. - -Strikethrough versus subscript: -. -~~strikethrough~~ versus ~subscript~ -. -

strikethrough versus subscript

-. - -Subscript in strikethrough (beginning): -. -~~~subscript~ in the beginning within a strikethrough is perceived as first line of a code block and hence ignored~~ -. -
-. - -Strikethrough in subscript (beginning): -. -~~~strikethrough~ in the beginning within a subscript is perceived as first line of a code block and hence ignored~~ -. -
-. - -Subscript in strikethrough (end): -. -~~strikethrough contains ~subscript~~~ -. -

strikethrough contains subscript

-. - -Strikethrough in subscript (end): -. -~subscript contains ~~strikethrough~~~ -TODO: This is not rendered correctly in markdown-it either, but can be fixed -. -

~subscript contains strikethrough~ -TODO: This is not rendered correctly in markdown-it either, but can be fixed

-. - -Subscript in strikethrough: -. -~~strikethrough with ~subscript~ text~~ -. -

strikethrough with subscript text

-. - -Strikethrough in subscript: -. -~subscript contains ~~strikethrough~~ text~ -TODO: This is not rendered correctly in markdown-it either, but can be fixed -. -

~subscript contains strikethrough text~ -TODO: This is not rendered correctly in markdown-it either, but can be fixed

. \ No newline at end of file diff --git a/tests/fixtures/subscript_strikethrough.md b/tests/fixtures/subscript_strikethrough.md new file mode 100644 index 0000000..0694837 --- /dev/null +++ b/tests/fixtures/subscript_strikethrough.md @@ -0,0 +1,68 @@ +Strikethrough versus subscript: +. +~~strikethrough~~versus~subscript~ +. +

strikethroughversussubscript

+. + +Subscript in strikethrough (beginning): +. +~~~subscript~strikethrough~~ +This ends up being rendered as a code block, but that's expected. +Hence, it has to be closed with `~~~` +~~~ +Only then will the following text be rendered as it is intended. +We cannot use `~~~subscript~strikethrough~~` at the beginning of a line. +. +
This ends up being rendered as a code block, but that's expected.
+Hence, it has to be closed with `~~~`
+
+

Only then will the following text be rendered as it is intended. +We cannot use ~~~subscript~strikethrough~~ at the beginning of a line.

+. + +Strikethrough in subscript (beginning): +. +~~~strikethrough~~subscript~ +This ends up being rendered as a code block, but that's expected. +Hence, it has to be closed with `~~~` +~~~ +Only then will the following text be rendered as it is intended. +We cannot use `~~~strikethrough~~subscript~` at the beginning of a line. +. +
This ends up being rendered as a code block, but that's expected.
+Hence, it has to be closed with `~~~`
+
+

Only then will the following text be rendered as it is intended. +We cannot use ~~~strikethrough~~subscript~ at the beginning of a line.

+. + +Subscript in strikethrough (end): +. +~~strikethrough~subscript~~~ +. +

strikethroughsubscript

+. + +Strikethrough in subscript (end): +. +TODO: ~subscript~~strikethrough~~~ +. +

TODO: subscriptstrikethrough~~

+. + +Subscript in strikethrough: +. +~~strikethrough~subscript~strikethrough~~ +. +

strikethroughsubscriptstrikethrough

+. + +Strikethrough in subscript: +. +TODO: ~subscript~~strikethrough~~subscript~ +This should have beeen similar to *emphasised**strong**emphasised*. +. +

TODO: subscriptstrikethroughsubscript +This should have beeen similar to emphasisedstrongemphasised.

+. \ No newline at end of file diff --git a/tests/test_subscript.py b/tests/test_subscript.py index 699beaf..e44638c 100644 --- a/tests/test_subscript.py +++ b/tests/test_subscript.py @@ -9,11 +9,28 @@ from mdit_py_plugins.subscript import sub_plugin FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "subscript.md") +STRIKETHROUGH_FIXTURE_PATH = Path(__file__).parent.joinpath( + "fixtures", "subscript_strikethrough.md" +) @pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH)) def test_all(line, title, input, expected): """Tests for subscript plugin.""" + md = MarkdownIt("commonmark").use(sub_plugin) + text = md.render(input) + try: + assert text.rstrip() == expected.rstrip() + except AssertionError: + print(text) + raise + + +@pytest.mark.parametrize( + "line,title,input,expected", read_fixture_file(STRIKETHROUGH_FIXTURE_PATH) +) +def test_all_strikethrough(line, title, input, expected): + """Tests for subscript plugin with strikethrough enabled.""" md = MarkdownIt("commonmark").enable("strikethrough").use(sub_plugin) text = md.render(input) try: