Ticket #394: external_ast_transformers.patch
| File external_ast_transformers.patch, 21.0 KB (added by Carsten Klein <carsten.klein@…>, 13 years ago) |
|---|
-
template/base.py
20 20 21 21 from genshi.core import Attrs, Stream, StreamEventKind, START, TEXT, _ensure 22 22 from genshi.input import ParseError 23 from genshi.template.astutil import ASTTransformer 23 24 24 25 __all__ = ['Context', 'DirectiveFactory', 'Template', 'TemplateError', 25 26 'TemplateRuntimeError', 'TemplateSyntaxError', 'BadDirectiveError'] … … 370 371 _number_conv = unicode # function used to convert numbers to event data 371 372 372 373 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): 374 375 """Initialize a template from either a string, a file-like object, or 375 376 an already parsed markup stream. 376 377 … … 386 387 default), "lenient", or a custom lookup class 387 388 :param allow_exec: whether Python code blocks in templates should be 388 389 allowed 390 :param xform: a dict in the form of 389 391 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 390 401 :note: Changed in 0.5: Added the `allow_exec` argument 402 :note: Changed in 0.7: Added the `xform` argument 391 403 """ 392 404 self.filepath = filepath or filename 393 405 self.filename = filename 394 406 self.loader = loader 395 407 self.lookup = lookup 396 408 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) 398 420 self._prepared = False 399 421 400 422 if isinstance(source, basestring): … … 418 440 def __repr__(self): 419 441 return '<%s "%s">' % (type(self).__name__, self.filename) 420 442 421 def _init_filters(self ):443 def _init_filters(self, xform=None): 422 444 self.filters = [self._flatten] 423 445 if self.loader: 424 self.filters.append(self._include )446 self.filters.append(self._include(xform=xform)) 425 447 426 448 @property 427 449 def stream(self): … … 476 498 # the template is inlined into the stream 477 499 try: 478 500 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) 480 503 for event in tmpl.stream: 481 504 yield event 482 505 except TemplateNotFound: … … 583 606 break 584 607 stream = pop() 585 608 586 def _include(self, stream, ctxt, **vars):609 def _include(self, xform=None): 587 610 """Internal stream filter that performs inclusion of external 588 611 template files. 589 612 """ 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 591 640 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 616 642 617 618 643 EXEC = Template.EXEC 619 644 EXPR = Template.EXPR 620 645 INCLUDE = Template.INCLUDE -
template/markup.py
61 61 _number_conv = Markup 62 62 63 63 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): 65 65 Template.__init__(self, source, filepath=filepath, filename=filename, 66 66 loader=loader, encoding=encoding, lookup=lookup, 67 allow_exec=allow_exec )67 allow_exec=allow_exec, xform=xform) 68 68 self.add_directives(self.DIRECTIVE_NAMESPACE, self) 69 69 70 70 def _init_filters(self): 71 71 Template._init_filters(self) 72 72 # Make sure the include filter comes after the match filter 73 73 if self.loader: 74 self.filters.remove(self._include )74 self.filters.remove(self._include(xform=xform)) 75 75 self.filters += [self._match] 76 76 if self.loader: 77 self.filters.append(self._include )77 self.filters.append(self._include(xform=xform)) 78 78 79 79 def _parse(self, source, encoding): 80 80 if not isinstance(source, Stream): … … 86 86 87 87 if kind is TEXT: 88 88 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): 90 91 stream.append((kind, data, pos)) 91 92 92 93 elif kind is PI and data[0] == 'python': … … 95 96 self.filepath, *pos[1:]) 96 97 try: 97 98 suite = Suite(data[1], self.filepath, pos[1], 98 lookup=self.lookup )99 lookup=self.lookup, xform=self.xform) 99 100 except SyntaxError, err: 100 101 raise TemplateSyntaxError(err, self.filepath, 101 102 pos[1] + (err.lineno or 1) - 1, … … 278 279 for name, value in attrs: 279 280 if value: 280 281 value = list(interpolate(value, self.filepath, pos[1], 281 pos[2], lookup=self.lookup)) 282 pos[2], lookup=self.lookup, 283 xform=self.xform)) 282 284 if len(value) == 1 and value[0][0] is TEXT: 283 285 value = value[0][1] 284 286 new_attrs.append((name, value)) -
template/text.py
132 132 133 133 def __init__(self, source, filepath=None, filename=None, loader=None, 134 134 encoding=None, lookup='strict', allow_exec=False, 135 delims=('{%', '%}', '{#', '#}') ):135 delims=('{%', '%}', '{#', '#}'), xform=None): 136 136 self.delimiters = delims 137 137 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) 139 140 140 141 def _get_delims(self): 141 142 return self._delims … … 179 180 if start > offset: 180 181 text = _escape_sub(_escape_repl, source[offset:start]) 181 182 for kind, data, pos in interpolate(text, self.filepath, lineno, 182 lookup=self.lookup): 183 lookup=self.lookup, 184 xform=self.xform): 183 185 stream.append((kind, data, pos)) 184 186 lineno += len(text.splitlines()) 185 187 … … 189 191 if command == 'include': 190 192 pos = (self.filename, lineno, 0) 191 193 value = list(interpolate(value, self.filepath, lineno, 0, 192 lookup=self.lookup ))194 lookup=self.lookup, xform=self.xform)) 193 195 if len(value) == 1 and value[0][0] is TEXT: 194 196 value = value[0][1] 195 197 stream.append((INCLUDE, (value, None, []), pos)) … … 200 202 self.filepath, lineno) 201 203 try: 202 204 suite = Suite(value, self.filepath, lineno, 203 lookup=self.lookup )205 lookup=self.lookup, xform=self.xform) 204 206 except SyntaxError, err: 205 207 raise TemplateSyntaxError(err, self.filepath, 206 208 lineno + (err.lineno or 1) - 1) … … 228 230 if offset < len(source): 229 231 text = _escape_sub(_escape_repl, source[offset:]) 230 232 for kind, data, pos in interpolate(text, self.filepath, lineno, 231 lookup=self.lookup): 233 lookup=self.lookup, 234 xform=self.xform): 232 235 stream.append((kind, data, pos)) 233 236 234 237 return stream … … 289 292 if start > offset: 290 293 text = source[offset:start] 291 294 for kind, data, pos in interpolate(text, self.filepath, lineno, 292 lookup=self.lookup): 295 lookup=self.lookup, 296 xform=self.xform): 293 297 stream.append((kind, data, pos)) 294 298 lineno += len(text.splitlines()) 295 299 … … 324 328 if offset < len(source): 325 329 text = source[offset:].replace('\\#', '#') 326 330 for kind, data, pos in interpolate(text, self.filepath, lineno, 327 lookup=self.lookup): 331 lookup=self.lookup, 332 xform=self.xform): 328 333 stream.append((kind, data, pos)) 329 334 330 335 return stream -
template/loader.py
19 19 except ImportError: 20 20 import dummy_threading as threading 21 21 22 from genshi.template.base import TemplateError 22 from genshi.template.astutil import ASTTransformer 23 from genshi.template.base import TemplateError, TemplateRuntimeError 23 24 from genshi.util import LRUCache 24 25 25 26 __all__ = ['TemplateLoader', 'TemplateNotFound', 'directory', 'package', … … 78 79 """ 79 80 def __init__(self, search_path=None, auto_reload=False, 80 81 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): 82 84 """Create the template laoder. 83 85 84 86 :param search_path: a list of absolute path names that should be … … 104 106 is passed the template object as only argument. This 105 107 callback can be used for example to add any desired 106 108 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 107 120 :see: `LenientLookup`, `StrictLookup` 108 121 109 122 :note: Changed in 0.5: Added the `allow_exec` argument … … 120 133 """Whether templates should be reloaded when the underlying file is 121 134 changed""" 122 135 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 123 145 self.default_encoding = default_encoding 124 146 self.default_class = default_class or MarkupTemplate 125 147 self.variable_lookup = variable_lookup … … 131 153 self._uptodate = {} 132 154 self._lock = threading.RLock() 133 155 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): 135 158 """Load the template with the given name. 136 159 137 160 If the `filename` parameter is relative, this method searches the … … 158 181 :param cls: the class of the template object to instantiate 159 182 :param encoding: the encoding of the template to load; defaults to the 160 183 ``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 161 195 :return: the loaded `Template` instance 162 196 :raises TemplateNotFound: if a template with the given name could not 163 197 be found … … 166 200 cls = self.default_class 167 201 search_path = self.search_path 168 202 203 if xform is None: 204 xform = self.xform 205 169 206 # Make the filename relative to the template file its being loaded 170 207 # from, but only if that file is specified as a relative path, or no 171 208 # search path has been set up … … 224 261 # search path 225 262 filename = filepath 226 263 tmpl = self._instantiate(cls, fileobj, filepath, 227 filename, encoding=encoding) 264 filename, encoding=encoding, 265 xform=xform) 228 266 if self.callback: 229 267 self.callback(tmpl) 230 268 self._cache[cachekey] = tmpl … … 239 277 finally: 240 278 self._lock.release() 241 279 242 def _instantiate(self, cls, fileobj, filepath, filename, encoding=None): 280 def _instantiate(self, cls, fileobj, filepath, filename, encoding=None, 281 xform=None): 243 282 """Instantiate and return the `Template` object based on the given 244 283 class and parameters. 245 284 … … 255 294 path 256 295 :param encoding: the encoding of the template to load; defaults to the 257 296 ``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 258 308 :return: the loaded `Template` instance 259 309 :rtype: `Template` 260 310 """ … … 262 312 encoding = self.default_encoding 263 313 return cls(fileobj, filepath=filepath, filename=filename, loader=self, 264 314 encoding=encoding, lookup=self.variable_lookup, 265 allow_exec=self.allow_exec )315 allow_exec=self.allow_exec, xform=xform) 266 316 267 317 @staticmethod 268 318 def directory(path): -
template/interpolation.py
37 37 )) 38 38 39 39 40 def interpolate(text, filepath=None, lineno=-1, offset=0, lookup='strict'): 40 def interpolate(text, filepath=None, lineno=-1, offset=0, lookup='strict', 41 xform=None): 41 42 """Parse the given string and extract expressions. 42 43 43 44 This function is a generator that yields `TEXT` events for literal strings, … … 58 59 (optional) 59 60 :param lookup: the variable lookup mechanism; either "lenient" (the 60 61 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. 61 64 :return: a list of `TEXT` and `EXPR` events 62 65 :raise TemplateSyntaxError: when a syntax error in an expression is 63 66 encountered … … 75 78 if chunk: 76 79 try: 77 80 expr = Expression(chunk.strip(), pos[0], pos[1], 78 lookup=lookup )81 lookup=lookup, xform=xform) 79 82 yield EXPR, expr, tuple(pos) 80 83 except SyntaxError, err: 81 84 raise TemplateSyntaxError(err, filepath, pos[1],
