~jonas-drange/ubuntu-start-page/1252899-mobile-friendly

« back to all changes in this revision

Viewing changes to src/mako/pygen.py

  • Committer: Matthew Nuzum
  • Date: 2008-04-18 01:58:53 UTC
  • Revision ID: matthew.nuzum@canonical.com-20080418015853-2b8rf979z2c2exxl
adding files

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# pygen.py
 
2
# Copyright (C) 2006, 2007 Michael Bayer mike_mp@zzzcomputing.com
 
3
#
 
4
# This module is part of Mako and is released under
 
5
# the MIT License: http://www.opensource.org/licenses/mit-license.php
 
6
 
 
7
"""utilities for generating and formatting literal Python code."""
 
8
 
 
9
import re, string
 
10
from StringIO import StringIO
 
11
 
 
12
class PythonPrinter(object):
 
13
    def __init__(self, stream):
 
14
        # indentation counter
 
15
        self.indent = 0
 
16
        
 
17
        # a stack storing information about why we incremented 
 
18
        # the indentation counter, to help us determine if we
 
19
        # should decrement it
 
20
        self.indent_detail = []
 
21
        
 
22
        # the string of whitespace multiplied by the indent
 
23
        # counter to produce a line
 
24
        self.indentstring = "    "
 
25
        
 
26
        # the stream we are writing to
 
27
        self.stream = stream
 
28
        
 
29
        # a list of lines that represents a buffered "block" of code,
 
30
        # which can be later printed relative to an indent level 
 
31
        self.line_buffer = []
 
32
        
 
33
        self.in_indent_lines = False
 
34
        
 
35
        self._reset_multi_line_flags()
 
36
 
 
37
    def write(self, text):
 
38
        self.stream.write(text)
 
39
        
 
40
    def write_indented_block(self, block):
 
41
        """print a line or lines of python which already contain indentation.
 
42
        
 
43
        The indentation of the total block of lines will be adjusted to that of
 
44
        the current indent level.""" 
 
45
        self.in_indent_lines = False
 
46
        for l in re.split(r'\r?\n', block):
 
47
            self.line_buffer.append(l)
 
48
    
 
49
    def writelines(self, *lines):
 
50
        """print a series of lines of python."""
 
51
        for line in lines:
 
52
            self.writeline(line)
 
53
                
 
54
    def writeline(self, line):
 
55
        """print a line of python, indenting it according to the current indent level.
 
56
        
 
57
        this also adjusts the indentation counter according to the content of the line."""
 
58
 
 
59
        if not self.in_indent_lines:
 
60
            self._flush_adjusted_lines()
 
61
            self.in_indent_lines = True
 
62
 
 
63
        decreased_indent = False
 
64
    
 
65
        if (line is None or 
 
66
            re.match(r"^\s*#",line) or
 
67
            re.match(r"^\s*$", line)
 
68
            ):
 
69
            hastext = False
 
70
        else:
 
71
            hastext = True
 
72
 
 
73
        is_comment = line and len(line) and line[0] == '#'
 
74
        
 
75
        # see if this line should decrease the indentation level
 
76
        if (not decreased_indent and 
 
77
            not is_comment and 
 
78
            (not hastext or self._is_unindentor(line))
 
79
            ):
 
80
            
 
81
            if self.indent > 0: 
 
82
                self.indent -=1
 
83
                # if the indent_detail stack is empty, the user
 
84
                # probably put extra closures - the resulting
 
85
                # module wont compile.  
 
86
                if len(self.indent_detail) == 0:  
 
87
                    raise "Too many whitespace closures"
 
88
                self.indent_detail.pop()
 
89
        
 
90
        if line is None:
 
91
            return
 
92
                
 
93
        # write the line
 
94
        self.stream.write(self._indent_line(line) + "\n")
 
95
        
 
96
        # see if this line should increase the indentation level.
 
97
        # note that a line can both decrase (before printing) and 
 
98
        # then increase (after printing) the indentation level.
 
99
 
 
100
        if re.search(r":[ \t]*(?:#.*)?$", line):
 
101
            # increment indentation count, and also
 
102
            # keep track of what the keyword was that indented us,
 
103
            # if it is a python compound statement keyword
 
104
            # where we might have to look for an "unindent" keyword
 
105
            match = re.match(r"^\s*(if|try|elif|while|for)", line)
 
106
            if match:
 
107
                # its a "compound" keyword, so we will check for "unindentors"
 
108
                indentor = match.group(1)
 
109
                self.indent +=1
 
110
                self.indent_detail.append(indentor)
 
111
            else:
 
112
                indentor = None
 
113
                # its not a "compound" keyword.  but lets also
 
114
                # test for valid Python keywords that might be indenting us,
 
115
                # else assume its a non-indenting line
 
116
                m2 = re.match(r"^\s*(def|class|else|elif|except|finally)", line)
 
117
                if m2:
 
118
                    self.indent += 1
 
119
                    self.indent_detail.append(indentor)
 
120
 
 
121
    def close(self):
 
122
        """close this printer, flushing any remaining lines."""
 
123
        self._flush_adjusted_lines()
 
124
    
 
125
    def _is_unindentor(self, line):
 
