diff --git a/mdit_py_plugins/leaf_directive.py b/mdit_py_plugins/leaf_directive.py new file mode 100644 index 0000000..fe015cc --- /dev/null +++ b/mdit_py_plugins/leaf_directive.py @@ -0,0 +1,86 @@ +from markdown_it import MarkdownIt +from markdown_it.common.utils import isSpace +from markdown_it.rules_block import StateBlock + + +def leaf_directive_plugin(md: MarkdownIt, parse_content: bool = True): + """This plugin allows for a sinlge div to be created, + with a single class and single paragraph. + + :param parse_content: Whether to parse the content of the div (as a paragraph) + or simply store it as a string (for later processing). + """ + + def _rule(state: StateBlock, startLine: int, endLine: int, silent: bool): + pos = state.bMarks[startLine] + state.tShift[startLine] + maximum = state.eMarks[startLine] + + # if it's indented more than 3 spaces, it should be a code block + if state.sCount[startLine] - state.blkIndent >= 4: + return False + + if pos + 3 > maximum: + return False + + # Assert the line starts with `::` + if state.srcCharCode[pos] != 0x3A or state.srcCharCode[pos + 1] != 0x3A: + return False + + # But don't allow ``:::``, which is a div + if state.srcCharCode[pos + 2] == 0x3A: + return False + + # it must also not be whitespace + if isSpace(state.srcCharCode[pos + 2]): + return False + + if silent: + return True + + # jump line-by-line until empty one or EOF + nextLine = startLine + 1 + while nextLine < endLine: + if state.isEmpty(nextLine): + break + nextLine += 1 + + text = state.getLines(startLine, nextLine, state.blkIndent, False).strip() + + state.line = nextLine + + klass, *other = text[2:].split(maxsplit=1) + content = other[0] if other else "" + + if parse_content: + token = state.push("leaf_div_open", "div", 1) + token.map = [startLine, state.line] + token.markup = "::" + token.attrSet("class", klass) + + if content: + token = state.push("paragraph_open", "p", 1) + token.map = [startLine, state.line] + + token = state.push("inline", "", 0) + token.content = content + token.map = [startLine, state.line] + token.children = [] + + token = state.push("paragraph_close", "p", -1) + + token = state.push("leaf_div_close", "div", -1) + + else: + token = state.push("leaf_div", "div", 0) + token.map = [startLine, state.line] + token.markup = "::" + token.attrSet("class", klass) + token.content = content + + return True + + md.block.ruler.before( + "fence", + "leaf_directive", + _rule, + ) diff --git a/tests/fixtures/leaf_directive.md b/tests/fixtures/leaf_directive.md new file mode 100644 index 0000000..b432396 --- /dev/null +++ b/tests/fixtures/leaf_directive.md @@ -0,0 +1,84 @@ +basic +. +::name This is the content +. +
+

This is the content

+
+. + +multiline +. +::name This is the content +it can run onto multiple lines + +But is interrupted by a blank line +. +
+

This is the content +it can run onto multiple lines

+
+

But is interrupted by a blank line

+. + +must have two : +. +:name This is the content +. +

:name This is the content

+. + +but not three : +. +:::name This is the content +. +

:::name This is the content

+. + +must be proceeded by a non-whitespace charater +. +:: + +:: This is the content +. +

::

+

:: This is the content

+. + +Just a name +. +::name +. +
+. + +Just a name with a space +. +::name +. +
+. + +It does not interrupt paragraphs or other blocks +. +Paragraph + +::name This is the content + +Paragraph +::name This is the content + +- list +::name This is the content +. +

Paragraph

+
+

This is the content

+
+

Paragraph +::name This is the content

+ +. diff --git a/tests/test_leaf_div.py b/tests/test_leaf_div.py new file mode 100644 index 0000000..da8e92a --- /dev/null +++ b/tests/test_leaf_div.py @@ -0,0 +1,26 @@ +from pathlib import Path + +from markdown_it import MarkdownIt +from markdown_it.utils import read_fixture_file +import pytest + +from mdit_py_plugins.leaf_directive import leaf_directive_plugin + +FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures") + + +@pytest.mark.parametrize( + "line,title,input,expected", read_fixture_file(FIXTURE_PATH / "leaf_directive.md") +) +def test_fixture(line, title, input, expected): + md = MarkdownIt("commonmark").use(leaf_directive_plugin) + md.options["xhtmlOut"] = False + text = md.render(input) + print(text) + assert text.rstrip() == expected.rstrip() + + +def test_no_content_parse(data_regression): + md = MarkdownIt().use(leaf_directive_plugin, parse_content=False) + tokens = md.parse("::name content\nmultiline") + data_regression.check([t.as_dict() for t in tokens]) diff --git a/tests/test_leaf_div/test_no_content_parse.yml b/tests/test_leaf_div/test_no_content_parse.yml new file mode 100644 index 0000000..d55650c --- /dev/null +++ b/tests/test_leaf_div/test_no_content_parse.yml @@ -0,0 +1,19 @@ +- attrs: + - - class + - name + block: true + children: null + content: 'content + + multiline' + hidden: false + info: '' + level: 0 + map: + - 0 + - 2 + markup: '::' + meta: {} + nesting: 0 + tag: div + type: leaf_div