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

178 statements  

« prev     ^ index     » next       coverage.py v7.2.1, created at 2023-03-14 06:12 +0000

1# Lists 

2import logging 

3 

4from ..common.utils import isSpace 

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): 

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

14 maximum = state.eMarks[startLine] 

15 

16 try: 

17 marker = state.srcCharCode[pos] 

18 except IndexError: 

19 return -1 

20 pos += 1 

21 # Check bullet /* * */ /* - */ /* + */ 

22 if marker != 0x2A and marker != 0x2D and marker != 0x2B: 

23 return -1 

24 

25 if pos < maximum: 

26 ch = state.srcCharCode[pos] 

27 

28 if not isSpace(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): 

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.srcCharCode[pos] 

47 pos += 1 

48 

49 # /* 0 */ /* 9 */ 

50 if ch < 0x30 or ch > 0x39: 

51 return -1 

52 

53 while True: 

54 # EOL -> fail 

55 if pos >= maximum: 

56 return -1 

57 

58 ch = state.srcCharCode[pos] 

59 pos += 1 

60 

61 # /* 0 */ /* 9 */ 

62 if ch >= 0x30 and ch <= 0x39: 

63 # List marker should have no more than 9 digits 

64 # (prevents integer overflow in browsers) 

65 if pos - start >= 10: 

66 return -1 

67 

68 continue 

69 

70 # found valid marker: /* ) */ /* . */ 

71 if ch == 0x29 or ch == 0x2E: 

72 break 

73 

74 return -1 

75 

76 if pos < maximum: 

77 ch = state.srcCharCode[pos] 

78 

79 if not isSpace(ch): 

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

81 return -1 

82 

83 return pos 

84 

85 

86def markTightParagraphs(state: StateBlock, idx: int): 

87 level = state.level + 2 

88 

89 i = idx + 2 

90 length = len(state.tokens) - 2 

91 while i < length: 

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

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

94 state.tokens[i].hidden = True 

95 i += 2 

96 i += 1 

97 

98 

99def list_block(state: StateBlock, startLine: int, endLine: int, silent: bool): 

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

101 

102 isTerminatingParagraph = False 

103 tight = True 

104 

105 # if it's indented more than 3 spaces, it should be a code block 

106 if state.sCount[startLine] - state.blkIndent >= 4: 

107 return False 

108 

109 # Special case: 

110 # - item 1 

111 # - item 2 

112 # - item 3 

113 # - item 4 

114 # - this one is a paragraph continuation 

115 if ( 

116 state.listIndent >= 0 

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

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

119 ): 

120 return False 

121 

122 # limit conditions when list can interrupt 

123 # a paragraph (validation mode only) 

124 if silent and state.parentType == "paragraph": 

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 state.tShift[startLine] >= state.blkIndent: 

131 isTerminatingParagraph = True 

132 

133 # Detect list type and position after marker 

134 posAfterMarker = skipOrderedListMarker(state, startLine) 

135 if posAfterMarker >= 0: 

136 isOrdered = True 

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

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

139 

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

141 # a paragraph, it should start with 1. 

142 if isTerminatingParagraph and markerValue != 1: 

143 return False 

144 else: 

145 posAfterMarker = skipBulletListMarker(state, startLine) 

146 if posAfterMarker >= 0: 

147 isOrdered = False 

148 else: 

149 return False 

150 

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

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

153 if isTerminatingParagraph: 

154 if state.skipSpaces(posAfterMarker) >= state.eMarks[startLine]: 

155 return False 

156 

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

158 markerCharCode = state.srcCharCode[posAfterMarker - 1] 

159 

160 # For validation mode we can terminate immediately 

161 if silent: 

162 return True 

163 

164 # Start list 

165 listTokIdx = len(state.tokens) 

166 

167 if isOrdered: 

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

169 if markerValue != 1: 

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

171 

172 else: 

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

174 

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

176 token.markup = chr(markerCharCode) 

