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

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

78 statements  

1from __future__ import annotations 

2 

3from typing import TYPE_CHECKING, Sequence 

4 

5from markdown_it import MarkdownIt 

6from markdown_it.common.utils import escapeHtml, unescapeAll 

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 colon_fence_plugin(md: MarkdownIt) -> None: 

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

19 

20 Example:: 

21 

22 :::name 

23 contained text 

24 ::: 

25 

26 """ 

27 

28 md.block.ruler.before( 

29 "fence", 

30 "colon_fence", 

31 _rule, 

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

33 ) 

34 md.add_render_rule("colon_fence", _render) 

35 

36 

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

38 if is_code_block(state, startLine): 

39 return False 

40 

41 haveEndMarker = False 

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

43 maximum = state.eMarks[startLine] 

44 

45 if pos + 3 > maximum: 

46 return False 

47 

48 marker = state.src[pos] 

49 

50 if marker != ":": 

51 return False 

52 

53 # scan marker length 

54 mem = pos 

55 pos = _skipCharsStr(state, pos, marker) 

56 

57 length = pos - mem 

58 

59 if length < 3: 

60 return False 

61 

62 markup = state.src[mem:pos] 

63 params = state.src[pos:maximum] 

64 

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

66 if silent: 

67 return True 

68 

69 # search end of block 

70 nextLine = startLine 

71 

72 while True: 

73 nextLine += 1 

74 if nextLine >= endLine: 

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

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

77 break 

78 

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

80 maximum = state.eMarks[nextLine] 

81 

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

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

84 # - ``` 

85 # test 

86 break 

87 

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

89 continue 

90 

91 if is_code_block(state, nextLine): 

92 continue 

93 

94 pos = _skipCharsStr(state, pos, marker) 

95 

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

97 if pos - mem < length: 

98 continue 

99 

100 # make sure tail has spaces only 

101 pos = state.skipSpaces(pos) 

102 

103 if pos < maximum: 

104 continue 

105 

106 haveEndMarker = True 

107 # found! 

108 break 

109 

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

111 length = state.sCount[startLine] 

112 

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

114 

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

116 token.info = params 

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

118 token.markup = markup 

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

120 

121 return True 

122 

123 

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

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

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

127 while True: 

128 try: 

129 current = state.src[pos] 

130 except IndexError: 

131 break 

132 if current != ch: 

133 break 

134 pos += 1 

135 return pos 

136 

137 

138def _render( 

139 self: RendererProtocol, 

140 tokens: Sequence[Token], 

141 idx: int, 

142 options: OptionsDict, 

143 env: EnvType, 

144) -> str: 

145 token = tokens[idx] 

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

147 content = escapeHtml(token.content) 

148 block_name = "" 

149 

150 if info: 

151 block_name = info.split()[0] 

152 

153 return ( 

154 "<pre><code" 

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

156 + ">" 

157 + content 

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

159 )