Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/markdown_it/rules_block/list.py: 99%

176 statements  

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

1# Lists 

2import logging 

3 

4from ..common.utils import isStrSpace 

5from .state_block import StateBlock 

6 

7LOGGER = logging.getLogger(__name__) 

8 

9 

10# Search `[-+*][\n ]`, returns next pos after marker on success 

11# or -1 on fail. 

12def skipBulletListMarker(state: StateBlock, startLine: int) -> int: 

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

14 maximum = state.eMarks[startLine] 

15 

16 try: 

17 marker = state.src[pos] 

18 except IndexError: 

19 return -1 

20 pos += 1 

21 

22 if marker not in ("*", "-", "+"): 

23 return -1 

24 

25 if pos < maximum: 

26 ch = state.src[pos] 

27 

28 if not isStrSpace(ch): 

29 # " -test " - is not a list item 

30 return -1 

31 

32 return pos 

33 

34 

35# Search `\d+[.)][\n ]`, returns next pos after marker on success 

36# or -1 on fail. 

37def skipOrderedListMarker(state: StateBlock, startLine: int) -> int: 

38 start = state.bMarks[startLine] + state.tShift[startLine] 

39 pos = start 

40 maximum = state.eMarks[startLine] 

41 

42 # List marker should have at least 2 chars (digit + dot) 

43 if pos + 1 >= maximum: 

44 return -1 

45 

46 ch = state.src[pos] 

47 pos += 1 

48 

49 ch_ord = ord(ch) 

50 # /* 0 */ /* 9 */ 

51 if ch_ord < 0x30 or ch_ord > 0x39: 

52 return -1 

53 

54 while True: 

55 # EOL -> fail 

56 if pos >= maximum: 

57 return -1 

58 

59 ch = state.src[pos] 

60 pos += 1 

61 

62 # /* 0 */ /* 9 */ 

63 ch_ord = ord(ch) 

64 if ch_ord >= 0x30 and ch_ord <= 0x39: 

65 # List marker should have no more than 9 digits 

66 # (prevents integer overflow in browsers) 

67 if pos - start >= 10: 

68 return -1 

69 

70 continue 

71 

72 # found valid marker 

73 if ch in (")", "."): 

74 break 

75 

76 return -1 

77 

78 if pos < maximum: 

79 ch = state.src[pos] 

80 

81 if not isStrSpace(ch): 

82 # " 1.test " - is not a list item 

83 return -1 

84 

85 return pos 

86 

87 

88def markTightParagraphs(state: StateBlock, idx: int) -> None: 

89 level = state.level + 2 

90 

91 i = idx + 2 

92 length = len(state.tokens) - 2 

93 while i < length: 

94 if state.tokens[i].level == level and state.tokens[i].type == "paragraph_open": 

95 state.tokens[i + 2].hidden = True 

96 state.tokens[i].hidden = True 

97 i += 2 

98 i += 1 

99 

100 

