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
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:15 +0000
1# Lists
2import logging
4from ..common.utils import isStrSpace
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) -> int:
13 pos = state.bMarks[startLine] + state.tShift[startLine]
14 maximum = state.eMarks[startLine]
16 try:
17 marker = state.src[pos]
18 except IndexError:
19 return -1
20 pos += 1
22 if marker not in ("*", "-", "+"):
23 return -1
25 if pos < maximum:
26 ch = state.src[pos]
28 if not isStrSpace(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) -> 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.src[pos]
47 pos += 1
49 ch_ord = ord(ch)
50 # /* 0 */ /* 9 */
51 if ch_ord < 0x30 or ch_ord > 0x39:
52 return -1
54 while True:
55 # EOL -> fail
56 if pos >= maximum:
57 return -1
59 ch = state.src[pos]
60 pos += 1
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
70 continue
72 # found valid marker
73 if ch in (")", "."):
74 break
76 return -1
78 if pos < maximum:
79 ch = state.src[pos]
81 if not isStrSpace(ch):
82 # " 1.test " - is not a list item
83 return -1
85 return pos
88def markTightParagraphs(state: StateBlock, idx: int) -> None:
89 level = state.level + 2
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
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)
104 isTerminatingParagraph = False
105 tight = True
107 if state.is_code_block(startLine):
108 return False
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
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
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])
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
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
163 # We should terminate list on style change. Remember first one to compare.
164 markerChar = state.src[posAfterMarker - 1]
166 # For validation mode we can terminate immediately
167 if silent:
168 return True
170 # Start list
171 listTokIdx = len(state.tokens)
173 if isOrdered:
174 token = state.push("ordered_list_open", "ol", 1)
175 if markerValue != 1:
176 token.attrs = {"start": markerValue}
178 else:
179 token = state.push("bullet_list_open", "ul", 1)
181 token.map = listLines = [startLine, 0]
182 token.markup = markerChar
184 #
185 # Iterate list items
186 #
188 nextLine = startLine
189 prevEmptyEnd = False
190 terminatorRules = state.md.block.ruler.getRules("list")
192 oldParentType = state.parentType
193 state.parentType = "list"
195 while nextLine < endLine:
196 pos = posAfterMarker
197 maximum = state.eMarks[nextLine]
199 initial = offset = (
200 state.sCount[nextLine]
201 + posAfterMarker
202 - (state.bMarks[startLine] + state.tShift[startLine])
203 )
205 while pos < maximum:
206 ch = state.src[pos]
208 if ch == "\t":
209 offset += 4 - (offset + state.bsCount[nextLine]) % 4
210 elif ch == " ":
211 offset += 1
212 else:
213 break
215 pos += 1
217 contentStart = pos
219 # trimming space in "- \n 3" case, indent is 1 here
220 indentAfterMarker = 1 if contentStart >= maximum else offset - initial
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
227 # " - test"
228 # ^^^^^ - calculating total length of this thing
229 indent = initial + indentAfterMarker
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]
238 # change current state, then restore it after parser subcall
239 oldTight = state.tight
240 oldTShift = state.tShift[startLine]
241 oldSCount = state.sCount[startLine]
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
251 state.tight = True
252 state.tShift[startLine] = contentStart - state.bMarks[startLine]
253 state.sCount[startLine] = offset
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)
270 # If any of list item is tight, mark list as tight
271 if (not state.tight) or prevEmptyEnd:
272 tight = False
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)
278 state.blkIndent = state.listIndent
279 state.listIndent = oldListIndent
280 state.tShift[startLine] = oldTShift
281 state.sCount[startLine] = oldSCount
282 state.tight = oldTight
284 token = state.push("list_item_close", "li", -1)
285 token.markup = markerChar
287 nextLine = startLine = state.line
288 itemLines[1] = nextLine
290 if nextLine >= endLine:
291 break
293 contentStart = state.bMarks[startLine]
295 #
296 # Try to check if list is terminated or continued.
297 #
298 if state.sCount[nextLine] < state.blkIndent:
299 break
301 if state.is_code_block(startLine):
302 break
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
311 if terminate:
312 break
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
325 if markerChar != state.src[posAfterMarker - 1]:
326 break
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)
334 token.markup = markerChar
336 listLines[1] = nextLine
337 state.line = nextLine
339 state.parentType = oldParentType
341 # mark paragraphs tight if needed
342 if tight:
343 markTightParagraphs(state, listTokIdx)
345 return True