# HG changeset patch
# User cmlenz
# Date 1236974666 0
# Node ID de82830f8816d57ec397ce43b13e138fe8c635e9
# Parent eb8aa869048043145cd880a7cac2c0ae5eee71ca
inline branch: synced with trunk@1038.
diff --git a/ChangeLog b/ChangeLog
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,6 +3,7 @@
(???, from branches/stable/0.6.x)
* Support for Python 2.3 has been dropped.
+ * Added caching in the serilization stage for improved performance.
Version 0.5.2
diff --git a/genshi/core.py b/genshi/core.py
--- a/genshi/core.py
+++ b/genshi/core.py
@@ -468,6 +468,7 @@
return Markup(unicode(self).join([escape(item, quotes=escape_quotes)
for item in seq]))
+ @classmethod
def escape(cls, text, quotes=True):
"""Create a Markup instance from a string and escape special characters
it may contain (<, >, & and \").
@@ -501,7 +502,6 @@
if quotes:
text = text.replace('"', '"')
return cls(text)
- escape = classmethod(escape)
def unescape(self):
"""Reverse-escapes &, <, >, and \" and returns a `unicode` object.
diff --git a/genshi/output.py b/genshi/output.py
--- a/genshi/output.py
+++ b/genshi/output.py
@@ -129,6 +129,7 @@
)
SVG = SVG_FULL
+ @classmethod
def get(cls, name):
"""Return the ``(name, pubid, sysid)`` tuple of the ``DOCTYPE``
declaration for the specified name.
@@ -164,7 +165,6 @@
'svg-basic': cls.SVG_BASIC,
'svg-tiny': cls.SVG_TINY
}.get(name.lower())
- get = classmethod(get)
class XMLSerializer(object):
@@ -179,7 +179,7 @@
_PRESERVE_SPACE = frozenset()
def __init__(self, doctype=None, strip_whitespace=True,
- namespace_prefixes=None):
+ namespace_prefixes=None, cache=True):
"""Initialize the XML serializer.
:param doctype: a ``(name, pubid, sysid)`` tuple that represents the
@@ -188,42 +188,60 @@
defined in `DocType.get`
:param strip_whitespace: whether extraneous whitespace should be
stripped from the output
+ :param cache: whether to cache the text output per event, which
+ improves performance for repetitive markup
:note: Changed in 0.4.2: The `doctype` parameter can now be a string.
+ :note: Changed in 0.6: The `cache` parameter was added
"""
self.filters = [EmptyTagFilter()]
if strip_whitespace:
self.filters.append(WhitespaceFilter(self._PRESERVE_SPACE))
- self.filters.append(NamespaceFlattener(prefixes=namespace_prefixes))
+ self.filters.append(NamespaceFlattener(prefixes=namespace_prefixes,
+ cache=cache))
if doctype:
self.filters.append(DocTypeInserter(doctype))
+ self.cache = cache
def __call__(self, stream):
have_decl = have_doctype = False
in_cdata = False
+ cache = {}
+ cache_get = cache.get
+ if self.cache:
+ def _emit(kind, input, output):
+ cache[kind, input] = output
+ return output
+ else:
+ def _emit(kind, input, output):
+ return output
+
for filter_ in self.filters:
stream = filter_(stream)
for kind, data, pos in stream:
+ cached = cache_get((kind, data))
+ if cached is not None:
+ yield cached
- if kind is START or kind is EMPTY:
+ elif kind is START or kind is EMPTY:
tag, attrib = data
buf = ['<', tag]
for attr, value in attrib:
buf += [' ', attr, '="', escape(value), '"']
buf.append(kind is EMPTY and '/>' or '>')
- yield Markup(u''.join(buf))
+ yield _emit(kind, data, Markup(u''.join(buf)))
elif kind is END:
- yield Markup('%s>' % data)
+ yield _emit(kind, data, Markup('%s>' % data))
elif kind is TEXT:
if in_cdata:
- yield data
+ yield _emit(kind, data, data)
else:
- yield escape(data, quotes=False)
+ yield _emit(kind, data, escape(data, quotes=False))
elif kind is COMMENT:
- yield Markup('' % data)
+ yield _emit(kind, data, Markup('' % data))
elif kind is XML_DECL and not have_decl:
version, encoding, standalone = data
@@ -259,7 +277,7 @@
in_cdata = False
elif kind is PI:
- yield Markup('%s %s?>' % data)
+ yield _emit(kind, data, Markup('%s %s?>' % data))
class XHTMLSerializer(XMLSerializer):
@@ -283,17 +301,19 @@
])
def __init__(self, doctype=None, strip_whitespace=True,
- namespace_prefixes=None, drop_xml_decl=True):
+ namespace_prefixes=None, drop_xml_decl=True, cache=True):
super(XHTMLSerializer, self).__init__(doctype, False)
self.filters = [EmptyTagFilter()]
if strip_whitespace:
self.filters.append(WhitespaceFilter(self._PRESERVE_SPACE))
namespace_prefixes = namespace_prefixes or {}
namespace_prefixes['http://www.w3.org/1999/xhtml'] = ''
- self.filters.append(NamespaceFlattener(prefixes=namespace_prefixes))
+ self.filters.append(NamespaceFlattener(prefixes=namespace_prefixes,
+ cache=cache))
if doctype:
self.filters.append(DocTypeInserter(doctype))
self.drop_xml_decl = drop_xml_decl
+ self.cache = cache
def __call__(self, stream):
boolean_attrs = self._BOOLEAN_ATTRS
@@ -302,11 +322,24 @@
have_decl = have_doctype = False
in_cdata = False
+ cache = {}
+ cache_get = cache.get
+ if self.cache:
+ def _emit(kind, input, output):
+ cache[kind, input] = output
+ return output
+ else:
+ def _emit(kind, input, output):
+ return output
+
for filter_ in self.filters:
stream = filter_(stream)
for kind, data, pos in stream:
+ cached = cache_get((kind, data))
+ if cached is not None:
+ yield cached
- if kind is START or kind is EMPTY:
+ elif kind is START or kind is EMPTY:
tag, attrib = data
buf = ['<', tag]
for attr, value in attrib:
@@ -324,19 +357,19 @@
buf.append('>%s>' % tag)
else:
buf.append('>')
- yield Markup(u''.join(buf))
+ yield _emit(kind, data, Markup(u''.join(buf)))
elif kind is END:
- yield Markup('%s>' % data)
+ yield _emit(kind, data, Markup('%s>' % data))
elif kind is TEXT:
if in_cdata:
- yield data
+ yield _emit(kind, data, data)
else:
- yield escape(data, quotes=False)
+ yield _emit(kind, data, escape(data, quotes=False))
elif kind is COMMENT:
- yield Markup('' % data)
+ yield _emit(kind, data, Markup('' % data))
elif kind is DOCTYPE and not have_doctype:
name, pubid, sysid = data
@@ -372,7 +405,7 @@
in_cdata = False
elif kind is PI:
- yield Markup('%s %s?>' % data)
+ yield _emit(kind, data, Markup('%s %s?>' % data))
class HTMLSerializer(XHTMLSerializer):
@@ -389,7 +422,7 @@
QName('style'), QName('http://www.w3.org/1999/xhtml}style')
])
- def __init__(self, doctype=None, strip_whitespace=True):
+ def __init__(self, doctype=None, strip_whitespace=True, cache=True):
"""Initialize the HTML serializer.
:param doctype: a ``(name, pubid, sysid)`` tuple that represents the
@@ -397,6 +430,9 @@
of the generated output
:param strip_whitespace: whether extraneous whitespace should be
stripped from the output
+ :param cache: whether to cache the text output per event, which
+ improves performance for repetitive markup
+ :note: Changed in 0.6: The `cache` parameter was added
"""
super(HTMLSerializer, self).__init__(doctype, False)
self.filters = [EmptyTagFilter()]
@@ -405,9 +441,10 @@
self._NOESCAPE_ELEMS))
self.filters.append(NamespaceFlattener(prefixes={
'http://www.w3.org/1999/xhtml': ''
- }))
+ }, cache=cache))
if doctype:
self.filters.append(DocTypeInserter(doctype))
+ self.cache = True
def __call__(self, stream):
boolean_attrs = self._BOOLEAN_ATTRS
@@ -416,11 +453,28 @@
have_doctype = False
noescape = False
+ cache = {}
+ cache_get = cache.get
+ if self.cache:
+ def _emit(kind, input, output):
+ cache[kind, input] = output
+ return output
+ else:
+ def _emit(kind, input, output):
+ return output
+
for filter_ in self.filters:
stream = filter_(stream)
- for kind, data, pos in stream:
+ for kind, data, _ in stream:
+ output = cache_get((kind, data))
+ if output is not None:
+ yield output
+ if kind is START or kind is EMPTY and data[0] in noescape_elems:
+ noescape = True
+ elif kind is END:
+ noescape = False
- if kind is START or kind is EMPTY:
+ elif kind is START or kind is EMPTY:
tag, attrib = data
buf = ['<', tag]
for attr, value in attrib:
@@ -436,22 +490,22 @@
if kind is EMPTY:
if tag not in empty_elems:
buf.append('%s>' % tag)
- yield Markup(u''.join(buf))
+ yield _emit(kind, data, Markup(u''.join(buf)))
if tag in noescape_elems:
noescape = True
elif kind is END:
- yield Markup('%s>' % data)
+ yield _emit(kind, data, Markup('%s>' % data))
noescape = False
elif kind is TEXT:
if noescape:
- yield data
+ yield _emit(kind, data, data)
else:
- yield escape(data, quotes=False)
+ yield _emit(kind, data, escape(data, quotes=False))
elif kind is COMMENT:
- yield Markup('' % data)
+ yield _emit(kind, data, Markup('' % data))
elif kind is DOCTYPE and not have_doctype:
name, pubid, sysid = data
@@ -467,7 +521,7 @@
have_doctype = True
elif kind is PI:
- yield Markup('%s %s?>' % data)
+ yield _emit(kind, data, Markup('%s %s?>' % data))
class TextSerializer(object):
@@ -561,17 +615,41 @@
END u'doc'
"""
- def __init__(self, prefixes=None):
+ def __init__(self, prefixes=None, cache=True):
self.prefixes = {XML_NAMESPACE.uri: 'xml'}
if prefixes is not None:
self.prefixes.update(prefixes)
+ self.cache = cache
def __call__(self, stream):
+ cache = {}
+ cache_get = cache.get
+ if self.cache:
+ def _emit(kind, input, output, pos):
+ cache[kind, input] = output
+ return kind, output, pos
+ else:
+ def _emit(kind, input, output, pos):
+ return output
+
prefixes = dict([(v, [k]) for k, v in self.prefixes.items()])
namespaces = {XML_NAMESPACE.uri: ['xml']}
def _push_ns(prefix, uri):
namespaces.setdefault(uri, []).append(prefix)
prefixes.setdefault(prefix, []).append(uri)
+ cache.clear()
+ def _pop_ns(prefix):
+ uris = prefixes.get(prefix)
+ uri = uris.pop()
+ if not uris:
+ del prefixes[prefix]
+ if uri not in uris or uri != uris[-1]:
+ uri_prefixes = namespaces[uri]
+ uri_prefixes.pop()
+ if not uri_prefixes:
+ del namespaces[uri]
+ cache.clear()
+ return uri
ns_attrs = []
_push_ns_attr = ns_attrs.append
@@ -586,8 +664,11 @@
_gen_prefix = _gen_prefix().next
for kind, data, pos in stream:
+ output = cache_get((kind, data))
+ if output is not None:
+ yield kind, output, pos
- if kind is START or kind is EMPTY:
+ elif kind is START or kind is EMPTY:
tag, attrs = data
tagname = tag.localname
@@ -616,7 +697,7 @@
attrname = u'%s:%s' % (prefix, attrname)
new_attrs.append((attrname, value))
- yield kind, (tagname, Attrs(ns_attrs + new_attrs)), pos
+ yield _emit(kind, data, (tagname, Attrs(ns_attrs + new_attrs)), pos)
del ns_attrs[:]
elif kind is END:
@@ -626,7 +707,7 @@
prefix = namespaces[tagns][-1]
if prefix:
tagname = u'%s:%s' % (prefix, tagname)
- yield kind, tagname, pos
+ yield _emit(kind, data, tagname, pos)
elif kind is START_NS:
prefix, uri = data
@@ -637,15 +718,7 @@
elif kind is END_NS:
if data in prefixes:
- uris = prefixes.get(data)
- uri = uris.pop()
- if not uris:
- del prefixes[data]
- if uri not in uris or uri != uris[-1]:
- uri_prefixes = namespaces[uri]
- uri_prefixes.pop()
- if not uri_prefixes:
- del namespaces[uri]
+ uri = _pop_ns(data)
if ns_attrs:
attr = _make_ns_attr(data, uri)
if attr in ns_attrs:
diff --git a/genshi/path.py b/genshi/path.py
--- a/genshi/path.py
+++ b/genshi/path.py
@@ -65,12 +65,12 @@
DESCENDANT_OR_SELF = 'descendant-or-self'
SELF = 'self'
+ @classmethod
def forname(cls, name):
"""Return the axis constant for the given name, or `None` if no such
axis was defined.
"""
return getattr(cls, name.upper().replace('-', '_'), None)
- forname = classmethod(forname)
ATTRIBUTE = Axis.ATTRIBUTE
@@ -674,8 +674,13 @@
# Tokenizer
- at_end = property(lambda self: self.pos == len(self.tokens) - 1)
- cur_token = property(lambda self: self.tokens[self.pos])
+ @property
+ def at_end(self):
+ return self.pos == len(self.tokens) - 1
+
+ @property
+ def cur_token(self):
+ return self.tokens[self.pos]
def next_token(self):
self.pos += 1
diff --git a/genshi/template/base.py b/genshi/template/base.py
--- a/genshi/template/base.py
+++ b/genshi/template/base.py
@@ -13,12 +13,7 @@
"""Basic templating functionality."""
-try:
- from collections import deque
-except ImportError:
- class deque(list):
- def appendleft(self, x): self.insert(0, x)
- def popleft(self): return self.pop(0)
+from collections import deque
import os
from StringIO import StringIO
import sys
@@ -254,7 +249,7 @@
"""Pop the top-most scope from the stack."""
-def _apply_directives(stream, directives, ctxt, **vars):
+def _apply_directives(stream, directives, ctxt, vars):
"""Apply the given directives to the stream.
:param stream: the stream the directives should be applied to
@@ -268,7 +263,8 @@
stream = directives[0](iter(stream), directives[1:], ctxt, **vars)
return stream
-def _eval_expr(expr, ctxt, **vars):
+
+def _eval_expr(expr, ctxt, vars=None):
"""Evaluate the given `Expression` object.
:param expr: the expression to evaluate
@@ -284,7 +280,8 @@
ctxt.pop()
return retval
-def _exec_suite(suite, ctxt, **vars):
+
+def _exec_suite(suite, ctxt, vars=None):
"""Execute the given `Suite` object.
:param suite: the code suite to execute
@@ -424,12 +421,12 @@
if self.loader:
self.filters.append(self._include)
- def _get_stream(self):
+ @property
+ def stream(self):
if not self._prepared:
self._stream = list(self._prepare(self._stream))
self._prepared = True
return self._stream
- stream = property(_get_stream)
def _parse(self, source, encoding):
"""Parse the template.
@@ -553,22 +550,20 @@
# this point, so do some evaluation
tag, attrs = data
new_attrs = []
- for name, substream in attrs:
- if type(substream) is list:
- values = []
- for event in self._flatten(substream, ctxt, **vars):
- if event[0] is TEXT:
- values.append(event[1])
- value = [x for x in values if x is not None]
- if not value:
+ for name, value in attrs:
+ if type(value) is list: # this is an interpolated string
+ values = [event[1]
+ for event in self._flatten(value, ctxt, **vars)
+ if event[0] is TEXT and event[1] is not None
+ ]
+ if not values:
continue
- else:
- value = substream
- new_attrs.append((name, u''.join(value)))
+ value = u''.join(values)
+ new_attrs.append((name, value))
yield kind, (tag, Attrs(new_attrs)), pos
elif kind is EXPR:
- result = _eval_expr(data, ctxt, **vars)
+ result = _eval_expr(data, ctxt, vars)
if result is not None:
# First check for a string, otherwise the iterable test
# below succeeds, and the string will be chopped up into
@@ -585,12 +580,12 @@
yield TEXT, unicode(result), pos
elif kind is EXEC:
- _exec_suite(data, ctxt, **vars)
+ _exec_suite(data, ctxt, vars)
elif kind is SUB:
# This event is a list of directives and a list of nested
# events to which those directives should be applied
- substream = _apply_directives(data[1], data[0], ctxt, **vars)
+ substream = _apply_directives(data[1], data[0], ctxt, vars)
for event in self._flatten(substream, ctxt, **vars):
yield event
diff --git a/genshi/template/directives.py b/genshi/template/directives.py
--- a/genshi/template/directives.py
+++ b/genshi/template/directives.py
@@ -60,6 +60,7 @@
offset=-1):
self.expr = self._parse_expr(value, template, lineno, offset)
+ @classmethod
def attach(cls, template, stream, value, namespaces, pos):
"""Called after the template stream has been completely parsed.
@@ -80,7 +81,6 @@
stream associated with the directive.
"""
return cls(value, template, namespaces, *pos[1:]), stream
- attach = classmethod(attach)
def __call__(self, stream, directives, ctxt, **vars):
"""Apply the directive to the given stream.
@@ -100,6 +100,7 @@
expr = ' "%s"' % self.expr.source
return '<%s%s>' % (self.__class__.__name__, expr)
+ @classmethod
def _parse_expr(cls, expr, template, lineno=-1, offset=-1):
"""Parses the given expression, raising a useful error message when a
syntax error is encountered.
@@ -112,7 +113,6 @@
cls.tagname)
raise TemplateSyntaxError(err, template.filepath, lineno,
offset + (err.offset or 0))
- _parse_expr = classmethod(_parse_expr)
def _assignment(ast):
@@ -166,7 +166,7 @@
def __call__(self, stream, directives, ctxt, **vars):
def _generate():
kind, (tag, attrib), pos = stream.next()
- attrs = _eval_expr(self.expr, ctxt, **vars)
+ attrs = _eval_expr(self.expr, ctxt, vars)
if attrs:
if isinstance(attrs, Stream):
try:
@@ -182,7 +182,7 @@
for event in stream:
yield event
- return _apply_directives(_generate(), directives, ctxt, **vars)
+ return _apply_directives(_generate(), directives, ctxt, vars)
class ContentDirective(Directive):
@@ -202,6 +202,7 @@
"""
__slots__ = []
+ @classmethod
def attach(cls, template, stream, value, namespaces, pos):
if type(value) is dict:
raise TemplateSyntaxError('The content directive can not be used '
@@ -209,7 +210,6 @@
*pos[1:])
expr = cls._parse_expr(value, template, *pos[1:])
return None, [stream[0], (EXPR, expr, pos), stream[-1]]
- attach = classmethod(attach)
class DefDirective(Directive):
@@ -281,12 +281,12 @@
else:
self.name = ast.id
+ @classmethod
def attach(cls, template, stream, value, namespaces, pos):
if type(value) is dict:
value = value.get('function')
return super(DefDirective, cls).attach(template, stream, value,
namespaces, pos)
- attach = classmethod(attach)
def __call__(self, stream, directives, ctxt, **vars):
stream = list(stream)
@@ -301,14 +301,14 @@
if name in kwargs:
val = kwargs.pop(name)
else:
- val = _eval_expr(self.defaults.get(name), ctxt, **vars)
+ val = _eval_expr(self.defaults.get(name), ctxt, vars)
scope[name] = val
if not self.star_args is None:
scope[self.star_args] = args
if not self.dstar_args is None:
scope[self.dstar_args] = kwargs
ctxt.push(scope)
- for event in _apply_directives(stream, directives, ctxt, **vars):
+ for event in _apply_directives(stream, directives, ctxt, vars):
yield event
ctxt.pop()
function.__name__ = self.name
@@ -350,15 +350,15 @@
self.filename = template.filepath
Directive.__init__(self, value, template, namespaces, lineno, offset)
+ @classmethod
def attach(cls, template, stream, value, namespaces, pos):
if type(value) is dict:
value = value.get('each')
return super(ForDirective, cls).attach(template, stream, value,
namespaces, pos)
- attach = classmethod(attach)
def __call__(self, stream, directives, ctxt, **vars):
- iterable = _eval_expr(self.expr, ctxt, **vars)
+ iterable = _eval_expr(self.expr, ctxt, vars)
if iterable is None:
return
@@ -368,7 +368,7 @@
for item in iterable:
assign(scope, item)
ctxt.push(scope)
- for event in _apply_directives(stream, directives, ctxt, **vars):
+ for event in _apply_directives(stream, directives, ctxt, vars):
yield event
ctxt.pop()
@@ -391,17 +391,17 @@
"""
__slots__ = []
+ @classmethod
def attach(cls, template, stream, value, namespaces, pos):
if type(value) is dict:
value = value.get('test')
return super(IfDirective, cls).attach(template, stream, value,
namespaces, pos)
- attach = classmethod(attach)
def __call__(self, stream, directives, ctxt, **vars):
- value = _eval_expr(self.expr, ctxt, **vars)
+ value = _eval_expr(self.expr, ctxt, vars)
if value:
- return _apply_directives(stream, directives, ctxt, **vars)
+ return _apply_directives(stream, directives, ctxt, vars)
return []
@@ -431,6 +431,7 @@
self.namespaces = namespaces or {}
self.hints = hints or ()
+ @classmethod
def attach(cls, template, stream, value, namespaces, pos):
hints = []
if type(value) is dict:
@@ -443,7 +444,6 @@
value = value.get('path')
return cls(value, template, frozenset(hints), namespaces, *pos[1:]), \
stream
- attach = classmethod(attach)
def __call__(self, stream, directives, ctxt, **vars):
ctxt._match_templates.append((self.path.test(ignore_context=True),
@@ -483,6 +483,7 @@
"""
__slots__ = []
+ @classmethod
def attach(cls, template, stream, value, namespaces, pos):
if type(value) is dict:
value = value.get('value')
@@ -491,7 +492,6 @@
template.filepath, *pos[1:])
expr = cls._parse_expr(value, template, *pos[1:])
return None, [(EXPR, expr, pos)]
- attach = classmethod(attach)
class StripDirective(Directive):
@@ -529,7 +529,7 @@
def __call__(self, stream, directives, ctxt, **vars):
def _generate():
- if _eval_expr(self.expr, ctxt, **vars):
+ if _eval_expr(self.expr, ctxt, vars):
stream.next() # skip start tag
previous = stream.next()
for event in stream:
@@ -538,14 +538,14 @@
else:
for event in stream:
yield event
- return _apply_directives(_generate(), directives, ctxt, **vars)
+ return _apply_directives(_generate(), directives, ctxt, vars)
+ @classmethod
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):
@@ -589,19 +589,19 @@
"""
__slots__ = ['matched', 'value']
+ @classmethod
def attach(cls, template, stream, value, namespaces, pos):
if type(value) is dict:
value = value.get('test')
return super(ChooseDirective, cls).attach(template, stream, value,
namespaces, pos)
- attach = classmethod(attach)
def __call__(self, stream, directives, ctxt, **vars):
info = [False, bool(self.expr), None]
if self.expr:
- info[2] = _eval_expr(self.expr, ctxt, **vars)
+ info[2] = _eval_expr(self.expr, ctxt, vars)
ctxt._choice_stack.append(info)
- for event in _apply_directives(stream, directives, ctxt, **vars):
+ for event in _apply_directives(stream, directives, ctxt, vars):
yield event
ctxt._choice_stack.pop()
@@ -618,12 +618,12 @@
Directive.__init__(self, value, template, namespaces, lineno, offset)
self.filename = template.filepath
+ @classmethod
def attach(cls, template, stream, value, namespaces, pos):
if type(value) is dict:
value = value.get('test')
return super(WhenDirective, cls).attach(template, stream, value,
namespaces, pos)
- attach = classmethod(attach)
def __call__(self, stream, directives, ctxt, **vars):
info = ctxt._choice_stack and ctxt._choice_stack[-1]
@@ -640,16 +640,16 @@
if info[1]:
value = info[2]
if self.expr:
- matched = value == _eval_expr(self.expr, ctxt, **vars)
+ matched = value == _eval_expr(self.expr, ctxt, vars)
else:
matched = bool(value)
else:
- matched = bool(_eval_expr(self.expr, ctxt, **vars))
+ matched = bool(_eval_expr(self.expr, ctxt, vars))
info[0] = matched
if not matched:
return []
- return _apply_directives(stream, directives, ctxt, **vars)
+ return _apply_directives(stream, directives, ctxt, vars)
class OtherwiseDirective(Directive):
@@ -674,7 +674,7 @@
return []
info[0] = True
- return _apply_directives(stream, directives, ctxt, **vars)
+ return _apply_directives(stream, directives, ctxt, vars)
class WithDirective(Directive):
@@ -712,21 +712,21 @@
raise TemplateSyntaxError(err, template.filepath, lineno,
offset + (err.offset or 0))
+ @classmethod
def attach(cls, template, stream, value, namespaces, pos):
if type(value) is dict:
value = value.get('vars')
return super(WithDirective, cls).attach(template, stream, value,
namespaces, pos)
- attach = classmethod(attach)
def __call__(self, stream, directives, ctxt, **vars):
frame = {}
ctxt.push(frame)
for targets, expr in self.vars:
- value = _eval_expr(expr, ctxt, **vars)
+ value = _eval_expr(expr, ctxt, vars)
for _, assign in targets:
assign(frame, value)
- for event in _apply_directives(stream, directives, ctxt, **vars):
+ for event in _apply_directives(stream, directives, ctxt, vars):
yield event
ctxt.pop()
diff --git a/genshi/template/eval.py b/genshi/template/eval.py
--- a/genshi/template/eval.py
+++ b/genshi/template/eval.py
@@ -281,6 +281,7 @@
class LookupBase(object):
"""Abstract base class for variable lookup implementations."""
+ @classmethod
def globals(cls, data):
"""Construct the globals dictionary to use as the execution context for
the expression or suite.
@@ -293,8 +294,8 @@
'_star_import_patch': _star_import_patch,
'UndefinedError': UndefinedError,
}
- globals = classmethod(globals)
+ @classmethod
def lookup_name(cls, data, name):
__traceback_hide__ = True
val = data.get(name, UNDEFINED)
@@ -303,8 +304,8 @@
if val is UNDEFINED:
val = cls.undefined(name)
return val
- lookup_name = classmethod(lookup_name)
+ @classmethod
def lookup_attr(cls, obj, key):
__traceback_hide__ = True
try:
@@ -318,8 +319,8 @@
except (KeyError, TypeError):
val = cls.undefined(key, owner=obj)
return val
- lookup_attr = classmethod(lookup_attr)
+ @classmethod
def lookup_item(cls, obj, key):
__traceback_hide__ = True
if len(key) == 1:
@@ -333,8 +334,8 @@
val = cls.undefined(key, owner=obj)
return val
raise
- lookup_item = classmethod(lookup_item)
+ @classmethod
def undefined(cls, key, owner=UNDEFINED):
"""Can be overridden by subclasses to specify behavior when undefined
variables are accessed.
@@ -343,7 +344,6 @@
:param owner: the owning object, if the variable is accessed as a member
"""
raise NotImplementedError
- undefined = classmethod(undefined)
class LenientLookup(LookupBase):
@@ -369,11 +369,12 @@
:see: `StrictLookup`
"""
+
+ @classmethod
def undefined(cls, key, owner=UNDEFINED):
"""Return an ``Undefined`` object."""
__traceback_hide__ = True
return Undefined(key, owner=owner)
- undefined = classmethod(undefined)
class StrictLookup(LookupBase):
@@ -397,11 +398,12 @@
...
UndefinedError: {} has no member named "nil"
"""
+
+ @classmethod
def undefined(cls, key, owner=UNDEFINED):
"""Raise an ``UndefinedError`` immediately."""
__traceback_hide__ = True
raise UndefinedError(key, owner=owner)
- undefined = classmethod(undefined)
def _parse(source, mode='eval'):
diff --git a/genshi/template/loader.py b/genshi/template/loader.py
--- a/genshi/template/loader.py
+++ b/genshi/template/loader.py
@@ -264,6 +264,7 @@
encoding=encoding, lookup=self.variable_lookup,
allow_exec=self.allow_exec)
+ @staticmethod
def directory(path):
"""Loader factory for loading templates from a local directory.
@@ -279,8 +280,8 @@
return mtime == os.path.getmtime(filepath)
return filepath, filename, fileobj, _uptodate
return _load_from_directory
- directory = staticmethod(directory)
+ @staticmethod
def package(name, path):
"""Loader factory for loading templates from egg package data.
@@ -294,8 +295,8 @@
filepath = os.path.join(path, filename)
return filepath, filename, resource_stream(name, filepath), None
return _load_from_package
- package = staticmethod(package)
+ @staticmethod
def prefixed(**delegates):
"""Factory for a load function that delegates to other loaders
depending on the prefix of the requested template path.
@@ -327,7 +328,7 @@
return filepath, filename, fileobj, uptodate
raise TemplateNotFound(filename, delegates.keys())
return _dispatch_by_prefix
- prefixed = staticmethod(prefixed)
+
directory = TemplateLoader.directory
package = TemplateLoader.package
diff --git a/genshi/template/markup.py b/genshi/template/markup.py
--- a/genshi/template/markup.py
+++ b/genshi/template/markup.py
@@ -356,7 +356,7 @@
pre_end -= 1
inner = _strip(stream)
if pre_end > 0:
- inner = self._match(inner, ctxt, end=pre_end)
+ inner = self._match(inner, ctxt, end=pre_end, **vars)
content = self._include(chain([event], inner, tail), ctxt)
if 'not_buffered' not in hints:
content = list(content)
@@ -371,7 +371,7 @@
# Recursively process the output
template = _apply_directives(template, directives, ctxt,
- **vars)
+ vars)
for event in self._match(self._flatten(template, ctxt,
**vars),
ctxt, start=idx + 1, **vars):
diff --git a/genshi/template/tests/markup.py b/genshi/template/tests/markup.py
--- a/genshi/template/tests/markup.py
+++ b/genshi/template/tests/markup.py
@@ -75,6 +75,10 @@
tmpl = MarkupTemplate('