177 

178 # 

179 # Iterate list items 

180 # 

181 

182 nextLine = startLine 

183 prevEmptyEnd = False 

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

185 

186 oldParentType = state.parentType 

187 state.parentType = "list" 

188 

189 while nextLine < endLine: 

190 pos = posAfterMarker 

191 maximum = state.eMarks[nextLine] 

192 

193 initial = offset = ( 

194 state.sCount[nextLine] 

195 + posAfterMarker 

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

197 ) 

198 

199 while pos < maximum: 

200 ch = state.srcCharCode[pos] 

201 

202 if ch == 0x09: # \t 

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

204 elif ch == 0x20: # \s 

205 offset += 1 

206 else: 

207 break 

208 

209 pos += 1 

210 

211 contentStart = pos 

212 

213 if contentStart >= maximum: 

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

215 indentAfterMarker = 1 

216 else: 

217 indentAfterMarker = offset - initial 

218 

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

220 # (the rest is just indented code block) 

221 if indentAfterMarker > 4: 

222 indentAfterMarker = 1 

223 

224 # " - test" 

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

226 indent = initial + indentAfterMarker 

227 

228 # Run subparser & write tokens 

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

230 token.markup = chr(markerCharCode) 

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

232 if isOrdered: 

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

234 

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

236 oldTight = state.tight 

237 oldTShift = state.tShift[startLine] 

238 oldSCount = state.sCount[startLine] 

239 

240 # - example list 

241 # ^ listIndent position will be here 

242 # ^ blkIndent position will be here 

243 # 

244 oldListIndent = state.listIndent 

245 state.listIndent = state.blkIndent 

246 state.blkIndent = indent 

247 

248 state.tight = True 

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

250 state.sCount[startLine] = offset 

251 

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

253 # workaround for this case 

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

255 # ~~~~~~~~ 

256 # - 

257 # 

258 # foo 

259 # ~~~~~~~~ 

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

261 else: 

262 # NOTE in list.js this was: 

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

264 # but tokeniz does not take the final parameter 

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

266 

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

268 if (not state.tight) or prevEmptyEnd: 

269 tight = False 

270 

271 # Item become loose if finish with empty line, 

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

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

274 

275 state.blkIndent = state.listIndent 

276 state.listIndent = oldListIndent 

277 state.tShift[startLine] = oldTShift 

278 state.sCount[startLine] = oldSCount 

279 state.tight = oldTight 

280 

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

282 token.markup = chr(markerCharCode) 

283 

284 nextLine = startLine = state.line 

285 itemLines[1] = nextLine 

286 

287 if nextLine >= endLine: 

288 break 

289 

290 contentStart = state.bMarks[startLine] 

291 

292 # 

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

294 # 

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

296 break 

297 

298 # if it's indented more than 3 spaces, it should be a code block 

299 if state.sCount[startLine] - state.blkIndent >= 4: 

300 break 

301 

302 # fail if terminating block found 

303 terminate = False 

304 for terminatorRule in terminatorRules: 

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

306 terminate = True 

307 break 

308 

309 if terminate: 

310 break 

311 

312 # fail if list has another type 

313 if isOrdered: 

314 posAfterMarker = skipOrderedListMarker(state, nextLine) 

315 if posAfterMarker < 0: 

316 break 

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

318 else: 

319 posAfterMarker = skipBulletListMarker(state, nextLine) 

320 if posAfterMarker < 0: 

321 break 

322 

323 if markerCharCode != state.srcCharCode[posAfterMarker - 1]: 

324 break 

325 

326 # Finalize list 

327 if isOrdered: 

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

329 else: 

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

331 

332 token.markup = chr(markerCharCode) 

333 

334 listLines[1] = nextLine 

335 state.line = nextLine 

336 

337 state.parentType = oldParentType 

338 

339 # mark paragraphs tight if needed 

340 if tight: 

341 markTightParagraphs(state, listTokIdx) 

342 

343 return True