Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mistune/plugins/def_list.py: 93%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

108 statements  

1import re 

2from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Match, Optional, Tuple 

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 = r"^:[ \t]+.*(?:\n|$)" 

16DD_START_RE = re.compile(r"^:[ \t]+", re.M) 

17DD_MARKER_RE = re.compile(r"^:[ \t]") 

18TRIM_RE = re.compile(r"^ {0,4}", re.M) 

19HAS_BLANK_LINE_RE = re.compile(r"\n[ \t]*\n$") 

20 

21 

22def parse_def_list(block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]: 

23 head = _get_previous_paragraph(state) 

24 if head is None: 

25 return None 

26 

27 definitions, end_pos = _collect_definitions(state.src, state.cursor, state.cursor_max, head[1]) 

28 if not definitions: 

29 return None 

30 

31 children = list(_parse_def_item(block, head[0], definitions)) 

32 _replace_previous_paragraph(state, children) 

33 return end_pos 

34 

35 

36def _parse_def_item( 

37 block: "BlockParser", 

38 head: str, 

39 definitions: List[Tuple[str, bool]], 

40) -> Iterable[Dict[str, Any]]: 

41 for line in head.splitlines(): 

42 yield { 

43 "type": "def_list_head", 

44 "text": line, 

45 } 

46 

47 for text, loose in definitions: 

48 children = _process_text(block, text, loose) 

49 yield { 

50 "type": "def_list_item", 

51 "children": children, 

52 } 

53 

54 

55def _process_text(block: "BlockParser", text: str, loose: bool) -> List[Any]: 

56 text = TRIM_RE.sub("", text) 

57 state = block.state_cls() 

58 state.process(strip_end(text)) 

59 # use default list rules 

60 block.parse(state, block.list_rules) 

61 tokens = state.tokens 

62 if not loose and len(tokens) == 1 and tokens[0]["type"] == "paragraph": 

63 tokens[0]["type"] = "block_text" 

64 return tokens 

65 

66 

67def _get_previous_paragraph(state: "BlockState") -> Optional[Tuple[str, bool]]: 

68 if not state.tokens: 

69 return None 

70 

71 last_token = state.tokens[-1] 

72 if last_token["type"] == "paragraph": 

73 return last_token["text"], False 

74 

75 if last_token["type"] == "blank_line" and len(state.tokens) > 1: 

76 prev_token = state.tokens[-2] 

77 if prev_token["type"] == "paragraph": 

78 return prev_token["text"], True 

79 

80 return None 

81 

82 

83def _replace_previous_paragraph(state: "BlockState", children: List[Dict[str, Any]]) -> None: 

84 if state.tokens[-1]["type"] == "blank_line": 

85 state.tokens.pop() 

86 state.tokens.pop() 

87 

88 if state.tokens and state.tokens[-1]["type"] == "def_list": 

89 state.tokens[-1]["children"].extend(children) 

90 else: 

91 state.append_token( 

92 { 

93 "type": "def_list", 

94 "children": children, 

95 } 

96 ) 

97 

98 

99def _collect_definitions(src: str, pos: int, max_pos: int, loose: bool) -> Tuple[List[Tuple[str, bool]], int]: 

100 definitions = [] 

101 while pos < max_pos: 

102 line = _get_line(src, pos, max_pos) 

103 if not DD_START_RE.match(line): 

104 break 

105 

106 start = pos 

107 pos += len(line) 

108 pos = _scan_definition_tail(src, pos, max_pos) 

109 # drop the ':' marker plus its trailing space or tab; a tab here would 

110 # otherwise survive TRIM_RE (spaces only) and make the line a code block 

111 text = DD_MARKER_RE.sub(" ", src[start:pos], count=1) 

112 definitions.append((text, loose)) 

113 loose = bool(HAS_BLANK_LINE_RE.search(text)) 

114 

115 return definitions, pos 

116 

117 

118def _scan_definition_tail(src: str, pos: int, max_pos: int) -> int: 

119 while pos < max_pos: 

120 line = _get_line(src, pos, max_pos) 

121 if DD_START_RE.match(line): 

122 break 

123 

124 if line.strip(): 

125 pos += len(line) 

126 continue 

127 

128 while pos < max_pos: 

129 line = _get_line(src, pos, max_pos) 

130 if line.strip(): 

131 break 

132 pos += len(line) 

133 

134 if pos >= max_pos: 

135 break 

136 

137 line = _get_line(src, pos, max_pos) 

138 if DD_START_RE.match(line): 

139 break 

140 if line.startswith((" ", "\t")): 

141 continue 

142 return pos 

143 

144 return pos 

145 

146 

147def _get_line(src: str, pos: int, max_pos: int) -> str: 

148 end = src.find("\n", pos, max_pos) 

149 if end == -1: 

150 return src[pos:max_pos] 

151 return src[pos : end + 1] 

152 

153 

154def render_def_list(renderer: "BaseRenderer", text: str) -> str: 

155 return "<dl>\n" + text + "</dl>\n" 

156 

157 

158def render_def_list_head(renderer: "BaseRenderer", text: str) -> str: 

159 return "<dt>" + text + "</dt>\n" 

160 

161 

162def render_def_list_item(renderer: "BaseRenderer", text: str) -> str: 

163 return "<dd>" + text + "</dd>\n" 

164 

165 

166def def_list(md: "Markdown") -> None: 

167 """A mistune plugin to support def list, spec defined at 

168 https://michelf.ca/projects/php-markdown/extra/#def-list 

169 

170 Here is an example: 

171 

172 .. code-block:: text 

173 

174 Apple 

175 : Pomaceous fruit of plants of the genus Malus in 

176 the family Rosaceae. 

177 

178 Orange 

179 : The fruit of an evergreen tree of the genus Citrus. 

180 

181 It will be converted into HTML: 

182 

183 .. code-block:: html 

184 

185 <dl> 

186 <dt>Apple</dt> 

187 <dd>Pomaceous fruit of plants of the genus Malus in 

188 the family Rosaceae.</dd> 

189 

190 <dt>Orange</dt> 

191 <dd>The fruit of an evergreen tree of the genus Citrus.</dd> 

192 </dl> 

193 

194 :param md: Markdown instance 

195 """ 

196 md.block.register("def_list", DEF_PATTERN, parse_def_list, before="paragraph") 

197 if md.renderer and md.renderer.NAME == "html": 

198 md.renderer.register("def_list", render_def_list) 

199 md.renderer.register("def_list_head", render_def_list_head) 

200 md.renderer.register("def_list_item", render_def_list_item)