-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathcolon_fence.py
159 lines (117 loc) · 3.86 KB
/
colon_fence.py
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
from __future__ import annotations
from typing import TYPE_CHECKING, Sequence
from markdown_it import MarkdownIt
from markdown_it.common.utils import escapeHtml, unescapeAll
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
def colon_fence_plugin(md: MarkdownIt) -> None:
"""This plugin directly mimics regular fences, but with `:` colons.
Example::
:::name
contained text
:::
"""
md.block.ruler.before(
"fence",
"colon_fence",
_rule,
{"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
)
md.add_render_rule("colon_fence", _render)
def _rule(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
if is_code_block(state, startLine):
return False
haveEndMarker = False
pos = state.bMarks[startLine] + state.tShift[startLine]
maximum = state.eMarks[startLine]
if pos + 3 > maximum:
return False
marker = state.src[pos]
if marker != ":":
return False
# scan marker length
mem = pos
pos = _skipCharsStr(state, pos, marker)
length = pos - mem
if length < 3:
return False
markup = state.src[mem:pos]
params = state.src[pos:maximum]
# Since start is found, we can report success here in validation mode
if silent:
return True
# search end of 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
pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
maximum = state.eMarks[nextLine]
if pos < maximum and state.sCount[nextLine] < state.blkIndent:
# non-empty line with negative indent should stop the list:
# - ```
# test
break
if state.src[pos] != marker:
continue
if is_code_block(state, nextLine):
continue
pos = _skipCharsStr(state, pos, marker)
# closing code fence must be at least as long as the opening one
if pos - mem < length:
continue
# make sure tail has spaces only
pos = state.skipSpaces(pos)
if pos < maximum:
continue
haveEndMarker = True
# found!
break
# If a fence has heading spaces, they should be removed from its inner block
length = state.sCount[startLine]
state.line = nextLine + (1 if haveEndMarker else 0)
token = state.push("colon_fence", "code", 0)
token.info = params
token.content = state.getLines(startLine + 1, nextLine, length, True)
token.markup = markup
token.map = [startLine, state.line]
return True
def _skipCharsStr(state: StateBlock, pos: int, ch: str) -> int:
"""Skip character string from given position."""
# TODO this can be replaced with StateBlock.skipCharsStr in markdown-it-py 3.0.0
while True:
try:
current = state.src[pos]
except IndexError:
break
if current != ch:
break
pos += 1
return pos
def _render(
self: RendererProtocol,
tokens: Sequence[Token],
idx: int,
options: OptionsDict,
env: EnvType,
) -> str:
token = tokens[idx]
info = unescapeAll(token.info).strip() if token.info else ""
content = escapeHtml(token.content)
block_name = ""
if info:
block_name = info.split()[0]
return (
"<pre><code"
+ (f' class="block-{block_name}" ' if block_name else "")
+ ">"
+ content
+ "</code></pre>\n"
)