Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/markdown_it/rules_block/fence.py: 97%

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

67 statements  

1# fences (``` lang, ~~~ lang) 

2from __future__ import annotations 

3 

4from collections.abc import Callable 

5import logging 

6 

7from .state_block import StateBlock 

8 

9LOGGER = logging.getLogger(__name__) 

10 

11 

12def make_fence_rule( 

13 *, 

14 markers: tuple[str, ...] = ("~", "`"), 

15 token_type: str = "fence", 

16 exact_match: bool = False, 

17 disallow_marker_in_info: tuple[str, ...] = ("`",), 

18 min_markers: int = 3, 

19) -> Callable[[StateBlock, int, int, bool], bool]: 

20 """Create a fence parsing rule with configurable options. 

21 

22 :param markers: Tuple of single characters that can be used as fence markers. 

23 :param token_type: The token type name to emit (e.g. "fence", "colon_fence"). 

24 :param exact_match: If True, the closing fence must have exactly the same 

25 number of marker characters as the opening fence (not "at least as many"). 

26 This enables nesting of fences with different marker counts. 

27 :param disallow_marker_in_info: Tuple of marker characters that are not allowed 

28 to appear in the info string. The check only applies when the actual opening 

29 marker is in this tuple (e.g. a tilde fence is unaffected by ``"`"`` being 

30 listed). Per CommonMark, backtick fences cannot have backticks in the info 

31 string. Use ``()`` to disable this restriction. 

32 :param min_markers: Minimum number of marker characters to form a fence. 

33 :return: A block rule function with signature 

34 ``(state, startLine, endLine, silent) -> bool``. 

35 """ 

36 

37 closing_matcher: Callable[[int, int], bool] 

38 if exact_match: 

39 # closing code fence must have exactly the same number of markers as the opening one 

40 closing_matcher = lambda opening_len, closing_len: closing_len == opening_len # noqa: E731 

41 else: 

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

43 closing_matcher = lambda opening_len, closing_len: closing_len >= opening_len # noqa: E731 

44 

45 def _fence_rule( 

46 state: StateBlock, startLine: int, endLine: int, silent: bool 

47 ) -> bool: 

48 LOGGER.debug( 

49 "entering fence: %s, %s, %s, %s", state, startLine, endLine, silent 

50 ) 

51 

52 haveEndMarker = False 

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

54 maximum = state.eMarks[startLine] 

55 

56 if state.is_code_block(startLine): 

57 return False 

58 

59 if pos + min_markers > maximum: 

60 return False 

61 

62 marker = state.src[pos] 

63 

64 if marker not in markers: 

65 return False 

66 

67 # scan marker length 

68 mem = pos 

69 pos = state.skipCharsStr(pos, marker) 

70 

71 length = pos - mem 

72 

73 if length < min_markers: 

74 return False 

75 

76 markup = state.src[mem:pos] 

77 params = state.src[pos:maximum] 

78 

79 if marker in disallow_marker_in_info and marker in params: 

80 return False 

81 

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

83 if silent: 

84 return True 

85 

86 # search end of block 

87 nextLine = startLine 

88 

89 while True: 

90 nextLine += 1 

91 if nextLine >= endLine: 

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

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

94 break 

95 

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

97 maximum = state.eMarks[nextLine] 

98 

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

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

101 # - ``` 

102 # test 

103 break 

104 

105 try: 

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

107 continue 

108 except IndexError: 

109 break 

110 

111 if state.is_code_block(nextLine): 

112 continue 

113 

114 pos = state.skipCharsStr(pos, marker) 

115 

116 if not closing_matcher(length, pos - mem): 

117 continue 

118 

119 # make sure tail has spaces only 

120 pos = state.skipSpaces(pos) 

121 

122 if pos < maximum: 

123 continue 

124 

125 haveEndMarker = True 

126 # found! 

127 break 

128 

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

130 length = state.sCount[startLine] 

131 

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

133 

134 token = state.push(token_type, "code", 0) 

135 token.info = params 

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

137 token.markup = markup 

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

139 

140 return True 

141 

142 return _fence_rule 

143 

144 

145#: The default fence rule (backtick and tilde markers, CommonMark compliant). 

146fence = make_fence_rule()