Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mdit_py_plugins/colon_fence.py: 88%

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

81 statements  

1from __future__ import annotations 

2 

3from collections.abc import Sequence 

4from typing import TYPE_CHECKING 

5 

6from markdown_it import MarkdownIt 

7from markdown_it.common.utils import escapeHtml, unescapeAll 

8from markdown_it.rules_block import StateBlock 

9 

10from mdit_py_plugins.utils import is_code_block 

11 

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 

16 

17 

18def colon_fence_plugin(md: MarkdownIt) -> None: 

19 """This plugin directly mimics regular fences, but with `:` colons. 

20 

21 Example:: 

22 

23 :::name 

24 contained text 

25 ::: 

26 

27 """ 

28 

29 md.block.ruler.before( 

30 "fence", 

31 "colon_fence", 

32 _rule, 

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

34 ) 

35 md.add_render_rule("colon_fence", _render) 

36 

37 

38def _rule(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: 

39 if is_code_block(state, startLine): 

40 return False 

41 

42 haveEndMarker = False 

43 pos = state.bMarks[startLine] + state.tShift[startLine] 

44 maximum = state.eMarks[startLine] 

45 

46 if pos + 3 > maximum: 

47 return False 

48 

49 marker = state.src[pos] 

50 

51 if marker != ":": 

52 return False 

53 

54 # scan marker length 

55 mem = pos 

56 pos = _skipCharsStr(state, pos, marker) 

57 

58 length = pos - mem 

59 

60 if length < 3: 

61 return False 

62 

63 markup = state.src[mem:pos] 

64 params = state.src[pos:maximum] 

65 

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

67 if silent: 

68 return True 

69 

70 # search end of block 

71 nextLine = startLine 

72 

73 while True: 

74 nextLine += 1 

75 if nextLine >= endLine: 

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

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

78 break 

79 

80 pos = mem = state.bMarks[nextLine] + state.tShift[nextLine] 

81 maximum = state.eMarks[nextLine] 

82 

83 if pos < maximum and state.sCount[nextLine] < state.blkIndent: 

84 # non-empty line with negative indent should stop the list: 

85 # - ``` 

86 # test 

87 break 

88 

89 if state.src[pos] != marker: 

90 continue 

91 

92 if is_code_block(state, nextLine): 

93 continue 

94 

95 pos = _skipCharsStr(state, pos, marker) 

96 

97 # closing code fence must be at least as long as the opening one 

98 if pos - mem < length: 

99 continue 

100 

101 # make sure tail has spaces only 

102 pos = state.skipSpaces(pos) 

103 

104 if pos < maximum: 

105 continue 

106 

107 haveEndMarker = True 

108 # found! 

109 break 

110 

111 # If a fence has heading spaces, they should be removed from its inner block 

112 length = state.sCount[startLine] 

113 

114 state.line = nextLine + (1 if haveEndMarker else 0) 

115 

116 token = state.push("colon_fence", "code", 0) 

117 token.info = params 

118 token.content = state.getLines(startLine + 1, nextLine, length, True) 

119 token.markup = markup 

120 token.map = [startLine, state.line] 

121 

122 return True 

123 

124 

125def _skipCharsStr(state: StateBlock, pos: int, ch: str) -> int: 

126 """Skip character string from given position.""" 

127 # TODO this can be replaced with StateBlock.skipCharsStr in markdown-it-py 3.0.0 

128 while True: 

129 try: 

130 current = state.src[pos] 

131 except IndexError: 

132 break 

133 if current != ch: 

134 break 

135 pos += 1 

136 return pos 

137 

138 

139def _render( 

140 self: RendererProtocol, 

141 tokens: Sequence[Token], 

142 idx: int, 

143 options: OptionsDict, 

144 env: EnvType, 

145) -> str: 

146 token = tokens[idx] 

147 info = unescapeAll(token.info).strip() if token.info else "" 

148 content = escapeHtml(token.content) 

149 block_name = "" 

150 

151 if info: 

152 block_name = info.split()[0] 

153 

154 return ( 

155 "<pre><code" 

156 + (f' class="block-{block_name}" ' if block_name else "") 

157 + ">" 

158 + content 

159 + "</code></pre>\n" 

160 )