Edgewall Software

Ticket #394: external_ast_transformers.patch

File external_ast_transformers.patch, 21.0 KB (added by Carsten Klein <carsten.klein@…>, 7 years ago)
  • template/base.py

     
    2020
    2121from genshi.core import Attrs, Stream, StreamEventKind, START, TEXT, _ensure
    2222from genshi.input import ParseError
     23from genshi.template.astutil import ASTTransformer
    2324
    2425__all__ = ['Context', 'DirectiveFactory', 'Template', 'TemplateError',
    2526           'TemplateRuntimeError', 'TemplateSyntaxError', 'BadDirectiveError']
     
    370371    _number_conv = unicode # function used to convert numbers to event data
    371372
    372373    def __init__(self, source, filepath=None, filename=None, loader=None,
    373                  encoding=None, lookup='strict', allow_exec=True):
     374                 encoding=None, lookup='strict', allow_exec=True, xform=None):
    374375        """Initialize a template from either a string, a file-like object, or
    375376        an already parsed markup stream.
    376377       
     
    386387                       default), "lenient", or a custom lookup class
    387388        :param allow_exec: whether Python code blocks in templates should be
    388389                           allowed
     390        :param xform: a dict in the form of
    389391       
     392                      { 'eval' : <class>, 'exec' : <class> }
     393                     
     394                      where <class> is the respective AST transformer class that
     395                      should be applied to the code representing either a python
     396                      expression or executable python code; if `None`, or either
     397                      of the two is omitted, then the default transformation is
     398                      chosen depending on the current execution mode, which is
     399                      either 'eval' or 'exec'.
     400
    390401        :note: Changed in 0.5: Added the `allow_exec` argument
     402        :note: Changed in 0.7: Added the `xform` argument
    391403        """
    392404        self.filepath = filepath or filename
    393405        self.filename = filename
    394406        self.loader = loader
    395407        self.lookup = lookup
    396408        self.allow_exec = allow_exec
    397         self._init_filters()
     409
     410        if xform is not None :
     411            if 'eval' in xform and not ASTTransformer in xform['eval'].__mro__:
     412                raise TemplateRuntimeError("xform['eval'] must inherit from "
     413                                           "ASTTransformer.")
     414            if 'exec' in xform and not ASTTransformer in xform['exec'].__mro__:
     415                raise TemplateRuntimeError("xform['exec'] must inherit from "
     416                                           "ASTTransformer.")
     417
     418        self.xform = xform
     419        self._init_filters(xform=xform)
    398420        self._prepared = False
    399421
    400422        if isinstance(source, basestring):
     
    418440    def __repr__(self):
    419441        return '<%s "%s">' % (type(self).__name__, self.filename)
    420442
    421     def _init_filters(self):
     443    def _init_filters(self, xform=None):
    422444        self.filters = [self._flatten]
    423445        if self.loader:
    424             self.filters.append(self._include)
     446            self.filters.append(self._include(xform=xform))
    425447
    426448    @property
    427449    def stream(self):
     
    476498                        # the template is inlined into the stream
    477499                        try:
    478500                            tmpl = self.loader.load(href, relative_to=pos[0],
    479                                                     cls=cls or self.__class__)
     501                                                    cls=cls or self.__class__,
     502                                                    xform=self.xform)
    480503                            for event in tmpl.stream:
    481504                                yield event
    482505                        except TemplateNotFound:
     
    583606                    break
    584607                stream = pop()
    585608
    586     def _include(self, stream, ctxt, **vars):
     609    def _include(self, xform=None):
    587610        """Internal stream filter that performs inclusion of external
    588611        template files.
    589612        """
    590         from genshi.template.loader import TemplateNotFound
     613        def real_include(stream, ctxt, **vars):
     614            from genshi.template.loader import TemplateNotFound
     615   
     616            for event in stream:
     617                if event[0] is INCLUDE:
     618                    href, cls, fallback = event[1]
     619                    if not isinstance(href, basestring):
     620                        parts = []
     621                        for subkind, subdata, subpos in self._flatten(href, ctxt,
     622                                                                      **vars):
     623                            if subkind is TEXT:
     624                                parts.append(subdata)
     625                        href = ''.join([x for x in parts if x is not None])
     626                    try:
     627                        tmpl = self.loader.load(href, relative_to=event[2][0],
     628                                                cls=cls or self.__class__)
     629                        for event in tmpl.generate(ctxt, **vars):
     630                            yield event
     631                    except TemplateNotFound:
     632                        if fallback is None:
     633                            raise
     634                        for filter_ in self.filters:
     635                            fallback = filter_(iter(fallback), ctxt, **vars)
     636                        for event in fallback:
     637                            yield event
     638                else:
     639                    yield event
    591640
    592         for event in stream:
    593             if event[0] is INCLUDE:
    594                 href, cls, fallback = event[1]
    595                 if not isinstance(href, basestring):
    596                     parts = []
    597                     for subkind, subdata, subpos in self._flatten(href, ctxt,
    598                                                                   **vars):
    599                         if subkind is TEXT:
    600                             parts.append(subdata)
    601                     href = ''.join([x for x in parts if x is not None])
    602                 try:
    603                     tmpl = self.loader.load(href, relative_to=event[2][0],
    604                                             cls=cls or self.__class__)
    605                     for event in tmpl.generate(ctxt, **vars):
    606                         yield event
    607                 except TemplateNotFound:
    608                     if fallback is None:
    609                         raise
    610                     for filter_ in self.filters:
    611                         fallback = filter_(iter(fallback), ctxt, **vars)
    612                     for event in fallback:
    613                         yield event
    614             else:
    615                 yield event
     641        return real_include
    616642
    617 
    618643EXEC = Template.EXEC
    619644EXPR = Template.EXPR
    620645INCLUDE = Template.INCLUDE
  • template/markup.py

     
    6161    _number_conv = Markup
    6262
    6363    def __init__(self, source, filepath=None, filename=None, loader=None,
    64                  encoding=None, lookup='strict', allow_exec=True):
     64                 encoding=None, lookup='strict', allow_exec=True, xform=None):
    6565        Template.__init__(self, source, filepath=filepath, filename=filename,
    6666                          loader=loader, encoding=encoding, lookup=lookup,
    67                           allow_exec=allow_exec)
     67                          allow_exec=allow_exec, xform=xform)
    6868        self.add_directives(self.DIRECTIVE_NAMESPACE, self)
    6969
    7070    def _init_filters(self):
    7171        Template._init_filters(self)
    7272        # Make sure the include filter comes after the match filter
    7373        if self.loader:
    74             self.filters.remove(self._include)
     74            self.filters.remove(self._include(xform=xform))
    7575        self.filters += [self._match]
    7676        if self.loader:
    77             self.filters.append(self._include)
     77            self.filters.append(self._include(xform=xform))
    7878
    7979    def _parse(self, source, encoding):
    8080        if not isinstance(source, Stream):
     
    8686
    8787            if kind is TEXT:
    8888                for kind, data, pos in interpolate(data, self.filepath, pos[1],
    89                                                    pos[2], lookup=self.lookup):
     89                                                   pos[2], lookup=self.lookup,
     90                                                   xform=self.xform):
    9091                    stream.append((kind, data, pos))
    9192
    9293            elif kind is PI and data[0] == 'python':
     
    9596                                              self.filepath, *pos[1:])
    9697                try:
    9798                    suite = Suite(data[1], self.filepath, pos[1],
    98                                   lookup=self.lookup)
     99                                  lookup=self.lookup, xform=self.xform)
    99100                except SyntaxError, err:
    100101                    raise TemplateSyntaxError(err, self.filepath,
    101102                                              pos[1] + (err.lineno or 1) - 1,
     
    278279                for name, value in attrs:
    279280                    if value:
    280281                        value = list(interpolate(value, self.filepath, pos[1],
    281                                                  pos[2], lookup=self.lookup))
     282                                                 pos[2], lookup=self.lookup,
     283                                                 xform=self.xform))
    282284                        if len(value) == 1 and value[0][0] is TEXT:
    283285                            value = value[0][1]
    284286                    new_attrs.append((name, value))
  • template/text.py

     
    132132
    133133    def __init__(self, source, filepath=None, filename=None, loader=None,
    134134                 encoding=None, lookup='strict', allow_exec=False,
    135                  delims=('{%', '%}', '{#', '#}')):
     135                 delims=('{%', '%}', '{#', '#}'), xform=None):
    136136        self.delimiters = delims
    137137        Template.__init__(self, source, filepath=filepath, filename=filename,
    138                           loader=loader, encoding=encoding, lookup=lookup)
     138                          loader=loader, encoding=encoding, lookup=lookup,
     139                          xform=xform)
    139140
    140141    def _get_delims(self):
    141142        return self._delims
     
    179180            if start > offset:
    180181                text = _escape_sub(_escape_repl, source[offset:start])
    181182                for kind, data, pos in interpolate(text, self.filepath, lineno,
    182                                                    lookup=self.lookup):
     183                                                   lookup=self.lookup,
     184                                                   xform=self.xform):
    183185                    stream.append((kind, data, pos))
    184186                lineno += len(text.splitlines())
    185187
     
    189191            if command == 'include':
    190192                pos = (self.filename, lineno, 0)
    191193                value = list(interpolate(value, self.filepath, lineno, 0,
    192                                          lookup=self.lookup))
     194                                         lookup=self.lookup, xform=self.xform))
    193195                if len(value) == 1 and value[0][0] is TEXT:
    194196                    value = value[0][1]
    195197                stream.append((INCLUDE, (value, None, []), pos))
     
    200202                                              self.filepath, lineno)
    201203                try:
    202204                    suite = Suite(value, self.filepath, lineno,
    203                                   lookup=self.lookup)
     205                                  lookup=self.lookup, xform=self.xform)
    204206                except SyntaxError, err:
    205207                    raise TemplateSyntaxError(err, self.filepath,
    206208                                              lineno + (err.lineno or 1) - 1)
     
    228230        if offset < len(source):
    229231            text = _escape_sub(_escape_repl, source[offset:])
    230232            for kind, data, pos in interpolate(text, self.filepath, lineno,
    231                                                lookup=self.lookup):
     233                                               lookup=self.lookup,
     234                                               xform=self.xform):
    232235                stream.append((kind, data, pos))
    233236
    234237        return stream
     
    289292            if start > offset:
    290293                text = source[offset:start]
    291294                for kind, data, pos in interpolate(text, self.filepath, lineno,
    292                                                    lookup=self.lookup):
     295                                                   lookup=self.lookup,
     296                                                   xform=self.xform):
    293297                    stream.append((kind, data, pos))
    294298                lineno += len(text.splitlines())
    295299
     
    324328        if offset < len(source):
    325329            text = source[offset:].replace('\\#', '#')
    326330            for kind, data, pos in interpolate(text, self.filepath, lineno,
    327                                                lookup=self.lookup):
     331                                               lookup=self.lookup,
     332                                               xform=self.xform):
    328333                stream.append((kind, data, pos))
    329334
    330335        return stream
  • template/loader.py

     
    1919except ImportError:
    2020    import dummy_threading as threading
    2121
    22 from genshi.template.base import TemplateError
     22from genshi.template.astutil import ASTTransformer
     23from genshi.template.base import TemplateError, TemplateRuntimeError
    2324from genshi.util import LRUCache
    2425
    2526__all__ = ['TemplateLoader', 'TemplateNotFound', 'directory', 'package',
     
    7879    """
    7980    def __init__(self, search_path=None, auto_reload=False,
    8081                 default_encoding=None, max_cache_size=25, default_class=None,
    81                  variable_lookup='strict', allow_exec=True, callback=None):
     82                 variable_lookup='strict', allow_exec=True, callback=None,
     83                 xform=None):
    8284        """Create the template laoder.
    8385       
    8486        :param search_path: a list of absolute path names that should be
     
    104106                         is passed the template object as only argument. This
    105107                         callback can be used for example to add any desired
    106108                         filters to the template
     109        :param xform: a dict in the form of
     110       
     111                      { 'eval' : <class>, 'exec' : <class> }
     112                     
     113                      where <class> is the respective AST transformer class that
     114                      should be applied to the code representing either a python
     115                      expression or executable python code; if `None`, or either
     116                      of the two is omitted, then the default transformation is
     117                      chosen depending on the current execution mode, which is
     118                      either 'eval' or 'exec'.
     119
    107120        :see: `LenientLookup`, `StrictLookup`
    108121       
    109122        :note: Changed in 0.5: Added the `allow_exec` argument
     
    120133        """Whether templates should be reloaded when the underlying file is
    121134        changed"""
    122135
     136        if xform is not None :
     137            if 'eval' in xform and not ASTTransformer in xform['eval'].__mro__:
     138                raise TemplateRuntimeError("xform['eval'] must inherit from "
     139                                           "ASTTransformer.")
     140            if 'exec' in xform and not ASTTransformer in xform['exec'].__mro__:
     141                raise TemplateRuntimeError("xform['exec'] must inherit from "
     142                                           "ASTTransformer.")
     143
     144        self.xform = xform
    123145        self.default_encoding = default_encoding
    124146        self.default_class = default_class or MarkupTemplate
    125147        self.variable_lookup = variable_lookup
     
    131153        self._uptodate = {}
    132154        self._lock = threading.RLock()
    133155
    134     def load(self, filename, relative_to=None, cls=None, encoding=None):
     156    def load(self, filename, relative_to=None, cls=None, encoding=None,
     157             xform=None):
    135158        """Load the template with the given name.
    136159       
    137160        If the `filename` parameter is relative, this method searches the
     
    158181        :param cls: the class of the template object to instantiate
    159182        :param encoding: the encoding of the template to load; defaults to the
    160183                         ``default_encoding`` of the loader instance
     184        :param xform: a dict in the form of
     185       
     186                      { 'eval' : <class>, 'exec' : <class> }
     187                     
     188                      where <class> is the respective AST transformer class that
     189                      should be applied to the code representing either a python
     190                      expression or executable python code; if `None`, or either
     191                      of the two is omitted, then the default transformation is
     192                      chosen depending on the current execution mode, which is
     193                      either 'eval' or 'exec'.
     194
    161195        :return: the loaded `Template` instance
    162196        :raises TemplateNotFound: if a template with the given name could not
    163197                                  be found
     
    166200            cls = self.default_class
    167201        search_path = self.search_path
    168202
     203        if xform is None:
     204            xform = self.xform
     205
    169206        # Make the filename relative to the template file its being loaded
    170207        # from, but only if that file is specified as a relative path, or no
    171208        # search path has been set up
     
    224261                            # search path
    225262                            filename = filepath
    226263                        tmpl = self._instantiate(cls, fileobj, filepath,
    227                                                  filename, encoding=encoding)
     264                                                 filename, encoding=encoding,
     265                                                 xform=xform)
    228266                        if self.callback:
    229267                            self.callback(tmpl)
    230268                        self._cache[cachekey] = tmpl
     
    239277        finally:
    240278            self._lock.release()
    241279
    242     def _instantiate(self, cls, fileobj, filepath, filename, encoding=None):
     280    def _instantiate(self, cls, fileobj, filepath, filename, encoding=None,
     281                     xform=None):
    243282        """Instantiate and return the `Template` object based on the given
    244283        class and parameters.
    245284       
     
    255294                         path
    256295        :param encoding: the encoding of the template to load; defaults to the
    257296                         ``default_encoding`` of the loader instance
     297        :param xform: a dict in the form of
     298       
     299                      { 'eval' : <class>, 'exec' : <class> }
     300                     
     301                      where <class> is the respective AST transformer class that
     302                      should be applied to the code representing either a python
     303                      expression or executable python code; if `None`, or either
     304                      of the two is omitted, then the default transformation is
     305                      chosen depending on the current execution mode, which is
     306                      either 'eval' or 'exec'.
     307
    258308        :return: the loaded `Template` instance
    259309        :rtype: `Template`
    260310        """
     
    262312            encoding = self.default_encoding
    263313        return cls(fileobj, filepath=filepath, filename=filename, loader=self,
    264314                   encoding=encoding, lookup=self.variable_lookup,
    265                    allow_exec=self.allow_exec)
     315                   allow_exec=self.allow_exec, xform=xform)
    266316
    267317    @staticmethod
    268318    def directory(path):
  • template/interpolation.py

     
    3737))
    3838
    3939
    40 def interpolate(text, filepath=None, lineno=-1, offset=0, lookup='strict'):
     40def interpolate(text, filepath=None, lineno=-1, offset=0, lookup='strict',
     41                xform=None):
    4142    """Parse the given string and extract expressions.
    4243   
    4344    This function is a generator that yields `TEXT` events for literal strings,
     
    5859                   (optional)
    5960    :param lookup: the variable lookup mechanism; either "lenient" (the
    6061                   default), "strict", or a custom lookup class
     62    :param xform:  a dict containing class references to AST transformers being
     63                   applied on evaluation of expressions.
    6164    :return: a list of `TEXT` and `EXPR` events
    6265    :raise TemplateSyntaxError: when a syntax error in an expression is
    6366                                encountered
     
    7578            if chunk:
    7679                try:
    7780                    expr = Expression(chunk.strip(), pos[0], pos[1],
    78                                       lookup=lookup)
     81                                      lookup=lookup, xform=xform)
    7982                    yield EXPR, expr, tuple(pos)
    8083                except SyntaxError, err:
    8184                    raise TemplateSyntaxError(err, filepath, pos[1],