101def list_block(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: 

102 LOGGER.debug("entering list: %s, %s, %s, %s", state, startLine, endLine, silent) 

103 

104 isTerminatingParagraph = False 

105 tight = True 

106 

107 if state.is_code_block(startLine): 

108 return False 

109 

110 # Special case: 

111 # - item 1 

112 # - item 2 

113 # - item 3 

114 # - item 4 

115 # - this one is a paragraph continuation 

116 if ( 

117 state.listIndent >= 0 

118 and state.sCount[startLine] - state.listIndent >= 4 

119 and state.sCount[startLine] < state.blkIndent 

120 ): 

121 return False 

122 

123 # limit conditions when list can interrupt 

124 # a paragraph (validation mode only) 

125 # Next list item should still terminate previous list item 

126 # 

127 # This code can fail if plugins use blkIndent as well as lists, 

128 # but I hope the spec gets fixed long before that happens. 

129 # 

130 if ( 

131 silent 

132 and state.parentType == "paragraph" 

133 and state.sCount[startLine] >= state.blkIndent 

134 ): 

135 isTerminatingParagraph = True 

136 

137 # Detect list type and position after marker 

138 posAfterMarker = skipOrderedListMarker(state, startLine) 

139 if posAfterMarker >= 0: 

140 isOrdered = True 

141 start = state.bMarks[startLine] + state.tShift[startLine] 

142 markerValue = int(state.src[start : posAfterMarker - 1]) 

143 

144 # If we're starting a new ordered list right after 

145 # a paragraph, it should start with 1. 

146 if isTerminatingParagraph and markerValue != 1: 

147 return False 

148 else: 

149 posAfterMarker = skipBulletListMarker(state, startLine) 

150 if posAfterMarker >= 0: 

151 isOrdered = False 

152 else: 

153 return False 

154 

155 # If we're starting a new unordered list right after 

156 # a paragraph, first line should not be empty. 

157 if ( 

158 isTerminatingParagraph 

159 and state.skipSpaces(posAfterMarker) >= state.eMarks[startLine] 

160 ): 

161 return False 

162 

163 # We should terminate list on style change. Remember first one to compare. 

164 markerChar = state.src[posAfterMarker - 1] 

165 

166 # For validation mode we can terminate immediately 

167 if silent: 

168 return True 

169 

170 # Start list 

171 listTokIdx = len(state.tokens) 

172 

173 if isOrdered: 

174 token = state.push("ordered_list_open", "ol", 1) 

175 if markerValue != 1: 

176 token.attrs = {"start": markerValue} 

177 

178 else: 

179 token = state.push("bullet_list_open", "ul", 1) 

180 

181 token.map = listLines = [startLine, 0] 

182 token.markup = markerChar 

183 

184 # 

185 # Iterate list items 

186 # 

187 

188 nextLine = startLine 

189 prevEmptyEnd = False 

190 terminatorRules = state.md.block.ruler.getRules("list") 

191 

192 oldParentType = state.parentType 

193 state.parentType = "list" 

194 

195 while nextLine < endLine: 

196 pos = posAfterMarker 

197 maximum = state.eMarks[nextLine] 

198 

199 initial = offset = ( 

200 state.sCount[nextLine] 

201 + posAfterMarker 

202 - (state.bMarks[startLine] + state.tShift[startLine]) 

203 ) 

204 

205 while pos < maximum: 

206 ch = state.src[pos] 

207 

208 if ch == "\t": 

209 offset += 4 - (offset + state.bsCount[nextLine]) % 4 

210 elif ch == " ": 

211 offset += 1 

212 else: 

213 break 

214 

215 pos += 1 

216 

217 contentStart = pos 

218 

219 # trimming space in "- \n 3" case, indent is 1 here 

220 indentAfterMarker = 1 if contentStart >= maximum else offset - initial 

221 

222 # If we have more than 4 spaces, the indent is 1 

223 # (the rest is just indented code block) 

224 if indentAfterMarker > 4: 

225 indentAfterMarker = 1 

226 

227 # " - test" 

228 # ^^^^^ - calculating total length of this thing 

229 indent = initial + indentAfterMarker 

230 

231 # Run subparser & write tokens 

232 token = state.push("list_item_open", "li", 1) 

233 token.markup = markerChar 

234 token.map = itemLines = [startLine, 0] 

235 if isOrdered: 

236 token.info = state.src[start : posAfterMarker - 1] 

237 

238 # change current state, then restore it after parser subcall 

239 oldTight = state.tight 

240 oldTShift = state.tShift[startLine] 

241 oldSCount = state.sCount[startLine] 

242 

243 # - example list 

244 # ^ listIndent position will be here 

245 # ^ blkIndent position will be here 

246 # 

247 oldListIndent = state.listIndent 

248 state.listIndent = state.blkIndent 

249 state.blkIndent = indent 

250 

251 state.tight = True 

252 state.tShift[startLine] = contentStart - state.bMarks[startLine] 

253 state.sCount[startLine] = offset 

254 

255 if contentStart >= maximum and state.isEmpty(startLine + 1): 

256 # workaround for this case 

257 # (list item is empty, list terminates before "foo"): 

258 # ~~~~~~~~ 

259 # - 

260 # 

261 # foo 

262 # ~~~~~~~~ 

263 state.line = min(state.line + 2, endLine) 

264 else: 

265 # NOTE in list.js this was: 

266 # state.md.block.tokenize(state, startLine, endLine, True) 

267 # but tokeniz does not take the final parameter 

268 state.md.block.tokenize(state, startLine, endLine) 

269 

270 # If any of list item is tight, mark list as tight 

271 if (not state.tight) or prevEmptyEnd: 

272 tight = False 

273 

274 # Item become loose if finish with empty line, 

275 # but we should filter last element, because it means list finish 

276 prevEmptyEnd = (state.line - startLine) > 1 and state.isEmpty(state.line - 1) 

277 

278 state.blkIndent = state.listIndent 

279 state.listIndent = oldListIndent 

280 state.tShift[startLine] = oldTShift 

281 state.sCount[startLine] = oldSCount 

282 state.tight = oldTight 

283 

284 token = state.push("list_item_close", "li", -1) 

285 token.markup = markerChar 

286 

287 nextLine = startLine = state.line 

288 itemLines[1] = nextLine 

289 

290 if nextLine >= endLine: 

291 break 

292 

293 contentStart = state.bMarks[startLine] 

294 

295 # 

296 # Try to check if list is terminated or continued. 

297 # 

298 if state.sCount[nextLine] < state.blkIndent: 

299 break 

300 

301 if state.is_code_block(startLine): 

302 break 

303 

304 # fail if terminating block found 

305 terminate = False 

306 for terminatorRule in terminatorRules: 

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

308 terminate = True 

309 break 

310 

311 if terminate: 

312 break 

313 

314 # fail if list has another type 

315 if isOrdered: 

316 posAfterMarker = skipOrderedListMarker(state, nextLine) 

317 if posAfterMarker < 0: 

318 break 

319 start = state.bMarks[nextLine] + state.tShift[nextLine] 

320 else: 

321 posAfterMarker = skipBulletListMarker(state, nextLine) 

322 if posAfterMarker < 0: 

323 break 

324 

325 if markerChar != state.src[posAfterMarker - 1]: 

326 break 

327 

328 # Finalize list 

329 if isOrdered: 

330 token = state.push("ordered_list_close", "ol", -1) 

331 else: 

332 token = state.push("bullet_list_close", "ul", -1) 

333 

334 token.markup = markerChar 

335 

336 listLines[1] = nextLine 

337 state.line = nextLine 

338 

339 state.parentType = oldParentType 

340 

341 # mark paragraphs tight if needed 

342 if tight: 

343 markTightParagraphs(state, listTokIdx) 

344 

345 return True