more updates on the extension API
--HG--
branch : trunk
diff --git a/jinja2/environment.py b/jinja2/environment.py
index e771dd6..2fbe217 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -222,6 +222,15 @@
_environment_sanity_check(self)
+ def extend(self, **attributes):
+ """Add the items to the instance of the environment if they do not exist
+ yet. This is used by :ref:`extensions <writing-extensions>` to register
+ callbacks and configuration values without breaking inheritance.
+ """
+ for key, value in attributes.iteritems():
+ if not hasattr(self, key):
+ setattr(self, key, value)
+
def overlay(self, block_start_string=missing, block_end_string=missing,
variable_start_string=missing, variable_end_string=missing,
comment_start_string=missing, comment_end_string=missing,
diff --git a/jinja2/ext.py b/jinja2/ext.py
index 22adf82..f60b85a 100644
--- a/jinja2/ext.py
+++ b/jinja2/ext.py
@@ -40,6 +40,17 @@
may not store environment specific data on `self`. The reason for this is
that an extension can be bound to another environment (for overlays) by
creating a copy and reassigning the `environment` attribute.
+
+ As extensions are created by the environment they cannot accept any
+ arguments for configuration. One may want to work around that by using
+ a factory function, but that is not possible as extensions are identified
+ by their import name. The correct way to configure the extension is
+ storing the configuration values on the environment. Because this way the
+ environment ends up acting as central configuration storage the
+ attributes may clash which is why extensions have to ensure that the names
+ they choose for configuration are not too generic. ``prefix`` for example
+ is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
+ name as includes the name of the extension (fragment cache).
"""
__metaclass__ = ExtensionRegistry
@@ -74,49 +85,40 @@
return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
-class CacheExtension(Extension):
- """An example extension that adds cacheable blocks."""
- tags = set(['cache'])
-
- def __init__(self, environment):
- Extension.__init__(self, environment)
- environment.globals['__cache_ext_support'] = self.cache_support
-
- def cache_support(self, name, timeout, caller):
- """Helper for the cache_fragment function."""
- if not hasattr(environment, 'cache_support'):
- return caller()
- args = [name]
- if timeout is not None:
- args.append(timeout)
- return self.environment.cache_support(generate=caller, *args)
-
- def parse(self, parser):
- lineno = parser.stream.next().lineno
- args = [parser.parse_expression()]
- if parser.stream.current.type is 'comma':
- parser.stream.next()
- args.append(parser.parse_expression())
- else:
- args.append(nodes.Const(None, lineno=lineno))
- body = parser.parse_statements(('name:endcache',), drop_needle=True)
- return nodes.CallBlock(
- nodes.Call(nodes.Name('__cache_ext_support', 'load', lineno=lineno),
- args, [], None, None), [], [], body, lineno=lineno
- )
-
-
class InternationalizationExtension(Extension):
- """This extension adds gettext support to Jinja."""
+ """This extension adds gettext support to Jinja2."""
tags = set(['trans'])
def __init__(self, environment):
Extension.__init__(self, environment)
- environment.globals.update({
- '_': contextfunction(lambda c, x: c['gettext'](x)),
- 'gettext': lambda x: x,
- 'ngettext': lambda s, p, n: (s, p)[n != 1]
- })
+ environment.globals['_'] = contextfunction(lambda c, x: c['gettext'](x))
+ environment.extend(
+ install_gettext_translations=self._install,
+ install_null_translations=self._install_null,
+ uninstall_gettext_translations=self._uninstall,
+ extract_translations=self._extract
+ )
+
+ def _install(self, translations):
+ self.environment.globals.update(
+ gettext=translations.ugettext,
+ ngettext=translations.ungettext
+ )
+
+ def _install_null(self):
+ self.environment.globals.update(
+ gettext=lambda x: x,
+ ngettext=lambda s, p, n: (n != 1 and (p,) or (s,))[0]
+ )
+
+ def _uninstall(self, translations):
+ for key in 'gettext', 'ngettext':
+ self.environment.globals.pop(key, None)
+
+ def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
+ if isinstance(source, basestring):
+ source = self.environment.parse(source)
+ return extract_from_ast(source, gettext_functions)
def parse(self, parser):
"""Parse a translatable tag."""
@@ -132,7 +134,7 @@
parser.stream.expect('comma')
# skip colon for python compatibility
- if parser.ignore_colon():
+ if parser.skip_colon():
break
name = parser.stream.expect('name')
diff --git a/jinja2/nodes.py b/jinja2/nodes.py
index 0d921a7..2519682 100644
--- a/jinja2/nodes.py
+++ b/jinja2/nodes.py
@@ -742,7 +742,8 @@
class InternalName(Expr):
"""An internal name in the compiler. You cannot create these nodes
- yourself but the parser provides a `free_identifier` method that creates
+ yourself but the parser provides a
+ :meth:`~jinja2.parser.Parser.free_identifier` method that creates
a new identifier for you. This identifier is not available from the
template and is not threated specially by the compiler.
"""
diff --git a/jinja2/parser.py b/jinja2/parser.py
index 91a848b..427cb05 100644
--- a/jinja2/parser.py
+++ b/jinja2/parser.py
@@ -42,13 +42,20 @@
'rparen') or \
self.stream.current.test('name:in')
- def ignore_colon(self):
+ def skip_colon(self):
"""If there is a colon, skip it and return `True`, else `False`."""
if self.stream.current.type is 'colon':
self.stream.next()
return True
return False
+ def skip_comma(self):
+ """If there is a comma, skip it and return `True`, else `False`."""
+ if self.stream.current.type is 'comma':
+ self.stream.next()
+ return True
+ return False
+
def free_identifier(self, lineno=None):
"""Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
self._last_identifier += 1
@@ -100,7 +107,7 @@
can be set to `True` and the end token is removed.
"""
# the first token may be a colon for python compatibility
- self.ignore_colon()
+ self.skip_colon()
# in the future it would be possible to add whole code sections
# by adding some sort of end of statement token and parsing those here.