diff options
Diffstat (limited to 'mdit_py_plugins/myst_blocks')
-rw-r--r-- | mdit_py_plugins/myst_blocks/__init__.py | 1 | ||||
-rw-r--r-- | mdit_py_plugins/myst_blocks/index.py | 153 |
2 files changed, 154 insertions, 0 deletions
diff --git a/mdit_py_plugins/myst_blocks/__init__.py b/mdit_py_plugins/myst_blocks/__init__.py new file mode 100644 index 0000000..434cad6 --- /dev/null +++ b/mdit_py_plugins/myst_blocks/__init__.py @@ -0,0 +1 @@ +from .index import myst_block_plugin # noqa: F401 diff --git a/mdit_py_plugins/myst_blocks/index.py b/mdit_py_plugins/myst_blocks/index.py new file mode 100644 index 0000000..d0e4cf6 --- /dev/null +++ b/mdit_py_plugins/myst_blocks/index.py @@ -0,0 +1,153 @@ +import itertools + +from markdown_it import MarkdownIt +from markdown_it.common.utils import escapeHtml, isSpace +from markdown_it.rules_block import StateBlock + + +def myst_block_plugin(md: MarkdownIt): + """Parse MyST targets (``(name)=``), blockquotes (``% comment``) and block breaks (``+++``).""" + md.block.ruler.before( + "blockquote", + "myst_line_comment", + line_comment, + {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]}, + ) + md.block.ruler.before( + "hr", + "myst_block_break", + block_break, + {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]}, + ) + md.block.ruler.before( + "hr", + "myst_target", + target, + {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]}, + ) + md.add_render_rule("myst_target", render_myst_target) + md.add_render_rule("myst_line_comment", render_myst_line_comment) + + +def line_comment(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 state.src[pos] != "%": + return False + + if silent: + return True + + token = state.push("myst_line_comment", "", 0) + token.attrSet("class", "myst-line-comment") + token.content = state.src[pos + 1 : maximum].rstrip() + token.markup = "%" + + # search end of block while appending lines to `token.content` + for nextLine in itertools.count(startLine + 1): + if nextLine >= endLine: + break + pos = state.bMarks[nextLine] + state.tShift[nextLine] + maximum = state.eMarks[nextLine] + + if state.src[pos] != "%": + break + token.content += "\n" + state.src[pos + 1 : maximum].rstrip() + + state.line = nextLine + token.map = [startLine, nextLine] + + return True + + +def block_break(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 + + marker = state.srcCharCode[pos] + pos += 1 + + # Check block marker /* + */ + if marker != 0x2B: + return False + + # markers can be mixed with spaces, but there should be at least 3 of them + + cnt = 1 + while pos < maximum: + ch = state.srcCharCode[pos] + if ch != marker and not isSpace(ch): + break + if ch == marker: + cnt += 1 + pos += 1 + + if cnt < 3: + return False + + if silent: + return True + + state.line = startLine + 1 + + token = state.push("myst_block_break", "hr", 0) + token.attrSet("class", "myst-block") + token.content = state.src[pos:maximum].strip() + token.map = [startLine, state.line] + token.markup = chr(marker) * cnt + + return True + + +def target(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 + + text = state.src[pos:maximum].strip() + if not text.startswith("("): + return False + if not text.endswith(")="): + return False + if not text[1:-2]: + return False + + if silent: + return True + + state.line = startLine + 1 + + token = state.push("myst_target", "", 0) + token.attrSet("class", "myst-target") + token.content = text[1:-2] + token.map = [startLine, state.line] + + return True + + +def render_myst_target(self, tokens, idx, options, env): + label = tokens[idx].content + class_name = "myst-target" + target = f'<a href="#{label}">({label})=</a>' + return f'<div class="{class_name}">{target}</div>' + + +def render_myst_line_comment(self, tokens, idx, options, env): + # Strip leading whitespace from all lines + content = "\n".join(line.lstrip() for line in tokens[idx].content.split("\n")) + return f"<!-- {escapeHtml(content)} -->" |