1from collections.abc import Sequence
2import re
3from typing import TYPE_CHECKING
4
5from markdown_it import MarkdownIt
6from markdown_it.common.utils import escapeHtml
7from markdown_it.rules_inline import StateInline
8
9if TYPE_CHECKING:
10 from markdown_it.renderer import RendererProtocol
11 from markdown_it.token import Token
12 from markdown_it.utils import EnvType, OptionsDict
13
14VALID_NAME_PATTERN = re.compile(r"^\{([a-zA-Z0-9\_\-\+\:]+)\}")
15
16
17def myst_role_plugin(md: MarkdownIt) -> None:
18 """Parse ``{role-name}`content```"""
19 md.inline.ruler.before("backticks", "myst_role", myst_role)
20 md.add_render_rule("myst_role", render_myst_role)
21
22
23def myst_role(state: StateInline, silent: bool) -> bool:
24 # check name
25 match = VALID_NAME_PATTERN.match(state.src[state.pos :])
26 if not match:
27 return False
28 name = match.group(1)
29
30 # check for starting backslash escape
31 try:
32 if state.src[state.pos - 1] == "\\":
33 # escaped (this could be improved in the case of edge case '\\{')
34 return False
35 except IndexError:
36 pass
37
38 # scan opening tick length
39 start = pos = state.pos + match.end()
40 try:
41 while state.src[pos] == "`":
42 pos += 1
43 except IndexError:
44 return False
45
46 tick_length = pos - start
47 if not tick_length:
48 return False
49
50 # search for closing ticks
51 match = re.search("`" * tick_length, state.src[pos + 1 :])
52 if not match:
53 return False
54 content = state.src[pos : pos + match.start() + 1].replace("\n", " ")
55
56 if not silent:
57 token = state.push("myst_role", "", 0)
58 token.meta = {"name": name}
59 token.content = content
60
61 state.pos = pos + match.end() + 1
62
63 return True
64
65
66def render_myst_role(
67 self: "RendererProtocol",
68 tokens: Sequence["Token"],
69 idx: int,
70 options: "OptionsDict",
71 env: "EnvType",
72) -> str:
73 token = tokens[idx]
74 name = token.meta.get("name", "unknown")
75 return f'<code class="myst role">{{{name}}}[{escapeHtml(token.content)}]</code>'