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

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

142 statements  

1import logging 

2 

3from ..common.utils import charCodeAt, isSpace, normalizeReference 

4from .state_block import StateBlock 

5 

6LOGGER = logging.getLogger(__name__) 

7 

8 

9def reference(state: StateBlock, startLine: int, _endLine: int, silent: bool) -> bool: 

10 LOGGER.debug( 

11 "entering reference: %s, %s, %s, %s", state, startLine, _endLine, silent 

12 ) 

13 

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

15 maximum = state.eMarks[startLine] 

16 nextLine = startLine + 1 

17 

18 if state.is_code_block(startLine): 

19 return False 

20 

21 if state.src[pos] != "[": 

22 return False 

23 

24 string = state.src[pos : maximum + 1] 

25 

26 # string = state.getLines(startLine, nextLine, state.blkIndent, False).strip() 

27 maximum = len(string) 

28 

29 labelEnd = None 

30 pos = 1 

31 while pos < maximum: 

32 ch = charCodeAt(string, pos) 

33 if ch == 0x5B: # /* [ */ 

34 return False 

35 elif ch == 0x5D: # /* ] */ 

36 labelEnd = pos 

37 break 

38 elif ch == 0x0A: # /* \n */ 

39 if (lineContent := getNextLine(state, nextLine)) is not None: 

40 string += lineContent 

41 maximum = len(string) 

42 nextLine += 1 

43 elif ch == 0x5C: # /* \ */ 

44 pos += 1 

45 if ( 

46 pos < maximum 

47 and charCodeAt(string, pos) == 0x0A 

48 and (lineContent := getNextLine(state, nextLine)) is not None 

49 ): 

50 string += lineContent 

51 maximum = len(string) 

52 nextLine += 1 

53 pos += 1 

54 

55 if ( 

56 labelEnd is None or labelEnd < 0 or charCodeAt(string, labelEnd + 1) != 0x3A 

57 ): # /* : */ 

58 return False 

59 

60 # [label]: destination 'title' 

61 # ^^^ skip optional whitespace here 

62 pos = labelEnd + 2 

63 while pos < maximum: 

64 ch = charCodeAt(string, pos) 

65 if ch == 0x0A: 

66 if (lineContent := getNextLine(state, nextLine)) is not None: 

67 string += lineContent 

68 maximum = len(string) 

69 nextLine += 1 

70 elif isSpace(ch): 

71 pass 

72 else: 

73 break 

74 pos += 1 

75 

76 # [label]: destination 'title' 

77 # ^^^^^^^^^^^ parse this 

78 destRes = state.md.helpers.parseLinkDestination(string, pos, maximum) 

79 if not destRes.ok: 

80 return False 

81 

82 href = state.md.normalizeLink(destRes.str) 

83 if not state.md.validateLink(href): 

84 return False 

85 

86 pos = destRes.pos 

87 

88 # save cursor state, we could require to rollback later 

89 destEndPos = pos 

90 destEndLineNo = nextLine 

91 

92 # [label]: destination 'title' 

93 # ^^^ skipping those spaces 

94 start = pos 

95 while pos < maximum: 

96 ch = charCodeAt(string, pos) 

97 if ch == 0x0A: 

98 if (lineContent := getNextLine(state, nextLine)) is not None: 

99 string += lineContent 

100 maximum = len(string) 

101 nextLine += 1 

102 elif isSpace(ch): 

103 pass 

104 else: 

105 break 

106 pos += 1 

107 

108 # [label]: destination 'title' 

109 # ^^^^^^^ parse this 

110 titleRes = state.md.helpers.parseLinkTitle(string, pos, maximum, None) 

111 while titleRes.can_continue: 

112 if (lineContent := getNextLine(state, nextLine)) is None: 

113 break 

114 string += lineContent 

115 pos = maximum 

116 maximum = len(string) 

117 nextLine += 1 

118 titleRes = state.md.helpers.parseLinkTitle(string, pos, maximum, titleRes) 

119 

120 if pos < maximum and start != pos and titleRes.ok: 

121 title = titleRes.str 

122 pos = titleRes.pos 

123 else: 

124 title = "" 

125 pos = destEndPos 

126 nextLine = destEndLineNo 

127 

128 # skip trailing spaces until the rest of the line 

129 while pos < maximum: 

130 ch = charCodeAt(string, pos) 

131 if not isSpace(ch): 

132 break 

133 pos += 1 

134 

135 if pos < maximum and charCodeAt(string, pos) != 0x0A and title: 

136 # garbage at the end of the line after title, 

137 # but it could still be a valid reference if we roll back 

138 title = "" 

139 pos = destEndPos 

140 nextLine = destEndLineNo 

141 while pos < maximum: 

142 ch = charCodeAt(string, pos) 

143 if not isSpace(ch): 

144 break 

145 pos += 1 

146 

147 if pos < maximum and charCodeAt(string, pos) != 0x0A: 

148 # garbage at the end of the line 

149 return False 

150 

151 label = normalizeReference(string[1:labelEnd]) 

152 if not label: 

153 # CommonMark 0.20 disallows empty labels 

154 return False 

155 

156 # Reference can not terminate anything. This check is for safety only. 

157 if silent: 

158 return True 

159 

160 if "references" not in state.env: 

161 state.env["references"] = {} 

162 

163 state.line = nextLine 

164 

165 # note, this is not part of markdown-it JS, but is useful for renderers 

166 if state.md.options.get("inline_definitions", False): 

167 token = state.push("definition", "", 0) 

168 token.meta = { 

169 "id": label, 

170 "title": title, 

171 "url": href, 

172 "label": string[1:labelEnd], 

173 } 

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

175 

176 if label not in state.env["references"]: 

177 state.env["references"][label] = { 

178 "title": title, 

179 "href": href, 

180 "map": [startLine, state.line], 

181 } 

182 else: 

183 state.env.setdefault("duplicate_refs", []).append( 

184 { 

185 "title": title, 

186 "href": href, 

187 "label": label, 

188 "map": [startLine, state.line], 

189 } 

190 ) 

191 

192 return True 

193 

194 

195def getNextLine(state: StateBlock, nextLine: int) -> None | str: 

196 endLine = state.lineMax 

197 

198 if nextLine >= endLine or state.isEmpty(nextLine): 

199 # empty line or end of input 

200 return None 

201 

202 isContinuation = False 

203 

204 # this would be a code block normally, but after paragraph 

205 # it's considered a lazy continuation regardless of what's there 

206 if state.is_code_block(nextLine): 

207 isContinuation = True 

208 

209 # quirk for blockquotes, this line should already be checked by that rule 

210 if state.sCount[nextLine] < 0: 

211 isContinuation = True 

212 

213 if not isContinuation: 

214 terminatorRules = state.md.block.ruler.getRules("reference") 

215 oldParentType = state.parentType 

216 state.parentType = "reference" 

217 

218 # Some tags can terminate paragraph without empty line. 

219 terminate = False 

220 for terminatorRule in terminatorRules: 

221 if terminatorRule(state, nextLine, endLine, True): 

222 terminate = True 

223 break 

224 

225 state.parentType = oldParentType 

226 

227 if terminate: 

228 # terminated by another block 

229 return None 

230 

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

232 maximum = state.eMarks[nextLine] 

233 

234 # max + 1 explicitly includes the newline 

235 return state.src[pos : maximum + 1]