|  | # 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 __builtin__ | 
|  | 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 self.formatter.locals and item in self.frame.f_locals: | 
|  | return self.frame.f_locals[item] | 
|  |  | 
|  | if item in self.dict: | 
|  | return self.dict[item] | 
|  |  | 
|  | if self.formatter.globals and item in self.frame.f_globals: | 
|  | return self.frame.f_globals[item] | 
|  |  | 
|  | if item in __builtin__.__dict__: | 
|  | return __builtin__.__dict__[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, count=1): | 
|  | self._indent_level += self._indent_spaces * count | 
|  |  | 
|  | def dedent(self, count=1): | 
|  | assert self._indent_level >= (self._indent_spaces * count) | 
|  | self._indent_level -= self._indent_spaces * count | 
|  |  | 
|  | 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 __call__(self, *args, **kwargs): | 
|  | if not args: | 
|  | self._data.append('\n') | 
|  | return | 
|  |  | 
|  | format = args[0] | 
|  | args = args[1:] | 
|  |  | 
|  | 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, |