1from markdown_it import MarkdownIt
2from markdown_it.rules_block import StateBlock
3from markdown_it.rules_inline import StateInline
4
5from mdit_py_plugins.utils import is_code_block
6
7
8def substitution_plugin(
9 md: MarkdownIt, start_delimiter: str = "{", end_delimiter: str = "}"
10) -> None:
11 """A plugin to create substitution tokens.
12
13 These, token should be handled by the renderer.
14
15 Example::
16
17 {{ block }}
18
19 a {{ inline }} b
20
21 """
22
23 def _substitution_inline(state: StateInline, silent: bool) -> bool:
24 try:
25 if (
26 state.src[state.pos] != start_delimiter
27 or state.src[state.pos + 1] != start_delimiter
28 ):
29 return False
30 except IndexError:
31 return False
32
33 pos = state.pos + 2
34 found_closing = False
35 while True:
36 try:
37 end = state.src.index(end_delimiter, pos)
38 except ValueError:
39 return False
40 try:
41 if state.src[end + 1] == end_delimiter:
42 found_closing = True
43 break
44 except IndexError:
45 return False
46 pos = end + 2
47
48 if not found_closing:
49 return False
50
51 text = state.src[state.pos + 2 : end].strip()
52 state.pos = end + 2
53
54 if silent:
55 return True
56
57 token = state.push("substitution_inline", "span", 0)
58 token.block = False
59 token.content = text
60 token.attrSet("class", "substitution")
61 token.attrSet("text", text)
62 token.markup = f"{start_delimiter}{end_delimiter}"
63
64 return True
65
66 def _substitution_block(
67 state: StateBlock, startLine: int, endLine: int, silent: bool
68 ) -> bool:
69 if is_code_block(state, startLine):
70 return False
71
72 startPos = state.bMarks[startLine] + state.tShift[startLine]
73 end = state.eMarks[startLine]
74
75 lineText = state.src[startPos:end].strip()
76
77 try:
78 if (
79 lineText[0] != start_delimiter
80 or lineText[1] != start_delimiter
81 or lineText[-1] != end_delimiter
82 or lineText[-2] != end_delimiter
83 or len(lineText) < 5
84 ):
85 return False
86 except IndexError:
87 return False
88
89 text = lineText[2:-2].strip()
90
91 # special case if multiple on same line, e.g. {{a}}{{b}}
92 if (end_delimiter * 2) in text:
93 return False
94
95 state.line = startLine + 1
96
97 if silent:
98 return True
99
100 token = state.push("substitution_block", "div", 0)
101 token.block = True
102 token.content = text
103 token.attrSet("class", "substitution")
104 token.attrSet("text", text)
105 token.markup = f"{start_delimiter}{end_delimiter}"
106 token.map = [startLine, state.line]
107
108 return True
109
110 md.block.ruler.before("fence", "substitution_block", _substitution_block)
111 md.inline.ruler.before("escape", "substitution_inline", _substitution_inline)