Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mdit_py_plugins/myst_blocks/index.py: 91%

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

98 statements  

1from __future__ import annotations 

2 

3from collections.abc import Sequence 

4import itertools 

5from typing import TYPE_CHECKING 

6 

7from markdown_it import MarkdownIt 

8from markdown_it.common.utils import escapeHtml 

9from markdown_it.rules_block import StateBlock 

10 

11from mdit_py_plugins.utils import is_code_block 

12 

13if TYPE_CHECKING: 

14 from markdown_it.renderer import RendererProtocol 

15 from markdown_it.token import Token 

16 from markdown_it.utils import EnvType, OptionsDict 

17 

18 

19def myst_block_plugin(md: MarkdownIt) -> None: 

20 """Parse MyST targets (``(name)=``), blockquotes (``% comment``) and block breaks (``+++``).""" 

21 md.block.ruler.before( 

22 "blockquote", 

23 "myst_line_comment", 

24 line_comment, 

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

26 ) 

27 md.block.ruler.before( 

28 "hr", 

29 "myst_block_break", 

30 block_break, 

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

32 ) 

33 md.block.ruler.before( 

34 "hr", 

35 "myst_target", 

36 target, 

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

38 ) 

39 md.add_render_rule("myst_target", render_myst_target) 

40 md.add_render_rule("myst_line_comment", render_myst_line_comment) 

41 

42 

43def line_comment(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: 

44 if is_code_block(state, startLine): 

45 return False 

46 

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

48 maximum = state.eMarks[startLine] 

49 

50 if state.src[pos] != "%": 

51 return False 

52 

53 if silent: 

54 return True 

55 

56 token = state.push("myst_line_comment", "", 0) 

57 token.attrSet("class", "myst-line-comment") 

58 token.content = state.src[pos + 1 : maximum].rstrip() 

59 token.markup = "%" 

60 

61 # search end of block while appending lines to `token.content` 

62 for nextLine in itertools.count(startLine + 1): 

63 if nextLine >= endLine: 

64 break 

65 pos = state.bMarks[nextLine] + state.tShift[nextLine] 

66 maximum = state.eMarks[nextLine] 

67 

68 if state.src[pos] != "%": 

69 break 

70 token.content += "\n" + state.src[pos + 1 : maximum].rstrip() 

71 

72 state.line = nextLine 

73 token.map = [startLine, nextLine] 

74 

75 return True 

76 

77 

78def block_break(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: 

79 if is_code_block(state, startLine): 

80 return False 

81 

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

83 maximum = state.eMarks[startLine] 

84 

85 marker = state.src[pos] 

86 pos += 1 

87 

88 # Check block marker 

89 if marker != "+": 

90 return False 

91 

92 # markers can be mixed with spaces, but there should be at least 3 of them 

93 

94 cnt = 1 

95 while pos < maximum: 

96 ch = state.src[pos] 

97 if ch != marker and ch not in ("\t", " "): 

98 break 

99 if ch == marker: 

100 cnt += 1 

101 pos += 1 

102 

103 if cnt < 3: 

104 return False 

105 

106 if silent: 

107 return True 

108 

109 state.line = startLine + 1 

110 

111 token = state.push("myst_block_break", "hr", 0) 

112 token.attrSet("class", "myst-block") 

113 token.content = state.src[pos:maximum].strip() 

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

115 token.markup = marker * cnt 

116 

117 return True 

118 

119 

120def target(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: 

121 if is_code_block(state, startLine): 

122 return False 

123 

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

125 maximum = state.eMarks[startLine] 

126 

127 text = state.src[pos:maximum].strip() 

128 if not text.startswith("("): 

129 return False 

130 if not text.endswith(")="): 

131 return False 

132 if not text[1:-2]: 

133 return False 

134 

135 if silent: 

136 return True 

137 

138 state.line = startLine + 1 

139 

140 token = state.push("myst_target", "", 0) 

141 token.attrSet("class", "myst-target") 

142 token.content = text[1:-2] 

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

144 

145 return True 

146 

147 

148def render_myst_target( 

149 self: RendererProtocol, 

150 tokens: Sequence[Token], 

151 idx: int, 

152 options: OptionsDict, 

153 env: EnvType, 

154) -> str: 

155 label = tokens[idx].content 

156 class_name = "myst-target" 

157 target = f'<a href="#{label}">({label})=</a>' 

158 return f'<div class="{class_name}">{target}</div>' 

159 

160 

161def render_myst_line_comment( 

162 self: RendererProtocol, 

163 tokens: Sequence[Token], 

164 idx: int, 

165 options: OptionsDict, 

166 env: EnvType, 

167) -> str: 

168 # Strip leading whitespace from all lines 

169 content = "\n".join(line.lstrip() for line in tokens[idx].content.split("\n")) 

170 return f"<!-- {escapeHtml(content)} -->"