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
« prev ^ index » next coverage.py v7.2.1, created at 2023-03-14 06:12 +0000
1# Lists
2import logging
4from ..common.utils import isSpace
5from .state_block import StateBlock
7LOGGER = logging.getLogger(__name__)
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]
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
25 if pos < maximum:
26 ch = state.srcCharCode[pos]
28 if not isSpace(ch):
29 # " -test " - is not a list item
30 return -1
32 return pos
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]
42 # List marker should have at least 2 chars (digit + dot)
43 if pos + 1 >= maximum:
44 return -1
46 ch = state.srcCharCode[pos]
47 pos += 1
49 # /* 0 */ /* 9 */
50 if ch < 0x30 or ch > 0x39:
51 return -1
53 while True:
54 # EOL -> fail
55 if pos >= maximum:
56 return -1
58 ch = state.srcCharCode[pos]
59 pos += 1
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
68 continue
70 # found valid marker: /* ) */ /* . */
71 if ch == 0x29 or ch == 0x2E:
72 break
74 return -1
76 if pos < maximum:
77 ch = state.srcCharCode[pos]
79 if not isSpace(ch):
80 # " 1.test " - is not a list item
81 return -1
83 return pos
86def markTightParagraphs(state: StateBlock, idx: int):
87 level = state.level + 2
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
99def list_block(state: StateBlock, startLine: int, endLine: int, silent: bool):
100 LOGGER.debug("entering list: %s, %s, %s, %s", state, startLine, endLine, silent)
102 isTerminatingParagraph = False
103 tight = True
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
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
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
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])
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
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
157 # We should terminate list on style change. Remember first one to compare.
158 markerCharCode = state.srcCharCode[posAfterMarker - 1]
160 # For validation mode we can terminate immediately
161 if silent:
162 return True
164 # Start list
165 listTokIdx = len(state.tokens)
167 if isOrdered:
168 token = state.push("ordered_list_open", "ol", 1)
169 if markerValue != 1:
170 token.attrs = {"start": markerValue}
172 else:
173 token = state.push("bullet_list_open", "ul", 1)
175 token.map = listLines = [startLine, 0]
176 token.markup = chr(markerCharCode)
178 #
179 # Iterate list items
180 #
182 nextLine = startLine
183 prevEmptyEnd = False
184 terminatorRules = state.md.block.ruler.getRules("list")
186 oldParentType = state.parentType
187 state.parentType = "list"
189 while nextLine < endLine:
190 pos = posAfterMarker
191 maximum = state.eMarks[nextLine]
193 initial = offset = (
194 state.sCount[nextLine]
195 + posAfterMarker
196 - (state.bMarks[startLine] + state.tShift[startLine])
197 )
199 while pos < maximum:
200 ch = state.srcCharCode[pos]
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
209 pos += 1
211 contentStart = pos
213 if contentStart >= maximum:
214 # trimming space in "- \n 3" case, indent is 1 here
215 indentAfterMarker = 1
216 else:
217 indentAfterMarker = offset - initial
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
224 # " - test"
225 # ^^^^^ - calculating total length of this thing
226 indent = initial + indentAfterMarker
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]
235 # change current state, then restore it after parser subcall
236 oldTight = state.tight
237 oldTShift = state.tShift[startLine]
238 oldSCount = state.sCount[startLine]
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
248 state.tight = True
249 state.tShift[startLine] = contentStart - state.bMarks[startLine]
250 state.sCount[startLine] = offset
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)
267 # If any of list item is tight, mark list as tight
268 if (not state.tight) or prevEmptyEnd:
269 tight = False
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)
275 state.blkIndent = state.listIndent
276 state.listIndent = oldListIndent
277 state.tShift[startLine] = oldTShift
278 state.sCount[startLine] = oldSCount
279 state.tight = oldTight
281 token = state.push("list_item_close", "li", -1)
282 token.markup = chr(markerCharCode)
284 nextLine = startLine = state.line
285 itemLines[1] = nextLine
287 if nextLine >= endLine:
288 break
290 contentStart = state.bMarks[startLine]
292 #
293 # Try to check if list is terminated or continued.
294 #
295 if state.sCount[nextLine] < state.blkIndent:
296 break
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
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
309 if terminate:
310 break
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
323 if markerCharCode != state.srcCharCode[posAfterMarker - 1]:
324 break
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)
332 token.markup = chr(markerCharCode)
334 listLines[1] = nextLine
335 state.line = nextLine
337 state.parentType = oldParentType
339 # mark paragraphs tight if needed
340 if tight:
341 markTightParagraphs(state, listTokIdx)
343 return True