| # 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. |
| |
| from __future__ import print_function |
| from six import add_metaclass |
| |
| try: |
| import builtins |
| except ImportError: |
| # Python 2 fallback |
| import __builtin__ as builtins |
| import inspect |
| import os |
| import re |
| |
| 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 builtins.__dict__: |
| return builtins.__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) |
| |
| @add_metaclass(code_formatter_meta) |
| class code_formatter(object): |
| 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 = open(os.path.join(*args), "w") |
| for data in self._data: |
| f.write(data) |
| f.close() |
| |
| def __str__(self): |
| data = ''.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 range(4))}}") |
| f('${x}') |
| f('$x') |
| f.indent() |
| for i in range(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, end=' ') |