126
        """return true if the given line is an 'unindentor', relative to the last 'indent' event received."""
 
127
                
 
128
        # no indentation detail has been pushed on; return False
 
129
        if len(self.indent_detail) == 0: 
 
130
            return False
 
131
 
 
132
        indentor = self.indent_detail[-1]
 
133
        
 
134
        # the last indent keyword we grabbed is not a 
 
135
        # compound statement keyword; return False
 
136
        if indentor is None: 
 
137
            return False
 
138
        
 
139
        # if the current line doesnt have one of the "unindentor" keywords,
 
140
        # return False
 
141
        match = re.match(r"^\s*(else|elif|except|finally)", line)
 
142
        if not match: 
 
143
            return False
 
144
        
 
145
        # whitespace matches up, we have a compound indentor,
 
146
        # and this line has an unindentor, this
 
147
        # is probably good enough
 
148
        return True
 
149
        
 
150
        # should we decide that its not good enough, heres
 
151
        # more stuff to check.
 
152
        #keyword = match.group(1)
 
153
        
 
154
        # match the original indent keyword 
 
155
        #for crit in [
 
156
        #   (r'if|elif', r'else|elif'),
 
157
        #   (r'try', r'except|finally|else'),
 
158
        #   (r'while|for', r'else'),
 
159
        #]:
 
160
        #   if re.match(crit[0], indentor) and re.match(crit[1], keyword): return True
 
161
        
 
162
        #return False
 
163
        
 
164
    def _indent_line(self, line, stripspace = ''):
 
165
        """indent the given line according to the current indent level.
 
166
        
 
167
        stripspace is a string of space that will be truncated from the start of the line
 
168
        before indenting."""
 
169
        return re.sub(r"^%s" % stripspace, self.indentstring * self.indent, line)
 
170
 
 
171
    def _reset_multi_line_flags(self):
 
172
        """reset the flags which would indicate we are in a backslashed or triple-quoted section."""
 
173
        (self.backslashed, self.triplequoted) = (False, False) 
 
174
        
 
175
    def _in_multi_line(self, line):
 
176
        """return true if the given line is part of a multi-line block, via backslash or triple-quote."""
 
177
        # we are only looking for explicitly joined lines here,
 
178
        # not implicit ones (i.e. brackets, braces etc.).  this is just
 
179
        # to guard against the possibility of modifying the space inside 
 
180
        # of a literal multiline string with unfortunately placed whitespace
 
181
         
 
182
        current_state = (self.backslashed or self.triplequoted) 
 
183
                        
 
184
        if re.search(r"\\$", line):
 
185
            self.backslashed = True
 
186
        else:
 
187
            self.backslashed = False
 
188
            
 
189
        triples = len(re.findall(r"\"\"\"|\'\'\'", line))
 
190
        if triples == 1 or triples % 2 != 0:
 
191
            self.triplequoted = not self.triplequoted
 
192
            
 
193
        return current_state
 
194
 
 
195
    def _flush_adjusted_lines(self):
 
196
        stripspace = None
 
197
        self._reset_multi_line_flags()
 
198
        
 
199
        for entry in self.line_buffer:
 
200
            if self._in_multi_line(entry):
 
201
                self.stream.write(entry + "\n")
 
202
            else:
 
203
                entry = string.expandtabs(entry)
 
204
                if stripspace is None and re.search(r"^[ \t]*[^# \t]", entry):
 
205
                    stripspace = re.match(r"^([ \t]*)", entry).group(1)
 
206
                self.stream.write(self._indent_line(entry, stripspace) + "\n")
 
207
            
 
208
        self.line_buffer = []
 
209
        self._reset_multi_line_flags()
 
210
 
 
211
 
 
212
def adjust_whitespace(text):
 
213
    """remove the left-whitespace margin of a block of Python code."""
 
214
    state = [False, False]
 
215
    (backslashed, triplequoted) = (0, 1)
 
216
    def in_multi_line(line):
 
217
        current_state = (state[backslashed] or state[triplequoted]) 
 
218
        if re.search(r"\\$", line):
 
219
            state[backslashed] = True
 
220
        else:
 
221
            state[backslashed] = False
 
222
        line = re.split(r'#', line)[0]
 
223
        triples = len(re.findall(r"\"\"\"|\'\'\'", line))
 
224
        if triples == 1 or triples % 2 != 0:
 
225
            state[triplequoted] = not state[triplequoted]
 
226
        return current_state
 
227
 
 
228
    def _indent_line(line, stripspace = ''):
 
229
        return re.sub(r"^%s" % stripspace, '', line)
 
230
 
 
231
    stream = StringIO()
 
232
    stripspace = None
 
233
 
 
234
    for line in re.split(r'\r?\n', text):
 
235
        if in_multi_line(line):
 
236
            stream.write(line + "\n")
 
237
        else:
 
238
            line = string.expandtabs(line)
 
239
            if stripspace is None and re.search(r"^[ \t]*[^# \t]", line):
 
240
                stripspace = re.match(r"^([ \t]*)", line).group(1)
 
241
            stream.write(_indent_line(line, stripspace) + "\n")
 
242
    return stream.getvalue()