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

97 statements  

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

1from __future__ import annotations 

2 

3import itertools 

4from typing import TYPE_CHECKING, Sequence 

5 

6from markdown_it import MarkdownIt 

7from markdown_it.common.utils import escapeHtml 

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

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

20 md.block.ruler.before( 

21 "blockquote", 

22 "myst_line_comment", 

23 line_comment, 

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

25 ) 

26 md.block.ruler.before( 

27 "hr", 

28 "myst_block_break", 

29 block_break, 

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

31 ) 

32 md.block.ruler.before( 

33 "hr", 

34 "myst_target", 

35 target, 

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

37 ) 

38 md.add_render_rule("myst_target", render_myst_target) 

39 md.add_render_rule("myst_line_comment", render_myst_line_comment) 

40 

41 

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

43 if is_code_block(state, startLine): 

44 return False 

45 

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

47 maximum = state.eMarks[startLine] 

48 

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

50 return False 

51 

52 if silent: 

53 return True 

54 

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

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

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

58 token.markup = "%" 

59 

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

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

62 if nextLine >= endLine: 

63 break 

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

65 maximum = state.eMarks[nextLine] 

66 

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

68 break 

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

70 

71 state.line = nextLine 

72 token.map = [startLine, nextLine] 

73 

74 return True 

75 

76 

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

78 if is_code_block(state, startLine): 

79 return False 

80 

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

82 maximum = state.eMarks[startLine] 

83 

84 marker = state.src[pos] 

85 pos += 1 

86 

87 # Check block marker 

88 if marker != "+": 

89 return False 

90 

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

92 

93 cnt = 1 

94 while pos < maximum: 

95 ch = state.src[pos] 

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

97 break 

98 if ch == marker: 

99 cnt += 1 

100 pos += 1 

101 

102 if cnt < 3: 

103 return False 

104 

105 if silent: 

106 return True 

107 

108 state.line = startLine + 1 

109 

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

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

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

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

114 token.markup = marker * cnt 

115 

116 return True 

117 

118 

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

120 if is_code_block(state, startLine): 

121 return False 

122 

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

124 maximum = state.eMarks[startLine] 

125 

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

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

128 return False 

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

130 return False 

131 if not text[1:-2]: 

132 return False 

133 

134 if silent: 

135 return True 

136 

137 state.line = startLine + 1 

138 

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

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

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

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

143 

144 return True 

145 

146 

147def render_myst_target( 

148 self: RendererProtocol, 

149 tokens: Sequence[Token], 

150 idx: int, 

151 options: OptionsDict, 

152 env: EnvType, 

153) -> str: 

154 label = tokens[idx].content 

155 class_name = "myst-target" 

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

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

158 

159 

160def render_myst_line_comment( 

161 self: RendererProtocol, 

162 tokens: Sequence[Token], 

163 idx: int, 

164 options: OptionsDict, 

165 env: EnvType, 

166) -> str: 

167 # Strip leading whitespace from all lines 

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

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