code_formatter.py revision 13709
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 27from __future__ import print_function 28 29import __builtin__ 30import inspect 31import os 32import re 33import string 34 35class lookup(object): 36 def __init__(self, formatter, frame, *args, **kwargs): 37 self.frame = frame 38 self.formatter = formatter 39 self.dict = self.formatter._dict 40 self.args = args 41 self.kwargs = kwargs 42 self.locals = {} 43 44 def __setitem__(self, item, val): 45 self.locals[item] = val 46 47 def __getitem__(self, item): 48 if item in self.locals: 49 return self.locals[item] 50 51 if item in self.kwargs: 52 return self.kwargs[item] 53 54 if item == '__file__': 55 return self.frame.f_code.co_filename 56 57 if item == '__line__': 58 return self.frame.f_lineno 59 60 if self.formatter.locals and item in self.frame.f_locals: 61 return self.frame.f_locals[item] 62 63 if item in self.dict: 64 return self.dict[item] 65 66 if self.formatter.globals and item in self.frame.f_globals: 67 return self.frame.f_globals[item] 68 69 if item in __builtin__.__dict__: 70 return __builtin__.__dict__[item] 71 72 try: 73 item = int(item) 74 return self.args[item] 75 except ValueError: 76 pass 77 raise IndexError("Could not find '%s'" % item) 78 79class code_formatter_meta(type): 80 pattern = r""" 81 (?: 82 %(delim)s(?P<escaped>%(delim)s) | # escaped delimiter 83 ^(?P<indent>[ ]*)%(delim)s(?P<lone>%(ident)s)$ | # lone identifier 84 %(delim)s(?P<ident>%(ident)s) | # identifier 85 %(delim)s%(lb)s(?P<b_ident>%(ident)s)%(rb)s | # braced identifier 86 %(delim)s(?P<pos>%(pos)s) | # positional parameter 87 %(delim)s%(lb)s(?P<b_pos>%(pos)s)%(rb)s | # braced positional 88 %(delim)s%(ldb)s(?P<eval>.*?)%(rdb)s | # double braced expression 89 %(delim)s(?P<invalid>) # ill-formed delimiter exprs 90 ) 91 """ 92 def __init__(cls, name, bases, dct): 93 super(code_formatter_meta, cls).__init__(name, bases, dct) 94 if 'pattern' in dct: 95 pat = cls.pattern 96 else: 97 # tuple expansion to ensure strings are proper length 98 lb,rb = cls.braced 99 lb1,lb2,rb2,rb1 = cls.double_braced 100 pat = code_formatter_meta.pattern % { 101 'delim' : re.escape(cls.delim), 102 'ident' : cls.ident, 103 'pos' : cls.pos, 104 'lb' : re.escape(lb), 105 'rb' : re.escape(rb), 106 'ldb' : re.escape(lb1+lb2), 107 'rdb' : re.escape(rb2+rb1), 108 } 109 cls.pattern = re.compile(pat, re.VERBOSE | re.DOTALL | re.MULTILINE) 110 111class code_formatter(object): 112 __metaclass__ = code_formatter_meta 113 114 delim = r'$' 115 ident = r'[_A-z]\w*' 116 pos = r'[0-9]+' 117 braced = r'{}' 118 double_braced = r'{{}}' 119 120 globals = True 121 locals = True 122 fix_newlines = True 123 def __init__(self, *args, **kwargs): 124 self._data = [] 125 self._dict = {} 126 self._indent_level = 0 127 self._indent_spaces = 4 128 self.globals = kwargs.pop('globals', type(self).globals) 129 self.locals = kwargs.pop('locals', type(self).locals) 130 self._fix_newlines = \ 131 kwargs.pop('fix_newlines', type(self).fix_newlines) 132 133 if args: 134 self.__call__(args) 135 136 def indent(self, count=1): 137 self._indent_level += self._indent_spaces * count 138 139 def dedent(self, count=1): 140 assert self._indent_level >= (self._indent_spaces * count) 141 self._indent_level -= self._indent_spaces * count 142 143 def fix(self, status): 144 previous = self._fix_newlines 145 self._fix_newlines = status 146 return previous 147 148 def nofix(self): 149 previous = self._fix_newlines 150 self._fix_newlines = False 151 return previous 152 153 def clear(): 154 self._data = [] 155 156 def write(self, *args): 157 f = open(os.path.join(*args), "w") 158 for data in self._data: 159 f.write(data) 160 f.close() 161 162 def __str__(self): 163 data = string.join(self._data, '') 164 self._data = [ data ] 165 return data 166 167 def __getitem__(self, item): 168 return self._dict[item] 169 170 def __setitem__(self, item, value): 171 self._dict[item] = value 172 173 def __delitem__(self, item): 174 del self._dict[item] 175 176 def __contains__(self, item): 177 return item in self._dict 178 179 def __iadd__(self, data): 180 self.append(data) 181 182 def append(self, data): 183 if isinstance(data, code_formatter): 184 self._data.extend(data._data) 185 else: 186 self._append(str(data)) 187 188 def _append(self, data): 189 if not self._fix_newlines: 190 self._data.append(data) 191 return 192 193 initial_newline = not self._data or self._data[-1] == '\n' 194 for line in data.splitlines(): 195 if line: 196 if self._indent_level: 197 self._data.append(' ' * self._indent_level) 198 self._data.append(line) 199 200 if line or not initial_newline: 201 self._data.append('\n') 202 203 initial_newline = False 204 205 def __call__(self, *args, **kwargs): 206 if not args: 207 self._data.append('\n') 208 return 209 210 format = args[0] 211 args = args[1:] 212 213 frame = inspect.currentframe().f_back 214 215 l = lookup(self, frame, *args, **kwargs) 216 def convert(match): 217 ident = match.group('lone') 218 # check for a lone identifier 219 if ident: 220 indent = match.group('indent') # must be spaces 221 lone = '%s' % (l[ident], ) 222 223 def indent_lines(gen): 224 for line in gen: 225 yield indent 226 yield line 227 return ''.join(indent_lines(lone.splitlines(True))) 228 229 # check for an identifier, braced or not 230 ident = match.group('ident') or match.group('b_ident') 231 if ident is not None: 232 return '%s' % (l[ident], ) 233 234 # check for a positional parameter, braced or not 235 pos = match.group('pos') or match.group('b_pos') 236 if pos is not None: 237 pos = int(pos) 238 if pos > len(args): 239 raise ValueError \ 240 ('Positional parameter #%d not found in pattern' % pos, 241 code_formatter.pattern) 242 return '%s' % (args[int(pos)], ) 243 244 # check for a double braced expression 245 eval_expr = match.group('eval') 246 if eval_expr is not None: 247 result = eval(eval_expr, {}, l) 248 return '%s' % (result, ) 249 250 # check for an escaped delimiter 251 if match.group('escaped') is not None: 252 return '$' 253 254 # At this point, we have to match invalid 255 if match.group('invalid') is None: 256 # didn't match invalid! 257 raise ValueError('Unrecognized named group in pattern', 258 code_formatter.pattern) 259 260 i = match.start('invalid') 261 if i == 0: 262 colno = 1 263 lineno = 1 264 else: 265 lines = format[:i].splitlines(True) 266 colno = i - reduce(lambda x,y: x+y, (len(z) for z in lines)) 267 lineno = len(lines) 268 269 raise ValueError('Invalid format string: line %d, col %d' % 270 (lineno, colno)) 271 272 d = code_formatter.pattern.sub(convert, format) 273 self._append(d) 274 275__all__ = [ "code_formatter" ] 276 277if __name__ == '__main__': 278 from code_formatter import code_formatter 279 f = code_formatter() 280 281 class Foo(dict): 282 def __init__(self, **kwargs): 283 self.update(kwargs) 284 def __getattr__(self, attr): 285 return self[attr] 286 287 x = "this is a test" 288 l = [ [Foo(x=[Foo(y=9)])] ] 289 290 y = code_formatter() 291 y(''' 292{ 293 this_is_a_test(); 294} 295''') 296 f(' $y') 297 f('''$__file__:$__line__ 298{''') 299 f("${{', '.join(str(x) for x in range(4))}}") 300 f('${x}') 301 f('$x') 302 f.indent() 303 for i in range(5): 304 f('$x') 305 f('$i') 306 f('$0', "zero") 307 f('$1 $0', "zero", "one") 308 f('${0}', "he went") 309 f('${0}asdf', "he went") 310 f.dedent() 311 312 f(''' 313 ${{l[0][0]["x"][0].y}} 314} 315''', 1, 9) 316 317 print(f, end=' ') 318