1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
"""Process block-level custom containers."""
from math import floor
from typing import Callable, Optional
from markdown_it import MarkdownIt
from markdown_it.common.utils import charCodeAt
from markdown_it.rules_block import StateBlock
def container_plugin(
md: MarkdownIt,
name: str,
marker: str = ":",
validate: Optional[Callable[[str, str], bool]] = None,
render=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):
return params.strip().split(" ", 2)[0] == name
def renderDefault(self, tokens, idx, _options, env):
# add a class to the opening tag
if tokens[idx].nesting == 1:
tokens[idx].attrJoin("class", name)
return self.renderToken(tokens, idx, _options, env)
min_markers = 3
marker_str = marker
marker_char = charCodeAt(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):
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.srcCharCode[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.srcCharCode[start]:
continue
if state.sCount[nextLine] - state.blkIndent >= 4:
# closing fence should be indented less than 4 spaces
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)
|