Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/mako/pygen.py : 13%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# mako/pygen.py
2# Copyright 2006-2020 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(object):
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 def _update_lineno(self, num):
47 self.lineno += num
49 def start_source(self, lineno):
50 if self.lineno not in self.source_map:
51 self.source_map[self.lineno] = lineno
53 def write_blanks(self, num):
54 self.stream.write("\n" * num)
55 self._update_lineno(num)
57 def write_indented_block(self, block, starting_lineno=None):
58 """print a line or lines of python which already contain indentation.
60 The indentation of the total block of lines will be adjusted to that of
61 the current indent level."""
62 self.in_indent_lines = False
63 for i, l in enumerate(re.split(r"\r?\n", block)):
64 self.line_buffer.append(l)
65 if starting_lineno is not None:
66 self.start_source(starting_lineno + i)
67 self._update_lineno(1)
69 def writelines(self, *lines):
70 """print a series of lines of python."""
71 for line in lines:
72 self.writeline(line)
74 def writeline(self, line):
75 """print a line of python, indenting it according to the current
76 indent level.
78 this also adjusts the indentation counter according to the
79 content of the line.
81 """
83 if not self.in_indent_lines:
84 self._flush_adjusted_lines()
85 self.in_indent_lines = True
87 if (
88 line is None
89 or re.match(r"^\s*#", line)
90 or re.match(r"^\s*$", line)
91 ):
92 hastext = False
93 else:
94 hastext = True
96 is_comment = line and len(line) and line[0] == "#"
98 # see if this line should decrease the indentation level
99 if not is_comment and (not hastext or self._is_unindentor(line)):
101 if self.indent > 0:
102 self.indent -= 1
103 # if the indent_detail stack is empty, the user
104 # probably put extra closures - the resulting
105 # module wont compile.
106 if len(self.indent_detail) == 0:
107 raise exceptions.SyntaxException(
108 "Too many whitespace closures"
109 )
110 self.indent_detail.pop()
112 if line is None:
113 return
115 # write the line
116 self.stream.write(self._indent_line(line) + "\n")
117 self._update_lineno(len(line.split("\n")))
119 # see if this line should increase the indentation level.
120 # note that a line can both decrase (before printing) and
121 # then increase (after printing) the indentation level.
123 if re.search(r":[ \t]*(?:#.*)?$", line):
124 # increment indentation count, and also
125 # keep track of what the keyword was that indented us,
126 # if it is a python compound statement keyword
127 # where we might have to look for an "unindent" keyword
128 match = re.match(r"^\s*(if|try|elif|while|for|with)", line)
129 if match:
130 # its a "compound" keyword, so we will check for "unindentors"
131 indentor = match.group(1)
132 self.indent += 1
133 self.indent_detail.append(indentor)
134 else:
135 indentor = None
136 # its not a "compound" keyword. but lets also
137 # test for valid Python keywords that might be indenting us,
138 # else assume its a non-indenting line
139 m2 = re.match(
140 r"^\s*(def|class|else|elif|except|finally)", line
141 )
142 if m2:
143 self.indent += 1
144 self.indent_detail.append(indentor)
146 def close(self):
147 """close this printer, flushing any remaining lines."""
148 self._flush_adjusted_lines()
150 def _is_unindentor(self, line):
151 """return true if the given line is an 'unindentor',
152 relative to the last 'indent' event received.
154 """
156 # no indentation detail has been pushed on; return False
157 if len(self.indent_detail) == 0:
158 return False
160 indentor = self.indent_detail[-1]
162 # the last indent keyword we grabbed is not a
163 # compound statement keyword; return False
164 if indentor is None:
165 return False
167 # if the current line doesnt have one of the "unindentor" keywords,
168 # return False
169 match = re.match(r"^\s*(else|elif|except|finally).*\:", line)
170 if not match:
171 return False
173 # whitespace matches up, we have a compound indentor,
174 # and this line has an unindentor, this
175 # is probably good enough
176 return True
178 # should we decide that its not good enough, heres
179 # more stuff to check.
180 # keyword = match.group(1)
182 # match the original indent keyword
183 # for crit in [
184 # (r'if|elif', r'else|elif'),
185 # (r'try', r'except|finally|else'),
186 # (r'while|for', r'else'),
187 # ]:
188 # if re.match(crit[0], indentor) and re.match(crit[1], keyword):
189 # return True
191 # return False
193 def _indent_line(self, line, stripspace=""):
194 """indent the given line according to the current indent level.
196 stripspace is a string of space that will be truncated from the
197 start of the line before indenting."""
199 return re.sub(
200 r"^%s" % stripspace, self.indentstring * self.indent, line
201 )
203 def _reset_multi_line_flags(self):
204 """reset the flags which would indicate we are in a backslashed
205 or triple-quoted section."""
207 self.backslashed, self.triplequoted = False, False
209 def _in_multi_line(self, line):
210 """return true if the given line is part of a multi-line block,
211 via backslash or triple-quote."""
213 # we are only looking for explicitly joined lines here, not
214 # implicit ones (i.e. brackets, braces etc.). this is just to
215 # guard against the possibility of modifying the space inside of
216 # a literal multiline string with unfortunately placed
217 # whitespace
219 current_state = self.backslashed or self.triplequoted
221 if re.search(r"\\$", line):
222 self.backslashed = True
223 else:
224 self.backslashed = False
226 triples = len(re.findall(r"\"\"\"|\'\'\'", line))
227 if triples == 1 or triples % 2 != 0:
228 self.triplequoted = not self.triplequoted
230 return current_state
232 def _flush_adjusted_lines(self):
233 stripspace = None
234 self._reset_multi_line_flags()
236 for entry in self.line_buffer:
237 if self._in_multi_line(entry):
238 self.stream.write(entry + "\n")
239 else:
240 entry = entry.expandtabs()
241 if stripspace is None and re.search(r"^[ \t]*[^# \t]", entry):
242 stripspace = re.match(r"^([ \t]*)", entry).group(1)
243 self.stream.write(self._indent_line(entry, stripspace) + "\n")
245 self.line_buffer = []
246 self._reset_multi_line_flags()
249def adjust_whitespace(text):
250 """remove the left-whitespace margin of a block of Python code."""
252 state = [False, False]
253 (backslashed, triplequoted) = (0, 1)
255 def in_multi_line(line):
256 start_state = state[backslashed] or state[triplequoted]
258 if re.search(r"\\$", line):
259 state[backslashed] = True
260 else:
261 state[backslashed] = False
263 def match(reg, t):
264 m = re.match(reg, t)
265 if m:
266 return m, t[len(m.group(0)) :]
267 else:
268 return None, t
270 while line:
271 if state[triplequoted]:
272 m, line = match(r"%s" % state[triplequoted], line)
273 if m:
274 state[triplequoted] = False
275 else:
276 m, line = match(r".*?(?=%s|$)" % state[triplequoted], line)
277 else:
278 m, line = match(r"#", line)
279 if m:
280 return start_state
282 m, line = match(r"\"\"\"|\'\'\'", line)
283 if m:
284 state[triplequoted] = m.group(0)
285 continue
287 m, line = match(r".*?(?=\"\"\"|\'\'\'|#|$)", line)
289 return start_state
291 def _indent_line(line, stripspace=""):
292 return re.sub(r"^%s" % stripspace, "", line)
294 lines = []
295 stripspace = None
297 for line in re.split(r"\r?\n", text):
298 if in_multi_line(line):
299 lines.append(line)
300 else:
301 line = line.expandtabs()
302 if stripspace is None and re.search(r"^[ \t]*[^# \t]", line):
303 stripspace = re.match(r"^([ \t]*)", line).group(1)
304 lines.append(_indent_line(line, stripspace))
305 return "\n".join(lines)