Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/markdown_it/rules_block/blockquote.py: 97%
149 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# Block quotes
2from __future__ import annotations
4import logging
6from ..common.utils import isSpace
7from .state_block import StateBlock
9LOGGER = logging.getLogger(__name__)
12def blockquote(state: StateBlock, startLine: int, endLine: int, silent: bool):
13 LOGGER.debug(
14 "entering blockquote: %s, %s, %s, %s", state, startLine, endLine, silent
15 )
17 oldLineMax = state.lineMax
18 pos = state.bMarks[startLine] + state.tShift[startLine]
19 max = state.eMarks[startLine]
21 # if it's indented more than 3 spaces, it should be a code block
22 if (state.sCount[startLine] - state.blkIndent) >= 4:
23 return False
25 # check the block quote marker
26 try:
27 if state.srcCharCode[pos] != 0x3E: # /* > */
28 return False
29 except IndexError:
30 return False
31 pos += 1
33 # we know that it's going to be a valid blockquote,
34 # so no point trying to find the end of it in silent mode
35 if silent:
36 return True
38 # set offset past spaces and ">"
39 initial = offset = state.sCount[startLine] + 1
41 try:
42 second_char_code: int | None = state.srcCharCode[pos]
43 except IndexError:
44 second_char_code = None
46 # skip one optional space after '>'
47 if second_char_code == 0x20: # /* space */
48 # ' > test '
49 # ^ -- position start of line here:
50 pos += 1
51 initial += 1
52 offset += 1
53 adjustTab = False
54 spaceAfterMarker = True
55 elif second_char_code == 0x09: # /* tab */
56 spaceAfterMarker = True
58 if (state.bsCount[startLine] + offset) % 4 == 3:
59 # ' >\t test '
60 # ^ -- position start of line here (tab has width==1)
61 pos += 1
62 initial += 1
63 offset += 1
64 adjustTab = False
65 else:
66 # ' >\t test '
67 # ^ -- position start of line here + shift bsCount slightly
68 # to make extra space appear
69 adjustTab = True
71 else:
72 spaceAfterMarker = False
74 oldBMarks = [state.bMarks[startLine]]
75 state.bMarks[startLine] = pos
77 while pos < max:
78 ch = state.srcCharCode[pos]
80 if isSpace(ch):
81 if ch == 0x09: # / tab /
82 offset += (
83 4
84 - (offset + state.bsCount[startLine] + (1 if adjustTab else 0)) % 4
85 )
86 else:
87 offset += 1
89 else:
90 break
92 pos += 1
94 oldBSCount = [state.bsCount[startLine]]
95 state.bsCount[startLine] = (
96 state.sCount[startLine] + 1 + (1 if spaceAfterMarker else 0)
97 )
99 lastLineEmpty = pos >= max
101 oldSCount = [state.sCount[startLine]]
102 state.sCount[startLine] = offset - initial
104 oldTShift = [state.tShift[startLine]]
105 state.tShift[startLine] = pos - state.bMarks[startLine]
107 terminatorRules = state.md.block.ruler.getRules("blockquote")
109 oldParentType = state.parentType
110 state.parentType = "blockquote"
112 # Search the end of the block
113 #
114 # Block ends with either:
115 # 1. an empty line outside:
116 # ```
117 # > test
118 #
119 # ```
120 # 2. an empty line inside:
121 # ```
122 # >
123 # test
124 # ```
125 # 3. another tag:
126 # ```
127 # > test
128 # - - -
129 # ```
131 # for (nextLine = startLine + 1; nextLine < endLine; nextLine++) {
132 nextLine = startLine + 1
133 while nextLine < endLine:
134 # check if it's outdented, i.e. it's inside list item and indented
135 # less than said list item:
136 #
137 # ```
138 # 1. anything
139 # > current blockquote
140 # 2. checking this line
141 # ```
142 isOutdented = state.sCount[nextLine] < state.blkIndent
144 pos = state.bMarks[nextLine] + state.tShift[nextLine]
145 max = state.eMarks[nextLine]
147 if pos >= max:
148 # Case 1: line is not inside the blockquote, and this line is empty.
149 break
151 evaluatesTrue = state.srcCharCode[pos] == 0x3E and not isOutdented # /* > */
152 pos += 1
153 if evaluatesTrue:
154 # This line is inside the blockquote.
156 # set offset past spaces and ">"
157 initial = offset = state.sCount[nextLine] + 1
159 try:
160 next_char: int | None = state.srcCharCode[pos]
161 except IndexError:
162 next_char = None
164 # skip one optional space after '>'
165 if next_char == 0x20: # /* space */
166 # ' > test '
167 # ^ -- position start of line here:
168 pos += 1
169 initial += 1
170 offset += 1
171 adjustTab = False
172 spaceAfterMarker = True
173 elif next_char == 0x09: # /* tab */
174 spaceAfterMarker = True
176 if (state.bsCount[nextLine] + offset) % 4 == 3:
177 # ' >\t test '
178 # ^ -- position start of line here (tab has width==1)
179 pos += 1
180 initial += 1
181 offset += 1
182 adjustTab = False
183 else:
184 # ' >\t test '
185 # ^ -- position start of line here + shift bsCount slightly
186 # to make extra space appear
187 adjustTab = True
189 else:
190 spaceAfterMarker = False
192 oldBMarks.append(state.bMarks[nextLine])
193 state.bMarks[nextLine] = pos
195 while pos < max:
196 ch = state.srcCharCode[pos]
198 if isSpace(ch):
199 if ch == 0x09:
200 offset += (
201 4
202 - (
203 offset
204 + state.bsCount[nextLine]
205 + (1 if adjustTab else 0)
206 )
207 % 4
208 )
209 else:
210 offset += 1
211 else:
212 break
214 pos += 1
216 lastLineEmpty = pos >= max
218 oldBSCount.append(state.bsCount[nextLine])
219 state.bsCount[nextLine] = (
220 state.sCount[nextLine] + 1 + (1 if spaceAfterMarker else 0)
221 )
223 oldSCount.append(state.sCount[nextLine])
224 state.sCount[nextLine] = offset - initial
226 oldTShift.append(state.tShift[nextLine])
227 state.tShift[nextLine] = pos - state.bMarks[nextLine]
229 nextLine += 1
230 continue
232 # Case 2: line is not inside the blockquote, and the last line was empty.
233 if lastLineEmpty:
234 break
236 # Case 3: another tag found.
237 terminate = False
239 for terminatorRule in terminatorRules:
240 if terminatorRule(state, nextLine, endLine, True):
241 terminate = True
242 break
244 if terminate:
245 # Quirk to enforce "hard termination mode" for paragraphs;
246 # normally if you call `tokenize(state, startLine, nextLine)`,
247 # paragraphs will look below nextLine for paragraph continuation,
248 # but if blockquote is terminated by another tag, they shouldn't
249 state.lineMax = nextLine
251 if state.blkIndent != 0:
252 # state.blkIndent was non-zero, we now set it to zero,
253 # so we need to re-calculate all offsets to appear as
254 # if indent wasn't changed
255 oldBMarks.append(state.bMarks[nextLine])
256 oldBSCount.append(state.bsCount[nextLine])
257 oldTShift.append(state.tShift[nextLine])
258 oldSCount.append(state.sCount[nextLine])
259 state.sCount[nextLine] -= state.blkIndent
261 break
263 oldBMarks.append(state.bMarks[nextLine])
264 oldBSCount.append(state.bsCount[nextLine])
265 oldTShift.append(state.tShift[nextLine])
266 oldSCount.append(state.sCount[nextLine])
268 # A negative indentation means that this is a paragraph continuation
269 #
270 state.sCount[nextLine] = -1
272 nextLine += 1
274 oldIndent = state.blkIndent
275 state.blkIndent = 0
277 token = state.push("blockquote_open", "blockquote", 1)
278 token.markup = ">"
279 token.map = lines = [startLine, 0]
281 state.md.block.tokenize(state, startLine, nextLine)
283 token = state.push("blockquote_close", "blockquote", -1)
284 token.markup = ">"
286 state.lineMax = oldLineMax
287 state.parentType = oldParentType
288 lines[1] = state.line
290 # Restore original tShift; this might not be necessary since the parser
291 # has already been here, but just to make sure we can do that.
292 for i, item in enumerate(oldTShift):
293 state.bMarks[i + startLine] = oldBMarks[i]
294 state.tShift[i + startLine] = item
295 state.sCount[i + startLine] = oldSCount[i]
296 state.bsCount[i + startLine] = oldBSCount[i]
298 state.blkIndent = oldIndent
300 return True