Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Tree: 3c16b84368
Fetching contributors…

Cannot retrieve contributors at this time

269 lines (210 sloc) 9.54 KB
# Copyright (C) 2006, 2007, 2008, 2009, 2010 Michael Bayer
# This module is part of Mako and is released under
# the MIT License:
"""utilities for generating and formatting literal Python code."""
import re, string
from StringIO import StringIO
from mako import exceptions
class PythonPrinter(object):
def __init__(self, stream):
# indentation counter
self.indent = 0
# a stack storing information about why we incremented
# the indentation counter, to help us determine if we
# should decrement it
self.indent_detail = []
# the string of whitespace multiplied by the indent
# counter to produce a line
self.indentstring = " "
# the stream we are writing to = stream
# a list of lines that represents a buffered "block" of code,
# which can be later printed relative to an indent level
self.line_buffer = []
self.in_indent_lines = False
def write(self, text):
def write_indented_block(self, block):
"""print a line or lines of python which already contain indentation.
The indentation of the total block of lines will be adjusted to that of
the current indent level."""
self.in_indent_lines = False
for l in re.split(r'\r?\n', block):
def writelines(self, *lines):
"""print a series of lines of python."""
for line in lines:
def writeline(self, line):
"""print a line of python, indenting it according to the current indent level.
this also adjusts the indentation counter according to the content of the line."""
if not self.in_indent_lines:
self.in_indent_lines = True
decreased_indent = False
if (line is None or
re.match(r"^\s*#",line) or
re.match(r"^\s*$", line)
hastext = False
hastext = True
is_comment = line and len(line) and line[0] == '#'
# see if this line should decrease the indentation level
if (not decreased_indent and
not is_comment and
(not hastext or self._is_unindentor(line))
if self.indent > 0:
self.indent -=1
# if the indent_detail stack is empty, the user
# probably put extra closures - the resulting
# module wont compile.
if len(self.indent_detail) == 0:
raise exceptions.SyntaxException("Too many whitespace closures")
if line is None:
# write the line + "\n")
# see if this line should increase the indentation level.
# note that a line can both decrase (before printing) and
# then increase (after printing) the indentation level.
if":[ \t]*(?:#.*)?$", line):
# increment indentation count, and also
# keep track of what the keyword was that indented us,
# if it is a python compound statement keyword
# where we might have to look for an "unindent" keyword
match = re.match(r"^\s*(if|try|elif|while|for)", line)
if match:
# its a "compound" keyword, so we will check for "unindentors"
indentor =
self.indent +=1
indentor = None
# its not a "compound" keyword. but lets also
# test for valid Python keywords that might be indenting us,
# else assume its a non-indenting line
m2 = re.match(r"^\s*(def|class|else|elif|except|finally)", line)
if m2:
self.indent += 1
def close(self):
"""close this printer, flushing any remaining lines."""
def _is_unindentor(self, line):
"""return true if the given line is an 'unindentor', relative to the last 'indent' event received."""
# no indentation detail has been pushed on; return False
if len(self.indent_detail) == 0:
return False
indentor = self.indent_detail[-1]
# the last indent keyword we grabbed is not a
# compound statement keyword; return False
if indentor is None:
return False
# if the current line doesnt have one of the "unindentor" keywords,
# return False
match = re.match(r"^\s*(else|elif|except|finally).*\:", line)
if not match:
return False
# whitespace matches up, we have a compound indentor,
# and this line has an unindentor, this
# is probably good enough
return True
# should we decide that its not good enough, heres
# more stuff to check.
#keyword =
# match the original indent keyword
#for crit in [
# (r'if|elif', r'else|elif'),
# (r'try', r'except|finally|else'),
# (r'while|for', r'else'),
# if re.match(crit[0], indentor) and re.match(crit[1], keyword): return True
#return False
def _indent_line(self, line, stripspace = ''):
"""indent the given line according to the current indent level.
stripspace is a string of space that will be truncated from the start of the line
before indenting."""
return re.sub(r"^%s" % stripspace, self.indentstring * self.indent, line)
def _reset_multi_line_flags(self):
"""reset the flags which would indicate we are in a backslashed or triple-quoted section."""
(self.backslashed, self.triplequoted) = (False, False)
def _in_multi_line(self, line):
"""return true if the given line is part of a multi-line block, via backslash or triple-quote."""
# we are only looking for explicitly joined lines here,
# not implicit ones (i.e. brackets, braces etc.). this is just
# to guard against the possibility of modifying the space inside
# of a literal multiline string with unfortunately placed whitespace
current_state = (self.backslashed or self.triplequoted)
if"\\$", line):
self.backslashed = True
self.backslashed = False
triples = len(re.findall(r"\"\"\"|\'\'\'", line))
if triples == 1 or triples % 2 != 0:
self.triplequoted = not self.triplequoted
return current_state
def _flush_adjusted_lines(self):
stripspace = None
for entry in self.line_buffer:
if self._in_multi_line(entry): + "\n")
entry = entry.expandtabs()
if stripspace is None and"^[ \t]*[^# \t]", entry):
stripspace = re.match(r"^([ \t]*)", entry).group(1), stripspace) + "\n")
self.line_buffer = []
def adjust_whitespace(text):
"""remove the left-whitespace margin of a block of Python code."""
state = [False, False]
(backslashed, triplequoted) = (0, 1)
def in_multi_line(line):
start_state = (state[backslashed] or state[triplequoted])
if"\\$", line):
state[backslashed] = True
state[backslashed] = False
def match(reg, t):
m = re.match(reg, t)
if m:
return m, t[len(]
return None, t
while line:
if state[triplequoted]:
m, line = match(r"%s" % state[triplequoted], line)
if m:
state[triplequoted] = False
m, line = match(r".*?(?=%s|$)" % state[triplequoted], line)
m, line = match(r'#', line)
if m:
return start_state
m, line = match(r"\"\"\"|\'\'\'", line)
if m:
state[triplequoted] =
m, line = match(r".*?(?=\"\"\"|\'\'\'|#|$)", line)
return start_state
def _indent_line(line, stripspace = ''):
return re.sub(r"^%s" % stripspace, '', line)
lines = []
stripspace = None
for line in re.split(r'\r?\n', text):
if in_multi_line(line):
line = line.expandtabs()
if stripspace is None and"^[ \t]*[^# \t]", line):
stripspace = re.match(r"^([ \t]*)", line).group(1)
lines.append(_indent_line(line, stripspace))
return "\n".join(lines)
Jump to Line
Something went wrong with that request. Please try again.