Mercurial > genshi > genshi-test
comparison genshi/template/base.py @ 902:09cc3627654c experimental-inline
Sync `experimental/inline` branch with [source:trunk@1126].
author | cmlenz |
---|---|
date | Fri, 23 Apr 2010 21:08:26 +0000 |
parents | de82830f8816 |
children |
comparison
equal
deleted
inserted
replaced
830:de82830f8816 | 902:09cc3627654c |
---|---|
1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
2 # | 2 # |
3 # Copyright (C) 2006-2008 Edgewall Software | 3 # Copyright (C) 2006-2010 Edgewall Software |
4 # All rights reserved. | 4 # All rights reserved. |
5 # | 5 # |
6 # This software is licensed as described in the file COPYING, which | 6 # This software is licensed as described in the file COPYING, which |
7 # you should have received as part of this distribution. The terms | 7 # you should have received as part of this distribution. The terms |
8 # are also available at http://genshi.edgewall.org/wiki/License. | 8 # are also available at http://genshi.edgewall.org/wiki/License. |
316 :since: version 0.6 | 316 :since: version 0.6 |
317 """ | 317 """ |
318 __metaclass__ = DirectiveFactoryMeta | 318 __metaclass__ = DirectiveFactoryMeta |
319 | 319 |
320 directives = [] | 320 directives = [] |
321 """A list of `(name, cls)` tuples that define the set of directives | 321 """A list of ``(name, cls)`` tuples that define the set of directives |
322 provided by this factory. | 322 provided by this factory. |
323 """ | 323 """ |
324 | |
325 def compare_directives(self): | |
326 """Return a function that takes two directive classes and compares | |
327 them to determine their relative ordering. | |
328 """ | |
329 def _get_index(cls): | |
330 if cls in self._dir_order: | |
331 return self._dir_order.index(cls) | |
332 return 0 | |
333 return lambda a, b: cmp(_get_index(a[0]), _get_index(b[0])) | |
334 | 324 |
335 def get_directive(self, name): | 325 def get_directive(self, name): |
336 """Return the directive class for the given name. | 326 """Return the directive class for the given name. |
337 | 327 |
338 :param name: the directive name as used in the template | 328 :param name: the directive name as used in the template |
339 :return: the directive class | 329 :return: the directive class |
340 :see: `Directive` | 330 :see: `Directive` |
341 """ | 331 """ |
342 return self._dir_by_name.get(name) | 332 return self._dir_by_name.get(name) |
333 | |
334 def get_directive_index(self, dir_cls): | |
335 """Return a key for the given directive class that should be used to | |
336 sort it among other directives on the same `SUB` event. | |
337 | |
338 The default implementation simply returns the index of the directive in | |
339 the `directives` list. | |
340 | |
341 :param dir_cls: the directive class | |
342 :return: the sort key | |
343 """ | |
344 if dir_cls in self._dir_order: | |
345 return self._dir_order.index(dir_cls) | |
346 return len(self._dir_order) | |
343 | 347 |
344 | 348 |
345 class Template(DirectiveFactory): | 349 class Template(DirectiveFactory): |
346 """Abstract template base class. | 350 """Abstract template base class. |
347 | 351 |
390 self.filename = filename | 394 self.filename = filename |
391 self.loader = loader | 395 self.loader = loader |
392 self.lookup = lookup | 396 self.lookup = lookup |
393 self.allow_exec = allow_exec | 397 self.allow_exec = allow_exec |
394 self._init_filters() | 398 self._init_filters() |
399 self._init_loader() | |
395 self._module = None | 400 self._module = None |
396 self._prepared = False | 401 self._prepared = False |
397 | 402 |
398 if isinstance(source, basestring): | 403 if isinstance(source, basestring): |
399 source = StringIO(source) | 404 source = StringIO(source) |
412 def __setstate__(self, state): | 417 def __setstate__(self, state): |
413 self.__dict__ = state | 418 self.__dict__ = state |
414 self._init_filters() | 419 self._init_filters() |
415 | 420 |
416 def __repr__(self): | 421 def __repr__(self): |
417 return '<%s "%s">' % (self.__class__.__name__, self.filename) | 422 return '<%s "%s">' % (type(self).__name__, self.filename) |
418 | 423 |
419 def _init_filters(self): | 424 def _init_filters(self): |
420 self.filters = [self._flatten] | 425 self.filters = [self._flatten, self._include] |
421 if self.loader: | 426 |
422 self.filters.append(self._include) | 427 def _init_loader(self): |
428 if self.loader is None: | |
429 from genshi.template.loader import TemplateLoader | |
430 if self.filename: | |
431 if self.filepath != self.filename: | |
432 basedir = os.path.normpath(self.filepath)[:-len( | |
433 os.path.normpath(self.filename)) | |
434 ] | |
435 else: | |
436 basedir = os.path.dirname(self.filename) | |
437 else: | |
438 basedir = '.' | |
439 self.loader = TemplateLoader([os.path.abspath(basedir)]) | |
423 | 440 |
424 @property | 441 @property |
425 def stream(self): | 442 def stream(self): |
426 if not self._prepared: | 443 if not self._prepared: |
427 self._stream = list(self._prepare(self._stream)) | 444 self._stream = list(self._prepare(self._stream)) |
451 | 468 |
452 for kind, data, pos in stream: | 469 for kind, data, pos in stream: |
453 if kind is SUB: | 470 if kind is SUB: |
454 directives = [] | 471 directives = [] |
455 substream = data[1] | 472 substream = data[1] |
456 for cls, value, namespaces, pos in data[0]: | 473 for _, cls, value, namespaces, pos in sorted(data[0]): |
457 directive, substream = cls.attach(self, substream, value, | 474 directive, substream = cls.attach(self, substream, value, |
458 namespaces, pos) | 475 namespaces, pos) |
459 if directive: | 476 if directive: |
460 directives.append(directive) | 477 directives.append(directive) |
461 substream = self._prepare(substream) | 478 substream = self._prepare(substream) |
540 | 557 |
541 return Stream(stream, self.serializer) | 558 return Stream(stream, self.serializer) |
542 | 559 |
543 def _flatten(self, stream, ctxt, **vars): | 560 def _flatten(self, stream, ctxt, **vars): |
544 number_conv = self._number_conv | 561 number_conv = self._number_conv |
545 | 562 stack = [] |
546 for kind, data, pos in stream: | 563 push = stack.append |
547 | 564 pop = stack.pop |
548 if kind is START and data[1]: | 565 stream = iter(stream) |
549 # Attributes may still contain expressions in start tags at | 566 |
550 # this point, so do some evaluation | 567 while 1: |
551 tag, attrs = data | 568 for kind, data, pos in stream: |
552 new_attrs = [] | 569 |
553 for name, value in attrs: | 570 if kind is START and data[1]: |
554 if type(value) is list: # this is an interpolated string | 571 # Attributes may still contain expressions in start tags at |
555 values = [event[1] | 572 # this point, so do some evaluation |
556 for event in self._flatten(value, ctxt, **vars) | 573 tag, attrs = data |
557 if event[0] is TEXT and event[1] is not None | 574 new_attrs = [] |
558 ] | 575 for name, value in attrs: |
559 if not values: | 576 if type(value) is list: # this is an interpolated string |
560 continue | 577 values = [event[1] |
561 value = u''.join(values) | 578 for event in self._flatten(value, ctxt, **vars) |
562 new_attrs.append((name, value)) | 579 if event[0] is TEXT and event[1] is not None |
563 yield kind, (tag, Attrs(new_attrs)), pos | 580 ] |
564 | 581 if not values: |
565 elif kind is EXPR: | 582 continue |
566 result = _eval_expr(data, ctxt, vars) | 583 value = ''.join(values) |
567 if result is not None: | 584 new_attrs.append((name, value)) |
568 # First check for a string, otherwise the iterable test | 585 yield kind, (tag, Attrs(new_attrs)), pos |
569 # below succeeds, and the string will be chopped up into | 586 |
570 # individual characters | 587 elif kind is EXPR: |
571 if isinstance(result, basestring): | 588 result = _eval_expr(data, ctxt, vars) |
572 yield TEXT, result, pos | 589 if result is not None: |
573 elif isinstance(result, (int, float, long)): | 590 # First check for a string, otherwise the iterable test |
574 yield TEXT, number_conv(result), pos | 591 # below succeeds, and the string will be chopped up into |
575 elif hasattr(result, '__iter__'): | 592 # individual characters |
576 for event in self._flatten(_ensure(result), ctxt, | 593 if isinstance(result, basestring): |
577 **vars): | 594 yield TEXT, result, pos |
578 yield event | 595 elif isinstance(result, (int, float, long)): |
579 else: | 596 yield TEXT, number_conv(result), pos |
580 yield TEXT, unicode(result), pos | 597 elif hasattr(result, '__iter__'): |
581 | 598 push(stream) |
582 elif kind is EXEC: | 599 stream = _ensure(result) |
583 _exec_suite(data, ctxt, vars) | 600 break |
584 | 601 else: |
585 elif kind is SUB: | 602 yield TEXT, unicode(result), pos |
586 # This event is a list of directives and a list of nested | 603 |
587 # events to which those directives should be applied | 604 elif kind is SUB: |
588 substream = _apply_directives(data[1], data[0], ctxt, vars) | 605 # This event is a list of directives and a list of nested |
589 for event in self._flatten(substream, ctxt, **vars): | 606 # events to which those directives should be applied |
590 yield event | 607 push(stream) |
608 stream = _apply_directives(data[1], data[0], ctxt, vars) | |
609 break | |
610 | |
611 elif kind is EXEC: | |
612 _exec_suite(data, ctxt, vars) | |
613 | |
614 else: | |
615 yield kind, data, pos | |
591 | 616 |
592 else: | 617 else: |
593 yield kind, data, pos | 618 if not stack: |
619 break | |
620 stream = pop() | |
594 | 621 |
595 def _include(self, stream, ctxt, **vars): | 622 def _include(self, stream, ctxt, **vars): |
596 """Internal stream filter that performs inclusion of external | 623 """Internal stream filter that performs inclusion of external |
597 template files. | 624 template files. |
598 """ | 625 """ |
605 parts = [] | 632 parts = [] |
606 for subkind, subdata, subpos in self._flatten(href, ctxt, | 633 for subkind, subdata, subpos in self._flatten(href, ctxt, |
607 **vars): | 634 **vars): |
608 if subkind is TEXT: | 635 if subkind is TEXT: |
609 parts.append(subdata) | 636 parts.append(subdata) |
610 href = u''.join([x for x in parts if x is not None]) | 637 href = ''.join([x for x in parts if x is not None]) |
611 try: | 638 try: |
612 tmpl = self.loader.load(href, relative_to=event[2][0], | 639 tmpl = self.loader.load(href, relative_to=event[2][0], |
613 cls=cls or self.__class__) | 640 cls=cls or self.__class__) |
614 for event in tmpl.generate(ctxt, **vars): | 641 for event in tmpl.generate(ctxt, **vars): |
615 yield event | 642 yield event |