refactored extensions a bit
authorArmin Ronacher <armin.ronacher@active-4.com>
Sun, 20 Apr 2008 11:27:49 +0000 (13:27 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sun, 20 Apr 2008 11:27:49 +0000 (13:27 +0200)
--HG--
branch : trunk

examples/translate.py
jinja2/defaults.py
jinja2/environment.py
jinja2/ext.py [new file with mode: 0644]
jinja2/i18n.py
jinja2/parser.py

index 0169c88fb727e524f9e3c0211c6ebd09c528d54e..335876583e1e75a388af7f4f2949002d8527707e 100644 (file)
@@ -1,6 +1,6 @@
 from jinja2 import Environment
 
-print Environment(parser_extensions=['jinja2.i18n.trans']).from_string("""\
+print Environment(extensions=['jinja2.i18n.TransExtension']).from_string("""\
 {% trans %}Hello {{ user }}!{% endtrans %}
 {% trans count=users|count %}{{ count }} user{% pluralize %}{{ count }} users{% endtrans %}
 """).render(user="someone")
index ee698a0873edbb906026e717bc33233e3a7f0b39..6462f32ef80d4667412cec9a67e7377edd761295 100644 (file)
@@ -13,9 +13,5 @@ from jinja2.tests import TESTS as DEFAULT_TESTS
 
 
 DEFAULT_NAMESPACE = {
-    'range':        xrange,
-    # fake translators so that {% trans %} is a noop by default
-    '_':            lambda x: x,
-    'gettext':      lambda x: x,
-    'ngettext':     lambda s, p, n: (s, p)[n != 1]
+    'range':        xrange
 }
index 004ef74888c47c2a2d4b43dbdde648a9c2f30773..5b705e40c65af30213334fb4778c3d7387975cf1 100644 (file)
@@ -10,7 +10,7 @@
 """
 import sys
 from jinja2.lexer import Lexer
-from jinja2.parser import Parser, ParserExtension
+from jinja2.parser import Parser
 from jinja2.optimizer import optimize
 from jinja2.compiler import generate
 from jinja2.runtime import Undefined
@@ -44,7 +44,7 @@ class Environment(object):
                  optimized=True,
                  undefined=Undefined,
                  loader=None,
-                 parser_extensions=(),
+                 extensions=(),
                  finalize=unicode):
         """Here the possible initialization parameters:
 
@@ -70,7 +70,7 @@ class Environment(object):
         `undefined`               a subclass of `Undefined` that is used to
                                   represent undefined variables.
         `loader`                  the loader which should be used.
-        `parser_extensions`       List of parser extensions to use.
+        `extensions`              List of Jinja extensions to use.
         `finalize`                A callable that finalizes the variable.  Per
                                   default this is `unicode`, other useful
                                   builtin finalizers are `escape`.
@@ -93,12 +93,11 @@ class Environment(object):
         self.comment_end_string = comment_end_string
         self.line_statement_prefix = line_statement_prefix
         self.trim_blocks = trim_blocks
-        self.parser_extensions = {}
-        for extension in parser_extensions:
+        self.extensions = []
+        for extension in extensions:
             if isinstance(extension, basestring):
                 extension = import_string(extension)
-            self.parser_extensions[extension.tag] = extension
-
+            self.extensions.append(extension(self))
 
         # runtime information
         self.undefined = undefined
@@ -109,6 +108,8 @@ class Environment(object):
         self.filters = DEFAULT_FILTERS.copy()
         self.tests = DEFAULT_TESTS.copy()
         self.globals = DEFAULT_NAMESPACE.copy()
+        for extension in self.extensions:
+            extension.update_globals(self.globals)
 
         # set the loader provided
         self.loader = loader
diff --git a/jinja2/ext.py b/jinja2/ext.py
new file mode 100644 (file)
index 0000000..75952ed
--- /dev/null
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+"""
+    jinja2.ext
+    ~~~~~~~~~~
+
+    Jinja extensions (EXPERIMENAL)
+
+    The plan: i18n and caching becomes a parser extension. cache/endcache
+    as well as trans/endtrans are not keyword and don't have nodes but
+    translate into regular jinja nodes so that the person who writes such
+    custom tags doesn't have to generate python code himself.
+
+    :copyright: Copyright 2008 by Armin Ronacher.
+    :license: BSD.
+"""
+from jinja2 import nodes
+
+
+class Extension(object):
+    """Instances of this class store parser extensions."""
+
+    #: if this extension parses this is the list of tags it's listening to.
+    tags = set()
+
+    def __init__(self, environment):
+        self.environment = environment
+
+    def update_globals(self, globals):
+        """Called to inject runtime variables into the globals."""
+        pass
+
+    def parse(self, parser):
+        """Called if one of the tags matched."""
+
+
+class CacheExtension(Extension):
+    tags = set(['cache'])
+
+    def parse(self, parser):
+        lineno = parser.stream.next().lineno
+        args = [parser.parse_expression()]
+        if self.stream.current.type is 'comma':
+            args.append(parser.parse_expression())
+        body = parser.parse_statements(('name:endcache',), drop_needle=True)
+        return nodes.CallBlock(
+            nodes.Call(nodes.Name('cache_support'), args, [], None, None),
+            [], [], body
+        )
index 026750bc98b0d6c84b514dd21a5ce59a03c99a7d..ef932d975093e781b572d9dc64e98149522082fc 100644 (file)
@@ -11,7 +11,8 @@
 from collections import deque
 from jinja2 import nodes
 from jinja2.environment import Environment
-from jinja2.parser import ParserExtension, statement_end_tokens
+from jinja2.parser import statement_end_tokens
+from jinja2.ext import Extension
 from jinja2.exceptions import TemplateAssertionError
 
 
@@ -81,149 +82,154 @@ def babel_extract(fileobj, keywords, comment_tags, options):
         yield lineno, func, message, []
 
 
-def parse_trans(parser):
-    """Parse a translatable tag."""
-    lineno = parser.stream.next().lineno
+class TransExtension(Extension):
+    tags = set(['trans'])
 
-    # skip colon for python compatibility
-    if parser.stream.current.type is 'colon':
-        parser.stream.next()
+    def update_globals(self, globals):
+        """Inject noop translation functions."""
+        globals.update({
+            '_':        lambda x: x,
+            'gettext':  lambda x: x,
+            'ngettext': lambda s, p, n: (s, p)[n != 1]
+        })
 
-    # 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.
-    plural_expr = None
-    variables = {}
-    while parser.stream.current.type is not 'block_end':
-        if variables:
-            parser.stream.expect('comma')
-        name = parser.stream.expect('name')
-        if name.value in variables:
-            raise TemplateAssertionError('translatable variable %r defined '
-                                         'twice.' % name.value, name.lineno,
-                                         parser.filename)
-
-        # expressions
-        if parser.stream.current.type is 'assign':
-            parser.stream.next()
-            variables[name.value] = var = parser.parse_expression()
-        else:
-            variables[name.value] = var = nodes.Name(name.value, 'load')
-        if plural_expr is None:
-            plural_expr = var
-    parser.stream.expect('block_end')
+    def parse(self, parser):
+        """Parse a translatable tag."""
+        lineno = parser.stream.next().lineno
 
-    plural = plural_names = None
-    have_plural = False
-    referenced = set()
+        # skip colon for python compatibility
+        if parser.stream.current.type is 'colon':
+            parser.stream.next()
 
-    # now parse until endtrans or pluralize
-    singular_names, singular = _parse_block(parser, True)
-    if singular_names:
-        referenced.update(singular_names)
-        if plural_expr is None:
-            plural_expr = nodes.Name(singular_names[0], 'load')
-
-    # if we have a pluralize block, we parse that too
-    if parser.stream.current.test('name:pluralize'):
-        have_plural = True
-        parser.stream.next()
-        if parser.stream.current.type is not 'block_end':
-            plural_expr = parser.parse_expression()
-        parser.stream.expect('block_end')
-        plural_names, plural = _parse_block(parser, False)
-        parser.stream.next()
-        referenced.update(plural_names)
-    else:
-        parser.stream.next()
-        parser.end_statement()
-
-    # register free names as simple name expressions
-    for var in referenced:
-        if var not in variables:
-            variables[var] = nodes.Name(var, 'load')
-
-    # no variables referenced?  no need to escape
-    if not referenced:
-        singular = singular.replace('%%', '%')
-        if plural:
-            plural = plural.replace('%%', '%')
-
-    if not have_plural:
-        if plural_expr is None:
-            raise TemplateAssertionError('pluralize without variables',
-                                         lineno, parser.filename)
+        # 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.
         plural_expr = None
+        variables = {}
+        while parser.stream.current.type is not 'block_end':
+            if variables:
+                parser.stream.expect('comma')
+            name = parser.stream.expect('name')
+            if name.value in variables:
+                raise TemplateAssertionError('translatable variable %r defined '
+                                             'twice.' % name.value, name.lineno,
+                                             parser.filename)
+
+            # expressions
+            if parser.stream.current.type is 'assign':
+                parser.stream.next()
+                variables[name.value] = var = parser.parse_expression()
+            else:
+                variables[name.value] = var = nodes.Name(name.value, 'load')
+            if plural_expr is None:
+                plural_expr = var
+        parser.stream.expect('block_end')
 
-    if variables:
-        variables = nodes.Dict([nodes.Pair(nodes.Const(x, lineno=lineno), y)
-                                for x, y in variables.items()])
-    else:
-        vairables = None
-
-    node = _make_node(singular, plural, variables, plural_expr)
-    node.set_lineno(lineno)
-    return node
+        plural = plural_names = None
+        have_plural = False
+        referenced = set()
 
+        # now parse until endtrans or pluralize
+        singular_names, singular = self._parse_block(parser, True)
+        if singular_names:
+            referenced.update(singular_names)
+            if plural_expr is None:
+                plural_expr = nodes.Name(singular_names[0], 'load')
 
-def _parse_block(parser, allow_pluralize):
-    """Parse until the next block tag with a given name."""
-    referenced = []
-    buf = []
-    while 1:
-        if parser.stream.current.type is 'data':
-            buf.append(parser.stream.current.value.replace('%', '%%'))
+        # if we have a pluralize block, we parse that too
+        if parser.stream.current.test('name:pluralize'):
+            have_plural = True
             parser.stream.next()
-        elif parser.stream.current.type is 'variable_begin':
+            if parser.stream.current.type is not 'block_end':
+                plural_expr = parser.parse_expression()
+            parser.stream.expect('block_end')
+            plural_names, plural = self._parse_block(parser, False)
             parser.stream.next()
-            name = parser.stream.expect('name').value
-            referenced.append(name)
-            buf.append('%%(%s)s' % name)
-            parser.stream.expect('variable_end')
-        elif parser.stream.current.type is 'block_begin':
-            parser.stream.next()
-            if parser.stream.current.test('name:endtrans'):
-                break
-            elif parser.stream.current.test('name:pluralize'):
-                if allow_pluralize:
-                    break
-                raise TemplateSyntaxError('a translatable section can have '
-                                          'only one pluralize section',
-                                          parser.stream.current.lineno,
-                                          parser.filename)
-            raise TemplateSyntaxError('control structures in translatable '
-                                      'sections are not allowed.',
-                                      parser.stream.current.lineno,
-                                      parser.filename)
+            referenced.update(plural_names)
         else:
-            assert False, 'internal parser error'
+            parser.stream.next()
+            parser.end_statement()
 
-    return referenced, u''.join(buf)
+        # register free names as simple name expressions
+        for var in referenced:
+            if var not in variables:
+                variables[var] = nodes.Name(var, 'load')
 
+        # no variables referenced?  no need to escape
+        if not referenced:
+            singular = singular.replace('%%', '%')
+            if plural:
+                plural = plural.replace('%%', '%')
+
+        if not have_plural:
+            if plural_expr is None:
+                raise TemplateAssertionError('pluralize without variables',
+                                             lineno, parser.filename)
+            plural_expr = None
 
-def _make_node(singular, plural, variables, plural_expr):
-    """Generates a useful node from the data provided.  The nodes won't have
-    a line number information so the caller must set it via `set_lineno` later.
-    """
-    # singular only:
-    if plural_expr is None:
-        gettext = nodes.Name('gettext', 'load')
-        node = nodes.Call(gettext, [nodes.Const(singular)],
-                          [], None, None)
-        if variables:
-            node = nodes.Mod(node, variables)
-
-    # singular and plural
-    else:
-        ngettext = nodes.Name('ngettext', 'load')
-        node = nodes.Call(ngettext, [
-            nodes.Const(singular),
-            nodes.Const(plural),
-            plural_expr
-        ], [], None, None)
         if variables:
-            node = nodes.Mod(node, variables)
-    return nodes.Output([node])
+            variables = nodes.Dict([nodes.Pair(nodes.Const(x, lineno=lineno), y)
+                                    for x, y in variables.items()])
+        else:
+            vairables = None
+
+        node = self._make_node(singular, plural, variables, plural_expr)
+        node.set_lineno(lineno)
+        return node
+
+    def _parse_block(self, parser, allow_pluralize):
+        """Parse until the next block tag with a given name."""
+        referenced = []
+        buf = []
+        while 1:
+            if parser.stream.current.type is 'data':
+                buf.append(parser.stream.current.value.replace('%', '%%'))
+                parser.stream.next()
+            elif parser.stream.current.type is 'variable_begin':
+                parser.stream.next()
+                name = parser.stream.expect('name').value
+                referenced.append(name)
+                buf.append('%%(%s)s' % name)
+                parser.stream.expect('variable_end')
+            elif parser.stream.current.type is 'block_begin':
+                parser.stream.next()
+                if parser.stream.current.test('name:endtrans'):
+                    break
+                elif parser.stream.current.test('name:pluralize'):
+                    if allow_pluralize:
+                        break
+                    raise TemplateSyntaxError('a translatable section can '
+                                              'have only one pluralize '
+                                              'section',
+                                              parser.stream.current.lineno,
+                                              parser.filename)
+                raise TemplateSyntaxError('control structures in translatable'
+                                          ' sections are not allowed.',
+                                          parser.stream.current.lineno,
+                                          parser.filename)
+            else:
+                assert False, 'internal parser error'
 
+        return referenced, u''.join(buf)
 
-trans = ParserExtension('trans', parse_trans)
+    def _make_node(self, singular, plural, variables, plural_expr):
+        """Generates a useful node from the data provided."""
+        # singular only:
+        if plural_expr is None:
+            gettext = nodes.Name('gettext', 'load')
+            node = nodes.Call(gettext, [nodes.Const(singular)],
+                              [], None, None)
+            if variables:
+                node = nodes.Mod(node, variables)
+
+        # singular and plural
+        else:
+            ngettext = nodes.Name('ngettext', 'load')
+            node = nodes.Call(ngettext, [
+                nodes.Const(singular),
+                nodes.Const(plural),
+                plural_expr
+            ], [], None, None)
+            if variables:
+                node = nodes.Mod(node, variables)
+        return nodes.Output([node])
index 4204c6aae9418a1f534479959e8556aeab6b1007..27f54ef1c14397562081a7cb449c6261d72413c9 100644 (file)
@@ -35,7 +35,10 @@ class Parser(object):
         self.filename = filename
         self.closed = False
         self.stream = environment.lexer.tokenize(source, filename)
-        self.extensions = environment.parser_extensions
+        self.extensions = {}
+        for extension in environment.extensions:
+            for tag in extension.tags:
+                self.extensions[tag] = extension.parse
 
     def end_statement(self):
         """Make sure that the statement ends properly."""
@@ -686,20 +689,3 @@ class Parser(object):
         result = nodes.Template(self.subparse(), lineno=1)
         result.set_environment(self.environment)
         return result
-
-
-class ParserExtension(tuple):
-    """Instances of this class store parser extensions."""
-    __slots__ = ()
-
-    def __new__(cls, tag, parse_func):
-        return tuple.__new__(cls, (tag, parse_func))
-
-    def __call__(self, parser):
-        return self.parse_func(parser)
-
-    def __repr__(self):
-        return '<%s %r>' % (self.__class__.__name__, self.tag)
-
-    tag = property(itemgetter(0))
-    parse_func = property(itemgetter(1))