Mercurial > genshi > mirror
comparison markup/template.py @ 21:b4d17897d053 trunk
* Include paths are now interpreted relative to the path of the including template. Closes #3.
* The filename is now included as first item in the `pos` tuple of stream events.
* Simplified the "basic" example so that it actually ''is'' basic.
* Added a more complex example using nested relative includes in [source:/trunk/examples/includes/ examples/includes].
author | cmlenz |
---|---|
date | Tue, 20 Jun 2006 13:05:37 +0000 |
parents | 5420cfe42d36 |
children | 2483fe549959 |
comparison
equal
deleted
inserted
replaced
20:cc92d74ce9e5 | 21:b4d17897d053 |
---|---|
41 * Could we generate byte code from expressions? | 41 * Could we generate byte code from expressions? |
42 """ | 42 """ |
43 | 43 |
44 import compiler | 44 import compiler |
45 import os | 45 import os |
46 import posixpath | |
46 import re | 47 import re |
47 from StringIO import StringIO | 48 from StringIO import StringIO |
48 | 49 |
49 from markup.core import Attributes, Namespace, Stream, StreamEventKind | 50 from markup.core import Attributes, Namespace, Stream, StreamEventKind |
50 from markup.eval import Expression | 51 from markup.eval import Expression |
572 ('attrs', AttrsDirective), | 573 ('attrs', AttrsDirective), |
573 ('strip', StripDirective)] | 574 ('strip', StripDirective)] |
574 _dir_by_name = dict(directives) | 575 _dir_by_name = dict(directives) |
575 _dir_order = [directive[1] for directive in directives] | 576 _dir_order = [directive[1] for directive in directives] |
576 | 577 |
577 def __init__(self, source, filename=None): | 578 def __init__(self, source, basedir=None, filename=None): |
578 """Initialize a template from either a string or a file-like object.""" | 579 """Initialize a template from either a string or a file-like object.""" |
579 if isinstance(source, basestring): | 580 if isinstance(source, basestring): |
580 self.source = StringIO(source) | 581 self.source = StringIO(source) |
581 else: | 582 else: |
582 self.source = source | 583 self.source = source |
584 self.basedir = basedir | |
583 self.filename = filename or '<string>' | 585 self.filename = filename or '<string>' |
586 if basedir and filename: | |
587 self.filepath = os.path.join(basedir, filename) | |
588 else: | |
589 self.filepath = '<string>' | |
584 | 590 |
585 self.filters = [self._eval, self._match] | 591 self.filters = [self._eval, self._match] |
586 self.parse() | 592 self.parse() |
587 | 593 |
588 def __repr__(self): | 594 def __repr__(self): |
589 return '<%s "%s">' % (self.__class__.__name__, | 595 return '<%s "%s">' % (self.__class__.__name__, self.filename) |
590 os.path.basename(self.filename)) | |
591 | 596 |
592 def parse(self): | 597 def parse(self): |
593 """Parse the template. | 598 """Parse the template. |
594 | 599 |
595 The parsing stage parses the XML template and constructs a list of | 600 The parsing stage parses the XML template and constructs a list of |
601 stream = [] # list of events of the "compiled" template | 606 stream = [] # list of events of the "compiled" template |
602 dirmap = {} # temporary mapping of directives to elements | 607 dirmap = {} # temporary mapping of directives to elements |
603 ns_prefix = {} | 608 ns_prefix = {} |
604 depth = 0 | 609 depth = 0 |
605 | 610 |
606 for kind, data, pos in XMLParser(self.source): | 611 for kind, data, pos in XMLParser(self.source, filename=self.filename): |
607 | 612 |
608 if kind is Stream.START_NS: | 613 if kind is Stream.START_NS: |
609 # Strip out the namespace declaration for template directives | 614 # Strip out the namespace declaration for template directives |
610 prefix, uri = data | 615 prefix, uri = data |
611 if uri == self.NAMESPACE: | 616 if uri == self.NAMESPACE: |
626 new_attrib = [] | 631 new_attrib = [] |
627 for name, value in attrib: | 632 for name, value in attrib: |
628 if name in self.NAMESPACE: | 633 if name in self.NAMESPACE: |
629 cls = self._dir_by_name.get(name.localname) | 634 cls = self._dir_by_name.get(name.localname) |
630 if cls is None: | 635 if cls is None: |
631 raise BadDirectiveError(name, self.filename, pos[0]) | 636 raise BadDirectiveError(name, self.filename, pos[1]) |
632 else: | 637 else: |
633 directives.append(cls(self, value, pos)) | 638 directives.append(cls(self, value, pos)) |
634 else: | 639 else: |
635 value = list(self._interpolate(value, *pos)) | 640 value = list(self._interpolate(value, *pos)) |
636 new_attrib.append((name, value)) | 641 new_attrib.append((name, value)) |
664 self.stream = stream | 669 self.stream = stream |
665 | 670 |
666 _FULL_EXPR_RE = re.compile(r'(?<!\$)\$\{(.+?)\}') | 671 _FULL_EXPR_RE = re.compile(r'(?<!\$)\$\{(.+?)\}') |
667 _SHORT_EXPR_RE = re.compile(r'(?<!\$)\$([a-zA-Z][a-zA-Z0-9_\.]*)') | 672 _SHORT_EXPR_RE = re.compile(r'(?<!\$)\$([a-zA-Z][a-zA-Z0-9_\.]*)') |
668 | 673 |
669 def _interpolate(cls, text, lineno=-1, offset=-1): | 674 def _interpolate(cls, text, filename=None, lineno=-1, offset=-1): |
670 """Parse the given string and extract expressions. | 675 """Parse the given string and extract expressions. |
671 | 676 |
672 This method returns a list containing both literal text and `Expression` | 677 This method returns a list containing both literal text and `Expression` |
673 objects. | 678 objects. |
674 | 679 |
766 yield event | 771 yield event |
767 continue | 772 continue |
768 else: | 773 else: |
769 yield kind, data, pos | 774 yield kind, data, pos |
770 except SyntaxError, err: | 775 except SyntaxError, err: |
771 raise TemplateSyntaxError(err, self.filename, pos[0], | 776 raise TemplateSyntaxError(err, self.filename, pos[1], |
772 pos[1] + (err.offset or 0)) | 777 pos[2] + (err.offset or 0)) |
773 | 778 |
774 def _match(self, stream, ctxt=None): | 779 def _match(self, stream, ctxt=None): |
775 for kind, data, pos in stream: | 780 for kind, data, pos in stream: |
776 | 781 |
777 # We (currently) only care about start and end events for matching | 782 # We (currently) only care about start and end events for matching |
781 continue | 786 continue |
782 | 787 |
783 for idx, (test, path, template) in enumerate(ctxt._match_templates): | 788 for idx, (test, path, template) in enumerate(ctxt._match_templates): |
784 if (kind, data, pos) in template[::len(template)]: | 789 if (kind, data, pos) in template[::len(template)]: |
785 # This is the event this match template produced itself, so | 790 # This is the event this match template produced itself, so |
786 # matching it again would result in an infinite loop | 791 # matching it again would result in an infinite loop |
787 continue | 792 continue |
788 | 793 |
789 result = test(kind, data, pos) | 794 result = test(kind, data, pos) |
790 | 795 |
791 if result: | 796 if result: |
802 content.append(event) | 807 content.append(event) |
803 | 808 |
804 # enable the path to keep track of the stream state | 809 # enable the path to keep track of the stream state |
805 test(*event) | 810 test(*event) |
806 | 811 |
807 content = list(self._flatten(content, ctxt, apply_filters=False)) | 812 content = list(self._flatten(content, ctxt, False)) |
808 | 813 |
809 def _apply(stream, ctxt): | 814 def _apply(stream, ctxt): |
810 stream = list(stream) | 815 stream = list(stream) |
811 ctxt.push(select=lambda path: Stream(stream).select(path)) | 816 ctxt.push(select=lambda path: Stream(stream).select(path)) |
812 for event in template: | 817 for event in template: |
863 self.search_path = [] | 868 self.search_path = [] |
864 self.auto_reload = auto_reload | 869 self.auto_reload = auto_reload |
865 self._cache = {} | 870 self._cache = {} |
866 self._mtime = {} | 871 self._mtime = {} |
867 | 872 |
868 def load(self, filename): | 873 def load(self, filename, relative_to=None): |
869 """Load the template with the given name. | 874 """Load the template with the given name. |
870 | 875 |
871 This method searches the search path trying to locate a template | 876 This method searches the search path trying to locate a template |
872 matching the given name. If no such template is found, a | 877 matching the given name. If no such template is found, a |
873 `TemplateNotFound` exception is raised. Otherwise, a `Template` object | 878 `TemplateNotFound` exception is raised. Otherwise, a `Template` object |
875 | 880 |
876 Template searches are cached to avoid having to parse the same template | 881 Template searches are cached to avoid having to parse the same template |
877 file more than once. Thus, subsequent calls of this method with the | 882 file more than once. Thus, subsequent calls of this method with the |
878 same template file name will return the same `Template` object. | 883 same template file name will return the same `Template` object. |
879 | 884 |
885 If the `relative_to` parameter is provided, the `filename` is | |
886 interpreted as being relative to that path. | |
887 | |
880 @param filename: the relative path of the template file to load | 888 @param filename: the relative path of the template file to load |
889 @param relative_to: the filename of the template from which the new | |
890 template is being loaded, or `None` if the template is being loaded | |
891 directly | |
881 """ | 892 """ |
893 if relative_to: | |
894 filename = posixpath.join(posixpath.dirname(relative_to), filename) | |
882 filename = os.path.normpath(filename) | 895 filename = os.path.normpath(filename) |
883 try: | 896 try: |
884 tmpl = self._cache[filename] | 897 tmpl = self._cache[filename] |
885 if not self.auto_reload or \ | 898 if not self.auto_reload or \ |
886 os.path.getmtime(tmpl.filename) == self._mtime[filename]: | 899 os.path.getmtime(tmpl.filepath) == self._mtime[filename]: |
887 return tmpl | 900 return tmpl |
888 except KeyError: | 901 except KeyError: |
889 pass | 902 pass |
890 for dirname in self.search_path: | 903 for dirname in self.search_path: |
891 filepath = os.path.join(dirname, filename) | 904 filepath = os.path.join(dirname, filename) |
892 try: | 905 try: |
893 fileobj = file(filepath, 'rt') | 906 fileobj = file(filepath, 'rt') |
894 try: | 907 try: |
895 tmpl = Template(fileobj, filename=filepath) | 908 tmpl = Template(fileobj, basedir=dirname, filename=filename) |
896 tmpl.filters.append(IncludeFilter(self)) | 909 tmpl.filters.append(IncludeFilter(self)) |
897 finally: | 910 finally: |
898 fileobj.close() | 911 fileobj.close() |
899 self._cache[filename] = tmpl | 912 self._cache[filename] = tmpl |
900 self._mtime[filename] = os.path.getmtime(filepath) | 913 self._mtime[filename] = os.path.getmtime(filepath) |