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: (c) 2010 by the Jinja Team.
13
from jinja2 import nodes
14
from jinja2.defaults import BLOCK_START_STRING, \
15
BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \
16
COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \
17
LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \
18
KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS
19
from jinja2.environment import Environment
20
from jinja2.runtime import concat
21
from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
22
from jinja2.utils import contextfunction, import_string, Markup
23
from jinja2._compat import next, with_metaclass, string_types, iteritems
26
# the only real useful gettext functions for a Jinja template. Note
27
# that ugettext must be assigned to gettext as Jinja doesn't support
28
# non unicode strings.
29
GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
32
class ExtensionRegistry(type):
33
"""Gives the extension an unique identifier."""
35
def __new__(cls, name, bases, d):
36
rv = type.__new__(cls, name, bases, d)
37
rv.identifier = rv.__module__ + '.' + rv.__name__
41
class Extension(with_metaclass(ExtensionRegistry, object)):
42
"""Extensions can be used to add extra functionality to the Jinja template
43
system at the parser level. Custom extensions are bound to an environment
44
but may not store environment specific data on `self`. The reason for
45
this is that an extension can be bound to another environment (for
46
overlays) by creating a copy and reassigning the `environment` attribute.
48
As extensions are created by the environment they cannot accept any
49
arguments for configuration. One may want to work around that by using
50
a factory function, but that is not possible as extensions are identified
51
by their import name. The correct way to configure the extension is
52
storing the configuration values on the environment. Because this way the
53
environment ends up acting as central configuration storage the
54
attributes may clash which is why extensions have to ensure that the names
55
they choose for configuration are not too generic. ``prefix`` for example
56
is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
57
name as includes the name of the extension (fragment cache).
60
#: if this extension parses this is the list of tags it's listening to.
63
#: the priority of that extension. This is especially useful for
64
#: extensions that preprocess values. A lower value means higher
67
#: .. versionadded:: 2.4
70
def __init__(self, environment):
71
self.environment = environment
73
def bind(self, environment):
74
"""Create a copy of this extension bound to another environment."""
75
rv = object.__new__(self.__class__)
76
rv.__dict__.update(self.__dict__)
77
rv.environment = environment
80
def preprocess(self, source, name, filename=None):
81
"""This method is called before the actual lexing and can be used to
82
preprocess the source. The `filename` is optional. The return value
83
must be the preprocessed source.
87
def filter_stream(self, stream):
88
"""It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
89
to filter tokens returned. This method has to return an iterable of
90
:class:`~jinja2.lexer.Token`\s, but it doesn't have to return a
91
:class:`~jinja2.lexer.TokenStream`.
93
In the `ext` folder of the Jinja2 source distribution there is a file
94
called `inlinegettext.py` which implements a filter that utilizes this
99
def parse(self, parser):
100
"""If any of the :attr:`tags` matched this method is called with the
101
parser as first argument. The token the parser stream is pointing at
102
is the name token that matched. This method has to return one or a
103
list of multiple nodes.
105
raise NotImplementedError()
107
def attr(self, name, lineno=None):
108
"""Return an attribute node for the current extension. This is useful
109
to pass constants on extensions to generated template code.
113
self.attr('_my_attribute', lineno=lineno)
115
return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
117
def call_method(self, name, args=None, kwargs=None, dyn_args=None,
118
dyn_kwargs=None, lineno=None):
119
"""Call a method of the extension. This is a shortcut for
120
:meth:`attr` + :class:`jinja2.nodes.Call`.
126
return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
127
dyn_args, dyn_kwargs, lineno=lineno)
131
def _gettext_alias(__context, *args, **kwargs):
132
return __context.call(__context.resolve('gettext'), *args, **kwargs)
135
def _make_new_gettext(func):
137
def gettext(__context, __string, **variables):
138
rv = __context.call(func, __string)
139
if __context.eval_ctx.autoescape:
141
return rv % variables
145
def _make_new_ngettext(func):
147
def ngettext(__context, __singular, __plural, __num, **variables):
148
variables.setdefault('num', __num)
149
rv = __context.call(func, __singular, __plural, __num)
150
if __context.eval_ctx.autoescape:
152
return rv % variables
156
class InternationalizationExtension(Extension):
157
"""This extension adds gettext support to Jinja2."""
158
tags = set(['trans'])
160
# TODO: the i18n extension is currently reevaluating values in a few
161
# situations. Take this example:
162
# {% trans count=something() %}{{ count }} foo{% pluralize
163
# %}{{ count }} fooss{% endtrans %}
164
# something is called twice here. One time for the gettext value and
165
# the other time for the n-parameter of the ngettext function.
167
def __init__(self, environment):
168
Extension.__init__(self, environment)
169
environment.globals['_'] = _gettext_alias
171
install_gettext_translations=self._install,
172
install_null_translations=self._install_null,
173
install_gettext_callables=self._install_callables,
174
uninstall_gettext_translations=self._uninstall,
175
extract_translations=self._extract,
176
newstyle_gettext=False
179
def _install(self, translations, newstyle=None):
180
gettext = getattr(translations, 'ugettext', None)
182
gettext = translations.gettext
183
ngettext = getattr(translations, 'ungettext', None)
185
ngettext = translations.ngettext
186
self._install_callables(gettext, ngettext, newstyle)
188
def _install_null(self, newstyle=None):
189
self._install_callables(
191
lambda s, p, n: (n != 1 and (p,) or (s,))[0],
195
def _install_callables(self, gettext, ngettext, newstyle=None):
196
if newstyle is not None:
197
self.environment.newstyle_gettext = newstyle
198
if self.environment.newstyle_gettext:
199
gettext = _make_new_gettext(gettext)
200
ngettext = _make_new_ngettext(ngettext)
201
self.environment.globals.update(
206
def _uninstall(self, translations):
207
for key in 'gettext', 'ngettext':
208
self.environment.globals.pop(key, None)
210
def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
211
if isinstance(source, string_types):
212
source = self.environment.parse(source)
213
return extract_from_ast(source, gettext_functions)
215
def parse(self, parser):
216
"""Parse a translatable tag."""
217
lineno = next(parser.stream).lineno
218
num_called_num = False
220
# find all the variables referenced. Additionally a variable can be
221
# defined in the body of the trans block too, but this is checked at
224
plural_expr_assignment = None
226
while parser.stream.current.type != 'block_end':
228
parser.stream.expect('comma')
230
# skip colon for python compatibility
231
if parser.stream.skip_if('colon'):
234
name = parser.stream.expect('name')
235
if name.value in variables:
236
parser.fail('translatable variable %r defined twice.' %
237
name.value, name.lineno,
238
exc=TemplateAssertionError)
241
if parser.stream.current.type == 'assign':
243
variables[name.value] = var = parser.parse_expression()
245
variables[name.value] = var = nodes.Name(name.value, 'load')
247
if plural_expr is None:
248
if isinstance(var, nodes.Call):
249
plural_expr = nodes.Name('_trans', 'load')
250
variables[name.value] = plural_expr
251
plural_expr_assignment = nodes.Assign(
252
nodes.Name('_trans', 'store'), var)
255
num_called_num = name.value == 'num'
257
parser.stream.expect('block_end')
259
plural = plural_names = None
263
# now parse until endtrans or pluralize
264
singular_names, singular = self._parse_block(parser, True)
266
referenced.update(singular_names)
267
if plural_expr is None:
268
plural_expr = nodes.Name(singular_names[0], 'load')
269
num_called_num = singular_names[0] == 'num'
271
# if we have a pluralize block, we parse that too
272
if parser.stream.current.test('name:pluralize'):
275
if parser.stream.current.type != 'block_end':
276
name = parser.stream.expect('name')
277
if name.value not in variables:
278
parser.fail('unknown variable %r for pluralization' %
279
name.value, name.lineno,
280
exc=TemplateAssertionError)
281
plural_expr = variables[name.value]
282
num_called_num = name.value == 'num'
283
parser.stream.expect('block_end')
284
plural_names, plural = self._parse_block(parser, False)
286
referenced.update(plural_names)
290
# register free names as simple name expressions
291
for var in referenced:
292
if var not in variables:
293
variables[var] = nodes.Name(var, 'load')
297
elif plural_expr is None:
298
parser.fail('pluralize without variables', lineno)
300
node = self._make_node(singular, plural, variables, plural_expr,
302
num_called_num and have_plural)
303
node.set_lineno(lineno)
304
if plural_expr_assignment is not None:
305
return [plural_expr_assignment, node]
309
def _parse_block(self, parser, allow_pluralize):
310
"""Parse until the next block tag with a given name."""
314
if parser.stream.current.type == 'data':
315
buf.append(parser.stream.current.value.replace('%', '%%'))
317
elif parser.stream.current.type == 'variable_begin':
319
name = parser.stream.expect('name').value
320
referenced.append(name)
321
buf.append('%%(%s)s' % name)
322
parser.stream.expect('variable_end')
323
elif parser.stream.current.type == 'block_begin':
325
if parser.stream.current.test('name:endtrans'):
327
elif parser.stream.current.test('name:pluralize'):
330
parser.fail('a translatable section can have only one '
332
parser.fail('control structures in translatable sections are '
334
elif parser.stream.eos:
335
parser.fail('unclosed translation block')
337
assert False, 'internal parser error'
339
return referenced, concat(buf)
341
def _make_node(self, singular, plural, variables, plural_expr,
342
vars_referenced, num_called_num):
343
"""Generates a useful node from the data provided."""
344
# no variables referenced? no need to escape for old style
345
# gettext invocations only if there are vars.
346
if not vars_referenced and not self.environment.newstyle_gettext:
347
singular = singular.replace('%%', '%')
349
plural = plural.replace('%%', '%')
352
if plural_expr is None:
353
gettext = nodes.Name('gettext', 'load')
354
node = nodes.Call(gettext, [nodes.Const(singular)],
357
# singular and plural
359
ngettext = nodes.Name('ngettext', 'load')
360
node = nodes.Call(ngettext, [
361
nodes.Const(singular),
366
# in case newstyle gettext is used, the method is powerful
367
# enough to handle the variable expansion and autoescape
369
if self.environment.newstyle_gettext:
370
for key, value in iteritems(variables):
371
# the function adds that later anyways in case num was
372
# called num, so just skip it.
373
if num_called_num and key == 'num':
375
node.kwargs.append(nodes.Keyword(key, value))
377
# otherwise do that here
379
# mark the return value as safe if we are in an
380
# environment with autoescaping turned on
381
node = nodes.MarkSafeIfAutoescape(node)
383
node = nodes.Mod(node, nodes.Dict([
384
nodes.Pair(nodes.Const(key), value)
385
for key, value in variables.items()
387
return nodes.Output([node])
390
class ExprStmtExtension(Extension):
391
"""Adds a `do` tag to Jinja2 that works like the print statement just
392
that it doesn't print the return value.
396
def parse(self, parser):
397
node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
398
node.node = parser.parse_tuple()
402
class LoopControlExtension(Extension):
403
"""Adds break and continue to the template engine."""
404
tags = set(['break', 'continue'])
406
def parse(self, parser):
407
token = next(parser.stream)
408
if token.value == 'break':
409
return nodes.Break(lineno=token.lineno)
410
return nodes.Continue(lineno=token.lineno)
413
class WithExtension(Extension):
414
"""Adds support for a django-like with block."""
417
def parse(self, parser):
418
node = nodes.Scope(lineno=next(parser.stream).lineno)
420
while parser.stream.current.type != 'block_end':
421
lineno = parser.stream.current.lineno
423
parser.stream.expect('comma')
424
target = parser.parse_assign_target()
425
parser.stream.expect('assign')
426
expr = parser.parse_expression()
427
assignments.append(nodes.Assign(target, expr, lineno=lineno))
428
node.body = assignments + \
429
list(parser.parse_statements(('name:endwith',),
434
class AutoEscapeExtension(Extension):
435
"""Changes auto escape rules for a scope."""
436
tags = set(['autoescape'])
438
def parse(self, parser):
439
node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno)
441
nodes.Keyword('autoescape', parser.parse_expression())
443
node.body = parser.parse_statements(('name:endautoescape',),
445
return nodes.Scope([node])
448
def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
450
"""Extract localizable strings from the given template node. Per
451
default this function returns matches in babel style that means non string
452
parameters as well as keyword arguments are returned as `None`. This
453
allows Babel to figure out what you really meant if you are using
454
gettext functions that allow keyword arguments for placeholder expansion.
455
If you don't want that behavior set the `babel_style` parameter to `False`
456
which causes only strings to be returned and parameters are always stored
457
in tuples. As a consequence invalid gettext calls (calls without a single
458
string parameter or string parameters after non-string parameters) are
461
This example explains the behavior:
463
>>> from jinja2 import Environment
464
>>> env = Environment()
465
>>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
466
>>> list(extract_from_ast(node))
467
[(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
468
>>> list(extract_from_ast(node, babel_style=False))
469
[(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
471
For every string found this function yields a ``(lineno, function,
472
message)`` tuple, where:
474
* ``lineno`` is the number of the line on which the string was found,
475
* ``function`` is the name of the ``gettext`` function used (if the
476
string was extracted from embedded Python code), and
477
* ``message`` is the string itself (a ``unicode`` object, or a tuple
478
of ``unicode`` objects for functions with multiple string arguments).
480
This extraction function operates on the AST and is because of that unable
481
to extract any comments. For comment support you have to use the babel
482
extraction interface or extract comments yourself.
484
for node in node.find_all(nodes.Call):
485
if not isinstance(node.node, nodes.Name) or \
486
node.node.name not in gettext_functions:
490
for arg in node.args:
491
if isinstance(arg, nodes.Const) and \
492
isinstance(arg.value, string_types):
493
strings.append(arg.value)
497
for arg in node.kwargs:
499
if node.dyn_args is not None:
501
if node.dyn_kwargs is not None:
505
strings = tuple(x for x in strings if x is not None)
509
if len(strings) == 1:
512
strings = tuple(strings)
513
yield node.lineno, node.node.name, strings
516
class _CommentFinder(object):
517
"""Helper class to find comments in a token stream. Can only
518
find comments for gettext calls forwards. Once the comment
519
from line 4 is found, a comment for line 1 will not return a
523
def __init__(self, tokens, comment_tags):
525
self.comment_tags = comment_tags
529
def find_backwards(self, offset):
531
for _, token_type, token_value in \
532
reversed(self.tokens[self.offset:offset]):
533
if token_type in ('comment', 'linecomment'):
535
prefix, comment = token_value.split(None, 1)
538
if prefix in self.comment_tags:
539
return [comment.rstrip()]
544
def find_comments(self, lineno):
545
if not self.comment_tags or self.last_lineno > lineno:
547
for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]):
548
if token_lineno > lineno:
549
return self.find_backwards(self.offset + idx)
550
return self.find_backwards(len(self.tokens))
553
def babel_extract(fileobj, keywords, comment_tags, options):
554
"""Babel extraction method for Jinja templates.
556
.. versionchanged:: 2.3
557
Basic support for translation comments was added. If `comment_tags`
558
is now set to a list of keywords for extraction, the extractor will
559
try to find the best preceeding comment that begins with one of the
560
keywords. For best results, make sure to not have more than one
561
gettext call in one line of code and the matching comment in the
562
same line or the line before.
564
.. versionchanged:: 2.5.1
565
The `newstyle_gettext` flag can be set to `True` to enable newstyle
568
.. versionchanged:: 2.7
569
A `silent` option can now be provided. If set to `False` template
570
syntax errors are propagated instead of being ignored.
572
:param fileobj: the file-like object the messages should be extracted from
573
:param keywords: a list of keywords (i.e. function names) that should be
574
recognized as translation functions
575
:param comment_tags: a list of translator tags to search for and include
577
:param options: a dictionary of additional options (optional)
578
:return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
579
(comments will be empty currently)
582
for extension in options.get('extensions', '').split(','):
583
extension = extension.strip()
586
extensions.add(import_string(extension))
587
if InternationalizationExtension not in extensions:
588
extensions.add(InternationalizationExtension)
590
def getbool(options, key, default=False):
591
return options.get(key, str(default)).lower() in \
592
('1', 'on', 'yes', 'true')
594
silent = getbool(options, 'silent', True)
595
environment = Environment(
596
options.get('block_start_string', BLOCK_START_STRING),
597
options.get('block_end_string', BLOCK_END_STRING),
598
options.get('variable_start_string', VARIABLE_START_STRING),
599
options.get('variable_end_string', VARIABLE_END_STRING),
600
options.get('comment_start_string', COMMENT_START_STRING),
601
options.get('comment_end_string', COMMENT_END_STRING),
602
options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
603
options.get('line_comment_prefix') or LINE_COMMENT_PREFIX,
604
getbool(options, 'trim_blocks', TRIM_BLOCKS),
605
getbool(options, 'lstrip_blocks', LSTRIP_BLOCKS),
607
getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE),
608
frozenset(extensions),
613
if getbool(options, 'newstyle_gettext'):
614
environment.newstyle_gettext = True
616
source = fileobj.read().decode(options.get('encoding', 'utf-8'))
618
node = environment.parse(source)
619
tokens = list(environment.lex(environment.preprocess(source)))
620
except TemplateSyntaxError as e:
623
# skip templates with syntax errors
626
finder = _CommentFinder(tokens, comment_tags)
627
for lineno, func, message in extract_from_ast(node, keywords):
628
yield lineno, func, message, finder.find_comments(lineno)
631
#: nicer import names
632
i18n = InternationalizationExtension
633
do = ExprStmtExtension
634
loopcontrols = LoopControlExtension
635
with_ = WithExtension
636
autoescape = AutoEscapeExtension