Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/mdit_py_plugins/myst_blocks/index.py: 91%
97 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:15 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:15 +0000
1from __future__ import annotations
3import itertools
4from typing import TYPE_CHECKING, Sequence
6from markdown_it import MarkdownIt
7from markdown_it.common.utils import escapeHtml
8from markdown_it.rules_block import StateBlock
10from mdit_py_plugins.utils import is_code_block
12if TYPE_CHECKING:
13 from markdown_it.renderer import RendererProtocol
14 from markdown_it.token import Token
15 from markdown_it.utils import EnvType, OptionsDict
18def myst_block_plugin(md: MarkdownIt) -> None:
19 """Parse MyST targets (``(name)=``), blockquotes (``% comment``) and block breaks (``+++``)."""
20 md.block.ruler.before(
21 "blockquote",
22 "myst_line_comment",
23 line_comment,
24 {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
25 )
26 md.block.ruler.before(
27 "hr",
28 "myst_block_break",
29 block_break,
30 {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
31 )
32 md.block.ruler.before(
33 "hr",
34 "myst_target",
35 target,
36 {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
37 )
38 md.add_render_rule("myst_target", render_myst_target)
39 md.add_render_rule("myst_line_comment", render_myst_line_comment)
42def line_comment(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
43 if is_code_block(state, startLine):
44 return False
46 pos = state.bMarks[startLine] + state.tShift[startLine]
47 maximum = state.eMarks[startLine]
49 if state.src[pos] != "%":
50 return False
52 if silent:
53 return True
55 token = state.push("myst_line_comment", "", 0)
56 token.attrSet("class", "myst-line-comment")
57 token.content = state.src[pos + 1 : maximum].rstrip()
58 token.markup = "%"
60 # search end of block while appending lines to `token.content`
61 for nextLine in itertools.count(startLine + 1):
62 if nextLine >= endLine:
63 break
64 pos = state.bMarks[nextLine] + state.tShift[nextLine]
65 maximum = state.eMarks[nextLine]
67 if state.src[pos] != "%":
68 break
69 token.content += "\n" + state.src[pos + 1 : maximum].rstrip()
71 state.line = nextLine
72 token.map = [startLine, nextLine]
74 return True
77def block_break(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
78 if is_code_block(state, startLine):
79 return False
81 pos = state.bMarks[startLine] + state.tShift[startLine]
82 maximum = state.eMarks[startLine]
84 marker = state.src[pos]
85 pos += 1
87 # Check block marker
88 if marker != "+":
89 return False
91 # markers can be mixed with spaces, but there should be at least 3 of them
93 cnt = 1
94 while pos < maximum:
95 ch = state.src[pos]
96 if ch != marker and ch not in ("\t", " "):
97 break
98 if ch == marker:
99 cnt += 1
100 pos += 1
102 if cnt < 3:
103 return False
105 if silent:
106 return True
108 state.line = startLine + 1
110 token = state.push("myst_block_break", "hr", 0)
111 token.attrSet("class", "myst-block")
112 token.content = state.src[pos:maximum].strip()
113 token.map = [startLine, state.line]
114 token.markup = marker * cnt
116 return True
119def target(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
120 if is_code_block(state, startLine):
121 return False
123 pos = state.bMarks[startLine] + state.tShift[startLine]
124 maximum = state.eMarks[startLine]
126 text = state.src[pos:maximum].strip()
127 if not text.startswith("("):
128 return False
129 if not text.endswith(")="):
130 return False
131 if not text[1:-2]:
132 return False
134 if silent:
135 return True
137 state.line = startLine + 1
139 token = state.push("myst_target", "", 0)
140 token.attrSet("class", "myst-target")
141 token.content = text[1:-2]
142 token.map = [startLine, state.line]
144 return True
147def render_myst_target(
148 self: RendererProtocol,
149 tokens: Sequence[Token],
150 idx: int,
151 options: OptionsDict,
152 env: EnvType,
153) -> str:
154 label = tokens[idx].content
155 class_name = "myst-target"
156 target = f'<a href="#{label}">({label})=</a>'
157 return f'<div class="{class_name}">{target}</div>'
160def render_myst_line_comment(
161 self: RendererProtocol,
162 tokens: Sequence[Token],
163 idx: int,
164 options: OptionsDict,
165 env: EnvType,
166) -> str:
167 # Strip leading whitespace from all lines
168 content = "\n".join(line.lstrip() for line in tokens[idx].content.split("\n"))
169 return f"<!-- {escapeHtml(content)} -->"