Source code for mdit_py_plugins.container.index

"""Process block-level custom containers."""
from __future__ import annotations

from math import floor
from typing import TYPE_CHECKING, Any, Callable, Sequence

from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock

from mdit_py_plugins.utils import is_code_block

if TYPE_CHECKING:
    from markdown_it.renderer import RendererProtocol
    from markdown_it.token import Token
    from markdown_it.utils import EnvType, OptionsDict


[docs]def container_plugin( md: MarkdownIt, name: str, marker: str = ":", validate: None | Callable[[str, str], bool] = None, render: None | Callable[..., str] = None, ) -> None: """Plugin ported from `markdown-it-container <https://github.com/markdown-it/markdown-it-container>`__. It is a plugin for creating block-level custom containers: .. code-block:: md :::: name ::: name *markdown* ::: :::: :param name: the name of the container to parse :param marker: the marker character to use :param validate: func(marker, param) -> bool, default matches against the name :param render: render func """ def validateDefault(params: str, *args: Any) -> bool: return params.strip().split(" ", 2)[0] == name def renderDefault( self: RendererProtocol, tokens: Sequence[Token], idx: int, _options: OptionsDict, env: EnvType, ) -> str: # add a class to the opening tag if tokens[idx].nesting == 1: tokens[idx].attrJoin("class", name) return self.renderToken(tokens, idx, _options, env) # type: ignore min_markers = 3 marker_str = marker marker_char = marker_str[0] marker_len = len(marker_str) validate = validate or validateDefault render = render or renderDefault def container_func( state: StateBlock, startLine: int, endLine: int, silent: bool ) -> bool: if is_code_block(state, startLine): return False auto_closed = False start = state.bMarks[startLine] + state.tShift[startLine] maximum = state.eMarks[startLine] # Check out the first character quickly, # this should filter out most of non-containers if marker_char != state.src[start]: return False # Check out the rest of the marker string pos = start + 1 while pos <= maximum: try: character = state.src[pos] except IndexError: break if marker_str[(pos - start) % marker_len] != character: break pos += 1 marker_count = floor((pos - start) / marker_len) if marker_count < min_markers: return False pos -= (pos - start) % marker_len markup = state.src[start:pos] params = state.src[pos:maximum] assert validate is not None if not validate(params, markup): return False # Since start is found, we can report success here in validation mode if silent: return True # Search for the end of the block nextLine = startLine while True: nextLine += 1 if nextLine >= endLine: # unclosed block should be autoclosed by end of document. # also block seems to be autoclosed by end of parent break start = state.bMarks[nextLine] + state.tShift[nextLine] maximum = state.eMarks[nextLine] if start < maximum and state.sCount[nextLine] < state.blkIndent: # non-empty line with negative indent should stop the list: # - ``` # test break if marker_char != state.src[start]: continue if is_code_block(state, nextLine): continue pos = start + 1 while pos <= maximum: try: character = state.src[pos] except IndexError: break if marker_str[(pos - start) % marker_len] != character: break pos += 1 # closing code fence must be at least as long as the opening one if floor((pos - start) / marker_len) < marker_count: continue # make sure tail has spaces only pos -= (pos - start) % marker_len pos = state.skipSpaces(pos) if pos < maximum: continue # found! auto_closed = True break old_parent = state.parentType old_line_max = state.lineMax state.parentType = "container" # this will prevent lazy continuations from ever going past our end marker state.lineMax = nextLine token = state.push(f"container_{name}_open", "div", 1) token.markup = markup token.block = True token.info = params token.map = [startLine, nextLine] state.md.block.tokenize(state, startLine + 1, nextLine) token = state.push(f"container_{name}_close", "div", -1) token.markup = state.src[start:pos] token.block = True state.parentType = old_parent state.lineMax = old_line_max state.line = nextLine + (1 if auto_closed else 0) return True md.block.ruler.before( "fence", "container_" + name, container_func, {"alt": ["paragraph", "reference", "blockquote", "list"]}, ) md.add_render_rule(f"container_{name}_open", render) md.add_render_rule(f"container_{name}_close", render)