1 # -*- coding: utf-8 -*-
6 Jinja extensions allow to add custom tags similar to the way django custom
7 tags work. By default two example extensions exist: an i18n and a cache
10 :copyright: Copyright 2008 by Armin Ronacher.
13 from collections import deque
14 from jinja2 import nodes
15 from jinja2.defaults import *
16 from jinja2.environment import get_spontaneous_environment
17 from jinja2.runtime import Undefined, concat
18 from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
19 from jinja2.lexer import Token
20 from jinja2.utils import contextfunction, import_string, Markup
23 # the only real useful gettext functions for a Jinja template. Note
24 # that ugettext must be assigned to gettext as Jinja doesn't support
25 # non unicode strings.
26 GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
29 class ExtensionRegistry(type):
30 """Gives the extension an unique identifier."""
32 def __new__(cls, name, bases, d):
33 rv = type.__new__(cls, name, bases, d)
34 rv.identifier = rv.__module__ + '.' + rv.__name__
38 class Extension(object):
39 """Extensions can be used to add extra functionality to the Jinja template
40 system at the parser level. Custom extensions are bound to an environment
41 but may not store environment specific data on `self`. The reason for
42 this is that an extension can be bound to another environment (for
43 overlays) by creating a copy and reassigning the `environment` attribute.
45 As extensions are created by the environment they cannot accept any
46 arguments for configuration. One may want to work around that by using
47 a factory function, but that is not possible as extensions are identified
48 by their import name. The correct way to configure the extension is
49 storing the configuration values on the environment. Because this way the
50 environment ends up acting as central configuration storage the
51 attributes may clash which is why extensions have to ensure that the names
52 they choose for configuration are not too generic. ``prefix`` for example
53 is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
54 name as includes the name of the extension (fragment cache).
56 __metaclass__ = ExtensionRegistry
58 #: if this extension parses this is the list of tags it's listening to.
61 def __init__(self, environment):
62 self.environment = environment
64 def bind(self, environment):
65 """Create a copy of this extension bound to another environment."""
66 rv = object.__new__(self.__class__)
67 rv.__dict__.update(self.__dict__)
68 rv.environment = environment
71 def preprocess(self, source, name, filename=None):
72 """This method is called before the actual lexing and can be used to
73 preprocess the source. The `filename` is optional. The return value
74 must be the preprocessed source.
78 def filter_stream(self, stream):
79 """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
80 to filter tokens returned. This method has to return an iterable of
81 :class:`~jinja2.lexer.Token`\s, but it doesn't have to return a
82 :class:`~jinja2.lexer.TokenStream`.
86 def parse(self, parser):
87 """If any of the :attr:`tags` matched this method is called with the
88 parser as first argument. The token the parser stream is pointing at
89 is the name token that matched. This method has to return one or a
90 list of multiple nodes.
92 raise NotImplementedError()
94 def attr(self, name, lineno=None):
95 """Return an attribute node for the current extension. This is useful
96 to pass constants on extensions to generated template code::
98 self.attr('_my_attribute', lineno=lineno)
100 return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
102 def call_method(self, name, args=None, kwargs=None, dyn_args=None,
103 dyn_kwargs=None, lineno=None):
104 """Call a method of the extension. This is a shortcut for
105 :meth:`attr` + :class:`jinja2.nodes.Call`.
111 return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
112 dyn_args, dyn_kwargs, lineno=lineno)
116 def _gettext_alias(context, string):
117 return context.resolve('gettext')(string)
120 class InternationalizationExtension(Extension):
121 """This extension adds gettext support to Jinja2."""
122 tags = set(['trans'])
124 def __init__(self, environment):
125 Extension.__init__(self, environment)
126 environment.globals['_'] = _gettext_alias
128 install_gettext_translations=self._install,
129 install_null_translations=self._install_null,
130 uninstall_gettext_translations=self._uninstall,
131 extract_translations=self._extract
134 def _install(self, translations):
135 self.environment.globals.update(
136 gettext=translations.ugettext,
137 ngettext=translations.ungettext
140 def _install_null(self):
141 self.environment.globals.update(
143 ngettext=lambda s, p, n: (n != 1 and (p,) or (s,))[0]
146 def _uninstall(self, translations):
147 for key in 'gettext', 'ngettext':
148 self.environment.globals.pop(key, None)
150 def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
151 if isinstance(source, basestring):
152 source = self.environment.parse(source)
153 return extract_from_ast(source, gettext_functions)
155 def parse(self, parser):
156 """Parse a translatable tag."""
157 lineno = parser.stream.next().lineno
159 # find all the variables referenced. Additionally a variable can be
160 # defined in the body of the trans block too, but this is checked at
164 while parser.stream.current.type is not 'block_end':
166 parser.stream.expect('comma')
168 # skip colon for python compatibility
169 if parser.stream.skip_if('colon'):
172 name = parser.stream.expect('name')
173 if name.value in variables:
174 parser.fail('translatable variable %r defined twice.' %
175 name.value, name.lineno,
176 exc=TemplateAssertionError)
179 if parser.stream.current.type is 'assign':
181 variables[name.value] = var = parser.parse_expression()
183 variables[name.value] = var = nodes.Name(name.value, 'load')
184 if plural_expr is None:
187 parser.stream.expect('block_end')
189 plural = plural_names = None
193 # now parse until endtrans or pluralize
194 singular_names, singular = self._parse_block(parser, True)
196 referenced.update(singular_names)
197 if plural_expr is None:
198 plural_expr = nodes.Name(singular_names[0], 'load')
200 # if we have a pluralize block, we parse that too
201 if parser.stream.current.test('name:pluralize'):
204 if parser.stream.current.type is not 'block_end':
205 plural_expr = parser.parse_expression()
206 parser.stream.expect('block_end')
207 plural_names, plural = self._parse_block(parser, False)
209 referenced.update(plural_names)
213 # register free names as simple name expressions
214 for var in referenced:
215 if var not in variables:
216 variables[var] = nodes.Name(var, 'load')
218 # no variables referenced? no need to escape
220 singular = singular.replace('%%', '%')
222 plural = plural.replace('%%', '%')
226 elif plural_expr is None:
227 parser.fail('pluralize without variables', lineno)
230 variables = nodes.Dict([nodes.Pair(nodes.Const(x, lineno=lineno), y)
231 for x, y in variables.items()])
235 node = self._make_node(singular, plural, variables, plural_expr)
236 node.set_lineno(lineno)
239 def _parse_block(self, parser, allow_pluralize):
240 """Parse until the next block tag with a given name."""
244 if parser.stream.current.type is 'data':
245 buf.append(parser.stream.current.value.replace('%', '%%'))
247 elif parser.stream.current.type is 'variable_begin':
249 name = parser.stream.expect('name').value
250 referenced.append(name)
251 buf.append('%%(%s)s' % name)
252 parser.stream.expect('variable_end')
253 elif parser.stream.current.type is 'block_begin':
255 if parser.stream.current.test('name:endtrans'):
257 elif parser.stream.current.test('name:pluralize'):
260 parser.fail('a translatable section can have only one '
262 parser.fail('control structures in translatable sections are '
265 assert False, 'internal parser error'
267 return referenced, concat(buf)
269 def _make_node(self, singular, plural, variables, plural_expr):
270 """Generates a useful node from the data provided."""
272 if plural_expr is None:
273 gettext = nodes.Name('gettext', 'load')
274 node = nodes.Call(gettext, [nodes.Const(singular)],
277 # singular and plural
279 ngettext = nodes.Name('ngettext', 'load')
280 node = nodes.Call(ngettext, [
281 nodes.Const(singular),
286 # mark the return value as safe if we are in an
287 # environment with autoescaping turned on
288 if self.environment.autoescape:
289 node = nodes.MarkSafe(node)
292 node = nodes.Mod(node, variables)
293 return nodes.Output([node])
296 class ExprStmtExtension(Extension):
297 """Adds a `do` tag to Jinja2 that works like the print statement just
298 that it doesn't print the return value.
302 def parse(self, parser):
303 node = nodes.ExprStmt(lineno=parser.stream.next().lineno)
304 node.node = parser.parse_tuple()
308 class LoopControlExtension(Extension):
309 """Adds break and continue to the template engine."""
310 tags = set(['break', 'continue'])
312 def parse(self, parser):
313 token = parser.stream.next()
314 if token.value == 'break':
315 return nodes.Break(lineno=token.lineno)
316 return nodes.Continue(lineno=token.lineno)
319 def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS):
320 """Extract localizable strings from the given template node.
322 For every string found this function yields a ``(lineno, function,
323 message)`` tuple, where:
325 * ``lineno`` is the number of the line on which the string was found,
326 * ``function`` is the name of the ``gettext`` function used (if the
327 string was extracted from embedded Python code), and
328 * ``message`` is the string itself (a ``unicode`` object, or a tuple
329 of ``unicode`` objects for functions with multiple string arguments).
331 for node in node.find_all(nodes.Call):
332 if not isinstance(node.node, nodes.Name) or \
333 node.node.name not in gettext_functions:
337 for arg in node.args:
338 if isinstance(arg, nodes.Const) and \
339 isinstance(arg.value, basestring):
340 strings.append(arg.value)
344 if len(strings) == 1:
347 strings = tuple(strings)
348 yield node.lineno, node.node.name, strings
351 def babel_extract(fileobj, keywords, comment_tags, options):
352 """Babel extraction method for Jinja templates.
354 :param fileobj: the file-like object the messages should be extracted from
355 :param keywords: a list of keywords (i.e. function names) that should be
356 recognized as translation functions
357 :param comment_tags: a list of translator tags to search for and include
358 in the results. (Unused)
359 :param options: a dictionary of additional options (optional)
360 :return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
361 (comments will be empty currently)
364 for extension in options.get('extensions', '').split(','):
365 extension = extension.strip()
368 extensions.add(import_string(extension))
369 if InternationalizationExtension not in extensions:
370 extensions.add(InternationalizationExtension)
372 environment = get_spontaneous_environment(
373 options.get('block_start_string', BLOCK_START_STRING),
374 options.get('block_end_string', BLOCK_END_STRING),
375 options.get('variable_start_string', VARIABLE_START_STRING),
376 options.get('variable_end_string', VARIABLE_END_STRING),
377 options.get('comment_start_string', COMMENT_START_STRING),
378 options.get('comment_end_string', COMMENT_END_STRING),
379 options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
380 str(options.get('trim_blocks', TRIM_BLOCKS)).lower() in \
381 ('1', 'on', 'yes', 'true'),
382 NEWLINE_SEQUENCE, frozenset(extensions),
383 # fill with defaults so that environments are shared
384 # with other spontaneus environments. The rest of the
385 # arguments are optimizer, undefined, finalize, autoescape,
386 # loader, cache size and auto reloading setting
387 True, Undefined, None, False, None, 0, False
390 source = fileobj.read().decode(options.get('encoding', 'utf-8'))
391 node = environment.parse(source)
392 for lineno, func, message in extract_from_ast(node, keywords):
393 yield lineno, func, message, []
396 #: nicer import names
397 i18n = InternationalizationExtension
398 do = ExprStmtExtension
399 loopcontrols = LoopControlExtension