refactored extensions a bit
--HG--
branch : trunk
diff --git a/examples/translate.py b/examples/translate.py
index 0169c88..3358765 100644
--- a/examples/translate.py
+++ b/examples/translate.py
@@ -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")
diff --git a/jinja2/defaults.py b/jinja2/defaults.py
index ee698a0..6462f32 100644
--- a/jinja2/defaults.py
+++ b/jinja2/defaults.py
@@ -13,9 +13,5 @@
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
}
diff --git a/jinja2/environment.py b/jinja2/environment.py
index 004ef74..5b705e4 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -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 @@
optimized=True,
undefined=Undefined,
loader=None,
- parser_extensions=(),
+ extensions=(),
finalize=unicode):
"""Here the possible initialization parameters:
@@ -70,7 +70,7 @@
`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 @@
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 @@
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
index 0000000..75952ed
--- /dev/null
+++ b/jinja2/ext.py
@@ -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
+ )
diff --git a/jinja2/i18n.py b/jinja2/i18n.py
index 026750b..ef932d9 100644
--- a/jinja2/i18n.py
+++ b/jinja2/i18n.py
@@ -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 @@
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)
+ def parse(self, parser):
+ """Parse a translatable tag."""
+ lineno = parser.stream.next().lineno
- # expressions
- if parser.stream.current.type is 'assign':
+ # skip colon for python compatibility
+ if parser.stream.current.type is 'colon':
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')
- plural = plural_names = None
- have_plural = False
- referenced = set()
-
- # 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)
- if variables:
- variables = nodes.Dict([nodes.Pair(nodes.Const(x, lineno=lineno), y)
- for x, y in variables.items()])
- else:
- vairables = None
+ # 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')
- 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':
+ referenced.update(plural_names)
+ else:
parser.stream.next()
- if parser.stream.current.test('name:endtrans'):
- break
- elif parser.stream.current.test('name:pluralize'):
- if allow_pluralize:
+ 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)
+ plural_expr = None
+
+ if variables:
+ 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
- raise TemplateSyntaxError('a translatable section can have '
- 'only one pluralize section',
+ 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)
- 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)
+
+ 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:
- assert False, 'internal parser error'
-
- return referenced, u''.join(buf)
-
-
-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])
-
-
-trans = ParserExtension('trans', parse_trans)
+ 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])
diff --git a/jinja2/parser.py b/jinja2/parser.py
index 4204c6a..27f54ef 100644
--- a/jinja2/parser.py
+++ b/jinja2/parser.py
@@ -35,7 +35,10 @@
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 @@
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))