Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add leaf div #72

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
86 changes: 86 additions & 0 deletions mdit_py_plugins/leaf_directive.py
Original file line number Diff line number Diff line change
@@ -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,
)
84 changes: 84 additions & 0 deletions tests/fixtures/leaf_directive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
basic
.
::name This is the content
.
<div class="name">
<p>This is the content</p>
</div>
.

multiline
.
::name This is the content
it can run onto multiple lines

But is interrupted by a blank line
.
<div class="name">
<p>This is the content
it can run onto multiple lines</p>
</div>
<p>But is interrupted by a blank line</p>
.

must have two :
.
:name This is the content
.
<p>:name This is the content</p>
.

but not three :
.
:::name This is the content
.
<p>:::name This is the content</p>
.

must be proceeded by a non-whitespace charater
.
::

:: This is the content
.
<p>::</p>
<p>:: This is the content</p>
.

Just a name
.
::name
.
<div class="name"></div>
.

Just a name with a space
.
::name
.
<div class="name"></div>
.

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
.
<p>Paragraph</p>
<div class="name">
<p>This is the content</p>
</div>
<p>Paragraph
::name This is the content</p>
<ul>
<li>list
::name This is the content</li>
</ul>
.
26 changes: 26 additions & 0 deletions tests/test_leaf_div.py
Original file line number Diff line number Diff line change
@@ -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])
19 changes: 19 additions & 0 deletions tests/test_leaf_div/test_no_content_parse.yml
Original file line number Diff line number Diff line change
@@ -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