First extension interface documentation and updates in that interface

--HG--
branch : trunk
diff --git a/jinja2/ext.py b/jinja2/ext.py
index 5d2251d..22adf82 100644
--- a/jinja2/ext.py
+++ b/jinja2/ext.py
@@ -24,6 +24,15 @@
 GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
 
 
+class ExtensionRegistry(type):
+    """Gives the extension a unique identifier."""
+
+    def __new__(cls, name, bases, d):
+        rv = type.__new__(cls, name, bases, d)
+        rv.identifier = rv.__module__ + '.' + rv.__name__
+        return rv
+
+
 class Extension(object):
     """Extensions can be used to add extra functionality to the Jinja template
     system at the parser level.  This is a supported but currently
@@ -32,6 +41,7 @@
     that an extension can be bound to another environment (for overlays) by
     creating a copy and reassigning the `environment` attribute.
     """
+    __metaclass__ = ExtensionRegistry
 
     #: if this extension parses this is the list of tags it's listening to.
     tags = set()
@@ -47,7 +57,21 @@
         return rv
 
     def parse(self, parser):
-        """Called if one of the tags matched."""
+        """If any of the :attr:`tags` matched this method is called with the
+        parser as first argument.  The token the parser stream is pointing at
+        is the name token that matched.  This method has to return one or a
+        list of multiple nodes.
+        """
+
+    def attr(self, name, lineno=None):
+        """Return an attribute node for the current extension.  This is useful
+        to pass callbacks to template code::
+
+            nodes.Call(self.attr('_my_callback'), args, kwargs, None, None)
+
+        That would call `self._my_callback` when the template is evaluated.
+        """
+        return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
 
 
 class CacheExtension(Extension):
@@ -56,10 +80,16 @@
 
     def __init__(self, environment):
         Extension.__init__(self, environment)
-        def dummy_cache_support(name, timeout=None, caller=None):
-            if caller is not None:
-                return caller()
-        environment.globals['cache_support'] = dummy_cache_support
+        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
@@ -67,10 +97,12 @@
         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_support', 'load'), args, [], None, None),
-            [], [], body
+            nodes.Call(nodes.Name('__cache_ext_support', 'load', lineno=lineno),
+            args, [], None, None), [], [], body, lineno=lineno
         )
 
 
@@ -90,10 +122,6 @@
         """Parse a translatable tag."""
         lineno = parser.stream.next().lineno
 
-        # skip colon for python compatibility
-        if parser.stream.current.type is 'colon':
-            parser.stream.next()
-
         # find all the variables referenced.  Additionally a variable can be
         # defined in the body of the trans block too, but this is checked at
         # a later state.
@@ -102,6 +130,11 @@
         while parser.stream.current.type is not 'block_end':
             if variables:
                 parser.stream.expect('comma')
+
+            # skip colon for python compatibility
+            if parser.ignore_colon():
+                break
+
             name = parser.stream.expect('name')
             if name.value in variables:
                 raise TemplateAssertionError('translatable variable %r defined '
@@ -116,6 +149,7 @@
                 variables[name.value] = var = nodes.Name(name.value, 'load')
             if plural_expr is None:
                 plural_expr = var
+
         parser.stream.expect('block_end')
 
         plural = plural_names = None
@@ -314,4 +348,3 @@
 
 #: nicer import names
 i18n = InternationalizationExtension
-cache = CacheExtension