changeset 362:0910d5978f45

Follow-up to [431]: directives are no longer instantiated directly at parse time, but instead by the `attach()` method of the directive class (which replaces the `prepare()` method).
author cmlenz
date Wed, 22 Nov 2006 10:37:24 +0000
parents e59d542b0c42
children caf7b68ab5dc
files genshi/template/core.py genshi/template/directives.py genshi/template/markup.py genshi/template/text.py
diffstat 4 files changed, 111 insertions(+), 96 deletions(-) [+]
line wrap: on
line diff
--- a/genshi/template/core.py
+++ b/genshi/template/core.py
@@ -138,72 +138,6 @@
         """Pop the top-most scope from the stack."""
 
 
-class DirectiveMeta(type):
-    """Meta class for template directives."""
-
-    def __new__(cls, name, bases, d):
-        d['tagname'] = name.lower().replace('directive', '')
-        return type.__new__(cls, name, bases, d)
-
-
-
-class Directive(object):
-    """Abstract base class for template directives.
-    
-    A directive is basically a callable that takes three positional arguments:
-    `ctxt` is the template data context, `stream` is an iterable over the
-    events that the directive applies to, and `directives` is is a list of
-    other directives on the same stream that need to be applied.
-    
-    Directives can be "anonymous" or "registered". Registered directives can be
-    applied by the template author using an XML attribute with the
-    corresponding name in the template. Such directives should be subclasses of
-    this base class that can  be instantiated with the value of the directive
-    attribute as parameter.
-    
-    Anonymous directives are simply functions conforming to the protocol
-    described above, and can only be applied programmatically (for example by
-    template filters).
-    """
-    __metaclass__ = DirectiveMeta
-    __slots__ = ['expr']
-
-    def __init__(self, value, namespaces=None, filename=None, lineno=-1,
-                 offset=-1):
-        try:
-            self.expr = value and Expression(value, filename, lineno) or None
-        except SyntaxError, err:
-            err.msg += ' in expression "%s" of "%s" directive' % (value,
-                                                                  self.tagname)
-            raise TemplateSyntaxError(err, filename, lineno,
-                                      offset + (err.offset or 0))
-
-    def __call__(self, stream, ctxt, directives):
-        """Apply the directive to the given stream.
-        
-        @param stream: the event stream
-        @param ctxt: the context data
-        @param directives: a list of the remaining directives that should
-            process the stream
-        """
-        raise NotImplementedError
-
-    def __repr__(self):
-        expr = ''
-        if self.expr is not None:
-            expr = ' "%s"' % self.expr.source
-        return '<%s%s>' % (self.__class__.__name__, expr)
-
-    def prepare(self, directives, stream):
-        """Called after the template stream has been completely parsed.
-        
-        The part of the template stream associated with the directive will be
-        replaced by what this function returns. This allows the directive to
-        optimize the template or validate the way the directive is used.
-        """
-        return stream
-
-
 def _apply_directives(stream, ctxt, directives):
     """Apply the given directives to the stream."""
     if directives:
@@ -307,15 +241,16 @@
     _interpolate = classmethod(_interpolate)
 
     def _prepare(self, stream):
-        """Call the `prepare` method of every directive instance in the
-        template so that various optimization and validation tasks can be
-        performed.
-        """
+        """Call the `attach` method of every directive found in the template."""
         for kind, data, pos in stream:
             if kind is SUB:
-                directives, substream = data
-                for directive in directives[:]:
-                    substream = directive.prepare(directives, substream)
+                directives = []
+                substream = data[1]
+                for cls, value, namespaces, pos in data[0]:
+                    directive, substream = cls.attach(self, substream, value,
+                                                      namespaces, pos)
+                    if directive:
+                        directives.append(directive)
                 substream = self._prepare(substream)
                 if directives:
                     yield kind, (directives, list(substream)), pos
--- a/genshi/template/directives.py
+++ b/genshi/template/directives.py
@@ -17,8 +17,8 @@
 
 from genshi.core import Attrs, Stream
 from genshi.path import Path
-from genshi.template.core import EXPR, Directive, TemplateRuntimeError, \
-                                 TemplateSyntaxError, _apply_directives
+from genshi.template.core import TemplateRuntimeError, TemplateSyntaxError, \
+                                 EXPR, _apply_directives
 from genshi.template.eval import Expression, _parse
 
 __all__ = ['AttrsDirective', 'ChooseDirective', 'ContentDirective',
@@ -27,6 +27,88 @@
            'WhenDirective', 'WithDirective']
 
 
