Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/mako/pygen.py: 40%
141 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:02 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:02 +0000
1# mako/pygen.py
2# Copyright 2006-2023 the Mako authors and contributors <see AUTHORS file>
3#
4# This module is part of Mako and is released under
5# the MIT License: http://www.opensource.org/licenses/mit-license.php
7"""utilities for generating and formatting literal Python code."""
9import re
11from mako import exceptions
14class PythonPrinter:
15 def __init__(self, stream):
16 # indentation counter
17 self.indent = 0
19 # a stack storing information about why we incremented
20 # the indentation counter, to help us determine if we
21 # should decrement it
22 self.indent_detail = []
24 # the string of whitespace multiplied by the indent
25 # counter to produce a line
26 self.indentstring = " "
28 # the stream we are writing to
29 self.stream = stream
31 # current line number
32 self.lineno = 1
34 # a list of lines that represents a buffered "block" of code,
35 # which can be later printed relative to an indent level
36 self.line_buffer = []
38 self.in_indent_lines = False
40 self._reset_multi_line_flags()
42 # mapping of generated python lines to template
43 # source lines
44 self.source_map = {}
46 self._re_space_comment = re.compile(r"^\s*#")
47 self._re_space = re.compile(r"^\s*$")
48 self._re_indent = re.compile(r":[ \t]*(?:#.*)?$")
49 self._re_compound = re.compile(r"^\s*(if|try|elif|while|for|with)")
50 self._re_indent_keyword = re.compile(
51 r"^\s*(def|class|else|elif|except|finally)"
52 )
53 self._re_unindentor = re.compile(r"^\s*(else|elif|except|finally).*\:")
55 def _update_lineno(self, num):
56 self.lineno += num
58 def start_source(self, lineno):
59 if self.lineno not in self.source_map:
60 self.source_map[self.lineno] = lineno
62 def write_blanks(self, num):
63 self.stream.write("\n" * num)
64 self._update_lineno(num)
66 def write_indented_block(self, block, starting_lineno=None):
67 """print a line or lines of python which already contain indentation.
69 The indentation of the total block of lines will be adjusted to that of
70 the current indent level."""
71 self.in_indent_lines = False
72 for i, l in enumerate(re.split(r"\r?\n", block)):
73 self.line_buffer.append(l)
74 if starting_lineno is not None:
75 self.start_source(starting_lineno + i)
76 self._update_lineno(1)
78 def writelines(self, *lines):
79 """print a series of lines of python."""
80 for line in lines:
81 self.writeline(line)
83 def writeline(self, line):
84 """print a line of python, indenting it according to the current
85 indent level.
87 this also adjusts the indentation counter according to the
88 content of the line.
90 """
92 if not self.in_indent_lines:
93 self._flush_adjusted_lines()
94 self.in_indent_lines = True
96 if (
97 line is None
98 or self._re_space_comment.match(line)
99 or self._re_space.match(line)
100 ):
101 hastext = False
102 else:
103 hastext = True
105 is_comment = line and len(line) and line[0] == "#"
107 # see if this line should decrease the indentation level
108 if (
109 not is_comment
110 and (not hastext or self._is_unindentor(line))
111 and self.indent > 0
112 ):
113 self.indent -= 1
114 # if the indent_detail stack is empty, the user
115 # probably put extra closures - the resulting
116 # module wont compile.
117 if len(self.indent_detail) == 0:
118 # TODO: no coverage here
119 raise exceptions.MakoException("Too many whitespace closures")
120 self.indent_detail.pop()
122 if line is None:
123 return
125 # write the line
126 self.stream.write(self._indent_line(line) + "\n")
127 self._update_lineno(len(line.split("\n")))
129 # see if this line should increase the indentation level.
130 # note that a line can both decrase (before printing) and
131 # then increase (after printing) the indentation level.
133 if self._re_indent.search(line):
134 # increment indentation count, and also
135 # keep track of what the keyword was that indented us,
136 # if it is a python compound statement keyword
137 # where we might have to look for an "unindent" keyword
138 match = self._re_compound.match(line)
139 if match:
140 # its a "compound" keyword, so we will check for "unindentors"
141 indentor = match.group(1)
142 self.indent += 1
143 self.indent_detail.append(indentor)
144 else:
145 indentor = None
146 # its not a "compound" keyword. but lets also
147 # test for valid Python keywords that might be indenting us,
148 # else assume its a non-indenting line
149 m2 = self._re_indent_keyword.match(line)
150 if m2:
151 self.indent += 1
152 self.indent_detail.append(indentor)
154 def close(self):
155 """close this printer, flushing any remaining lines."""
156 self._flush_adjusted_lines()
158 def _is_unindentor(self, line):
159 """return true if the given line is an 'unindentor',
160 relative to the last 'indent' event received.
162 """
164 # no indentation detail has been pushed on; return False
165 if len(self.indent_detail) == 0:
166 return False
168 indentor = self.indent_detail[-1]
170 # the last indent keyword we grabbed is not a
171 # compound statement keyword; return False
172 if indentor is None:
173 return False
175 # if the current line doesnt have one of the "unindentor" keywords,
176 # return False
177 match = self._re_unindentor.match(line)
178 # if True, whitespace matches up, we have a compound indentor,
179 # and this line has an unindentor, this
180 # is probably good enough
181 return bool(match)
183 # should we decide that its not good enough, heres
184 # more stuff to check.
185 # keyword = match.group(1)
187 # match the original indent keyword
188 # for crit in [
189 # (r'if|elif', r'else|elif'),
190 # (r'try', r'except|finally|else'),
191 # (r'while|for', r'else'),
192 # ]:
193 # if re.match(crit[0], indentor) and re.match(crit[1], keyword):
194 # return True
196 # return False
198 def _indent_line(self, line, stripspace=""):
199 """indent the given line according to the current indent level.
201 stripspace is a string of space that will be truncated from the
202 start of the line before indenting."""
203 if stripspace == "":
204 # Fast path optimization.
205 return self.indentstring * self.indent + line
207 return re.sub(
208 r"^%s" % stripspace, self.indentstring * self.indent, line
209 )
211 def _reset_multi_line_flags(self):
212 """reset the flags which would indicate we are in a backslashed
213 or triple-quoted section."""
215 self.backslashed, self.triplequoted = False, False
217 def _in_multi_line(self, line):
218 """return true if the given line is part of a multi-line block,
219 via backslash or triple-quote."""
221 # we are only looking for explicitly joined lines here, not
222 # implicit ones (i.e. brackets, braces etc.). this is just to
223 # guard against the possibility of modifying the space inside of
224 # a literal multiline string with unfortunately placed
225 # whitespace
227 current_state = self.backslashed or self.triplequoted
229 self.backslashed = bool(re.search(r"\\$", line))
230 triples = len(re.findall(r"\"\"\"|\'\'\'", line))
231 if triples == 1 or triples % 2 != 0:
232 self.triplequoted = not self.triplequoted
234 return current_state
236 def _flush_adjusted_lines(self):
237 stripspace = None
238 self._reset_multi_line_flags()
240 for entry in self.line_buffer:
241 if self._in_multi_line(entry):
242 self.stream.write(entry + "\n")
243 else:
244 entry = entry.expandtabs()
245 if stripspace is None and re.search(r"^[ \t]*[^# \t]", entry):
246 stripspace = re.match(r"^([ \t]*)", entry).group(1)
247 self.stream.write(self._indent_line(entry, stripspace) + "\n")
249 self.line_buffer = []
250 self._reset_multi_line_flags()
253def adjust_whitespace(text):
254 """remove the left-whitespace margin of a block of Python code."""
256 state = [False, False]
257 (backslashed, triplequoted) = (0, 1)
259 def in_multi_line(line):
260 start_state = state[backslashed] or state[triplequoted]
262 if re.search(r"\\$", line):
263 state[backslashed] = True
264 else:
265 state[backslashed] = False
267 def match(reg, t):
268 m = re.match(reg, t)
269 if m:
270 return m, t[len(m.group(0)) :]
271 else:
272 return None, t
274 while line:
275 if state[triplequoted]:
276 m, line = match(r"%s" % state[triplequoted], line)
277 if m:
278 state[triplequoted] = False
279 else:
280 m, line = match(r".*?(?=%s|$)" % state[triplequoted], line)
281 else:
282 m, line = match(r"#", line)
283 if m:
284 return start_state
286 m, line = match(r"\"\"\"|\'\'\'", line)
287 if m:
288 state[triplequoted] = m.group(0)
289 continue
291 m, line = match(r".*?(?=\"\"\"|\'\'\'|#|$)", line)
293 return start_state
295 def _indent_line(line, stripspace=""):
296 return re.sub(r"^%s" % stripspace, "", line)
298 lines = []
299 stripspace = None
301 for line in re.split(r"\r?\n", text):
302 if in_multi_line(line):
303 lines.append(line)
304 else:
305 line = line.expandtabs()
306 if stripspace is None and re.search(r"^[ \t]*[^# \t]", line):
307 stripspace = re.match(r"^([ \t]*)", line).group(1)
308 lines.append(_indent_line(line, stripspace))
309 return "\n".join(lines)