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.