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

175 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:07 +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 marker = state.srcCharCode[pos] 

17 pos += 1 

18 # Check bullet /* * */ /* - */ /* + */ 

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

20 return -1 

21 

22 if pos < maximum: 

23 ch = state.srcCharCode[pos] 

24 

25 if not isSpace(ch): 

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

27 return -1 

28 

29 return pos 

30 

31 

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

33# or -1 on fail. 

34def skipOrderedListMarker(state: StateBlock, startLine: int): 

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

36 pos = start 

37 maximum = state.eMarks[startLine] 

38 

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

40 if pos + 1 >= maximum: 

41 return -1 

42 

43 ch = state.srcCharCode[pos] 

44 pos += 1 

45 

46 # /* 0 */ /* 9 */ 

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

48 return -1 

49 

50 while True: 

51 # EOL -> fail 

52 if pos >= maximum: 

53 return -1 

54 

55 ch = state.srcCharCode[pos] 

56 pos += 1 

57 

58 # /* 0 */ /* 9 */ 

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

60 # List marker should have no more than 9 digits 

61 # (prevents integer overflow in browsers) 

62 if pos - start >= 10: 

63 return -1 

64 

65 continue 

66 

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

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

69 break 

70 

71 return -1 

72 

73 if pos < maximum: 

74 ch = state.srcCharCode[pos] 

75 

76 if not isSpace(ch): 

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

78 return -1 

79 

80 return pos 

81 

82 

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

84 level = state.level + 2 

85 

86 i = idx + 2 

87 length = len(state.tokens) - 2 

88 while i < length: 

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

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

91 state.tokens[i].hidden = True 

92 i += 2 

93 i += 1 

94 

95 

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

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

98 

99 isTerminatingParagraph = False 

100 tight = True 

101 

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

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

104 return False 

105 

106 # Special case: 

107 # - item 1 

108 # - item 2 

109 # - item 3 

110 # - item 4 

111 # - this one is a paragraph continuation 

112 if ( 

113 state.listIndent >= 0 

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

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

116 ): 

117 return False 

118 

119 # limit conditions when list can interrupt 

120 # a paragraph (validation mode only) 

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

122 # Next list item should still terminate previous list item 

123 # 

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

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

126 # 

127 if state.tShift[startLine] >= state.blkIndent: 

128 isTerminatingParagraph = True 

129 

130 # Detect list type and position after marker 

131 posAfterMarker = skipOrderedListMarker(state, startLine) 

132 if posAfterMarker >= 0: 

133 isOrdered = True 

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

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

136 

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

138 # a paragraph, it should start with 1. 

139 if isTerminatingParagraph and markerValue != 1: 

140 return False 

141 else: 

142 posAfterMarker = skipBulletListMarker(state, startLine) 

143 if posAfterMarker >= 0: 

144 isOrdered = False 

145 else: 

146 return False 

147 

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

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

150 if isTerminatingParagraph: 

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

152 return False 

153 

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

155 markerCharCode = state.srcCharCode[posAfterMarker - 1] 

156 

157 # For validation mode we can terminate immediately 

158 if silent: 

159 return True 

160 

161 # Start list 

162 listTokIdx = len(state.tokens) 

163 

164 if isOrdered: 

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

166 if markerValue != 1: 

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

168 

169 else: 

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

171 

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

173 token.markup = chr(markerCharCode) 

174 

175 # 

176 # Iterate list items 

177 # 

178 

179 nextLine = startLine 

180 prevEmptyEnd = False 

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

182 

183 oldParentType = state.parentType 

184 state.parentType = "list" 

185 

186 while nextLine < endLine: 

187 pos = posAfterMarker 

188 maximum = state.eMarks[nextLine] 

189 

190 initial = offset = ( 

191 state.sCount[nextLine] 

192 + posAfterMarker 

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

194 ) 

195 

196 while pos < maximum: 

197 ch = state.srcCharCode[pos] 

198 

199 if ch == 0x09: # \t 

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

201 elif ch == 0x20: # \s 

202 offset += 1 

203 else: 

204 break 

205 

206 pos += 1 

207 

208 contentStart = pos 

209 

210 if contentStart >= maximum: 

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

212 indentAfterMarker = 1 

213 else: 

214 indentAfterMarker = offset - initial 

215 

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

217 # (the rest is just indented code block) 

218 if indentAfterMarker > 4: 

219 indentAfterMarker = 1 

220 

221 # " - test" 

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

223 indent = initial + indentAfterMarker 

224 

225 # Run subparser & write tokens 

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

227 token.markup = chr(markerCharCode) 

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

229 if isOrdered: 

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

231 

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

233 oldTight = state.tight 

234 oldTShift = state.tShift[startLine] 

235 oldSCount = state.sCount[startLine] 

236 

237 # - example list 

238 # ^ listIndent position will be here 

239 # ^ blkIndent position will be here 

240 # 

241 oldListIndent = state.listIndent 

242 state.listIndent = state.blkIndent 

243 state.blkIndent = indent 

244 

245 state.tight = True 

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

247 state.sCount[startLine] = offset 

248 

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

250 # workaround for this case 

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

252 # ~~~~~~~~ 

253 # - 

254 # 

255 # foo 

256 # ~~~~~~~~ 

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

258 else: 

259 # NOTE in list.js this was: 

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

261 # but tokeniz does not take the final parameter 

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

263 

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

265 if (not state.tight) or prevEmptyEnd: 

266 tight = False 

267 

268 # Item become loose if finish with empty line, 

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

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

271 

272 state.blkIndent = state.listIndent 

273 state.listIndent = oldListIndent 

274 state.tShift[startLine] = oldTShift 

275 state.sCount[startLine] = oldSCount 

276 state.tight = oldTight 

277 

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

279 token.markup = chr(markerCharCode) 

280 

281 nextLine = startLine = state.line 

282 itemLines[1] = nextLine 

283 

284 if nextLine >= endLine: 

285 break 

286 

287 contentStart = state.bMarks[startLine] 

288 

289 # 

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

291 # 

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

293 break 

294 

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

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

297 break 

298 

299 # fail if terminating block found 

300 terminate = False 

301 for terminatorRule in terminatorRules: 

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

303 terminate = True 

304 break 

305 

306 if terminate: 

307 break 

308 

309 # fail if list has another type 

310 if isOrdered: 

311 posAfterMarker = skipOrderedListMarker(state, nextLine) 

312 if posAfterMarker < 0: 

313 break 

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

315 else: 

316 posAfterMarker = skipBulletListMarker(state, nextLine) 

317 if posAfterMarker < 0: 

318 break 

319 

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

321 break 

322 

323 # Finalize list 

324 if isOrdered: 

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

326 else: 

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

328 

329 token.markup = chr(markerCharCode) 

330 

331 listLines[1] = nextLine 

332 state.line = nextLine 

333 

334 state.parentType = oldParentType 

335 

336 # mark paragraphs tight if needed 

337 if tight: 

338 markTightParagraphs(state, listTokIdx) 

339 

340 return True