Edgewall Software

Ticket #394: external_ast_transformers.patch

File external_ast_transformers.patch, 21.0 KB (added by Carsten Klein <carsten.klein@…>, 5 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],