Changeset 819
- Timestamp:
- Apr 1, 2008, 12:47:50 AM (16 years ago)
- Location:
- branches/experimental/match-fastpaths
- Files:
-
- 1 added
- 22 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/experimental/match-fastpaths/ChangeLog
r798 r819 47 47 * Assigning to a variable named `data` in a Python code block no longer 48 48 breaks context lookup. 49 * The `Stream.render` now accepts an optional `out` parameter that can be 50 used to pass in a writable file-like object to use for assembling the 51 output, instead of building a big string and returning it. 52 * The XHTML serializer now strips `xml:space` attributes as they are only 53 allowed on very few tags. 54 * Match templates are now applied in a more controlled fashion: in the order 55 they are declared in the template source, all match templates up to (and 56 including) the matching template itself are applied to the matched content, 57 whereas the match templates declared after the matching template are only 58 applied to the generated content (ticket #186). 59 * The `TemplateLoader` class now provides an `instantiate()` method that can 60 be overridden by subclasses to implement advanced template instantiation 61 logic (ticket #204). 62 * The search path of the `TemplateLoader` class can now contain ''load 63 functions'' in addition to path strings. A load function is passed the 64 name of the requested template file, and should return a file-like object 65 and some metadata. New load functions are supplied for loading from egg 66 package data, and loading from different loaders depending on the path 67 prefix of the requested filename (ticket #182). 68 * Match templates can now be processed without keeping the complete matched 69 content in memory, which could cause excessive memory use on long pages. 70 The buffering can be disabled using the new `buffer` optimization hint on 71 the `<py:match>` directive. 72 * Improve error reporting when accessing an attribute in a Python expression 73 raises an `AttributeError` (ticket #191). 49 74 50 75 -
branches/experimental/match-fastpaths/doc/templates.txt
r765 r819 271 271 using plain expressions. 272 272 273 .. warning:: Unfortunately, code blocks are severely limited when running 274 under Python 2.3: For example, it is not possible to access 275 variables defined in outer scopes. If you plan to use code blocks 276 extensively, it is strongly recommended that you run Python 2.4 277 or later. 278 273 279 274 280 .. _`error handling`: -
branches/experimental/match-fastpaths/doc/text-templates.txt
r706 r819 216 216 .. code-block:: genshitext 217 217 218 {% include '%s.txt' % filename%}218 {% include ${'%s.txt' % filename} %} 219 219 220 220 Note that a ``TemplateNotFound`` exception is raised if an included file can't … … 350 350 end of a directive is removed automatically. 351 351 352 .. note:: If you're using this old syntax, it is strongly recommended to migrate 353 to the new syntax. Simply replace any references to ``TextTemplate`` 354 by ``NewTextTemplate``. On the other hand, if you want to stick with 355 the old syntax for a while longer, replace references to 352 .. note:: If you're using this old syntax, it is strongly recommended to 353 migrate to the new syntax. Simply replace any references to 354 ``TextTemplate`` by ``NewTextTemplate`` (and also change the 355 text templates, of course). On the other hand, if you want to stick 356 with the old syntax for a while longer, replace references to 356 357 ``TextTemplate`` by ``OldTextTemplate``; while ``TextTemplate`` is 357 358 still an alias for the old language at this point, that will change 358 in a future release. 359 in a future release. But also note that the old syntax may be 360 dropped entirely in a future release. -
branches/experimental/match-fastpaths/doc/upgrade.txt
r723 r819 30 30 warned that lenient error handling may be removed completely in a 31 31 future release. 32 33 There has also been a subtle change to how ``py:match`` templates are 34 processed: in previous versions, all match templates would be applied 35 to the content generated by the matching template, and only the 36 matching template itself was applied recursively to the original 37 content. This behavior resulted in problems with many kinds of 38 recursive matching, and hence was changed for 0.5: now, all match 39 templates declared before the matching template are applied to the 40 original content, and match templates declared after the matching 41 template are applied to the generated content. This change should not 42 have any effect on most applications, but you may want to check your 43 use of match templates to make sure. 32 44 33 45 -
branches/experimental/match-fastpaths/doc/xml-templates.txt
r773 r819 323 323 .. _`Using XPath`: streams.html#using-xpath 324 324 325 Match templates are applied both to the original markup as well to the 326 generated markup. The order in which they are applied depends on the order 327 they are declared in the template source: a match template defined after 328 another match template is applied to the output generated by the first match 329 template. The match templates basically form a pipeline. 330 325 331 This directive can also be used as an element: 326 332 … … 353 359 | Attribute | Default | Description | 354 360 +===============+===========+===============================================+ 361 | ``buffer`` | ``true`` | Whether the matched content should be | 362 | | | buffered in memory. Buffering can improve | 363 | | | performance a bit at the cost of needing more | 364 | | | memory during rendering. Buffering is | 365 | | | ''required'' for match templates that contain | 366 | | | more than one invocation of the ``select()`` | 367 | | | function. If there is only one call, and the | 368 | | | matched content can potentially be very long, | 369 | | | consider disabling buffering to avoid | 370 | | | excessive memory use. | 371 +---------------+-----------+-----------------------------------------------+ 355 372 | ``once`` | ``false`` | Whether the engine should stop looking for | 356 373 | | | more matching elements after the first match. | -
branches/experimental/match-fastpaths/genshi/core.py
r801 r819 150 150 return reduce(operator.or_, (self,) + filters) 151 151 152 def render(self, method=None, encoding='utf-8', **kwargs):152 def render(self, method=None, encoding='utf-8', out=None, **kwargs): 153 153 """Return a string representation of the stream. 154 154 … … 162 162 :param encoding: how the output string should be encoded; if set to 163 163 `None`, this method returns a `unicode` object 164 :return: a `str` or `unicode` object 164 :param out: a file-like object that the output should be written to 165 instead of being returned as one big string; note that if 166 this is a file or socket (or similar), the `encoding` must 167 not be `None` (that is, the output must be encoded) 168 :return: a `str` or `unicode` object (depending on the `encoding` 169 parameter), or `None` if the `out` parameter is provided 165 170 :rtype: `basestring` 171 166 172 :see: XMLSerializer, XHTMLSerializer, HTMLSerializer, TextSerializer 173 :note: Changed in 0.5: added the `out` parameter 167 174 """ 168 175 from genshi.output import encode … … 170 177 method = self.serializer or 'xml' 171 178 generator = self.serialize(method=method, **kwargs) 172 return encode(generator, method=method, encoding=encoding )179 return encode(generator, method=method, encoding=encoding, out=out) 173 180 174 181 def select(self, path, namespaces=None, variables=None): -
branches/experimental/match-fastpaths/genshi/output.py
r788 r819 31 31 __docformat__ = 'restructuredtext en' 32 32 33 def encode(iterator, method='xml', encoding='utf-8' ):33 def encode(iterator, method='xml', encoding='utf-8', out=None): 34 34 """Encode serializer output into a string. 35 35 … … 40 40 :param encoding: how the output string should be encoded; if set to `None`, 41 41 this method returns a `unicode` object 42 :return: a string or unicode object (depending on the `encoding` parameter) 42 :param out: a file-like object that the output should be written to 43 instead of being returned as one big string; note that if 44 this is a file or socket (or similar), the `encoding` must 45 not be `None` (that is, the output must be encoded) 46 :return: a `str` or `unicode` object (depending on the `encoding` 47 parameter), or `None` if the `out` parameter is provided 48 43 49 :since: version 0.4.1 44 """45 output = u''.join(list(iterator))50 :note: Changed in 0.5: added the `out` parameter 51 """ 46 52 if encoding is not None: 47 53 errors = 'replace' 48 54 if method != 'text' and not isinstance(method, TextSerializer): 49 55 errors = 'xmlcharrefreplace' 50 return output.encode(encoding, errors) 51 return output 56 _encode = lambda string: string.encode(encoding, errors) 57 else: 58 _encode = lambda string: string 59 if out is None: 60 return _encode(u''.join(list(iterator))) 61 for chunk in iterator: 62 out.write(_encode(chunk)) 52 63 53 64 def get_serializer(method='xml', **kwargs): … … 299 310 elif attr == u'xml:lang' and u'lang' not in attrib: 300 311 buf += [' lang="', escape(value), '"'] 312 elif attr == u'xml:space': 313 continue 301 314 buf += [' ', attr, '="', escape(value), '"'] 302 315 if kind is EMPTY: -
branches/experimental/match-fastpaths/genshi/template/base.py
r803 r819 256 256 257 257 258 def _apply_directives(stream, ctxt, directives):258 def _apply_directives(stream, directives, ctxt, **vars): 259 259 """Apply the given directives to the stream. 260 260 261 261 :param stream: the stream the directives should be applied to 262 :param directives: the list of directives to apply 262 263 :param ctxt: the `Context` 263 :param directives: the list of directives to apply 264 :param vars: additional variables that should be available when Python 265 code is executed 264 266 :return: the stream with the given directives applied 265 267 """ 266 268 if directives: 267 stream = directives[0](iter(stream), ctxt, directives[1:])269 stream = directives[0](iter(stream), directives[1:], ctxt, **vars) 268 270 return stream 271 272 def _eval_expr(expr, ctxt, **vars): 273 """Evaluate the given `Expression` object. 274 275 :param expr: the expression to evaluate 276 :param ctxt: the `Context` 277 :param vars: additional variables that should be available to the 278 expression 279 :return: the result of the evaluation 280 """ 281 if vars: 282 ctxt.push(vars) 283 retval = expr.evaluate(ctxt) 284 if vars: 285 ctxt.pop() 286 return retval 287 288 def _exec_suite(suite, ctxt, **vars): 289 """Execute the given `Suite` object. 290 291 :param suite: the code suite to execute 292 :param ctxt: the `Context` 293 :param vars: additional variables that should be available to the 294 code 295 """ 296 if vars: 297 ctxt.push(vars) 298 ctxt.push({}) 299 suite.execute(_ctxt2dict(ctxt)) 300 if vars: 301 top = ctxt.pop() 302 ctxt.pop() 303 ctxt.frames[0].update(top) 269 304 270 305 … … 428 463 the template to the context data. 429 464 """ 465 vars = {} 430 466 if args: 431 467 assert len(args) == 1 … … 433 469 if ctxt is None: 434 470 ctxt = Context(**kwargs) 471 else: 472 vars = kwargs 435 473 assert isinstance(ctxt, Context) 436 474 else: … … 439 477 stream = self.stream 440 478 for filter_ in self.filters: 441 stream = filter_(iter(stream), ctxt )479 stream = filter_(iter(stream), ctxt, **vars) 442 480 return Stream(stream, self.serializer) 443 481 444 def _eval(self, stream, ctxt ):482 def _eval(self, stream, ctxt, **vars): 445 483 """Internal stream filter that evaluates any expressions in `START` and 446 484 `TEXT` events. … … 462 500 values = [] 463 501 for subkind, subdata, subpos in self._eval(substream, 464 ctxt): 502 ctxt, 503 **vars): 465 504 if subkind is TEXT: 466 505 values.append(subdata) … … 472 511 473 512 elif kind is EXPR: 474 result = data.evaluate(ctxt)513 result = _eval_expr(data, ctxt, **vars) 475 514 if result is not None: 476 515 # First check for a string, otherwise the iterable test … … 484 523 substream = _ensure(result) 485 524 for filter_ in filters: 486 substream = filter_(substream, ctxt )525 substream = filter_(substream, ctxt, **vars) 487 526 for event in substream: 488 527 yield event … … 493 532 yield kind, data, pos 494 533 495 def _exec(self, stream, ctxt ):534 def _exec(self, stream, ctxt, **vars): 496 535 """Internal stream filter that executes Python code blocks.""" 497 536 for event in stream: 498 537 if event[0] is EXEC: 499 event[1].execute(_ctxt2dict(ctxt))538 _exec_suite(event[1], ctxt, **vars) 500 539 else: 501 540 yield event 502 541 503 def _flatten(self, stream, ctxt ):542 def _flatten(self, stream, ctxt, **vars): 504 543 """Internal stream filter that expands `SUB` events in the stream.""" 505 544 for event in stream: … … 508 547 # events to which those directives should be applied 509 548 directives, substream = event[1] 510 substream = _apply_directives(substream, ctxt, directives) 511 for event in self._flatten(substream, ctxt): 549 substream = _apply_directives(substream, directives, ctxt, 550 **vars) 551 for event in self._flatten(substream, ctxt, **vars): 512 552 yield event 513 553 else: 514 554 yield event 515 555 516 def _include(self, stream, ctxt ):556 def _include(self, stream, ctxt, **vars): 517 557 """Internal stream filter that performs inclusion of external 518 558 template files. … … 525 565 if not isinstance(href, basestring): 526 566 parts = [] 527 for subkind, subdata, subpos in self._eval(href, ctxt): 567 for subkind, subdata, subpos in self._eval(href, ctxt, 568 **vars): 528 569 if subkind is TEXT: 529 570 parts.append(subdata) … … 532 573 tmpl = self.loader.load(href, relative_to=event[2][0], 533 574 cls=cls or self.__class__) 534 for event in tmpl.generate(ctxt ):575 for event in tmpl.generate(ctxt, **vars): 535 576 yield event 536 577 except TemplateNotFound: … … 538 579 raise 539 580 for filter_ in self.filters: 540 fallback = filter_(iter(fallback), ctxt )581 fallback = filter_(iter(fallback), ctxt, **vars) 541 582 for event in fallback: 542 583 yield event -
branches/experimental/match-fastpaths/genshi/template/directives.py
r803 r819 23 23 from genshi.path import Path 24 24 from genshi.template.base import TemplateRuntimeError, TemplateSyntaxError, \ 25 EXPR, _apply_directives, _ctxt2dict 25 EXPR, _apply_directives, _eval_expr, \ 26 _exec_suite 26 27 from genshi.template.eval import Expression, Suite, ExpressionASTTransformer, \ 27 28 _parse … … 89 90 attach = classmethod(attach) 90 91 91 def __call__(self, stream, ctxt, directives):92 def __call__(self, stream, directives, ctxt, **vars): 92 93 """Apply the directive to the given stream. 93 94 94 95 :param stream: the event stream 95 :param ctxt: the context data96 96 :param directives: a list of the remaining directives that should 97 97 process the stream 98 :param ctxt: the context data 99 :param vars: additional variables that should be made available when 100 Python code is executed 98 101 """ 99 102 raise NotImplementedError … … 168 171 __slots__ = [] 169 172 170 def __call__(self, stream, ctxt, directives):173 def __call__(self, stream, directives, ctxt, **vars): 171 174 def _generate(): 172 175 kind, (tag, attrib), pos = stream.next() 173 attrs = self.expr.evaluate(ctxt)176 attrs = _eval_expr(self.expr, ctxt, **vars) 174 177 if attrs: 175 178 if isinstance(attrs, Stream): … … 187 190 yield event 188 191 189 return _apply_directives(_generate(), ctxt, directives)192 return _apply_directives(_generate(), directives, ctxt, **vars) 190 193 191 194 … … 292 295 attach = classmethod(attach) 293 296 294 def __call__(self, stream, ctxt, directives):297 def __call__(self, stream, directives, ctxt, **vars): 295 298 stream = list(stream) 296 299 … … 305 308 val = kwargs.pop(name) 306 309 else: 307 val = self.defaults.get(name).evaluate(ctxt)310 val = _eval_expr(self.defaults.get(name), ctxt, **vars) 308 311 scope[name] = val 309 312 if not self.star_args is None: … … 312 315 scope[self.dstar_args] = kwargs 313 316 ctxt.push(scope) 314 for event in _apply_directives(stream, ctxt, directives):317 for event in _apply_directives(stream, directives, ctxt, **vars): 315 318 yield event 316 319 ctxt.pop() … … 365 368 attach = classmethod(attach) 366 369 367 def __call__(self, stream, ctxt, directives):368 iterable = self.expr.evaluate(ctxt)370 def __call__(self, stream, directives, ctxt, **vars): 371 iterable = _eval_expr(self.expr, ctxt, **vars) 369 372 if iterable is None: 370 373 return … … 376 379 assign(scope, item) 377 380 ctxt.push(scope) 378 for event in _apply_directives(stream, ctxt, directives):381 for event in _apply_directives(stream, directives, ctxt, **vars): 379 382 yield event 380 383 ctxt.pop() … … 406 409 attach = classmethod(attach) 407 410 408 def __call__(self, stream, ctxt, directives): 409 if self.expr.evaluate(ctxt): 410 return _apply_directives(stream, ctxt, directives) 411 def __call__(self, stream, directives, ctxt, **vars): 412 value = _eval_expr(self.expr, ctxt, **vars) 413 if value: 414 return _apply_directives(stream, directives, ctxt, **vars) 411 415 return [] 412 416 … … 441 445 hints = [] 442 446 if type(value) is dict: 447 if value.get('buffer', '').lower() == 'false': 448 hints.append('not_buffered') 443 449 if value.get('once', '').lower() == 'true': 444 450 hints.append('match_once') … … 450 456 attach = classmethod(attach) 451 457 452 def __call__(self, stream, ctxt, directives):458 def __call__(self, stream, directives, ctxt, **vars): 453 459 ctxt._match_set.add((self.path.test(ignore_context=True), 454 460 self.path, list(stream), self.hints, … … 532 538 __slots__ = [] 533 539 534 def __call__(self, stream, ctxt, directives):540 def __call__(self, stream, directives, ctxt, **vars): 535 541 def _generate(): 536 if self.expr.evaluate(ctxt):542 if _eval_expr(self.expr, ctxt, **vars): 537 543 stream.next() # skip start tag 538 544 previous = stream.next() … … 543 549 for event in stream: 544 550 yield event 545 return _apply_directives(_generate(), ctxt, directives)551 return _apply_directives(_generate(), directives, ctxt, **vars) 546 552 547 553 def attach(cls, template, stream, value, namespaces, pos): … … 601 607 attach = classmethod(attach) 602 608 603 def __call__(self, stream, ctxt, directives):609 def __call__(self, stream, directives, ctxt, **vars): 604 610 info = [False, bool(self.expr), None] 605 611 if self.expr: 606 info[2] = self.expr.evaluate(ctxt)612 info[2] = _eval_expr(self.expr, ctxt, **vars) 607 613 ctxt._choice_stack.append(info) 608 for event in _apply_directives(stream, ctxt, directives):614 for event in _apply_directives(stream, directives, ctxt, **vars): 609 615 yield event 610 616 ctxt._choice_stack.pop() … … 630 636 attach = classmethod(attach) 631 637 632 def __call__(self, stream, ctxt, directives):638 def __call__(self, stream, directives, ctxt, **vars): 633 639 info = ctxt._choice_stack and ctxt._choice_stack[-1] 634 640 if not info: … … 645 651 value = info[2] 646 652 if self.expr: 647 matched = value == self.expr.evaluate(ctxt)653 matched = value == _eval_expr(self.expr, ctxt, **vars) 648 654 else: 649 655 matched = bool(value) 650 656 else: 651 matched = bool( self.expr.evaluate(ctxt))657 matched = bool(_eval_expr(self.expr, ctxt, **vars)) 652 658 info[0] = matched 653 659 if not matched: 654 660 return [] 655 661 656 return _apply_directives(stream, ctxt, directives)662 return _apply_directives(stream, directives, ctxt, **vars) 657 663 658 664 … … 669 675 self.filename = template.filepath 670 676 671 def __call__(self, stream, ctxt, directives):677 def __call__(self, stream, directives, ctxt, **vars): 672 678 info = ctxt._choice_stack and ctxt._choice_stack[-1] 673 679 if not info: … … 679 685 info[0] = True 680 686 681 return _apply_directives(stream, ctxt, directives)687 return _apply_directives(stream, directives, ctxt, **vars) 682 688 683 689 … … 723 729 attach = classmethod(attach) 724 730 725 def __call__(self, stream, ctxt, directives): 726 frame = {} 727 ctxt.push(frame) 728 self.suite.execute(_ctxt2dict(ctxt)) 729 for event in _apply_directives(stream, ctxt, directives): 731 def __call__(self, stream, directives, ctxt, **vars): 732 ctxt.push({}) 733 _exec_suite(self.suite, ctxt, **vars) 734 for event in _apply_directives(stream, directives, ctxt, **vars): 730 735 yield event 731 736 ctxt.pop() -
branches/experimental/match-fastpaths/genshi/template/eval.py
r800 r819 140 140 """ 141 141 __traceback_hide__ = 'before_and_this' 142 _globals = self._globals() 143 _globals['__data__'] = data 142 _globals = self._globals(data) 144 143 return eval(self.code, _globals, {'__data__': data}) 145 144 … … 162 161 """ 163 162 __traceback_hide__ = 'before_and_this' 164 _globals = self._globals() 165 _globals['__data__'] = data 163 _globals = self._globals(data) 166 164 exec self.code in _globals, data 167 165 … … 249 247 """Abstract base class for variable lookup implementations.""" 250 248 251 def globals(cls ):249 def globals(cls, data): 252 250 """Construct the globals dictionary to use as the execution context for 253 251 the expression or suite. 254 252 """ 255 253 return { 254 '__data__': data, 256 255 '_lookup_name': cls.lookup_name, 257 256 '_lookup_attr': cls.lookup_attr, 258 257 '_lookup_item': cls.lookup_item, 259 'UndefinedError': UndefinedError 258 'UndefinedError': UndefinedError, 260 259 } 261 260 globals = classmethod(globals) … … 271 270 lookup_name = classmethod(lookup_name) 272 271 273 def lookup_attr(cls, data,obj, key):272 def lookup_attr(cls, obj, key): 274 273 __traceback_hide__ = True 275 val = getattr(obj, key, UNDEFINED) 276 if val is UNDEFINED: 277 try: 278 val = obj[key] 279 except (KeyError, TypeError): 280 val = cls.undefined(key, owner=obj) 274 try: 275 val = getattr(obj, key) 276 except AttributeError: 277 if hasattr(obj.__class__, key): 278 raise 279 else: 280 try: 281 val = obj[key] 282 except (KeyError, TypeError): 283 val = cls.undefined(key, owner=obj) 281 284 return val 282 285 lookup_attr = classmethod(lookup_attr) 283 286 284 def lookup_item(cls, data,obj, key):287 def lookup_item(cls, obj, key): 285 288 __traceback_hide__ = True 286 289 if len(key) == 1: … … 755 758 def visitGetattr(self, node): 756 759 return ast.CallFunc(ast.Name('_lookup_attr'), [ 757 ast.Name('__data__'),self.visit(node.expr),760 self.visit(node.expr), 758 761 ast.Const(node.attrname) 759 762 ]) … … 761 764 def visitSubscript(self, node): 762 765 return ast.CallFunc(ast.Name('_lookup_item'), [ 763 ast.Name('__data__'),self.visit(node.expr),766 self.visit(node.expr), 764 767 ast.Tuple([self.visit(sub) for sub in node.subs]) 765 768 ]) -
branches/experimental/match-fastpaths/genshi/template/loader.py
r722 r819 83 83 :param search_path: a list of absolute path names that should be 84 84 searched for template files, or a string containing 85 a single absolute path 85 a single absolute path; alternatively, any item on 86 the list may be a ''load function'' that is passed 87 a filename and returns a file-like object and some 88 metadata 86 89 :param auto_reload: whether to check the last modification time of 87 90 template files, and reload them if they have changed … … 110 113 if self.search_path is None: 111 114 self.search_path = [] 112 elif isinstance(self.search_path, basestring):115 elif not isinstance(self.search_path, (list, tuple)): 113 116 self.search_path = [self.search_path] 114 117 … … 131 134 """Load the template with the given name. 132 135 133 If the `filename` parameter is relative, this method searches the search134 path trying to locate a template matching the given name. If the file135 name is an absolute path, the search path is ignored.136 If the `filename` parameter is relative, this method searches the 137 search path trying to locate a template matching the given name. If the 138 file name is an absolute path, the search path is ignored. 136 139 137 140 If the requested template is not found, a `TemplateNotFound` exception … … 156 159 ``default_encoding`` of the loader instance 157 160 :return: the loaded `Template` instance 158 :raises TemplateNotFound: if a template with the given name could not be159 found161 :raises TemplateNotFound: if a template with the given name could not 162 be found 160 163 """ 161 164 if cls is None: 162 165 cls = self.default_class 163 if encoding is None:164 encoding = self.default_encoding165 166 if relative_to and not os.path.isabs(relative_to): 166 167 filename = os.path.join(os.path.dirname(relative_to), filename) 167 168 filename = os.path.normpath(filename) 169 cachekey = filename 168 170 169 171 self._lock.acquire() … … 171 173 # First check the cache to avoid reparsing the same file 172 174 try: 173 tmpl = self._cache[filename] 174 if not self.auto_reload or \ 175 os.path.getmtime(tmpl.filepath) == self._mtime[filename]: 175 tmpl = self._cache[cachekey] 176 if not self.auto_reload: 177 return tmpl 178 mtime = self._mtime[cachekey] 179 if mtime and mtime == os.path.getmtime(tmpl.filepath): 176 180 return tmpl 177 181 except KeyError, OSError: … … 191 195 dirname = os.path.dirname(relative_to) 192 196 if dirname not in search_path: 193 search_path = search_path+ [dirname]197 search_path = list(search_path) + [dirname] 194 198 isabs = True 195 199 … … 198 202 raise TemplateError('Search path for templates not configured') 199 203 200 for dirname in search_path: 201 filepath = os.path.join(dirname, filename) 204 for loadfunc in search_path: 205 if isinstance(loadfunc, basestring): 206 loadfunc = directory(loadfunc) 202 207 try: 203 fileobj = open(filepath, 'U') 208 dirname, filename, fileobj, mtime = loadfunc(filename) 209 except IOError: 210 continue 211 else: 204 212 try: 205 213 if isabs: … … 207 215 # including template is absolute, make sure the 208 216 # included template gets an absolute path, too, 209 # so that nested include work properly without a217 # so that nested includes work properly without a 210 218 # search path 211 219 filename = os.path.join(dirname, filename) 212 220 dirname = '' 213 tmpl = cls(fileobj, basedir=dirname, filename=filename, 214 loader=self, encoding=encoding, 215 lookup=self.variable_lookup, 216 allow_exec=self.allow_exec) 221 tmpl = self.instantiate(cls, fileobj, dirname, 222 filename, encoding=encoding) 217 223 if self.callback: 218 224 self.callback(tmpl) 219 self._cache[ filename] = tmpl220 self._mtime[ filename] = os.path.getmtime(filepath)225 self._cache[cachekey] = tmpl 226 self._mtime[cachekey] = mtime 221 227 finally: 222 fileobj.close() 228 if hasattr(fileobj, 'close'): 229 fileobj.close() 223 230 return tmpl 224 except IOError:225 continue226 231 227 232 raise TemplateNotFound(filename, search_path) … … 229 234 finally: 230 235 self._lock.release() 236 237 def instantiate(self, cls, fileobj, dirname, filename, encoding=None): 238 """Instantiate and return the `Template` object based on the given 239 class and parameters. 240 241 This function is intended for subclasses to override if they need to 242 implement special template instantiation logic. Code that just uses 243 the `TemplateLoader` should use the `load` method instead. 244 245 :param cls: the class of the template object to instantiate 246 :param fileobj: a readable file-like object containing the template 247 source 248 :param dirname: the name of the base directory containing the template 249 file 250 :param filename: the name of the template file, relative to the given 251 base directory 252 :param encoding: the encoding of the template to load; defaults to the 253 ``default_encoding`` of the loader instance 254 :return: the loaded `Template` instance 255 :rtype: `Template` 256 """ 257 if encoding is None: 258 encoding = self.default_encoding 259 return cls(fileobj, basedir=dirname, filename=filename, loader=self, 260 encoding=encoding, lookup=self.variable_lookup, 261 allow_exec=self.allow_exec) 262 263 def directory(path): 264 """Loader factory for loading templates from a local directory. 265 266 :param path: the path to the local directory containing the templates 267 :return: the loader function to load templates from the given directory 268 :rtype: ``function`` 269 """ 270 def _load_from_directory(filename): 271 filepath = os.path.join(path, filename) 272 fileobj = open(filepath, 'U') 273 return path, filename, fileobj, os.path.getmtime(filepath) 274 return _load_from_directory 275 directory = staticmethod(directory) 276 277 def package(name, path): 278 """Loader factory for loading templates from egg package data. 279 280 :param name: the name of the package containing the resources 281 :param path: the path inside the package data 282 :return: the loader function to load templates from the given package 283 :rtype: ``function`` 284 """ 285 from pkg_resources import resource_stream 286 def _load_from_package(filename): 287 filepath = os.path.join(path, filename) 288 return path, filename, resource_stream(name, filepath), None 289 return _load_from_package 290 package = staticmethod(package) 291 292 def prefixed(**delegates): 293 """Factory for a load function that delegates to other loaders 294 depending on the prefix of the requested template path. 295 296 The prefix is stripped from the filename when passing on the load 297 request to the delegate. 298 299 >>> load = prefixed( 300 ... app1 = lambda filename: ('app1', filename, None, None), 301 ... app2 = lambda filename: ('app2', filename, None, None) 302 ... ) 303 >>> print load('app1/foo.html') 304 ('', 'app1/foo.html', None, None) 305 >>> print load('app2/bar.html') 306 ('', 'app2/bar.html', None, None) 307 308 :param delegates: mapping of path prefixes to loader functions 309 :return: the loader function 310 :rtype: ``function`` 311 """ 312 def _dispatch_by_prefix(filename): 313 for prefix, delegate in delegates.items(): 314 if filename.startswith(prefix): 315 if isinstance(delegate, basestring): 316 delegate = directory(delegate) 317 path, _, fileobj, mtime = delegate( 318 filename[len(prefix):].lstrip('/\\') 319 ) 320 dirname = path[len(prefix):].rstrip('/\\') 321 return dirname, filename, fileobj, mtime 322 raise TemplateNotFound(filename, delegates.keys()) 323 return _dispatch_by_prefix 324 prefixed = staticmethod(prefixed) 325 326 directory = TemplateLoader.directory 327 package = TemplateLoader.package 328 prefixed = TemplateLoader.prefixed -
branches/experimental/match-fastpaths/genshi/template/markup.py
r806 r819 227 227 return streams[0] 228 228 229 def _match(self, stream, ctxt, match_set=None ):229 def _match(self, stream, ctxt, match_set=None, **vars): 230 230 """Internal stream filter that applies any defined match templates 231 231 to the stream. … … 264 264 match_template 265 265 if test(event, namespaces, ctxt) is True: 266 post_match_templates = \ 267 match_set.after_template(match_template) 268 266 269 if 'match_once' in hints: 270 271 # need to save this before we nuke 272 # match_template from match_set 273 pre_match_templates = \ 274 match_set.before_template(match_template, False) 275 276 # forcibly remove this template from this and 277 # all child match sets 267 278 match_set.remove(match_template) 268 279 del match_candidates[idx] 269 280 idx -= 1 281 else: 282 inclusive = True 283 if 'not_recursive' in hints: 284 inclusive=False 285 pre_match_templates = match_set.before_template(match_template, inclusive) 270 286 271 287 # Let the remaining match templates know about the event so … … 277 293 # corresponding to this start event is encountered 278 294 inner = _strip(stream) 279 if 'match_once' not in hints \ 280 and 'not_recursive' not in hints: 281 inner = self._match(inner, ctxt, 282 MatchSet.single_match(match_template)) 283 content = list(self._include(chain([event], inner, tail), 284 ctxt)) 295 if pre_match_templates: 296 inner = self._match(inner, ctxt, pre_match_templates) 297 content = self._include(chain([event], inner, tail), ctxt) 298 if 'not_buffered' not in hints: 299 content = list(content) 285 300 286 301 # Now tell all the match templates about the 287 302 # END event (tail[0]) 288 for test in [mt[0] for mt in match_candidates]: 289 test(tail[0], namespaces, ctxt, updateonly=True) 303 if tail: 304 for test in [mt[0] for mt in match_candidates]: 305 test(tail[0], namespaces, ctxt, updateonly=True) 290 306 291 307 # Make the select() function available in the body of the … … 293 309 def select(path): 294 310 return Stream(content).select(path, namespaces, ctxt) 295 ctxt.push(dict(select=select))311 vars = dict(select=select) 296 312 297 313 # Recursively process the output 298 template = _apply_directives(template, ctxt, directives)299 remaining = match_set300 if 'match_once' not in hints:301 # match has not been removed, so we need an302 # exclusion matchset303 remaining = match_set.with_exclusion(match_template)304 305 body = self._exec(self._eval(self._flatten(template, ctxt),306 ctxt), ctxt)307 for event in self._match(body, ctxt, remaining):314 template = _apply_directives(template, directives, ctxt, 315 **vars) 316 for event in self._match( 317 self._exec( 318 self._eval( 319 self._flatten(template, ctxt, **vars), 320 ctxt, **vars), 321 ctxt, **vars), 322 ctxt, post_match_templates, 323 **vars): 308 324 yield event 309 325 310 ctxt.pop()311 326 break 312 327 -
branches/experimental/match-fastpaths/genshi/template/match.py
r817 r819 3 3 4 4 from copy import copy 5 from itertools import ifilter 5 6 6 7 def is_simple_path(path): … … 36 37 If the path is more complex like "xyz[k=z]" then then that match 37 38 will always be returned by ``find_matches``. """ 38 def __init__(self, parent=None, exclude=None): 39 def __init__(self, parent=None, 40 min_index=None, 41 max_index=None): 39 42 """ 40 43 If a parent is given, it means this is a wrapper around another 41 44 set. 42 45 43 If exclude is given, it means include everything in the44 parent, but exclude a specific match template.45 46 """ 46 47 self.parent = parent 47 48 self.current_index = 049 48 50 49 if parent is None: … … 54 53 self.match_order = {} 55 54 55 self.min_index = None 56 self.max_index = None 57 56 58 # tag_templates are match templates whose path are simply 57 59 # a tag, like "body" or "img" … … 62 64 self.other_templates = [] 63 65 64 # exclude is a list of templates to ignore when iterating65 # through templates66 self.exclude = []67 if exclude is not None:68 self.exclude.append(exclude)69 66 else: 70 67 # We have a parent: Just copy references to member 71 # variables in parent so that there's no performance loss ,72 # but make our own exclusion set, so we don't have to73 # chain exclusions across a chain of MatchSets68 # variables in parent so that there's no performance loss 69 self.max_index = parent.max_index 70 self.min_index = parent.min_index 74 71 self.match_order = parent.match_order 75 72 self.tag_templates = parent.tag_templates 76 73 self.other_templates = parent.other_templates 77 78 self.exclude = copy(parent.exclude) 79 if exclude is not None: 80 self.exclude.append(exclude) 74 75 if max_index is not None: 76 assert self.max_index is None or max_index <= self.max_index 77 self.max_index = max_index 78 79 if min_index is not None: 80 assert self.min_index is None or min_index > self.min_index 81 self.min_index = min_index 82 81 83 82 84 def add(self, match_template): … … 92 94 path = match_template[1] 93 95 94 self.current_index += 195 96 if is_simple_path(path): 96 97 # special cache of tag … … 134 135 single_match = classmethod(single_match) 135 136 136 def with_exclusion(self, exclude): 137 """ 138 Factory for creating a MatchSet based on another MatchSet, but 139 with certain templates excluded 140 """ 141 cls = self.__class__ 142 new_match_set = cls(parent=self, exclude=exclude) 143 return new_match_set 144 137 def before_template(self, match_template, inclusive): 138 cls = type(self) 139 max_index = self.match_order[id(match_template)] 140 if not inclusive: 141 max_index -= 1 142 return cls(parent=self, max_index=max_index) 143 144 def after_template(self, match_template): 145 """ 146 Factory for creating a MatchSet that only matches templates after 147 the given match 148 """ 149 cls = type(self) 150 min_index = self.match_order[id(match_template)] + 1 151 return cls(parent=self, min_index=min_index) 152 145 153 def find_raw_matches(self, event): 146 154 """ Return a list of all valid templates that can be used for the … … 168 176 169 177 # remove exclusions 170 matches = filter(lambda template: template not in self.exclude, 171 self.find_raw_matches(event)) 178 def can_match(template): 179 # make sure that 180 if (self.min_index is not None and 181 self.match_order[id(template)] < self.min_index): 182 return False 183 184 if (self.max_index is not None and 185 self.match_order[id(template)] > self.max_index): 186 return False 187 188 return True 189 190 matches = ifilter(can_match, 191 self.find_raw_matches(event)) 172 192 173 193 # sort the results according to the order they were added … … 178 198 allow this to behave as a list 179 199 """ 200 201 # this is easy - before the first element there is nothing 202 if self.max_index == -1: 203 return False 204 205 # this isn't always right because match_order may shrink, but 206 # you'll never get a false-negative 207 if self.min_index == len(self.match_order): 208 return False 209 210 # check for a range that is completely constrained 211 if self.min_index is not None and self.max_index is not None: 212 if self.min_index >= self.max_index: 213 return False 214 180 215 return bool(self.tag_templates or self.other_templates) 181 216 … … 185 220 parent = ": child of 0x%x" % id(self.parent) 186 221 187 exclude = "" 188 if self.exclude: 189 exclude = " / excluding %d items" % len(self.exclude) 190 191 return "<MatchSet 0x%x %d tag templates, %d other templates%s%s>" % (id(self), len(self.tag_templates), len(self.other_templates), parent, exclude) 222 return "<MatchSet 0x%x %d tag templates, %d other templates, range=[%s:%s]%s>" % ( 223 id(self), len(self.tag_templates), len(self.other_templates), 224 self.min_index, self.max_index, 225 parent) -
branches/experimental/match-fastpaths/genshi/template/tests/__init__.py
r538 r819 17 17 def suite(): 18 18 from genshi.template.tests import base, directives, eval, interpolation, \ 19 loader, markup, plugin, text 19 loader, markup, plugin, text, match 20 20 suite = unittest.TestSuite() 21 21 suite.addTest(base.suite()) … … 27 27 suite.addTest(plugin.suite()) 28 28 suite.addTest(text.suite()) 29 suite.addTest(match.suite()) 29 30 return suite 30 31 -
branches/experimental/match-fastpaths/genshi/template/tests/directives.py
r773 r819 631 631 </body> 632 632 </html>""", str(tmpl.generate())) 633 634 def test_recursive_match_3(self): 635 tmpl = MarkupTemplate("""<test xmlns:py="http://genshi.edgewall.org/"> 636 <py:match path="b[@type='bullet']"> 637 <bullet>${select('*|text()')}</bullet> 638 </py:match> 639 <py:match path="group[@type='bullet']"> 640 <ul>${select('*')}</ul> 641 </py:match> 642 <py:match path="b"> 643 <generic>${select('*|text()')}</generic> 644 </py:match> 645 646 <b> 647 <group type="bullet"> 648 <b type="bullet">1</b> 649 <b type="bullet">2</b> 650 </group> 651 </b> 652 </test> 653 """) 654 self.assertEqual("""<test> 655 <generic> 656 <ul><bullet>1</bullet><bullet>2</bullet></ul> 657 </generic> 658 </test>""", str(tmpl.generate())) 633 659 634 660 def test_not_match_self(self): -
branches/experimental/match-fastpaths/genshi/template/tests/eval.py
r798 r819 343 343 def test_getattr_exception(self): 344 344 class Something(object): 345 def prop (self):345 def prop_a(self): 346 346 raise NotImplementedError 347 prop = property(prop) 347 prop_a = property(prop_a) 348 def prop_b(self): 349 raise AttributeError 350 prop_b = property(prop_b) 348 351 self.assertRaises(NotImplementedError, 349 Expression('s.prop').evaluate, {'s': Something()}) 352 Expression('s.prop_a').evaluate, {'s': Something()}) 353 self.assertRaises(AttributeError, 354 Expression('s.prop_b').evaluate, {'s': Something()}) 350 355 351 356 def test_getitem_undefined_string(self): -
branches/experimental/match-fastpaths/genshi/template/tests/loader.py
r531 r819 105 105 </html>""", tmpl.generate().render()) 106 106 107 def test_relative_include_samesubdir(self): 108 file1 = open(os.path.join(self.dirname, 'tmpl1.html'), 'w') 109 try: 110 file1.write("""<div>Included tmpl1.html</div>""") 111 finally: 112 file1.close() 113 114 os.mkdir(os.path.join(self.dirname, 'sub')) 115 file2 = open(os.path.join(self.dirname, 'sub', 'tmpl1.html'), 'w') 116 try: 117 file2.write("""<div>Included sub/tmpl1.html</div>""") 118 finally: 119 file2.close() 120 121 file3 = open(os.path.join(self.dirname, 'sub', 'tmpl2.html'), 'w') 122 try: 123 file3.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude"> 124 <xi:include href="tmpl1.html" /> 125 </html>""") 126 finally: 127 file3.close() 128 129 loader = TemplateLoader([self.dirname]) 130 tmpl = loader.load('sub/tmpl2.html') 131 self.assertEqual("""<html> 132 <div>Included sub/tmpl1.html</div> 133 </html>""", tmpl.generate().render()) 134 107 135 def test_relative_include_without_search_path(self): 108 136 file1 = open(os.path.join(self.dirname, 'tmpl1.html'), 'w') … … 172 200 <div>Included</div> 173 201 </html>""", tmpl2.generate().render()) 202 203 def test_relative_absolute_template_preferred(self): 204 file1 = open(os.path.join(self.dirname, 'tmpl1.html'), 'w') 205 try: 206 file1.write("""<div>Included</div>""") 207 finally: 208 file1.close() 209 210 os.mkdir(os.path.join(self.dirname, 'sub')) 211 file2 = open(os.path.join(self.dirname, 'sub', 'tmpl1.html'), 'w') 212 try: 213 file2.write("""<div>Included from sub</div>""") 214 finally: 215 file2.close() 216 217 file3 = open(os.path.join(self.dirname, 'sub', 'tmpl2.html'), 'w') 218 try: 219 file3.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude"> 220 <xi:include href="tmpl1.html" /> 221 </html>""") 222 finally: 223 file3.close() 224 225 loader = TemplateLoader() 226 tmpl = loader.load(os.path.abspath(os.path.join(self.dirname, 'sub', 227 'tmpl2.html'))) 228 self.assertEqual("""<html> 229 <div>Included from sub</div> 230 </html>""", tmpl.generate().render()) 231 232 def test_abspath_caching(self): 233 abspath = os.path.join(self.dirname, 'abs') 234 os.mkdir(abspath) 235 file1 = open(os.path.join(abspath, 'tmpl1.html'), 'w') 236 try: 237 file1.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude"> 238 <xi:include href="tmpl2.html" /> 239 </html>""") 240 finally: 241 file1.close() 242 243 file2 = open(os.path.join(abspath, 'tmpl2.html'), 'w') 244 try: 245 file2.write("""<div>Included from abspath.</div>""") 246 finally: 247 file2.close() 248 249 searchpath = os.path.join(self.dirname, 'searchpath') 250 os.mkdir(searchpath) 251 file3 = open(os.path.join(searchpath, 'tmpl2.html'), 'w') 252 try: 253 file3.write("""<div>Included from searchpath.</div>""") 254 finally: 255 file3.close() 256 257 loader = TemplateLoader(searchpath) 258 tmpl1 = loader.load(os.path.join(abspath, 'tmpl1.html')) 259 self.assertEqual("""<html> 260 <div>Included from searchpath.</div> 261 </html>""", tmpl1.generate().render()) 262 assert 'tmpl2.html' in loader._cache 174 263 175 264 def test_load_with_default_encoding(self): … … 220 309 </html>""", tmpl.generate().render()) 221 310 311 def test_prefix_delegation_to_directories(self): 312 """ 313 Test prefix delegation with the following layout: 314 315 templates/foo.html 316 sub1/templates/tmpl1.html 317 sub2/templates/tmpl2.html 318 319 Where sub1 and sub2 are prefixes, and both tmpl1.html and tmpl2.html 320 incldue foo.html. 321 """ 322 dir1 = os.path.join(self.dirname, 'templates') 323 os.mkdir(dir1) 324 file1 = open(os.path.join(dir1, 'foo.html'), 'w') 325 try: 326 file1.write("""<div>Included foo</div>""") 327 finally: 328 file1.close() 329 330 dir2 = os.path.join(self.dirname, 'sub1', 'templates') 331 os.makedirs(dir2) 332 file2 = open(os.path.join(dir2, 'tmpl1.html'), 'w') 333 try: 334 file2.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude"> 335 <xi:include href="../foo.html" /> from sub1 336 </html>""") 337 finally: 338 file2.close() 339 340 dir3 = os.path.join(self.dirname, 'sub2', 'templates') 341 os.makedirs(dir3) 342 file3 = open(os.path.join(dir3, 'tmpl2.html'), 'w') 343 try: 344 file3.write("""<div>tmpl2</div>""") 345 finally: 346 file3.close() 347 348 loader = TemplateLoader([dir1, TemplateLoader.prefixed( 349 sub1 = os.path.join(dir2), 350 sub2 = os.path.join(dir3) 351 )]) 352 tmpl = loader.load('sub1/tmpl1.html') 353 self.assertEqual("""<html> 354 <div>Included foo</div> from sub1 355 </html>""", tmpl.generate().render()) 356 357 def test_prefix_delegation_to_directories_with_subdirs(self): 358 """ 359 Test prefix delegation with the following layout: 360 361 templates/foo.html 362 sub1/templates/tmpl1.html 363 sub1/templates/tmpl2.html 364 sub1/templates/bar/tmpl3.html 365 366 Where sub1 is a prefix, and tmpl1.html includes all the others. 367 """ 368 dir1 = os.path.join(self.dirname, 'templates') 369 os.mkdir(dir1) 370 file1 = open(os.path.join(dir1, 'foo.html'), 'w') 371 try: 372 file1.write("""<div>Included foo</div>""") 373 finally: 374 file1.close() 375 376 dir2 = os.path.join(self.dirname, 'sub1', 'templates') 377 os.makedirs(dir2) 378 file2 = open(os.path.join(dir2, 'tmpl1.html'), 'w') 379 try: 380 file2.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude"> 381 <xi:include href="../foo.html" /> from sub1 382 <xi:include href="tmpl2.html" /> from sub1 383 <xi:include href="bar/tmpl3.html" /> from sub1 384 </html>""") 385 finally: 386 file2.close() 387 388 file3 = open(os.path.join(dir2, 'tmpl2.html'), 'w') 389 try: 390 file3.write("""<div>tmpl2</div>""") 391 finally: 392 file3.close() 393 394 dir3 = os.path.join(self.dirname, 'sub1', 'templates', 'bar') 395 os.makedirs(dir3) 396 file4 = open(os.path.join(dir3, 'tmpl3.html'), 'w') 397 try: 398 file4.write("""<div>bar/tmpl3</div>""") 399 finally: 400 file4.close() 401 402 loader = TemplateLoader([dir1, TemplateLoader.prefixed( 403 sub1 = os.path.join(dir2), 404 sub2 = os.path.join(dir3) 405 )]) 406 tmpl = loader.load('sub1/tmpl1.html') 407 self.assertEqual("""<html> 408 <div>Included foo</div> from sub1 409 <div>tmpl2</div> from sub1 410 <div>bar/tmpl3</div> from sub1 411 </html>""", tmpl.generate().render()) 412 222 413 223 414 def suite(): -
branches/experimental/match-fastpaths/genshi/template/tests/markup.py
r772 r819 612 612 </html>""", tmpl.generate().render()) 613 613 614 def test_with_in_match(self): 615 xml = ("""<html xmlns:py="http://genshi.edgewall.org/"> 616 <py:match path="body/p"> 617 <h1>${select('text()')}</h1> 618 ${select('.')} 619 </py:match> 620 <body><p py:with="foo='bar'">${foo}</p></body> 621 </html>""") 622 tmpl = MarkupTemplate(xml, filename='test.html') 623 self.assertEqual("""<html> 624 <body> 625 <h1>bar</h1> 626 <p>bar</p> 627 </body> 628 </html>""", tmpl.generate().render()) 629 614 630 def test_nested_include_matches(self): 615 631 # See ticket #157 -
branches/experimental/match-fastpaths/genshi/template/tests/text.py
r745 r819 233 233 ----- Included data above this line -----""", tmpl.generate().render()) 234 234 235 def test_include_expr(self): 236 file1 = open(os.path.join(self.dirname, 'tmpl1.txt'), 'w') 237 try: 238 file1.write("Included") 239 finally: 240 file1.close() 241 242 file2 = open(os.path.join(self.dirname, 'tmpl2.txt'), 'w') 243 try: 244 file2.write("""----- Included data below this line ----- 245 {% include ${'%s.txt' % ('tmpl1',)} %} 246 ----- Included data above this line -----""") 247 finally: 248 file2.close() 249 250 loader = TemplateLoader([self.dirname]) 251 tmpl = loader.load('tmpl2.txt', cls=NewTextTemplate) 252 self.assertEqual("""----- Included data below this line ----- 253 Included 254 ----- Included data above this line -----""", tmpl.generate().render()) 255 235 256 236 257 def suite(): -
branches/experimental/match-fastpaths/genshi/template/text.py
r745 r819 29 29 import re 30 30 31 from genshi.core import TEXT 31 32 from genshi.template.base import BadDirectiveError, Template, \ 32 33 TemplateSyntaxError, EXEC, INCLUDE, SUB 33 34 from genshi.template.eval import Suite 34 35 from genshi.template.directives import * 35 from genshi.template.directives import Directive , _apply_directives36 from genshi.template.directives import Directive 36 37 from genshi.template.interpolation import interpolate 37 38 … … 189 190 if command == 'include': 190 191 pos = (self.filename, lineno, 0) 191 stream.append((INCLUDE, (value.strip(), None, []), pos)) 192 value = list(interpolate(value, self.basedir, self.filename, 193 lineno, 0, lookup=self.lookup)) 194 if len(value) == 1 and value[0][0] is TEXT: 195 value = value[0][1] 196 stream.append((INCLUDE, (value, None, []), pos)) 192 197 193 198 elif command == 'python': -
branches/experimental/match-fastpaths/genshi/tests/core.py
r782 r819 15 15 import pickle 16 16 from StringIO import StringIO 17 try: 18 from cStringIO import StringIO as cStringIO 19 except ImportError: 20 cStringIO = StringIO 17 21 import unittest 18 22 … … 35 39 xml = XML('<li>Über uns</li>') 36 40 self.assertEqual('<li>Über uns</li>', xml.render(encoding='ascii')) 41 42 def test_render_output_stream_utf8(self): 43 xml = XML('<li>Über uns</li>') 44 strio = cStringIO() 45 self.assertEqual(None, xml.render(out=strio)) 46 self.assertEqual('<li>Über uns</li>', strio.getvalue()) 47 48 def test_render_output_stream_unicode(self): 49 xml = XML('<li>Über uns</li>') 50 strio = StringIO() 51 self.assertEqual(None, xml.render(encoding=None, out=strio)) 52 self.assertEqual(u'<li>Über uns</li>', strio.getvalue()) 37 53 38 54 def test_pickle(self): -
branches/experimental/match-fastpaths/genshi/tests/output.py
r787 r819 229 229 text = '<foo xml:space="preserve"> Do not mess \n\n with me </foo>' 230 230 output = XML(text).render(XHTMLSerializer) 231 self.assertEqual( text, output)231 self.assertEqual('<foo> Do not mess \n\n with me </foo>', output) 232 232 233 233 def test_empty_script(self):
Note: See TracChangeset
for help on using the changeset viewer.