Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/mdit_py_plugins/admon/index.py: 96%

116 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:15 +0000

1# Process admonitions and pass to cb. 

2from __future__ import annotations 

3 

4from typing import TYPE_CHECKING, Callable, Sequence 

5 

6from markdown_it import MarkdownIt 

7from markdown_it.rules_block import StateBlock 

8 

9from mdit_py_plugins.utils import is_code_block 

10 

11if TYPE_CHECKING: 

12 from markdown_it.renderer import RendererProtocol 

13 from markdown_it.token import Token 

14 from markdown_it.utils import EnvType, OptionsDict 

15 

16 

17def _get_tag(params: str) -> tuple[str, str]: 

18 """Separate the tag name from the admonition title.""" 

19 if not params.strip(): 

20 return "", "" 

21 

22 tag, *_title = params.strip().split(" ") 

23 joined = " ".join(_title) 

24 

25 title = "" 

26 if not joined: 

27 title = tag.title() 

28 elif joined != '""': 

29 title = joined 

30 return tag.lower(), title 

31 

32 

33def _validate(params: str) -> bool: 

34 """Validate the presence of the tag name after the marker.""" 

35 tag = params.strip().split(" ", 1)[-1] or "" 

36 return bool(tag) 

37 

38 

39MARKER_LEN = 3 # Regardless of extra characters, block indent stays the same 

40MARKERS = ("!!!", "???", "???+") 

41MARKER_CHARS = {_m[0] for _m in MARKERS} 

42MAX_MARKER_LEN = max(len(_m) for _m in MARKERS) 

43 

44 

45def _extra_classes(markup: str) -> list[str]: 

46 """Return the list of additional classes based on the markup.""" 

47 if markup.startswith("?"): 

48 if markup.endswith("+"): 

49 return ["is-collapsible collapsible-open"] 

50 return ["is-collapsible collapsible-closed"] 

51 return [] 

52 

53 

54def admonition(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: 

55 if is_code_block(state, startLine): 

56 return False 

57 

58 start = state.bMarks[startLine] + state.tShift[startLine] 

59 maximum = state.eMarks[startLine] 

60 

61 # Check out the first character quickly, which should filter out most of non-containers 

62 if state.src[start] not in MARKER_CHARS: 

63 return False 

64 

65 # Check out the rest of the marker string 

66 marker = "" 

67 marker_len = MAX_MARKER_LEN 

68 while marker_len > 0: 

69 marker_pos = start + marker_len 

70 markup = state.src[start:marker_pos] 

71 if markup in MARKERS: 

72 marker = markup 

73 break 

74 marker_len -= 1 

75 else: 

76 return False 

77 

78 params = state.src[marker_pos:maximum] 

79 

80 if not _validate(params): 

81 return False 

82 

83 # Since start is found, we can report success here in validation mode 

84 if silent: 

85 return True 

86 

87 old_parent = state.parentType 

88 old_line_max = state.lineMax 

89 old_indent = state.blkIndent 

90 

91 blk_start = marker_pos 

92 while blk_start < maximum and state.src[blk_start] == " ": 

93 blk_start += 1 

94 

95 state.parentType = "admonition" 

96 # Correct block indentation when extra marker characters are present 

97 marker_alignment_correction = MARKER_LEN - len(marker) 

98 state.blkIndent += blk_start - start + marker_alignment_correction 

99 

100 was_empty = False 

101 

102 # Search for the end of the block 

103 next_line = startLine 

104 while True: 

105 next_line += 1 

106 if next_line >= endLine: 

107 # unclosed block should be autoclosed by end of document. 

108 # also block seems to be autoclosed by end of parent 

109 break 

110 pos = state.bMarks[next_line] + state.tShift[next_line] 

111 maximum = state.eMarks[next_line] 

112 is_empty = state.sCount[next_line] < state.blkIndent 

113 

114 # two consecutive empty lines autoclose the block 

115 if is_empty and was_empty: 

116 break 

117 was_empty = is_empty 

118 

119 if pos < maximum and state.sCount[next_line] < state.blkIndent: 

120 # non-empty line with negative indent should stop the block: 

121 # - !!! 

122 # test 

123 break 

124 

125 # this will prevent lazy continuations from ever going past our end marker 

126 state.lineMax = next_line 

127 

128 tag, title = _get_tag(params) 

129 

130 token = state.push("admonition_open", "div", 1) 

131 token.markup = markup 

132 token.block = True 

133 token.attrs = {"class": " ".join(["admonition", tag, *_extra_classes(markup)])} 

134 token.meta = {"tag": tag} 

135 token.content = title 

136 token.info = params 

137 token.map = [startLine, next_line] 

138 

139 if title: 

140 title_markup = f"{markup} {tag}" 

141 token = state.push("admonition_title_open", "p", 1) 

142 token.markup = title_markup 

143 token.attrs = {"class": "admonition-title"} 

144 token.map = [startLine, startLine + 1] 

145 

146 token = state.push("inline", "", 0) 

147 token.content = title 

148 token.map = [startLine, startLine + 1] 

149 token.children = [] 

150 

151 token = state.push("admonition_title_close", "p", -1) 

152 

153 state.md.block.tokenize(state, startLine + 1, next_line) 

154 

155 token = state.push("admonition_close", "div", -1) 

156 token.markup = markup 

157 token.block = True 

158 

159 state.parentType = old_parent 

160 state.lineMax = old_line_max 

161 state.blkIndent = old_indent 

162 state.line = next_line 

163 

164 return True 

165 

166 

167def admon_plugin(md: MarkdownIt, render: None | Callable[..., str] = None) -> None: 

168 """Plugin to use 

169 `python-markdown style admonitions 

170 <https://python-markdown.github.io/extensions/admonition>`_. 

171 

172 .. code-block:: md 

173 

174 !!! note 

175 *content* 

176 

177 `And mkdocs-style collapsible blocks 

178 <https://squidfunk.github.io/mkdocs-material/reference/admonitions/#collapsible-blocks>`_. 

179 

180 .. code-block:: md 

181 

182 ???+ note 

183 *content* 

184 

185 Note, this is ported from 

186 `markdown-it-admon 

187 <https://github.com/commenthol/markdown-it-admon>`_. 

188 """ 

189 

190 def renderDefault( 

191 self: RendererProtocol, 

192 tokens: Sequence[Token], 

193 idx: int, 

194 _options: OptionsDict, 

195 env: EnvType, 

196 ) -> str: 

197 return self.renderToken(tokens, idx, _options, env) # type: ignore 

198 

199 render = render or renderDefault 

200 

201 md.add_render_rule("admonition_open", render) 

202 md.add_render_rule("admonition_close", render) 

203 md.add_render_rule("admonition_title_open", render) 

204 md.add_render_rule("admonition_title_close", render) 

205 

206 md.block.ruler.before( 

207 "fence", 

208 "admonition", 

209 admonition, 

210 {"alt": ["paragraph", "reference", "blockquote", "list"]}, 

211 )