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