# HG changeset patch # User cmlenz # Date 1164191844 0 # Node ID 0910d5978f4550d70d9435218cefa4ebb2400d48 # Parent e59d542b0c42ef2864e60f1cd08ca71156d07686 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). diff --git a/genshi/template/core.py b/genshi/template/core.py --- 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 diff --git a/genshi/template/directives.py b/genshi/template/directives.py --- 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): diff --git a/genshi/template/markup.py b/genshi/template/markup.py --- 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)) diff --git a/genshi/template/text.py b/genshi/template/text.py --- 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