blob: 8ded3d12fb418333443d5fc14711c53148660c88 [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"""
13from jinja2 import nodes
Armin Ronachera65f1eb2013-05-19 11:18:19 +010014from 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
Armin Ronacher4f77a302010-07-01 12:15:39 +020019from jinja2.environment import Environment
Daniel Neuhäuserd0708db2013-05-18 12:52:40 +020020from jinja2.runtime import concat
Benjamin Wieganda3152742008-04-28 18:07:52 +020021from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
Thomas Waldmann7d295622013-05-18 00:06:22 +020022from jinja2.utils import contextfunction, import_string, Markup
Armin Ronacherc87d4cf2013-05-19 13:46:22 +010023from jinja2._compat import next
Thomas Waldmanne0003552013-05-17 23:52:14 +020024import six
Armin Ronacherb5124e62008-04-25 00:36:14 +020025
26
27# the only real useful gettext functions for a Jinja template. Note
28# that ugettext must be assigned to gettext as Jinja doesn't support
29# non unicode strings.
30GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
Armin Ronacher05530932008-04-20 13:27:49 +020031
32
Armin Ronacher023b5e92008-05-08 11:03:10 +020033class ExtensionRegistry(type):
Armin Ronacher5c047ea2008-05-23 22:26:45 +020034 """Gives the extension an unique identifier."""
Armin Ronacher023b5e92008-05-08 11:03:10 +020035
36 def __new__(cls, name, bases, d):
37 rv = type.__new__(cls, name, bases, d)
38 rv.identifier = rv.__module__ + '.' + rv.__name__
39 return rv
40
41
Thomas Waldmann7d295622013-05-18 00:06:22 +020042class Extension(six.with_metaclass(ExtensionRegistry, object)):
Armin Ronacher7259c762008-04-30 13:03:59 +020043 """Extensions can be used to add extra functionality to the Jinja template
Armin Ronacher9d42abf2008-05-14 18:10:41 +020044 system at the parser level. Custom extensions are bound to an environment
45 but may not store environment specific data on `self`. The reason for
46 this is that an extension can be bound to another environment (for
47 overlays) by creating a copy and reassigning the `environment` attribute.
Armin Ronacher762079c2008-05-08 23:57:56 +020048
49 As extensions are created by the environment they cannot accept any
50 arguments for configuration. One may want to work around that by using
51 a factory function, but that is not possible as extensions are identified
52 by their import name. The correct way to configure the extension is
53 storing the configuration values on the environment. Because this way the
54 environment ends up acting as central configuration storage the
55 attributes may clash which is why extensions have to ensure that the names
56 they choose for configuration are not too generic. ``prefix`` for example
57 is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
58 name as includes the name of the extension (fragment cache).
Armin Ronacher7259c762008-04-30 13:03:59 +020059 """
Armin Ronacher023b5e92008-05-08 11:03:10 +020060 __metaclass__ = ExtensionRegistry
Armin Ronacher05530932008-04-20 13:27:49 +020061
62 #: if this extension parses this is the list of tags it's listening to.
63 tags = set()
64
Armin Ronacher5b3f4dc2010-04-12 14:04:14 +020065 #: the priority of that extension. This is especially useful for
66 #: extensions that preprocess values. A lower value means higher
67 #: priority.
68 #:
69 #: .. versionadded:: 2.4
70 priority = 100
71
Armin Ronacher05530932008-04-20 13:27:49 +020072 def __init__(self, environment):
73 self.environment = environment
74
Armin Ronacher7259c762008-04-30 13:03:59 +020075 def bind(self, environment):
76 """Create a copy of this extension bound to another environment."""
77 rv = object.__new__(self.__class__)
78 rv.__dict__.update(self.__dict__)
79 rv.environment = environment
80 return rv
81
Armin Ronacher9ad96e72008-06-13 22:44:01 +020082 def preprocess(self, source, name, filename=None):
83 """This method is called before the actual lexing and can be used to
84 preprocess the source. The `filename` is optional. The return value
85 must be the preprocessed source.
86 """
87 return source
88
89 def filter_stream(self, stream):
90 """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
91 to filter tokens returned. This method has to return an iterable of
92 :class:`~jinja2.lexer.Token`\s, but it doesn't have to return a
93 :class:`~jinja2.lexer.TokenStream`.
Armin Ronacherd02fc7d2008-06-14 14:19:47 +020094
95 In the `ext` folder of the Jinja2 source distribution there is a file
96 called `inlinegettext.py` which implements a filter that utilizes this
97 method.
Armin Ronacher9ad96e72008-06-13 22:44:01 +020098 """
99 return stream
100
Armin Ronacher05530932008-04-20 13:27:49 +0200101 def parse(self, parser):
Armin Ronacher023b5e92008-05-08 11:03:10 +0200102 """If any of the :attr:`tags` matched this method is called with the
103 parser as first argument. The token the parser stream is pointing at
104 is the name token that matched. This method has to return one or a
105 list of multiple nodes.
106 """
Armin Ronacher27069d72008-05-11 19:48:12 +0200107 raise NotImplementedError()
Armin Ronacher023b5e92008-05-08 11:03:10 +0200108
109 def attr(self, name, lineno=None):
110 """Return an attribute node for the current extension. This is useful
Armin Ronacher53278a32011-01-24 01:16:00 +0100111 to pass constants on extensions to generated template code.
112
113 ::
Armin Ronacher023b5e92008-05-08 11:03:10 +0200114
Armin Ronacher69e12db2008-05-12 09:00:03 +0200115 self.attr('_my_attribute', lineno=lineno)
Armin Ronacher023b5e92008-05-08 11:03:10 +0200116 """
117 return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
Armin Ronacher05530932008-04-20 13:27:49 +0200118
Armin Ronacher27069d72008-05-11 19:48:12 +0200119 def call_method(self, name, args=None, kwargs=None, dyn_args=None,
120 dyn_kwargs=None, lineno=None):
Armin Ronacher69e12db2008-05-12 09:00:03 +0200121 """Call a method of the extension. This is a shortcut for
122 :meth:`attr` + :class:`jinja2.nodes.Call`.
123 """
Armin Ronacher27069d72008-05-11 19:48:12 +0200124 if args is None:
125 args = []
126 if kwargs is None:
127 kwargs = []
128 return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
129 dyn_args, dyn_kwargs, lineno=lineno)
130
Armin Ronacher05530932008-04-20 13:27:49 +0200131
Armin Ronacher5c047ea2008-05-23 22:26:45 +0200132@contextfunction
Armin Ronacher4da90342010-05-29 17:35:10 +0200133def _gettext_alias(__context, *args, **kwargs):
Armin Ronacherb8892e72010-05-29 17:58:06 +0200134 return __context.call(__context.resolve('gettext'), *args, **kwargs)
Armin Ronacher4da90342010-05-29 17:35:10 +0200135
136
137def _make_new_gettext(func):
138 @contextfunction
139 def gettext(__context, __string, **variables):
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200140 rv = __context.call(func, __string)
Armin Ronacher4da90342010-05-29 17:35:10 +0200141 if __context.eval_ctx.autoescape:
142 rv = Markup(rv)
143 return rv % variables
144 return gettext
145
146
147def _make_new_ngettext(func):
148 @contextfunction
Armin Ronacherb98dad92010-05-29 22:31:17 +0200149 def ngettext(__context, __singular, __plural, __num, **variables):
150 variables.setdefault('num', __num)
151 rv = __context.call(func, __singular, __plural, __num)
Armin Ronacher4da90342010-05-29 17:35:10 +0200152 if __context.eval_ctx.autoescape:
153 rv = Markup(rv)
154 return rv % variables
155 return ngettext
Armin Ronacher5c047ea2008-05-23 22:26:45 +0200156
157
Armin Ronachered98cac2008-05-07 08:42:11 +0200158class InternationalizationExtension(Extension):
Armin Ronacher762079c2008-05-08 23:57:56 +0200159 """This extension adds gettext support to Jinja2."""
Armin Ronacherb5124e62008-04-25 00:36:14 +0200160 tags = set(['trans'])
161
Armin Ronacher4720c362008-09-06 16:15:38 +0200162 # TODO: the i18n extension is currently reevaluating values in a few
163 # situations. Take this example:
164 # {% trans count=something() %}{{ count }} foo{% pluralize
165 # %}{{ count }} fooss{% endtrans %}
166 # something is called twice here. One time for the gettext value and
167 # the other time for the n-parameter of the ngettext function.
168
Armin Ronacherb5124e62008-04-25 00:36:14 +0200169 def __init__(self, environment):
170 Extension.__init__(self, environment)
Armin Ronacher5c047ea2008-05-23 22:26:45 +0200171 environment.globals['_'] = _gettext_alias
Armin Ronacher762079c2008-05-08 23:57:56 +0200172 environment.extend(
173 install_gettext_translations=self._install,
174 install_null_translations=self._install_null,
Armin Ronacher4da90342010-05-29 17:35:10 +0200175 install_gettext_callables=self._install_callables,
Armin Ronacher762079c2008-05-08 23:57:56 +0200176 uninstall_gettext_translations=self._uninstall,
Armin Ronacher4da90342010-05-29 17:35:10 +0200177 extract_translations=self._extract,
178 newstyle_gettext=False
Armin Ronacher762079c2008-05-08 23:57:56 +0200179 )
180
Armin Ronacher4da90342010-05-29 17:35:10 +0200181 def _install(self, translations, newstyle=None):
Armin Ronacher32133552008-09-15 14:35:01 +0200182 gettext = getattr(translations, 'ugettext', None)
183 if gettext is None:
184 gettext = translations.gettext
185 ngettext = getattr(translations, 'ungettext', None)
186 if ngettext is None:
187 ngettext = translations.ngettext
Armin Ronacher4da90342010-05-29 17:35:10 +0200188 self._install_callables(gettext, ngettext, newstyle)
Armin Ronacher762079c2008-05-08 23:57:56 +0200189
Armin Ronacher4da90342010-05-29 17:35:10 +0200190 def _install_null(self, newstyle=None):
191 self._install_callables(
192 lambda x: x,
193 lambda s, p, n: (n != 1 and (p,) or (s,))[0],
194 newstyle
195 )
196
197 def _install_callables(self, gettext, ngettext, newstyle=None):
198 if newstyle is not None:
199 self.environment.newstyle_gettext = newstyle
200 if self.environment.newstyle_gettext:
201 gettext = _make_new_gettext(gettext)
202 ngettext = _make_new_ngettext(ngettext)
Armin Ronacher762079c2008-05-08 23:57:56 +0200203 self.environment.globals.update(
Armin Ronacher4da90342010-05-29 17:35:10 +0200204 gettext=gettext,
205 ngettext=ngettext
Armin Ronacher762079c2008-05-08 23:57:56 +0200206 )
207
208 def _uninstall(self, translations):
209 for key in 'gettext', 'ngettext':
210 self.environment.globals.pop(key, None)
211
212 def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
Thomas Waldmann7d295622013-05-18 00:06:22 +0200213 if isinstance(source, six.string_types):
Armin Ronacher762079c2008-05-08 23:57:56 +0200214 source = self.environment.parse(source)
215 return extract_from_ast(source, gettext_functions)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200216
217 def parse(self, parser):
218 """Parse a translatable tag."""
Armin Ronacherc87d4cf2013-05-19 13:46:22 +0100219 lineno = next(parser.stream).lineno
Armin Ronacherb98dad92010-05-29 22:31:17 +0200220 num_called_num = False
Armin Ronacherb5124e62008-04-25 00:36:14 +0200221
Armin Ronacherb5124e62008-04-25 00:36:14 +0200222 # find all the variables referenced. Additionally a variable can be
223 # defined in the body of the trans block too, but this is checked at
224 # a later state.
225 plural_expr = None
Florian Apolloner79c84752012-01-18 17:47:54 +0100226 plural_expr_assignment = None
Armin Ronacherb5124e62008-04-25 00:36:14 +0200227 variables = {}
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100228 while parser.stream.current.type != 'block_end':
Armin Ronacherb5124e62008-04-25 00:36:14 +0200229 if variables:
230 parser.stream.expect('comma')
Armin Ronacher023b5e92008-05-08 11:03:10 +0200231
232 # skip colon for python compatibility
Armin Ronacherfdf95302008-05-11 22:20:51 +0200233 if parser.stream.skip_if('colon'):
Armin Ronacher023b5e92008-05-08 11:03:10 +0200234 break
235
Armin Ronacherb5124e62008-04-25 00:36:14 +0200236 name = parser.stream.expect('name')
237 if name.value in variables:
Armin Ronacher7f15ef82008-05-16 09:11:39 +0200238 parser.fail('translatable variable %r defined twice.' %
239 name.value, name.lineno,
240 exc=TemplateAssertionError)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200241
242 # expressions
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100243 if parser.stream.current.type == 'assign':
Armin Ronacherc87d4cf2013-05-19 13:46:22 +0100244 next(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200245 variables[name.value] = var = parser.parse_expression()
246 else:
247 variables[name.value] = var = nodes.Name(name.value, 'load')
Armin Ronacherb98dad92010-05-29 22:31:17 +0200248
Armin Ronacherb5124e62008-04-25 00:36:14 +0200249 if plural_expr is None:
Florian Apolloner5a25a472012-01-18 17:08:48 +0100250 if isinstance(var, nodes.Call):
Florian Apolloner79c84752012-01-18 17:47:54 +0100251 plural_expr = nodes.Name('_trans', 'load')
252 variables[name.value] = plural_expr
253 plural_expr_assignment = nodes.Assign(
254 nodes.Name('_trans', 'store'), var)
Florian Apolloner5a25a472012-01-18 17:08:48 +0100255 else:
256 plural_expr = var
Armin Ronacherb98dad92010-05-29 22:31:17 +0200257 num_called_num = name.value == 'num'
Armin Ronacher023b5e92008-05-08 11:03:10 +0200258
Armin Ronacherb5124e62008-04-25 00:36:14 +0200259 parser.stream.expect('block_end')
260
261 plural = plural_names = None
262 have_plural = False
263 referenced = set()
264
265 # now parse until endtrans or pluralize
266 singular_names, singular = self._parse_block(parser, True)
267 if singular_names:
268 referenced.update(singular_names)
269 if plural_expr is None:
270 plural_expr = nodes.Name(singular_names[0], 'load')
Armin Ronacherb98dad92010-05-29 22:31:17 +0200271 num_called_num = singular_names[0] == 'num'
Armin Ronacherb5124e62008-04-25 00:36:14 +0200272
273 # if we have a pluralize block, we parse that too
274 if parser.stream.current.test('name:pluralize'):
275 have_plural = True
Armin Ronacherc87d4cf2013-05-19 13:46:22 +0100276 next(parser.stream)
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100277 if parser.stream.current.type != 'block_end':
Armin Ronacher4720c362008-09-06 16:15:38 +0200278 name = parser.stream.expect('name')
279 if name.value not in variables:
280 parser.fail('unknown variable %r for pluralization' %
281 name.value, name.lineno,
282 exc=TemplateAssertionError)
283 plural_expr = variables[name.value]
Armin Ronacherb98dad92010-05-29 22:31:17 +0200284 num_called_num = name.value == 'num'
Armin Ronacherb5124e62008-04-25 00:36:14 +0200285 parser.stream.expect('block_end')
286 plural_names, plural = self._parse_block(parser, False)
Armin Ronacherc87d4cf2013-05-19 13:46:22 +0100287 next(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200288 referenced.update(plural_names)
289 else:
Armin Ronacherc87d4cf2013-05-19 13:46:22 +0100290 next(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200291
292 # register free names as simple name expressions
293 for var in referenced:
294 if var not in variables:
295 variables[var] = nodes.Name(var, 'load')
296
Armin Ronacherb5124e62008-04-25 00:36:14 +0200297 if not have_plural:
298 plural_expr = None
299 elif plural_expr is None:
Armin Ronacher7f15ef82008-05-16 09:11:39 +0200300 parser.fail('pluralize without variables', lineno)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200301
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200302 node = self._make_node(singular, plural, variables, plural_expr,
Armin Ronacher4f77a302010-07-01 12:15:39 +0200303 bool(referenced),
304 num_called_num and have_plural)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200305 node.set_lineno(lineno)
Florian Apolloner79c84752012-01-18 17:47:54 +0100306 if plural_expr_assignment is not None:
307 return [plural_expr_assignment, node]
308 else:
309 return node
Armin Ronacherb5124e62008-04-25 00:36:14 +0200310
311 def _parse_block(self, parser, allow_pluralize):
312 """Parse until the next block tag with a given name."""
313 referenced = []
314 buf = []
315 while 1:
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100316 if parser.stream.current.type == 'data':
Armin Ronacherb5124e62008-04-25 00:36:14 +0200317 buf.append(parser.stream.current.value.replace('%', '%%'))
Armin Ronacherc87d4cf2013-05-19 13:46:22 +0100318 next(parser.stream)
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100319 elif parser.stream.current.type == 'variable_begin':
Armin Ronacherc87d4cf2013-05-19 13:46:22 +0100320 next(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200321 name = parser.stream.expect('name').value
322 referenced.append(name)
323 buf.append('%%(%s)s' % name)
324 parser.stream.expect('variable_end')
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100325 elif parser.stream.current.type == 'block_begin':
Armin Ronacherc87d4cf2013-05-19 13:46:22 +0100326 next(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200327 if parser.stream.current.test('name:endtrans'):
328 break
329 elif parser.stream.current.test('name:pluralize'):
330 if allow_pluralize:
331 break
Armin Ronacher7f15ef82008-05-16 09:11:39 +0200332 parser.fail('a translatable section can have only one '
333 'pluralize section')
334 parser.fail('control structures in translatable sections are '
335 'not allowed')
Armin Ronacherd02fc7d2008-06-14 14:19:47 +0200336 elif parser.stream.eos:
337 parser.fail('unclosed translation block')
Armin Ronacherb5124e62008-04-25 00:36:14 +0200338 else:
339 assert False, 'internal parser error'
340
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200341 return referenced, concat(buf)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200342
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200343 def _make_node(self, singular, plural, variables, plural_expr,
Armin Ronacherb98dad92010-05-29 22:31:17 +0200344 vars_referenced, num_called_num):
Armin Ronacherb5124e62008-04-25 00:36:14 +0200345 """Generates a useful node from the data provided."""
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200346 # no variables referenced? no need to escape for old style
Armin Ronacher4cccc222010-07-06 11:37:45 +0200347 # gettext invocations only if there are vars.
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200348 if not vars_referenced and not self.environment.newstyle_gettext:
349 singular = singular.replace('%%', '%')
350 if plural:
351 plural = plural.replace('%%', '%')
352
Armin Ronacherb5124e62008-04-25 00:36:14 +0200353 # singular only:
354 if plural_expr is None:
355 gettext = nodes.Name('gettext', 'load')
356 node = nodes.Call(gettext, [nodes.Const(singular)],
357 [], None, None)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200358
359 # singular and plural
360 else:
361 ngettext = nodes.Name('ngettext', 'load')
362 node = nodes.Call(ngettext, [
363 nodes.Const(singular),
364 nodes.Const(plural),
365 plural_expr
366 ], [], None, None)
Armin Ronacherd84ec462008-04-29 13:43:16 +0200367
Armin Ronacher4da90342010-05-29 17:35:10 +0200368 # in case newstyle gettext is used, the method is powerful
369 # enough to handle the variable expansion and autoescape
370 # handling itself
371 if self.environment.newstyle_gettext:
Thomas Waldmanne0003552013-05-17 23:52:14 +0200372 for key, value in six.iteritems(variables):
Armin Ronacherb98dad92010-05-29 22:31:17 +0200373 # the function adds that later anyways in case num was
374 # called num, so just skip it.
375 if num_called_num and key == 'num':
376 continue
Armin Ronacherb8892e72010-05-29 17:58:06 +0200377 node.kwargs.append(nodes.Keyword(key, value))
Armin Ronacherd84ec462008-04-29 13:43:16 +0200378
Armin Ronacher4da90342010-05-29 17:35:10 +0200379 # otherwise do that here
380 else:
381 # mark the return value as safe if we are in an
382 # environment with autoescaping turned on
383 node = nodes.MarkSafeIfAutoescape(node)
384 if variables:
Armin Ronacherb8892e72010-05-29 17:58:06 +0200385 node = nodes.Mod(node, nodes.Dict([
386 nodes.Pair(nodes.Const(key), value)
387 for key, value in variables.items()
388 ]))
Armin Ronacherb5124e62008-04-25 00:36:14 +0200389 return nodes.Output([node])
390
391
Armin Ronacher5d2733f2008-05-15 23:26:52 +0200392class ExprStmtExtension(Extension):
393 """Adds a `do` tag to Jinja2 that works like the print statement just
394 that it doesn't print the return value.
395 """
396 tags = set(['do'])
397
398 def parse(self, parser):
Armin Ronacherc87d4cf2013-05-19 13:46:22 +0100399 node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
Armin Ronacher5d2733f2008-05-15 23:26:52 +0200400 node.node = parser.parse_tuple()
401 return node
402
403
Armin Ronacher3da90312008-05-23 16:37:28 +0200404class LoopControlExtension(Extension):
405 """Adds break and continue to the template engine."""
406 tags = set(['break', 'continue'])
407
408 def parse(self, parser):
Armin Ronacherc87d4cf2013-05-19 13:46:22 +0100409 token = next(parser.stream)
Armin Ronacher3da90312008-05-23 16:37:28 +0200410 if token.value == 'break':
411 return nodes.Break(lineno=token.lineno)
412 return nodes.Continue(lineno=token.lineno)
413
414
Armin Ronacher9b4cc9f2010-02-07 03:55:15 +0100415class WithExtension(Extension):
416 """Adds support for a django-like with block."""
417 tags = set(['with'])
418
419 def parse(self, parser):
Armin Ronacherc87d4cf2013-05-19 13:46:22 +0100420 node = nodes.Scope(lineno=next(parser.stream).lineno)
Armin Ronacher9b4cc9f2010-02-07 03:55:15 +0100421 assignments = []
422 while parser.stream.current.type != 'block_end':
423 lineno = parser.stream.current.lineno
424 if assignments:
425 parser.stream.expect('comma')
426 target = parser.parse_assign_target()
427 parser.stream.expect('assign')
428 expr = parser.parse_expression()
429 assignments.append(nodes.Assign(target, expr, lineno=lineno))
430 node.body = assignments + \
431 list(parser.parse_statements(('name:endwith',),
432 drop_needle=True))
433 return node
434
435
Armin Ronacher8346bd72010-03-14 19:43:47 +0100436class AutoEscapeExtension(Extension):
437 """Changes auto escape rules for a scope."""
438 tags = set(['autoescape'])
439
440 def parse(self, parser):
Armin Ronacherc87d4cf2013-05-19 13:46:22 +0100441 node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno)
Armin Ronacher8346bd72010-03-14 19:43:47 +0100442 node.options = [
443 nodes.Keyword('autoescape', parser.parse_expression())
444 ]
445 node.body = parser.parse_statements(('name:endautoescape',),
446 drop_needle=True)
447 return nodes.Scope([node])
448
449
Armin Ronacherabd36572008-06-27 08:45:19 +0200450def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
451 babel_style=True):
452 """Extract localizable strings from the given template node. Per
453 default this function returns matches in babel style that means non string
454 parameters as well as keyword arguments are returned as `None`. This
455 allows Babel to figure out what you really meant if you are using
456 gettext functions that allow keyword arguments for placeholder expansion.
457 If you don't want that behavior set the `babel_style` parameter to `False`
458 which causes only strings to be returned and parameters are always stored
459 in tuples. As a consequence invalid gettext calls (calls without a single
460 string parameter or string parameters after non-string parameters) are
461 skipped.
462
463 This example explains the behavior:
464
465 >>> from jinja2 import Environment
466 >>> env = Environment()
467 >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
468 >>> list(extract_from_ast(node))
469 [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
470 >>> list(extract_from_ast(node, babel_style=False))
471 [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
Armin Ronacherb5124e62008-04-25 00:36:14 +0200472
473 For every string found this function yields a ``(lineno, function,
474 message)`` tuple, where:
475
476 * ``lineno`` is the number of the line on which the string was found,
477 * ``function`` is the name of the ``gettext`` function used (if the
478 string was extracted from embedded Python code), and
479 * ``message`` is the string itself (a ``unicode`` object, or a tuple
480 of ``unicode`` objects for functions with multiple string arguments).
Armin Ronacher531578d2010-02-06 16:34:54 +0100481
482 This extraction function operates on the AST and is because of that unable
483 to extract any comments. For comment support you have to use the babel
484 extraction interface or extract comments yourself.
Armin Ronacherb5124e62008-04-25 00:36:14 +0200485 """
486 for node in node.find_all(nodes.Call):
487 if not isinstance(node.node, nodes.Name) or \
488 node.node.name not in gettext_functions:
489 continue
490
491 strings = []
492 for arg in node.args:
493 if isinstance(arg, nodes.Const) and \
Thomas Waldmann7d295622013-05-18 00:06:22 +0200494 isinstance(arg.value, six.string_types):
Armin Ronacherb5124e62008-04-25 00:36:14 +0200495 strings.append(arg.value)
496 else:
497 strings.append(None)
498
Armin Ronacherabd36572008-06-27 08:45:19 +0200499 for arg in node.kwargs:
500 strings.append(None)
501 if node.dyn_args is not None:
502 strings.append(None)
503 if node.dyn_kwargs is not None:
504 strings.append(None)
505
506 if not babel_style:
507 strings = tuple(x for x in strings if x is not None)
508 if not strings:
509 continue
Armin Ronacherb5124e62008-04-25 00:36:14 +0200510 else:
Armin Ronacherabd36572008-06-27 08:45:19 +0200511 if len(strings) == 1:
512 strings = strings[0]
513 else:
514 strings = tuple(strings)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200515 yield node.lineno, node.node.name, strings
516
517
Armin Ronacher531578d2010-02-06 16:34:54 +0100518class _CommentFinder(object):
519 """Helper class to find comments in a token stream. Can only
520 find comments for gettext calls forwards. Once the comment
521 from line 4 is found, a comment for line 1 will not return a
522 usable value.
523 """
524
525 def __init__(self, tokens, comment_tags):
526 self.tokens = tokens
527 self.comment_tags = comment_tags
528 self.offset = 0
529 self.last_lineno = 0
530
531 def find_backwards(self, offset):
532 try:
533 for _, token_type, token_value in \
534 reversed(self.tokens[self.offset:offset]):
535 if token_type in ('comment', 'linecomment'):
536 try:
537 prefix, comment = token_value.split(None, 1)
538 except ValueError:
539 continue
540 if prefix in self.comment_tags:
541 return [comment.rstrip()]
542 return []
543 finally:
544 self.offset = offset
545
546 def find_comments(self, lineno):
547 if not self.comment_tags or self.last_lineno > lineno:
548 return []
549 for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]):
550 if token_lineno > lineno:
551 return self.find_backwards(self.offset + idx)
552 return self.find_backwards(len(self.tokens))
553
554
Armin Ronacherb5124e62008-04-25 00:36:14 +0200555def babel_extract(fileobj, keywords, comment_tags, options):
556 """Babel extraction method for Jinja templates.
557
Armin Ronacher531578d2010-02-06 16:34:54 +0100558 .. versionchanged:: 2.3
559 Basic support for translation comments was added. If `comment_tags`
560 is now set to a list of keywords for extraction, the extractor will
561 try to find the best preceeding comment that begins with one of the
562 keywords. For best results, make sure to not have more than one
563 gettext call in one line of code and the matching comment in the
564 same line or the line before.
565
Armin Ronacher4f77a302010-07-01 12:15:39 +0200566 .. versionchanged:: 2.5.1
567 The `newstyle_gettext` flag can be set to `True` to enable newstyle
568 gettext calls.
569
Armin Ronacher11619152011-12-15 11:50:27 +0100570 .. versionchanged:: 2.7
571 A `silent` option can now be provided. If set to `False` template
572 syntax errors are propagated instead of being ignored.
573
Armin Ronacherb5124e62008-04-25 00:36:14 +0200574 :param fileobj: the file-like object the messages should be extracted from
575 :param keywords: a list of keywords (i.e. function names) that should be
576 recognized as translation functions
577 :param comment_tags: a list of translator tags to search for and include
Armin Ronacher531578d2010-02-06 16:34:54 +0100578 in the results.
Armin Ronacherb5124e62008-04-25 00:36:14 +0200579 :param options: a dictionary of additional options (optional)
580 :return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
581 (comments will be empty currently)
582 """
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200583 extensions = set()
Armin Ronacherb5124e62008-04-25 00:36:14 +0200584 for extension in options.get('extensions', '').split(','):
585 extension = extension.strip()
586 if not extension:
587 continue
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200588 extensions.add(import_string(extension))
589 if InternationalizationExtension not in extensions:
590 extensions.add(InternationalizationExtension)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200591
Armin Ronacher4f77a302010-07-01 12:15:39 +0200592 def getbool(options, key, default=False):
Armin Ronacher11619152011-12-15 11:50:27 +0100593 return options.get(key, str(default)).lower() in \
594 ('1', 'on', 'yes', 'true')
Armin Ronacher4f77a302010-07-01 12:15:39 +0200595
Armin Ronacher11619152011-12-15 11:50:27 +0100596 silent = getbool(options, 'silent', True)
Armin Ronacher4f77a302010-07-01 12:15:39 +0200597 environment = Environment(
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200598 options.get('block_start_string', BLOCK_START_STRING),
599 options.get('block_end_string', BLOCK_END_STRING),
600 options.get('variable_start_string', VARIABLE_START_STRING),
601 options.get('variable_end_string', VARIABLE_END_STRING),
602 options.get('comment_start_string', COMMENT_START_STRING),
603 options.get('comment_end_string', COMMENT_END_STRING),
604 options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
Armin Ronacher59b6bd52009-03-30 21:00:16 +0200605 options.get('line_comment_prefix') or LINE_COMMENT_PREFIX,
Armin Ronacher4f77a302010-07-01 12:15:39 +0200606 getbool(options, 'trim_blocks', TRIM_BLOCKS),
Armin Ronachera65f1eb2013-05-19 11:18:19 +0100607 getbool(options, 'lstrip_blocks', LSTRIP_BLOCKS),
W. Trevor King7e912c62013-01-11 08:23:24 -0500608 NEWLINE_SEQUENCE,
609 getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE),
610 frozenset(extensions),
Armin Ronacher4f77a302010-07-01 12:15:39 +0200611 cache_size=0,
612 auto_reload=False
Armin Ronacherb5124e62008-04-25 00:36:14 +0200613 )
614
Armin Ronacher4f77a302010-07-01 12:15:39 +0200615 if getbool(options, 'newstyle_gettext'):
616 environment.newstyle_gettext = True
617
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200618 source = fileobj.read().decode(options.get('encoding', 'utf-8'))
Armin Ronacherc670b112008-06-29 17:23:04 +0200619 try:
620 node = environment.parse(source)
Armin Ronacher531578d2010-02-06 16:34:54 +0100621 tokens = list(environment.lex(environment.preprocess(source)))
Thomas Waldmanne0003552013-05-17 23:52:14 +0200622 except TemplateSyntaxError as e:
Armin Ronacher11619152011-12-15 11:50:27 +0100623 if not silent:
624 raise
Armin Ronacherc670b112008-06-29 17:23:04 +0200625 # skip templates with syntax errors
626 return
Armin Ronacher531578d2010-02-06 16:34:54 +0100627
628 finder = _CommentFinder(tokens, comment_tags)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200629 for lineno, func, message in extract_from_ast(node, keywords):
Armin Ronacher531578d2010-02-06 16:34:54 +0100630 yield lineno, func, message, finder.find_comments(lineno)
Armin Ronachered98cac2008-05-07 08:42:11 +0200631
632
633#: nicer import names
634i18n = InternationalizationExtension
Armin Ronacher5d2733f2008-05-15 23:26:52 +0200635do = ExprStmtExtension
Armin Ronacher3da90312008-05-23 16:37:28 +0200636loopcontrols = LoopControlExtension
Armin Ronacher9b4cc9f2010-02-07 03:55:15 +0100637with_ = WithExtension
Armin Ronacher8346bd72010-03-14 19:43:47 +0100638autoescape = AutoEscapeExtension