1import re
2from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Match
3
4from ..util import strip_end
5
6if TYPE_CHECKING:
7 from ..block_parser import BlockParser
8 from ..core import BaseRenderer, BlockState
9 from ..markdown import Markdown
10
11__all__ = ["def_list"]
12
13# https://michelf.ca/projects/php-markdown/extra/#def-list
14
15DEF_PATTERN = (
16 r"^(?P<def_list_head>(?:[^\n]+\n)+?)"
17 r"\n?(?:"
18 r"\:[ \t]+.*\n"
19 r"(?:[^\n]+\n)*" # lazy continue line
20 r"(?:(?:[ \t]*\n)*[ \t]+[^\n]+\n)*"
21 r"(?:[ \t]*\n)*"
22 r")+"
23)
24DEF_RE = re.compile(DEF_PATTERN, re.M)
25DD_START_RE = re.compile(r"^:[ \t]+", re.M)
26TRIM_RE = re.compile(r"^ {0,4}", re.M)
27HAS_BLANK_LINE_RE = re.compile(r"\n[ \t]*\n$")
28
29
30def parse_def_list(block: "BlockParser", m: Match[str], state: "BlockState") -> int:
31 pos = m.end()
32 children = list(_parse_def_item(block, m))
33
34 m2 = DEF_RE.match(state.src, pos)
35 while m2:
36 children.extend(list(_parse_def_item(block, m2)))
37 pos = m2.end()
38 m2 = DEF_RE.match(state.src, pos)
39
40 state.append_token(
41 {
42 "type": "def_list",
43 "children": children,
44 }
45 )
46 return pos
47
48
49def _parse_def_item(block: "BlockParser", m: Match[str]) -> Iterable[Dict[str, Any]]:
50 head = m.group("def_list_head")
51 for line in head.splitlines():
52 yield {
53 "type": "def_list_head",
54 "text": line,
55 }
56
57 src = m.group(0)
58 end = len(head)
59
60 m2 = DD_START_RE.search(src, end)
61 assert m2 is not None
62 start = m2.start()
63 prev_blank_line = src[end:start] == "\n"
64 while m2:
65 m2 = DD_START_RE.search(src, start + 1)
66 if not m2:
67 break
68
69 end = m2.start()
70 text = src[start:end].replace(":", " ", 1)
71 children = _process_text(block, text, prev_blank_line)
72 prev_blank_line = bool(HAS_BLANK_LINE_RE.search(text))
73 yield {
74 "type": "def_list_item",
75 "children": children,
76 }
77 start = end
78
79 text = src[start:].replace(":", " ", 1)
80 children = _process_text(block, text, prev_blank_line)
81 yield {
82 "type": "def_list_item",
83 "children": children,
84 }
85
86
87def _process_text(block: "BlockParser", text: str, loose: bool) -> List[Any]:
88 text = TRIM_RE.sub("", text)
89 state = block.state_cls()
90 state.process(strip_end(text))
91 # use default list rules
92 block.parse(state, block.list_rules)
93 tokens = state.tokens
94 if not loose and len(tokens) == 1 and tokens[0]["type"] == "paragraph":
95 tokens[0]["type"] = "block_text"
96 return tokens
97
98
99def render_def_list(renderer: "BaseRenderer", text: str) -> str:
100 return "<dl>\n" + text + "</dl>\n"
101
102
103def render_def_list_head(renderer: "BaseRenderer", text: str) -> str:
104 return "<dt>" + text + "</dt>\n"
105
106
107def render_def_list_item(renderer: "BaseRenderer", text: str) -> str:
108 return "<dd>" + text + "</dd>\n"
109
110
111def def_list(md: "Markdown") -> None:
112 """A mistune plugin to support def list, spec defined at
113 https://michelf.ca/projects/php-markdown/extra/#def-list
114
115 Here is an example:
116
117 .. code-block:: text
118
119 Apple
120 : Pomaceous fruit of plants of the genus Malus in
121 the family Rosaceae.
122
123 Orange
124 : The fruit of an evergreen tree of the genus Citrus.
125
126 It will be converted into HTML:
127
128 .. code-block:: html
129
130 <dl>
131 <dt>Apple</dt>
132 <dd>Pomaceous fruit of plants of the genus Malus in
133 the family Rosaceae.</dd>
134
135 <dt>Orange</dt>
136 <dd>The fruit of an evergreen tree of the genus Citrus.</dd>
137 </dl>
138
139 :param md: Markdown instance
140 """
141 md.block.register("def_list", DEF_PATTERN, parse_def_list, before="paragraph")
142 if md.renderer and md.renderer.NAME == "html":
143 md.renderer.register("def_list", render_def_list)
144 md.renderer.register("def_list_head", render_def_list_head)
145 md.renderer.register("def_list_item", render_def_list_item)