--- /dev/null
+# Copyright (c) 2006-2009 Nathan Binkert <nate@binkert.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met: redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer;
+# redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution;
+# neither the name of the copyright holders nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import inspect
+import os
+import re
+import string
+
+class lookup(object):
+ def __init__(self, formatter, frame, *args, **kwargs):
+ self.frame = frame
+ self.formatter = formatter
+ self.dict = self.formatter._dict
+ self.args = args
+ self.kwargs = kwargs
+ self.locals = {}
+
+ def __setitem__(self, item, val):
+ self.locals[item] = val
+
+ def __getitem__(self, item):
+ if item in self.locals:
+ return self.locals[item]
+
+ if item in self.kwargs:
+ return self.kwargs[item]
+
+ if item == '__file__':
+ return self.frame.f_code.co_filename
+
+ if item == '__line__':
+ return self.frame.f_lineno
+
+ if item in self.dict:
+ return self.dict[item]
+
+ if self.formatter.locals or self.formatter.globals:
+ if self.formatter.locals and item in self.frame.f_locals:
+ return self.frame.f_locals[item]
+
+ if self.formatter.globals and item in self.frame.f_globals:
+ return self.frame.f_globals[item]
+
+ if item in __builtins__:
+ return __builtins__[item]
+
+ try:
+ item = int(item)
+ return self.args[item]
+ except ValueError:
+ pass
+ raise IndexError, "Could not find '%s'" % item
+
+class code_formatter_meta(type):
+ pattern = r"""
+ (?:
+ %(delim)s(?P<escaped>%(delim)s) | # escaped delimiter
+ ^(?P<indent>[ ]*)%(delim)s(?P<lone>%(ident)s)$ | # lone identifier
+ %(delim)s(?P<ident>%(ident)s) | # identifier
+ %(delim)s%(lb)s(?P<b_ident>%(ident)s)%(rb)s | # braced identifier
+ %(delim)s(?P<pos>%(pos)s) | # positional parameter
+ %(delim)s%(lb)s(?P<b_pos>%(pos)s)%(rb)s | # braced positional
+ %(delim)s%(ldb)s(?P<eval>.*?)%(rdb)s | # double braced expression
+ %(delim)s(?P<invalid>) # ill-formed delimiter exprs
+ )
+ """
+ def __init__(cls, name, bases, dct):
+ super(code_formatter_meta, cls).__init__(name, bases, dct)
+ if 'pattern' in dct:
+ pat = cls.pattern
+ else:
+ # tuple expansion to ensure strings are proper length
+ lb,rb = cls.braced
+ lb1,lb2,rb2,rb1 = cls.double_braced
+ pat = code_formatter_meta.pattern % {
+ 'delim' : re.escape(cls.delim),
+ 'ident' : cls.ident,
+ 'pos' : cls.pos,
+ 'lb' : re.escape(lb),
+ 'rb' : re.escape(rb),
+ 'ldb' : re.escape(lb1+lb2),
+ 'rdb' : re.escape(rb2+rb1),
+ }
+ cls.pattern = re.compile(pat, re.VERBOSE | re.DOTALL | re.MULTILINE)
+
+class code_formatter(object):
+ __metaclass__ = code_formatter_meta
+
+ delim = r'$'
+ ident = r'[_A-z]\w*'
+ pos = r'[0-9]+'
+ braced = r'{}'
+ double_braced = r'{{}}'
+
+ globals = True
+ locals = True
+ fix_newlines = True
+ def __init__(self, *args, **kwargs):
+ self._data = []
+ self._dict = {}
+ self._indent_level = 0
+ self._indent_spaces = 4
+ self.globals = kwargs.pop('globals',type(self).globals)
+ self.locals = kwargs.pop('locals', type(self).locals)
+ self._fix_newlines = \
+ kwargs.pop('fix_newlines', type(self).fix_newlines)
+
+ if args:
+ self.__call__(args)
+
+ def indent(self):
+ self._indent_level += self._indent_spaces
+
+ def dedent(self):
+ assert self._indent_level >= self._indent_spaces
+ self._indent_level -= self._indent_spaces
+
+ def fix(self, status):
+ previous = self._fix_newlines
+ self._fix_newlines = status
+ return previous
+
+ def nofix(self):
+ previous = self._fix_newlines
+ self._fix_newlines = False
+ return previous
+
+ def clear():
+ self._data = []
+
+ def write(self, *args):
+ f = file(os.path.join(*args), "w")
+ for data in self._data:
+ f.write(data)
+ f.close()
+
+ def __str__(self):
+ data = string.join(self._data, '')
+ self._data = [ data ]
+ return data
+
+ def __getitem__(self, item):
+ return self._dict[item]
+
+ def __setitem__(self, item, value):
+ self._dict[item] = value
+
+ def __delitem__(self, item):
+ del self._dict[item]
+
+ def __contains__(self, item):
+ return item in self._dict
+
+ def __iadd__(self, data):
+ self.append(data)
+
+ def append(self, data):
+ if isinstance(data, code_formatter):
+ self._data.extend(data._data)
+ else:
+ self._append(str(data))
+
+ def _append(self, data):
+ if not self._fix_newlines:
+ self._data.append(data)
+ return
+
+ initial_newline = not self._data or self._data[-1] == '\n'
+ for line in data.splitlines():
+ if line:
+ if self._indent_level:
+ self._data.append(' ' * self._indent_level)
+ self._data.append(line)
+
+ if line or not initial_newline:
+ self._data.append('\n')
+
+ initial_newline = False
+
+ def insert_newline(self):
+ self._data.append('\n')
+
+ def __call__(self, format, *args, **kwargs):
+ frame = inspect.currentframe().f_back
+
+ l = lookup(self, frame, *args, **kwargs)
+ def convert(match):
+ ident = match.group('lone')
+ # check for a lone identifier
+ if ident:
+ indent = match.group('indent') # must be spaces
+ lone = '%s' % (l[ident], )
+
+ def indent_lines(gen):
+ for line in gen:
+ yield indent
+ yield line
+ return ''.join(indent_lines(lone.splitlines(True)))
+
+ # check for an identifier, braced or not
+ ident = match.group('ident') or match.group('b_ident')
+ if ident is not None:
+ return '%s' % (l[ident], )
+
+ # check for a positional parameter, braced or not
+ pos = match.group('pos') or match.group('b_pos')
+ if pos is not None:
+ pos = int(pos)
+ if pos > len(args):
+ raise ValueError \
+ ('Positional parameter #%d not found in pattern' % pos,
+ code_formatter.pattern)
+ return '%s' % (args[int(pos)], )
+
+ # check for a double braced expression
+ eval_expr = match.group('eval')
+ if eval_expr is not None:
+ result = eval(eval_expr, {}, l)
+ return '%s' % (result, )
+
+ # check for an escaped delimiter
+ if match.group('escaped') is not None:
+ return '$'
+
+ # At this point, we have to match invalid
+ if match.group('invalid') is None:
+ # didn't match invalid!
+ raise ValueError('Unrecognized named group in pattern',
+ code_formatter.pattern)
+
+ i = match.start('invalid')
+ if i == 0:
+ colno = 1
+ lineno = 1
+ else:
+ lines = format[:i].splitlines(True)
+ colno = i - reduce(lambda x,y: x+y, (len(z) for z in lines))
+ lineno = len(lines)
+
+ raise ValueError('Invalid format string: line %d, col %d' %
+ (lineno, colno))
+
+ d = code_formatter.pattern.sub(convert, format)
+ self._append(d)
+
+__all__ = [ "code_formatter" ]
+
+if __name__ == '__main__':
+ from code_formatter import code_formatter
+ f = code_formatter()
+
+ class Foo(dict):
+ def __init__(self, **kwargs):
+ self.update(kwargs)
+ def __getattr__(self, attr):
+ return self[attr]
+
+ x = "this is a test"
+ l = [ [Foo(x=[Foo(y=9)])] ]
+
+ y = code_formatter()
+ y('''
+{
+ this_is_a_test();
+}
+''')
+ f(' $y')
+ f('''$__file__:$__line__
+{''')
+ f("${{', '.join(str(x) for x in xrange(4))}}")
+ f('${x}')
+ f('$x')
+ f.indent()
+ for i in xrange(5):
+ f('$x')
+ f('$i')
+ f('$0', "zero")
+ f('$1 $0', "zero", "one")
+ f('${0}', "he went")
+ f('${0}asdf', "he went")
+ f.dedent()
+
+ f('''
+ ${{l[0][0]["x"][0].y}}
+}
+''', 1, 9)
+
+ print f,