+class DirectiveMeta(type):
+    """Meta class for template directives."""
+
+    def __new__(cls, name, bases, d):
+        d['tagname'] = name.lower().replace('directive', '')
+        return type.__new__(cls, name, bases, d)
+
+
+class Directive(object):
+    """Abstract base class for template directives.
+    
+    A directive is basically a callable that takes three positional arguments:
+    `ctxt` is the template data context, `stream` is an iterable over the
+    events that the directive applies to, and `directives` is is a list of
+    other directives on the same stream that need to be applied.
+    
+    Directives can be "anonymous" or "registered". Registered directives can be
+    applied by the template author using an XML attribute with the
+    corresponding name in the template. Such directives should be subclasses of
+    this base class that can  be instantiated with the value of the directive
+    attribute as parameter.
+    
+    Anonymous directives are simply functions conforming to the protocol
+    described above, and can only be applied programmatically (for example by
+    template filters).
+    """
+    __metaclass__ = DirectiveMeta
+    __slots__ = ['expr']
+
+    def __init__(self, value, namespaces=None, filename=None, lineno=-1,
+                 offset=-1):
+        self.expr = self._parse_expr(value, filename, lineno, offset)
+
+    def attach(cls, template, stream, value, namespaces, pos):
+        """Called after the template stream has been completely parsed.
+        
+        @param template: the `Template` object
+        @param stream: the event stream associated with the directive
+        @param value: the argument value for the directive
+        @param namespaces: a mapping of namespace URIs to prefixes
+        @param pos: a `(filename, lineno, offset)` tuple describing the location
+            where the directive was found in the source
+        
+        This class method should return a `(directive, stream)` tuple. If
+        `directive` is not `None`, it should be an instance of the `Directive`
+        class, and gets added to the list of directives applied to the substream
+        at runtime. `stream` is an event stream that replaces the original
+        stream associated with the directive.
+        """
+        return cls(value, namespaces, template.filename, *pos[1:]), stream
+    attach = classmethod(attach)
+
+    def __call__(self, stream, ctxt, directives):
+        """Apply the directive to the given stream.
+        
+        @param stream: the event stream
+        @param ctxt: the context data
+        @param directives: a list of the remaining directives that should
+            process the stream
+        """
+        raise NotImplementedError
+
+    def __repr__(self):
+        expr = ''
+        if self.expr is not None:
+            expr = ' "%s"' % self.expr.source
+        return '<%s%s>' % (self.__class__.__name__, expr)
+
+    def _parse_expr(cls, expr, filename=None, lineno=-1, offset=-1):
+        """Parses the given expression, raising a useful error message when a
+        syntax error is encountered.
+        """
+        try:
+            return expr and Expression(expr, filename, lineno) or None
+        except SyntaxError, err:
+            err.msg += ' in expression "%s" of "%s" directive' % (expr,
+                                                                  cls.tagname)
+            raise TemplateSyntaxError(err, filename, lineno,
+                                      offset + (err.offset or 0))
+    _parse_expr = classmethod(_parse_expr)
+
+
 def _assignment(ast):
     """Takes the AST representation of an assignment, and returns a function
     that applies the assignment of a given value to a dictionary.
@@ -114,9 +196,10 @@
     """
     __slots__ = []
 
-    def prepare(self, directives, stream):
-        directives.remove(self)
-        return [stream[0], (EXPR, self.expr, (None, -1, --1)),  stream[-1]]
+    def attach(cls, template, stream, value, namespaces, pos):
+        expr = cls._parse_expr(value, template.filename, *pos[1:])
+        return None, [stream[0], (EXPR, expr, pos),  stream[-1]]
+    attach = classmethod(attach)
 
 
 class DefDirective(Directive):
@@ -318,9 +401,7 @@
                  offset=-1):
         Directive.__init__(self, None, namespaces, filename, lineno, offset)
         self.path = Path(value, filename, lineno)
-        if namespaces is None:
-            namespaces = {}
-        self.namespaces = namespaces.copy()
+        self.namespaces = namespaces or {}
 
     def __call__(self, stream, ctxt, directives):
         ctxt._match_templates.append((self.path.test(ignore_context=True),
@@ -360,9 +441,10 @@
     """
     __slots__ = []
 
-    def prepare(self, directives, stream):
-        directives.remove(self)
-        return [(EXPR, self.expr, (None, -1, -1))]
+    def attach(cls, template, stream, value, namespaces, pos):
+        expr = cls._parse_expr(value, template.filename, *pos[1:])
+        return None, [(EXPR, expr, pos)]
+    attach = classmethod(attach)
 
 
 class StripDirective(Directive):
@@ -411,11 +493,12 @@
                     yield event
         return _apply_directives(_generate(), ctxt, directives)
 
-    def prepare(self, directives, stream):
-        if not self.expr:
-            directives.remove(self)
-            return stream[1:-1]
-        return stream
+    def attach(cls, template, stream, value, namespaces, pos):
+        if not value:
+            return None, stream[1:-1]
+        return super(StripDirective, cls).attach(template, stream, value,
+                                                 namespaces, pos)
+    attach = classmethod(attach)
 
 
 class ChooseDirective(Directive):
--- a/genshi/template/markup.py
+++ b/genshi/template/markup.py
@@ -94,8 +94,7 @@
                         raise BadDirectiveError(tag.localname, self.filepath,
                                                 pos[1])
                     value = attrib.get(getattr(cls, 'ATTRIBUTE', None), '')
-                    directives.append(cls(value, ns_prefix, self.filepath,
-                                          pos[1], pos[2]))
+                    directives.append((cls, value, ns_prefix.copy(), pos))
                     strip = True
 
                 new_attrib = []
@@ -105,8 +104,7 @@
                         if cls is None:
                             raise BadDirectiveError(name.localname,
                                                     self.filepath, pos[1])
-                        directives.append(cls(value, ns_prefix, self.filepath,
-                                              pos[1], pos[2]))
+                        directives.append((cls, value, ns_prefix.copy(), pos))
                     else:
                         if value:
                             value = list(self._interpolate(value, self.basedir,
@@ -119,8 +117,7 @@
 
                 if directives:
                     index = self._dir_order.index
-                    directives.sort(lambda a, b: cmp(index(a.__class__),
-                                                     index(b.__class__)))
+                    directives.sort(lambda a, b: cmp(index(a[0]), index(b[0])))
                     dirmap[(depth, tag)] = (directives, len(stream), strip)
 
                 stream.append((kind, (tag, Attrs(new_attrib)), pos))
--- a/genshi/template/text.py
+++ b/genshi/template/text.py
@@ -92,7 +92,7 @@
                 cls = self._dir_by_name.get(command)
                 if cls is None:
                     raise BadDirectiveError(command)
-                directive = cls(value, None, self.filepath, lineno, 0)
+                directive = cls, value, None, (self.filepath, lineno, 0)
                 dirmap[depth] = (directive, len(stream))
                 depth += 1
 
Copyright (C) 2012-2017 Edgewall Software