code_formatter.py revision 6651
1# Copyright (c) 2006-2009 Nathan Binkert <nate@binkert.org> 2# All rights reserved. 3# 4# Redistribution and use in source and binary forms, with or without 5# modification, are permitted provided that the following conditions are 6# met: redistributions of source code must retain the above copyright 7# notice, this list of conditions and the following disclaimer; 8# redistributions in binary form must reproduce the above copyright 9# notice, this list of conditions and the following disclaimer in the 10# documentation and/or other materials provided with the distribution; 11# neither the name of the copyright holders nor the names of its 12# contributors may be used to endorse or promote products derived from 13# this software without specific prior written permission. 14# 15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 27import __builtin__ 28import inspect 29import os 30import re 31import string 32 33class lookup(object): 34 def __init__(self, formatter, frame, *args, **kwargs): 35 self.frame = frame 36 self.formatter = formatter 37 self.dict = self.formatter._dict 38 self.args = args 39 self.kwargs = kwargs 40 self.locals = {} 41 42 def __setitem__(self, item, val): 43 self.locals[item] = val 44 45 def __getitem__(self, item): 46 if item in self.locals: 47 return self.locals[item] 48 49 if item in self.kwargs: 50 return self.kwargs[item] 51 52 if item == '__file__': 53 return self.frame.f_code.co_filename 54 55 if item == '__line__': 56 return self.frame.f_lineno 57 58 if item in self.dict: 59 return self.dict[item] 60 61 if self.formatter.locals or self.formatter.globals: 62 if self.formatter.locals and item in self.frame.f_locals: 63 return self.frame.f_locals[item] 64 65 if self.formatter.globals and item in self.frame.f_globals: 66 return self.frame.f_globals[item] 67 68 if item in __builtin__.__dict__: 69 return __builtin__.__dict__[item] 70 71 try: 72 item = int(item) 73 return self.args[item] 74 except ValueError: 75 pass 76 raise IndexError, "Could not find '%s'" % item 77 78class code_formatter_meta(type): 79 pattern = r""" 80 (?: 81 %(delim)s(?P<escaped>%(delim)s) | # escaped delimiter 82 ^(?P<indent>[ ]*)%(delim)s(?P<lone>%(ident)s)$ | # lone identifier 83 %(delim)s(?P<ident>%(ident)s) | # identifier 84 %(delim)s%(lb)s(?P<b_ident>%(ident)s)%(rb)s | # braced identifier 85 %(delim)s(?P<pos>%(pos)s) | # positional parameter 86 %(delim)s%(lb)s(?P<b_pos>%(pos)s)%(rb)s | # braced positional 87 %(delim)s%(ldb)s(?P<eval>.*?)%(rdb)s | # double braced expression 88 %(delim)s(?P<invalid>) # ill-formed delimiter exprs 89 ) 90 """ 91 def __init__(cls, name, bases, dct): 92 super(code_formatter_meta, cls).__init__(name, bases, dct) 93 if 'pattern' in dct: 94 pat = cls.pattern 95 else: 96 # tuple expansion to ensure strings are proper length 97 lb,rb = cls.braced 98 lb1,lb2,rb2,rb1 = cls.double_braced 99 pat = code_formatter_meta.pattern % { 100 'delim' : re.escape(cls.delim), 101 'ident' : cls.ident, 102 'pos' : cls.pos, 103 'lb' : re.escape(lb), 104 'rb' : re.escape(rb), 105 'ldb' : re.escape(lb1+lb2), 106 'rdb' : re.escape(rb2+rb1), 107 } 108 cls.pattern = re.compile(pat, re.VERBOSE | re.DOTALL | re.MULTILINE) 109 110class code_formatter(object): 111 __metaclass__ = code_formatter_meta 112 113 delim = r'$' 114 ident = r'[_A-z]\w*' 115 pos = r'[0-9]+' 116 braced = r'{}' 117 double_braced = r'{{}}' 118 119 globals = True 120 locals = True 121 fix_newlines = True 122 def __init__(self, *args, **kwargs): 123 self._data = [] 124 self._dict = {} 125 self._indent_level = 0 126 self._indent_spaces = 4 127 self.globals = kwargs.pop('globals',type(self).globals) 128 self.locals = kwargs.pop('locals', type(self).locals) 129 self._fix_newlines = \ 130 kwargs.pop('fix_newlines', type(self).fix_newlines) 131 132 if args: 133 self.__call__(args) 134 135 def indent(self): 136 self._indent_level += self._indent_spaces 137 138 def dedent(self): 139 assert self._indent_level >= self._indent_spaces 140 self._indent_level -= self._indent_spaces 141 142 def fix(self, status): 143 previous = self._fix_newlines 144 self._fix_newlines = status 145 return previous 146 147 def nofix(self): 148 previous = self._fix_newlines 149 self._fix_newlines = False 150 return previous 151 152 def clear(): 153 self._data = [] 154 155 def write(self, *args): 156 f = file(os.path.join(*args), "w") 157 for data in self._data: 158 f.write(data) 159 f.close() 160 161 def __str__(self): 162 data = string.join(self._data, '') 163 self._data = [ data ] 164 return data 165 166 def __getitem__(self, item): 167 return self._dict[item] 168 169 def __setitem__(self, item, value): 170 self._dict[item] = value 171 172 def __delitem__(self, item): 173 del self._dict[item] 174 175 def __contains__(self, item): 176 return item in self._dict 177 178 def __iadd__(self, data): 179 self.append(data) 180 181 def append(self, data): 182 if isinstance(data, code_formatter): 183 self._data.extend(data._data) 184 else: 185 self._append(str(data)) 186 187 def _append(self, data): 188 if not self._fix_newlines: 189 self._data.append(data) 190 return 191 192 initial_newline = not self._data or self._data[-1] == '\n' 193 for line in data.splitlines(): 194 if line: 195 if self._indent_level: 196 self._data.append(' ' * self._indent_level) 197 self._data.append(line) 198 199 if line or not initial_newline: 200 self._data.append('\n') 201 202 initial_newline = False 203 204 def insert_newline(self): 205 self._data.append('\n') 206 207 def __call__(self, format, *args, **kwargs): 208 frame = inspect.currentframe().f_back 209 210 l = lookup(self, frame, *args, **kwargs) 211 def convert(match): 212 ident = match.group('lone') 213 # check for a lone identifier 214 if ident: 215 indent = match.group('indent') # must be spaces 216 lone = '%s' % (l[ident], ) 217 218 def indent_lines(gen): 219 for line in gen: 220 yield indent 221 yield line 222 return ''.join(indent_lines(lone.splitlines(True))) 223 224 # check for an identifier, braced or not 225 ident = match.group('ident') or match.group('b_ident') 226 if ident is not None: 227 return '%s' % (l[ident], ) 228 229 # check for a positional parameter, braced or not 230 pos = match.group('pos') or match.group('b_pos') 231 if pos is not None: 232 pos = int(pos) 233 if pos > len(args): 234 raise ValueError \ 235 ('Positional parameter #%d not found in pattern' % pos, 236 code_formatter.pattern) 237 return '%s' % (args[int(pos)], ) 238 239 # check for a double braced expression 240 eval_expr = match.group('eval') 241 if eval_expr is not None: 242 result = eval(eval_expr, {}, l) 243 return '%s' % (result, ) 244 245 # check for an escaped delimiter 246 if match.group('escaped') is not None: 247 return '$' 248 249 # At this point, we have to match invalid 250 if match.group('invalid') is None: 251 # didn't match invalid! 252 raise ValueError('Unrecognized named group in pattern', 253 code_formatter.pattern) 254 255 i = match.start('invalid') 256 if i == 0: 257 colno = 1 258 lineno = 1 259 else: 260 lines = format[:i].splitlines(True) 261 colno = i - reduce(lambda x,y: x+y, (len(z) for z in lines)) 262 lineno = len(lines) 263 264 raise ValueError('Invalid format string: line %d, col %d' % 265 (lineno, colno)) 266 267 d = code_formatter.pattern.sub(convert, format) 268 self._append(d) 269 270__all__ = [ "code_formatter" ] 271 272if __name__ == '__main__': 273 from code_formatter import code_formatter 274 f = code_formatter() 275 276 class Foo(dict): 277 def __init__(self, **kwargs): 278 self.update(kwargs) 279 def __getattr__(self, attr): 280 return self[attr] 281 282 x = "this is a test" 283 l = [ [Foo(x=[Foo(y=9)])] ] 284 285 y = code_formatter() 286 y(''' 287{ 288 this_is_a_test(); 289} 290''') 291 f(' $y') 292 f('''$__file__:$__line__ 293{''') 294 f("${{', '.join(str(x) for x in xrange(4))}}") 295 f('${x}') 296 f('$x') 297 f.indent() 298 for i in xrange(5): 299 f('$x') 300 f('$i') 301 f('$0', "zero") 302 f('$1 $0', "zero", "one") 303 f('${0}', "he went") 304 f('${0}asdf', "he went") 305 f.dedent() 306 307 f(''' 308 ${{l[0][0]["x"][0].y}} 309} 310''', 1, 9) 311 312 print f, 313