blob: 984a4d958da7fdbd27865ff40c8386952276c48c [file] [log] [blame]
Armin Ronacher05530932008-04-20 13:27:49 +02001# -*- coding: utf-8 -*-
2"""
3 jinja2.ext
4 ~~~~~~~~~~
5
Armin Ronacherb5124e62008-04-25 00:36:14 +02006 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
8 extension.
Armin Ronacher05530932008-04-20 13:27:49 +02009
Armin Ronacher55494e42010-01-22 09:41:48 +010010 :copyright: (c) 2010 by the Jinja Team.
Armin Ronacher05530932008-04-20 13:27:49 +020011 :license: BSD.
12"""
Armin Ronacherb5124e62008-04-25 00:36:14 +020013from collections import deque
Armin Ronacher05530932008-04-20 13:27:49 +020014from jinja2 import nodes
Armin Ronacher4f5008f2008-05-23 23:36:07 +020015from jinja2.defaults import *
Armin Ronacher4f77a302010-07-01 12:15:39 +020016from jinja2.environment import Environment
Armin Ronacher2feed1d2008-04-26 16:26:52 +020017from jinja2.runtime import Undefined, concat
Benjamin Wieganda3152742008-04-28 18:07:52 +020018from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
Armin Ronacherbd357722009-08-05 20:25:06 +020019from jinja2.utils import contextfunction, import_string, Markup, next
Thomas Waldmanne0003552013-05-17 23:52:14 +020020import six
Armin Ronacherb5124e62008-04-25 00:36:14 +020021
22
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.
26GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
Armin Ronacher05530932008-04-20 13:27:49 +020027
28
Armin Ronacher023b5e92008-05-08 11:03:10 +020029class ExtensionRegistry(type):
Armin Ronacher5c047ea2008-05-23 22:26:45 +020030 """Gives the extension an unique identifier."""
Armin Ronacher023b5e92008-05-08 11:03:10 +020031
32 def __new__(cls, name, bases, d):
33 rv = type.__new__(cls, name, bases, d)
34 rv.identifier = rv.__module__ + '.' + rv.__name__
35 return rv
36
37
Armin Ronacher05530932008-04-20 13:27:49 +020038class Extension(object):
Armin Ronacher7259c762008-04-30 13:03:59 +020039 """Extensions can be used to add extra functionality to the Jinja template
Armin Ronacher9d42abf2008-05-14 18:10:41 +020040 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.
Armin Ronacher762079c2008-05-08 23:57:56 +020044
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).
Armin Ronacher7259c762008-04-30 13:03:59 +020055 """
Armin Ronacher023b5e92008-05-08 11:03:10 +020056 __metaclass__ = ExtensionRegistry
Armin Ronacher05530932008-04-20 13:27:49 +020057
58 #: if this extension parses this is the list of tags it's listening to.
59 tags = set()
60
Armin Ronacher5b3f4dc2010-04-12 14:04:14 +020061 #: the priority of that extension. This is especially useful for
62 #: extensions that preprocess values. A lower value means higher
63 #: priority.
64 #:
65 #: .. versionadded:: 2.4
66 priority = 100
67
Armin Ronacher05530932008-04-20 13:27:49 +020068 def __init__(self, environment):
69 self.environment = environment
70
Armin Ronacher7259c762008-04-30 13:03:59 +020071 def bind(self, environment):
72 """Create a copy of this extension bound to another environment."""
73 rv = object.__new__(self.__class__)
74 rv.__dict__.update(self.__dict__)
75 rv.environment = environment
76 return rv
77
Armin Ronacher9ad96e72008-06-13 22:44:01 +020078 def preprocess(self, source, name, filename=None):
79 """This method is called before the actual lexing and can be used to
80 preprocess the source. The `filename` is optional. The return value
81 must be the preprocessed source.
82 """
83 return source
84
85 def filter_stream(self, stream):
86 """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
87 to filter tokens returned. This method has to return an iterable of
88 :class:`~jinja2.lexer.Token`\s, but it doesn't have to return a
89 :class:`~jinja2.lexer.TokenStream`.
Armin Ronacherd02fc7d2008-06-14 14:19:47 +020090
91 In the `ext` folder of the Jinja2 source distribution there is a file
92 called `inlinegettext.py` which implements a filter that utilizes this
93 method.
Armin Ronacher9ad96e72008-06-13 22:44:01 +020094 """
95 return stream
96
Armin Ronacher05530932008-04-20 13:27:49 +020097 def parse(self, parser):
Armin Ronacher023b5e92008-05-08 11:03:10 +020098 """If any of the :attr:`tags` matched this method is called with the
99 parser as first argument. The token the parser stream is pointing at
100 is the name token that matched. This method has to return one or a
101 list of multiple nodes.
102 """
Armin Ronacher27069d72008-05-11 19:48:12 +0200103 raise NotImplementedError()
Armin Ronacher023b5e92008-05-08 11:03:10 +0200104
105 def attr(self, name, lineno=None):
106 """Return an attribute node for the current extension. This is useful
Armin Ronacher53278a32011-01-24 01:16:00 +0100107 to pass constants on extensions to generated template code.
108
109 ::
Armin Ronacher023b5e92008-05-08 11:03:10 +0200110
Armin Ronacher69e12db2008-05-12 09:00:03 +0200111 self.attr('_my_attribute', lineno=lineno)
Armin Ronacher023b5e92008-05-08 11:03:10 +0200112 """
113 return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
Armin Ronacher05530932008-04-20 13:27:49 +0200114
Armin Ronacher27069d72008-05-11 19:48:12 +0200115 def call_method(self, name, args=None, kwargs=None, dyn_args=None,
116 dyn_kwargs=None, lineno=None):
Armin Ronacher69e12db2008-05-12 09:00:03 +0200117 """Call a method of the extension. This is a shortcut for
118 :meth:`attr` + :class:`jinja2.nodes.Call`.
119 """
Armin Ronacher27069d72008-05-11 19:48:12 +0200120 if args is None:
121 args = []
122 if kwargs is None:
123 kwargs = []
124 return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
125 dyn_args, dyn_kwargs, lineno=lineno)
126
Armin Ronacher05530932008-04-20 13:27:49 +0200127
Armin Ronacher5c047ea2008-05-23 22:26:45 +0200128@contextfunction
Armin Ronacher4da90342010-05-29 17:35:10 +0200129def _gettext_alias(__context, *args, **kwargs):
Armin Ronacherb8892e72010-05-29 17:58:06 +0200130 return __context.call(__context.resolve('gettext'), *args, **kwargs)
Armin Ronacher4da90342010-05-29 17:35:10 +0200131
132
133def _make_new_gettext(func):
134 @contextfunction
135 def gettext(__context, __string, **variables):
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200136 rv = __context.call(func, __string)
Armin Ronacher4da90342010-05-29 17:35:10 +0200137 if __context.eval_ctx.autoescape:
138 rv = Markup(rv)
139 return rv % variables
140 return gettext
141
142
143def _make_new_ngettext(func):
144 @contextfunction
Armin Ronacherb98dad92010-05-29 22:31:17 +0200145 def ngettext(__context, __singular, __plural, __num, **variables):
146 variables.setdefault('num', __num)
147 rv = __context.call(func, __singular, __plural, __num)
Armin Ronacher4da90342010-05-29 17:35:10 +0200148 if __context.eval_ctx.autoescape:
149 rv = Markup(rv)
150 return rv % variables
151 return ngettext
Armin Ronacher5c047ea2008-05-23 22:26:45 +0200152
153
Armin Ronachered98cac2008-05-07 08:42:11 +0200154class InternationalizationExtension(Extension):
Armin Ronacher762079c2008-05-08 23:57:56 +0200155 """This extension adds gettext support to Jinja2."""
Armin Ronacherb5124e62008-04-25 00:36:14 +0200156 tags = set(['trans'])
157
Armin Ronacher4720c362008-09-06 16:15:38 +0200158 # TODO: the i18n extension is currently reevaluating values in a few
159 # situations. Take this example:
160 # {% trans count=something() %}{{ count }} foo{% pluralize
161 # %}{{ count }} fooss{% endtrans %}
162 # something is called twice here. One time for the gettext value and
163 # the other time for the n-parameter of the ngettext function.
164
Armin Ronacherb5124e62008-04-25 00:36:14 +0200165 def __init__(self, environment):
166 Extension.__init__(self, environment)
Armin Ronacher5c047ea2008-05-23 22:26:45 +0200167 environment.globals['_'] = _gettext_alias
Armin Ronacher762079c2008-05-08 23:57:56 +0200168 environment.extend(
169 install_gettext_translations=self._install,
170 install_null_translations=self._install_null,
Armin Ronacher4da90342010-05-29 17:35:10 +0200171 install_gettext_callables=self._install_callables,
Armin Ronacher762079c2008-05-08 23:57:56 +0200172 uninstall_gettext_translations=self._uninstall,
Armin Ronacher4da90342010-05-29 17:35:10 +0200173 extract_translations=self._extract,
174 newstyle_gettext=False
Armin Ronacher762079c2008-05-08 23:57:56 +0200175 )
176
Armin Ronacher4da90342010-05-29 17:35:10 +0200177 def _install(self, translations, newstyle=None):
Armin Ronacher32133552008-09-15 14:35:01 +0200178 gettext = getattr(translations, 'ugettext', None)
179 if gettext is None:
180 gettext = translations.gettext
181 ngettext = getattr(translations, 'ungettext', None)
182 if ngettext is None:
183 ngettext = translations.ngettext
Armin Ronacher4da90342010-05-29 17:35:10 +0200184 self._install_callables(gettext, ngettext, newstyle)
Armin Ronacher762079c2008-05-08 23:57:56 +0200185
Armin Ronacher4da90342010-05-29 17:35:10 +0200186 def _install_null(self, newstyle=None):
187 self._install_callables(
188 lambda x: x,
189 lambda s, p, n: (n != 1 and (p,) or (s,))[0],
190 newstyle
191 )
192
193 def _install_callables(self, gettext, ngettext, newstyle=None):
194 if newstyle is not None:
195 self.environment.newstyle_gettext = newstyle
196 if self.environment.newstyle_gettext:
197 gettext = _make_new_gettext(gettext)
198 ngettext = _make_new_ngettext(ngettext)
Armin Ronacher762079c2008-05-08 23:57:56 +0200199 self.environment.globals.update(
Armin Ronacher4da90342010-05-29 17:35:10 +0200200 gettext=gettext,
201 ngettext=ngettext
Armin Ronacher762079c2008-05-08 23:57:56 +0200202 )
203
204 def _uninstall(self, translations):
205 for key in 'gettext', 'ngettext':
206 self.environment.globals.pop(key, None)
207
208 def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
209 if isinstance(source, basestring):
210 source = self.environment.parse(source)
211 return extract_from_ast(source, gettext_functions)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200212
213 def parse(self, parser):
214 """Parse a translatable tag."""
Armin Ronacherbd357722009-08-05 20:25:06 +0200215 lineno = next(parser.stream).lineno
Armin Ronacherb98dad92010-05-29 22:31:17 +0200216 num_called_num = False
Armin Ronacherb5124e62008-04-25 00:36:14 +0200217
Armin Ronacherb5124e62008-04-25 00:36:14 +0200218 # find all the variables referenced. Additionally a variable can be
219 # defined in the body of the trans block too, but this is checked at
220 # a later state.
221 plural_expr = None
222 variables = {}
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100223 while parser.stream.current.type != 'block_end':
Armin Ronacherb5124e62008-04-25 00:36:14 +0200224 if variables:
225 parser.stream.expect('comma')
Armin Ronacher023b5e92008-05-08 11:03:10 +0200226
227 # skip colon for python compatibility
Armin Ronacherfdf95302008-05-11 22:20:51 +0200228 if parser.stream.skip_if('colon'):
Armin Ronacher023b5e92008-05-08 11:03:10 +0200229 break
230
Armin Ronacherb5124e62008-04-25 00:36:14 +0200231 name = parser.stream.expect('name')
232 if name.value in variables:
Armin Ronacher7f15ef82008-05-16 09:11:39 +0200233 parser.fail('translatable variable %r defined twice.' %
234 name.value, name.lineno,
235 exc=TemplateAssertionError)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200236
237 # expressions
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100238 if parser.stream.current.type == 'assign':
Armin Ronacherbd357722009-08-05 20:25:06 +0200239 next(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200240 variables[name.value] = var = parser.parse_expression()
241 else:
242 variables[name.value] = var = nodes.Name(name.value, 'load')
Armin Ronacherb98dad92010-05-29 22:31:17 +0200243
Armin Ronacherb5124e62008-04-25 00:36:14 +0200244 if plural_expr is None:
245 plural_expr = var
Armin Ronacherb98dad92010-05-29 22:31:17 +0200246 num_called_num = name.value == 'num'
Armin Ronacher023b5e92008-05-08 11:03:10 +0200247
Armin Ronacherb5124e62008-04-25 00:36:14 +0200248 parser.stream.expect('block_end')
249
250 plural = plural_names = None
251 have_plural = False
252 referenced = set()
253
254 # now parse until endtrans or pluralize
255 singular_names, singular = self._parse_block(parser, True)
256 if singular_names:
257 referenced.update(singular_names)
258 if plural_expr is None:
259 plural_expr = nodes.Name(singular_names[0], 'load')
Armin Ronacherb98dad92010-05-29 22:31:17 +0200260 num_called_num = singular_names[0] == 'num'
Armin Ronacherb5124e62008-04-25 00:36:14 +0200261
262 # if we have a pluralize block, we parse that too
263 if parser.stream.current.test('name:pluralize'):
264 have_plural = True
Armin Ronacherbd357722009-08-05 20:25:06 +0200265 next(parser.stream)
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100266 if parser.stream.current.type != 'block_end':
Armin Ronacher4720c362008-09-06 16:15:38 +0200267 name = parser.stream.expect('name')
268 if name.value not in variables:
269 parser.fail('unknown variable %r for pluralization' %
270 name.value, name.lineno,
271 exc=TemplateAssertionError)
272 plural_expr = variables[name.value]
Armin Ronacherb98dad92010-05-29 22:31:17 +0200273 num_called_num = name.value == 'num'
Armin Ronacherb5124e62008-04-25 00:36:14 +0200274 parser.stream.expect('block_end')
275 plural_names, plural = self._parse_block(parser, False)
Armin Ronacherbd357722009-08-05 20:25:06 +0200276 next(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200277 referenced.update(plural_names)
278 else:
Armin Ronacherbd357722009-08-05 20:25:06 +0200279 next(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200280
281 # register free names as simple name expressions
282 for var in referenced:
283 if var not in variables:
284 variables[var] = nodes.Name(var, 'load')
285
Armin Ronacherb5124e62008-04-25 00:36:14 +0200286 if not have_plural:
287 plural_expr = None
288 elif plural_expr is None:
Armin Ronacher7f15ef82008-05-16 09:11:39 +0200289 parser.fail('pluralize without variables', lineno)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200290
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200291 node = self._make_node(singular, plural, variables, plural_expr,
Armin Ronacher4f77a302010-07-01 12:15:39 +0200292 bool(referenced),
293 num_called_num and have_plural)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200294 node.set_lineno(lineno)
295 return node
296
297 def _parse_block(self, parser, allow_pluralize):
298 """Parse until the next block tag with a given name."""
299 referenced = []
300 buf = []
301 while 1:
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100302 if parser.stream.current.type == 'data':
Armin Ronacherb5124e62008-04-25 00:36:14 +0200303 buf.append(parser.stream.current.value.replace('%', '%%'))
Armin Ronacherbd357722009-08-05 20:25:06 +0200304 next(parser.stream)
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100305 elif parser.stream.current.type == 'variable_begin':
Armin Ronacherbd357722009-08-05 20:25:06 +0200306 next(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200307 name = parser.stream.expect('name').value
308 referenced.append(name)
309 buf.append('%%(%s)s' % name)
310 parser.stream.expect('variable_end')
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100311 elif parser.stream.current.type == 'block_begin':
Armin Ronacherbd357722009-08-05 20:25:06 +0200312 next(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200313 if parser.stream.current.test('name:endtrans'):
314 break
315 elif parser.stream.current.test('name:pluralize'):
316 if allow_pluralize:
317 break
Armin Ronacher7f15ef82008-05-16 09:11:39 +0200318 parser.fail('a translatable section can have only one '
319 'pluralize section')
320 parser.fail('control structures in translatable sections are '
321 'not allowed')
Armin Ronacherd02fc7d2008-06-14 14:19:47 +0200322 elif parser.stream.eos:
323 parser.fail('unclosed translation block')
Armin Ronacherb5124e62008-04-25 00:36:14 +0200324 else:
325 assert False, 'internal parser error'
326
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200327 return referenced, concat(buf)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200328
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200329 def _make_node(self, singular, plural, variables, plural_expr,
Armin Ronacherb98dad92010-05-29 22:31:17 +0200330 vars_referenced, num_called_num):
Armin Ronacherb5124e62008-04-25 00:36:14 +0200331 """Generates a useful node from the data provided."""
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200332 # no variables referenced? no need to escape for old style
Armin Ronacher4cccc222010-07-06 11:37:45 +0200333 # gettext invocations only if there are vars.
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200334 if not vars_referenced and not self.environment.newstyle_gettext:
335 singular = singular.replace('%%', '%')
336 if plural:
337 plural = plural.replace('%%', '%')
338
Armin Ronacherb5124e62008-04-25 00:36:14 +0200339 # singular only:
340 if plural_expr is None:
341 gettext = nodes.Name('gettext', 'load')
342 node = nodes.Call(gettext, [nodes.Const(singular)],
343 [], None, None)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200344
345 # singular and plural
346 else:
347 ngettext = nodes.Name('ngettext', 'load')
348 node = nodes.Call(ngettext, [
349 nodes.Const(singular),
350 nodes.Const(plural),
351 plural_expr
352 ], [], None, None)
Armin Ronacherd84ec462008-04-29 13:43:16 +0200353
Armin Ronacher4da90342010-05-29 17:35:10 +0200354 # in case newstyle gettext is used, the method is powerful
355 # enough to handle the variable expansion and autoescape
356 # handling itself
357 if self.environment.newstyle_gettext:
Thomas Waldmanne0003552013-05-17 23:52:14 +0200358 for key, value in six.iteritems(variables):
Armin Ronacherb98dad92010-05-29 22:31:17 +0200359 # the function adds that later anyways in case num was
360 # called num, so just skip it.
361 if num_called_num and key == 'num':
362 continue
Armin Ronacherb8892e72010-05-29 17:58:06 +0200363 node.kwargs.append(nodes.Keyword(key, value))
Armin Ronacherd84ec462008-04-29 13:43:16 +0200364
Armin Ronacher4da90342010-05-29 17:35:10 +0200365 # otherwise do that here
366 else:
367 # mark the return value as safe if we are in an
368 # environment with autoescaping turned on
369 node = nodes.MarkSafeIfAutoescape(node)
370 if variables:
Armin Ronacherb8892e72010-05-29 17:58:06 +0200371 node = nodes.Mod(node, nodes.Dict([
372 nodes.Pair(nodes.Const(key), value)
373 for key, value in variables.items()
374 ]))
Armin Ronacherb5124e62008-04-25 00:36:14 +0200375 return nodes.Output([node])
376
377
Armin Ronacher5d2733f2008-05-15 23:26:52 +0200378class ExprStmtExtension(Extension):
379 """Adds a `do` tag to Jinja2 that works like the print statement just
380 that it doesn't print the return value.
381 """
382 tags = set(['do'])
383
384 def parse(self, parser):
Armin Ronacherbd357722009-08-05 20:25:06 +0200385 node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
Armin Ronacher5d2733f2008-05-15 23:26:52 +0200386 node.node = parser.parse_tuple()
387 return node
388
389
Armin Ronacher3da90312008-05-23 16:37:28 +0200390class LoopControlExtension(Extension):
391 """Adds break and continue to the template engine."""
392 tags = set(['break', 'continue'])
393
394 def parse(self, parser):
Armin Ronacherbd357722009-08-05 20:25:06 +0200395 token = next(parser.stream)
Armin Ronacher3da90312008-05-23 16:37:28 +0200396 if token.value == 'break':
397 return nodes.Break(lineno=token.lineno)
398 return nodes.Continue(lineno=token.lineno)
399
400
Armin Ronacher9b4cc9f2010-02-07 03:55:15 +0100401class WithExtension(Extension):
402 """Adds support for a django-like with block."""
403 tags = set(['with'])
404
405 def parse(self, parser):
406 node = nodes.Scope(lineno=next(parser.stream).lineno)
407 assignments = []
408 while parser.stream.current.type != 'block_end':
409 lineno = parser.stream.current.lineno
410 if assignments:
411 parser.stream.expect('comma')
412 target = parser.parse_assign_target()
413 parser.stream.expect('assign')
414 expr = parser.parse_expression()
415 assignments.append(nodes.Assign(target, expr, lineno=lineno))
416 node.body = assignments + \
417 list(parser.parse_statements(('name:endwith',),
418 drop_needle=True))
419 return node
420
421
Armin Ronacher8346bd72010-03-14 19:43:47 +0100422class AutoEscapeExtension(Extension):
423 """Changes auto escape rules for a scope."""
424 tags = set(['autoescape'])
425
426 def parse(self, parser):
427 node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno)
428 node.options = [
429 nodes.Keyword('autoescape', parser.parse_expression())
430 ]
431 node.body = parser.parse_statements(('name:endautoescape',),
432 drop_needle=True)
433 return nodes.Scope([node])
434
435
Armin Ronacherabd36572008-06-27 08:45:19 +0200436def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
437 babel_style=True):
438 """Extract localizable strings from the given template node. Per
439 default this function returns matches in babel style that means non string
440 parameters as well as keyword arguments are returned as `None`. This
441 allows Babel to figure out what you really meant if you are using
442 gettext functions that allow keyword arguments for placeholder expansion.
443 If you don't want that behavior set the `babel_style` parameter to `False`
444 which causes only strings to be returned and parameters are always stored
445 in tuples. As a consequence invalid gettext calls (calls without a single
446 string parameter or string parameters after non-string parameters) are
447 skipped.
448
449 This example explains the behavior:
450
451 >>> from jinja2 import Environment
452 >>> env = Environment()
453 >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
454 >>> list(extract_from_ast(node))
455 [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
456 >>> list(extract_from_ast(node, babel_style=False))
457 [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
Armin Ronacherb5124e62008-04-25 00:36:14 +0200458
459 For every string found this function yields a ``(lineno, function,
460 message)`` tuple, where:
461
462 * ``lineno`` is the number of the line on which the string was found,
463 * ``function`` is the name of the ``gettext`` function used (if the
464 string was extracted from embedded Python code), and
465 * ``message`` is the string itself (a ``unicode`` object, or a tuple
466 of ``unicode`` objects for functions with multiple string arguments).
Armin Ronacher531578d2010-02-06 16:34:54 +0100467
468 This extraction function operates on the AST and is because of that unable
469 to extract any comments. For comment support you have to use the babel
470 extraction interface or extract comments yourself.
Armin Ronacherb5124e62008-04-25 00:36:14 +0200471 """
472 for node in node.find_all(nodes.Call):
473 if not isinstance(node.node, nodes.Name) or \
474 node.node.name not in gettext_functions:
475 continue
476
477 strings = []
478 for arg in node.args:
479 if isinstance(arg, nodes.Const) and \
480 isinstance(arg.value, basestring):
481 strings.append(arg.value)
482 else:
483 strings.append(None)
484
Armin Ronacherabd36572008-06-27 08:45:19 +0200485 for arg in node.kwargs:
486 strings.append(None)
487 if node.dyn_args is not None:
488 strings.append(None)
489 if node.dyn_kwargs is not None:
490 strings.append(None)
491
492 if not babel_style:
493 strings = tuple(x for x in strings if x is not None)
494 if not strings:
495 continue
Armin Ronacherb5124e62008-04-25 00:36:14 +0200496 else:
Armin Ronacherabd36572008-06-27 08:45:19 +0200497 if len(strings) == 1:
498 strings = strings[0]
499 else:
500 strings = tuple(strings)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200501 yield node.lineno, node.node.name, strings
502
503
Armin Ronacher531578d2010-02-06 16:34:54 +0100504class _CommentFinder(object):
505 """Helper class to find comments in a token stream. Can only
506 find comments for gettext calls forwards. Once the comment
507 from line 4 is found, a comment for line 1 will not return a
508 usable value.
509 """
510
511 def __init__(self, tokens, comment_tags):
512 self.tokens = tokens
513 self.comment_tags = comment_tags
514 self.offset = 0
515 self.last_lineno = 0
516
517 def find_backwards(self, offset):
518 try:
519 for _, token_type, token_value in \
520 reversed(self.tokens[self.offset:offset]):
521 if token_type in ('comment', 'linecomment'):
522 try:
523 prefix, comment = token_value.split(None, 1)
524 except ValueError:
525 continue
526 if prefix in self.comment_tags:
527 return [comment.rstrip()]
528 return []
529 finally:
530 self.offset = offset
531
532 def find_comments(self, lineno):
533 if not self.comment_tags or self.last_lineno > lineno:
534 return []
535 for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]):
536 if token_lineno > lineno:
537 return self.find_backwards(self.offset + idx)
538 return self.find_backwards(len(self.tokens))
539
540
Armin Ronacherb5124e62008-04-25 00:36:14 +0200541def babel_extract(fileobj, keywords, comment_tags, options):
542 """Babel extraction method for Jinja templates.
543
Armin Ronacher531578d2010-02-06 16:34:54 +0100544 .. versionchanged:: 2.3
545 Basic support for translation comments was added. If `comment_tags`
546 is now set to a list of keywords for extraction, the extractor will
547 try to find the best preceeding comment that begins with one of the
548 keywords. For best results, make sure to not have more than one
549 gettext call in one line of code and the matching comment in the
550 same line or the line before.
551
Armin Ronacher4f77a302010-07-01 12:15:39 +0200552 .. versionchanged:: 2.5.1
553 The `newstyle_gettext` flag can be set to `True` to enable newstyle
554 gettext calls.
555
Armin Ronacher11619152011-12-15 11:50:27 +0100556 .. versionchanged:: 2.7
557 A `silent` option can now be provided. If set to `False` template
558 syntax errors are propagated instead of being ignored.
559
Armin Ronacherb5124e62008-04-25 00:36:14 +0200560 :param fileobj: the file-like object the messages should be extracted from
561 :param keywords: a list of keywords (i.e. function names) that should be
562 recognized as translation functions
563 :param comment_tags: a list of translator tags to search for and include
Armin Ronacher531578d2010-02-06 16:34:54 +0100564 in the results.
Armin Ronacherb5124e62008-04-25 00:36:14 +0200565 :param options: a dictionary of additional options (optional)
566 :return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
567 (comments will be empty currently)
568 """
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200569 extensions = set()
Armin Ronacherb5124e62008-04-25 00:36:14 +0200570 for extension in options.get('extensions', '').split(','):
571 extension = extension.strip()
572 if not extension:
573 continue
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200574 extensions.add(import_string(extension))
575 if InternationalizationExtension not in extensions:
576 extensions.add(InternationalizationExtension)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200577
Armin Ronacher4f77a302010-07-01 12:15:39 +0200578 def getbool(options, key, default=False):
Armin Ronacher11619152011-12-15 11:50:27 +0100579 return options.get(key, str(default)).lower() in \
580 ('1', 'on', 'yes', 'true')
Armin Ronacher4f77a302010-07-01 12:15:39 +0200581
Armin Ronacher11619152011-12-15 11:50:27 +0100582 silent = getbool(options, 'silent', True)
Armin Ronacher4f77a302010-07-01 12:15:39 +0200583 environment = Environment(
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200584 options.get('block_start_string', BLOCK_START_STRING),
585 options.get('block_end_string', BLOCK_END_STRING),
586 options.get('variable_start_string', VARIABLE_START_STRING),
587 options.get('variable_end_string', VARIABLE_END_STRING),
588 options.get('comment_start_string', COMMENT_START_STRING),
589 options.get('comment_end_string', COMMENT_END_STRING),
590 options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
Armin Ronacher59b6bd52009-03-30 21:00:16 +0200591 options.get('line_comment_prefix') or LINE_COMMENT_PREFIX,
Armin Ronacher4f77a302010-07-01 12:15:39 +0200592 getbool(options, 'trim_blocks', TRIM_BLOCKS),
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200593 NEWLINE_SEQUENCE, frozenset(extensions),
Armin Ronacher4f77a302010-07-01 12:15:39 +0200594 cache_size=0,
595 auto_reload=False
Armin Ronacherb5124e62008-04-25 00:36:14 +0200596 )
597
Armin Ronacher4f77a302010-07-01 12:15:39 +0200598 if getbool(options, 'newstyle_gettext'):
599 environment.newstyle_gettext = True
600
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200601 source = fileobj.read().decode(options.get('encoding', 'utf-8'))
Armin Ronacherc670b112008-06-29 17:23:04 +0200602 try:
603 node = environment.parse(source)
Armin Ronacher531578d2010-02-06 16:34:54 +0100604 tokens = list(environment.lex(environment.preprocess(source)))
Thomas Waldmanne0003552013-05-17 23:52:14 +0200605 except TemplateSyntaxError as e:
Armin Ronacher11619152011-12-15 11:50:27 +0100606 if not silent:
607 raise
Armin Ronacherc670b112008-06-29 17:23:04 +0200608 # skip templates with syntax errors
609 return
Armin Ronacher531578d2010-02-06 16:34:54 +0100610
611 finder = _CommentFinder(tokens, comment_tags)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200612 for lineno, func, message in extract_from_ast(node, keywords):
Armin Ronacher531578d2010-02-06 16:34:54 +0100613 yield lineno, func, message, finder.find_comments(lineno)
Armin Ronachered98cac2008-05-07 08:42:11 +0200614
615
616#: nicer import names
617i18n = InternationalizationExtension
Armin Ronacher5d2733f2008-05-15 23:26:52 +0200618do = ExprStmtExtension
Armin Ronacher3da90312008-05-23 16:37:28 +0200619loopcontrols = LoopControlExtension
Armin Ronacher9b4cc9f2010-02-07 03:55:15 +0100620with_ = WithExtension
Armin Ronacher8346bd72010-03-14 19:43:47 +0100621autoescape = AutoEscapeExtension