Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/markdown_it/rules_block/state_block.py: 95%
139 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:07 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:07 +0000
1from __future__ import annotations
3from typing import TYPE_CHECKING
5from ..common.utils import isSpace
6from ..ruler import StateBase
7from ..token import Token
9if TYPE_CHECKING:
10 from markdown_it.main import MarkdownIt
13class StateBlock(StateBase):
14 def __init__(
15 self,
16 src: str,
17 md: MarkdownIt,
18 env,
19 tokens: list[Token],
20 srcCharCode: tuple[int, ...] | None = None,
21 ):
22 if srcCharCode is not None:
23 self._src = src
24 self.srcCharCode = srcCharCode
25 else:
26 self.src = src
28 # link to parser instance
29 self.md = md
31 self.env = env
33 #
34 # Internal state variables
35 #
37 self.tokens = tokens
39 self.bMarks = [] # line begin offsets for fast jumps
40 self.eMarks = [] # line end offsets for fast jumps
41 # offsets of the first non-space characters (tabs not expanded)
42 self.tShift = []
43 self.sCount = [] # indents for each line (tabs expanded)
45 # An amount of virtual spaces (tabs expanded) between beginning
46 # of each line (bMarks) and real beginning of that line.
47 #
48 # It exists only as a hack because blockquotes override bMarks
49 # losing information in the process.
50 #
51 # It's used only when expanding tabs, you can think about it as
52 # an initial tab length, e.g. bsCount=21 applied to string `\t123`
53 # means first tab should be expanded to 4-21%4 === 3 spaces.
54 #
55 self.bsCount = []
57 # block parser variables
58 self.blkIndent = 0 # required block content indent (for example, if we are
59 # inside a list, it would be positioned after list marker)
60 self.line = 0 # line index in src
61 self.lineMax = 0 # lines count
62 self.tight = False # loose/tight mode for lists
63 self.ddIndent = -1 # indent of the current dd block (-1 if there isn't any)
64 self.listIndent = -1 # indent of the current list block (-1 if there isn't any)
66 # can be 'blockquote', 'list', 'root', 'paragraph' or 'reference'
67 # used in lists to determine if they interrupt a paragraph
68 self.parentType = "root"
70 self.level = 0
72 # renderer
73 self.result = ""
75 # Create caches
76 # Generate markers.
77 indent_found = False
79 start = pos = indent = offset = 0
80 length = len(self.src)
82 for pos, character in enumerate(self.srcCharCode):
83 if not indent_found:
84 if isSpace(character):
85 indent += 1
87 if character == 0x09:
88 offset += 4 - offset % 4
89 else:
90 offset += 1
91 continue
92 else:
93 indent_found = True
95 if character == 0x0A or pos == length - 1:
96 if character != 0x0A:
97 pos += 1
98 self.bMarks.append(start)
99 self.eMarks.append(pos)
100 self.tShift.append(indent)
101 self.sCount.append(offset)
102 self.bsCount.append(0)
104 indent_found = False
105 indent = 0
106 offset = 0
107 start = pos + 1
109 # Push fake entry to simplify cache bounds checks
110 self.bMarks.append(length)
111 self.eMarks.append(length)
112 self.tShift.append(0)
113 self.sCount.append(0)
114 self.bsCount.append(0)
116 self.lineMax = len(self.bMarks) - 1 # don't count last fake line
118 def __repr__(self):
119 return (
120 f"{self.__class__.__name__}"
121 f"(line={self.line},level={self.level},tokens={len(self.tokens)})"
122 )
124 def push(self, ttype: str, tag: str, nesting: int) -> Token:
125 """Push new token to "stream"."""
126 token = Token(ttype, tag, nesting)
127 token.block = True
128 if nesting < 0:
129 self.level -= 1 # closing tag
130 token.level = self.level
131 if nesting > 0:
132 self.level += 1 # opening tag
133 self.tokens.append(token)
134 return token
136 def isEmpty(self, line: int) -> bool:
137 """."""
138 return (self.bMarks[line] + self.tShift[line]) >= self.eMarks[line]
140 def skipEmptyLines(self, from_pos: int) -> int:
141 """."""
142 while from_pos < self.lineMax:
143 try:
144 if (self.bMarks[from_pos] + self.tShift[from_pos]) < self.eMarks[
145 from_pos
146 ]:
147 break
148 except IndexError:
149 pass
150 from_pos += 1
151 return from_pos
153 def skipSpaces(self, pos: int) -> int:
154 """Skip spaces from given position."""
155 while pos < len(self.src):
156 if not isSpace(self.srcCharCode[pos]):
157 break
158 pos += 1
159 return pos
161 def skipSpacesBack(self, pos: int, minimum: int) -> int:
162 """Skip spaces from given position in reverse."""
163 if pos <= minimum:
164 return pos
165 while pos > minimum:
166 pos -= 1
167 if not isSpace(self.srcCharCode[pos]):
168 return pos + 1
169 return pos
171 def skipChars(self, pos: int, code: int) -> int:
172 """Skip char codes from given position."""
173 while pos < len(self.src):
174 if self.srcCharCode[pos] != code:
175 break
176 pos += 1
177 return pos
179 def skipCharsBack(self, pos: int, code: int, minimum: int) -> int:
180 """Skip char codes reverse from given position - 1."""
181 if pos <= minimum:
182 return pos
183 while pos > minimum:
184 pos -= 1
185 if code != self.srcCharCode[pos]:
186 return pos + 1
187 return pos
189 def getLines(self, begin: int, end: int, indent: int, keepLastLF: bool) -> str:
190 """Cut lines range from source."""
191 line = begin
192 if begin >= end:
193 return ""
195 queue = [""] * (end - begin)
197 i = 1
198 while line < end:
199 lineIndent = 0
200 lineStart = first = self.bMarks[line]
201 if line + 1 < end or keepLastLF:
202 last = self.eMarks[line] + 1
203 else:
204 last = self.eMarks[line]
206 while (first < last) and (lineIndent < indent):
207 ch = self.srcCharCode[first]
208 if isSpace(ch):
209 if ch == 0x09:
210 lineIndent += 4 - (lineIndent + self.bsCount[line]) % 4
211 else:
212 lineIndent += 1
213 elif first - lineStart < self.tShift[line]:
214 lineIndent += 1
215 else:
216 break
217 first += 1
219 if lineIndent > indent:
220 # partially expanding tabs in code blocks, e.g '\t\tfoobar'
221 # with indent=2 becomes ' \tfoobar'
222 queue[i - 1] = (" " * (lineIndent - indent)) + self.src[first:last]
223 else:
224 queue[i - 1] = self.src[first:last]
226 line += 1
227 i += 1
229 return "".join(queue)