blob: 639f159c952d88f17d41be3c59ae48a687edc49c [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
Thomas Waldmanne0003552013-05-17 23:52:14 +020023import six
Armin Ronacherb5124e62008-04-25 00:36:14 +020024
25
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.
29GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
Armin Ronacher05530932008-04-20 13:27:49 +020030
31
Armin Ronacher023b5e92008-05-08 11:03:10 +020032class ExtensionRegistry(type):
Armin Ronacher5c047ea2008-05-23 22:26:45 +020033 """Gives the extension an unique identifier."""
Armin Ronacher023b5e92008-05-08 11:03:10 +020034
35 def __new__(cls, name, bases, d):
36 rv = type.__new__(cls, name, bases, d)
37 rv.identifier = rv.__module__ + '.' + rv.__name__
38 return rv
39
40
Thomas Waldmann7d295622013-05-18 00:06:22 +020041class Extension(six.with_metaclass(ExtensionRegistry, object)):
Armin Ronacher7259c762008-04-30 13:03:59 +020042 """Extensions can be used to add extra functionality to the Jinja template
Armin Ronacher9d42abf2008-05-14 18:10:41 +020043 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.
Armin Ronacher762079c2008-05-08 23:57:56 +020047
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).
Armin Ronacher7259c762008-04-30 13:03:59 +020058 """
Armin Ronacher023b5e92008-05-08 11:03:10 +020059 __metaclass__ = ExtensionRegistry
Armin Ronacher05530932008-04-20 13:27:49 +020060
61 #: if this extension parses this is the list of tags it's listening to.
62 tags = set()
63
Armin Ronacher5b3f4dc2010-04-12 14:04:14 +020064 #: the priority of that extension. This is especially useful for
65 #: extensions that preprocess values. A lower value means higher
66 #: priority.
67 #:
68 #: .. versionadded:: 2.4
69 priority = 100
70
Armin Ronacher05530932008-04-20 13:27:49 +020071 def __init__(self, environment):
72 self.environment = environment
73
Armin Ronacher7259c762008-04-30 13:03:59 +020074 def bind(self, environment):
75 """Create a copy of this extension bound to another environment."""
76 rv = object.__new__(self.__class__)
77 rv.__dict__.update(self.__dict__)
78 rv.environment = environment
79 return rv
80
Armin Ronacher9ad96e72008-06-13 22:44:01 +020081 def preprocess(self, source, name, filename=None):
82 """This method is called before the actual lexing and can be used to
83 preprocess the source. The `filename` is optional. The return value
84 must be the preprocessed source.
85 """
86 return source
87
88 def filter_stream(self, stream):
89 """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
90 to filter tokens returned. This method has to return an iterable of
91 :class:`~jinja2.lexer.Token`\s, but it doesn't have to return a
92 :class:`~jinja2.lexer.TokenStream`.
Armin Ronacherd02fc7d2008-06-14 14:19:47 +020093
94 In the `ext` folder of the Jinja2 source distribution there is a file
95 called `inlinegettext.py` which implements a filter that utilizes this
96 method.
Armin Ronacher9ad96e72008-06-13 22:44:01 +020097 """
98 return stream
99
Armin Ronacher05530932008-04-20 13:27:49 +0200100 def parse(self, parser):
Armin Ronacher023b5e92008-05-08 11:03:10 +0200101 """If any of the :attr:`tags` matched this method is called with the
102 parser as first argument. The token the parser stream is pointing at
103 is the name token that matched. This method has to return one or a
104 list of multiple nodes.
105 """
Armin Ronacher27069d72008-05-11 19:48:12 +0200106 raise NotImplementedError()
Armin Ronacher023b5e92008-05-08 11:03:10 +0200107
108 def attr(self, name, lineno=None):
109 """Return an attribute node for the current extension. This is useful
Armin Ronacher53278a32011-01-24 01:16:00 +0100110 to pass constants on extensions to generated template code.
111
112 ::
Armin Ronacher023b5e92008-05-08 11:03:10 +0200113
Armin Ronacher69e12db2008-05-12 09:00:03 +0200114 self.attr('_my_attribute', lineno=lineno)
Armin Ronacher023b5e92008-05-08 11:03:10 +0200115 """
116 return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
Armin Ronacher05530932008-04-20 13:27:49 +0200117
Armin Ronacher27069d72008-05-11 19:48:12 +0200118 def call_method(self, name, args=None, kwargs=None, dyn_args=None,
119 dyn_kwargs=None, lineno=None):
Armin Ronacher69e12db2008-05-12 09:00:03 +0200120 """Call a method of the extension. This is a shortcut for
121 :meth:`attr` + :class:`jinja2.nodes.Call`.
122 """
Armin Ronacher27069d72008-05-11 19:48:12 +0200123 if args is None:
124 args = []
125 if kwargs is None:
126 kwargs = []
127 return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
128 dyn_args, dyn_kwargs, lineno=lineno)
129
Armin Ronacher05530932008-04-20 13:27:49 +0200130
Armin Ronacher5c047ea2008-05-23 22:26:45 +0200131@contextfunction
Armin Ronacher4da90342010-05-29 17:35:10 +0200132def _gettext_alias(__context, *args, **kwargs):
Armin Ronacherb8892e72010-05-29 17:58:06 +0200133 return __context.call(__context.resolve('gettext'), *args, **kwargs)
Armin Ronacher4da90342010-05-29 17:35:10 +0200134
135
136def _make_new_gettext(func):
137 @contextfunction
138 def gettext(__context, __string, **variables):
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200139 rv = __context.call(func, __string)
Armin Ronacher4da90342010-05-29 17:35:10 +0200140 if __context.eval_ctx.autoescape:
141 rv = Markup(rv)
142 return rv % variables
143 return gettext
144
145
146def _make_new_ngettext(func):
147 @contextfunction
Armin Ronacherb98dad92010-05-29 22:31:17 +0200148 def ngettext(__context, __singular, __plural, __num, **variables):
149 variables.setdefault('num', __num)
150 rv = __context.call(func, __singular, __plural, __num)
Armin Ronacher4da90342010-05-29 17:35:10 +0200151 if __context.eval_ctx.autoescape:
152 rv = Markup(rv)
153 return rv % variables
154 return ngettext
Armin Ronacher5c047ea2008-05-23 22:26:45 +0200155
156
Armin Ronachered98cac2008-05-07 08:42:11 +0200157class InternationalizationExtension(Extension):
Armin Ronacher762079c2008-05-08 23:57:56 +0200158 """This extension adds gettext support to Jinja2."""
Armin Ronacherb5124e62008-04-25 00:36:14 +0200159 tags = set(['trans'])
160
Armin Ronacher4720c362008-09-06 16:15:38 +0200161 # TODO: the i18n extension is currently reevaluating values in a few
162 # situations. Take this example:
163 # {% trans count=something() %}{{ count }} foo{% pluralize
164 # %}{{ count }} fooss{% endtrans %}
165 # something is called twice here. One time for the gettext value and
166 # the other time for the n-parameter of the ngettext function.
167
Armin Ronacherb5124e62008-04-25 00:36:14 +0200168 def __init__(self, environment):
169 Extension.__init__(self, environment)
Armin Ronacher5c047ea2008-05-23 22:26:45 +0200170 environment.globals['_'] = _gettext_alias
Armin Ronacher762079c2008-05-08 23:57:56 +0200171 environment.extend(
172 install_gettext_translations=self._install,
173 install_null_translations=self._install_null,
Armin Ronacher4da90342010-05-29 17:35:10 +0200174 install_gettext_callables=self._install_callables,
Armin Ronacher762079c2008-05-08 23:57:56 +0200175 uninstall_gettext_translations=self._uninstall,
Armin Ronacher4da90342010-05-29 17:35:10 +0200176 extract_translations=self._extract,
177 newstyle_gettext=False
Armin Ronacher762079c2008-05-08 23:57:56 +0200178 )
179
Armin Ronacher4da90342010-05-29 17:35:10 +0200180 def _install(self, translations, newstyle=None):
Armin Ronacher32133552008-09-15 14:35:01 +0200181 gettext = getattr(translations, 'ugettext', None)
182 if gettext is None:
183 gettext = translations.gettext
184 ngettext = getattr(translations, 'ungettext', None)
185 if ngettext is None:
186 ngettext = translations.ngettext
Armin Ronacher4da90342010-05-29 17:35:10 +0200187 self._install_callables(gettext, ngettext, newstyle)
Armin Ronacher762079c2008-05-08 23:57:56 +0200188
Armin Ronacher4da90342010-05-29 17:35:10 +0200189 def _install_null(self, newstyle=None):
190 self._install_callables(
191 lambda x: x,
192 lambda s, p, n: (n != 1 and (p,) or (s,))[0],
193 newstyle
194 )
195
196 def _install_callables(self, gettext, ngettext, newstyle=None):
197 if newstyle is not None:
198 self.environment.newstyle_gettext = newstyle
199 if self.environment.newstyle_gettext:
200 gettext = _make_new_gettext(gettext)
201 ngettext = _make_new_ngettext(ngettext)
Armin Ronacher762079c2008-05-08 23:57:56 +0200202 self.environment.globals.update(
Armin Ronacher4da90342010-05-29 17:35:10 +0200203 gettext=gettext,
204 ngettext=ngettext
Armin Ronacher762079c2008-05-08 23:57:56 +0200205 )
206
207 def _uninstall(self, translations):
208 for key in 'gettext', 'ngettext':
209 self.environment.globals.pop(key, None)
210
211 def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
Thomas Waldmann7d295622013-05-18 00:06:22 +0200212 if isinstance(source, six.string_types):
Armin Ronacher762079c2008-05-08 23:57:56 +0200213 source = self.environment.parse(source)
214 return extract_from_ast(source, gettext_functions)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200215
216 def parse(self, parser):
217 """Parse a translatable tag."""
Thomas Waldmann7d295622013-05-18 00:06:22 +0200218 lineno = six.advance_iterator(parser.stream).lineno
Armin Ronacherb98dad92010-05-29 22:31:17 +0200219 num_called_num = False
Armin Ronacherb5124e62008-04-25 00:36:14 +0200220
Armin Ronacherb5124e62008-04-25 00:36:14 +0200221 # find all the variables referenced. Additionally a variable can be
222 # defined in the body of the trans block too, but this is checked at
223 # a later state.
224 plural_expr = None
225 variables = {}
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100226 while parser.stream.current.type != 'block_end':
Armin Ronacherb5124e62008-04-25 00:36:14 +0200227 if variables:
228 parser.stream.expect('comma')
Armin Ronacher023b5e92008-05-08 11:03:10 +0200229
230 # skip colon for python compatibility
Armin Ronacherfdf95302008-05-11 22:20:51 +0200231 if parser.stream.skip_if('colon'):
Armin Ronacher023b5e92008-05-08 11:03:10 +0200232 break
233
Armin Ronacherb5124e62008-04-25 00:36:14 +0200234 name = parser.stream.expect('name')
235 if name.value in variables:
Armin Ronacher7f15ef82008-05-16 09:11:39 +0200236 parser.fail('translatable variable %r defined twice.' %
237 name.value, name.lineno,
238 exc=TemplateAssertionError)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200239
240 # expressions
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100241 if parser.stream.current.type == 'assign':
Thomas Waldmann7d295622013-05-18 00:06:22 +0200242 six.advance_iterator(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200243 variables[name.value] = var = parser.parse_expression()
244 else:
245 variables[name.value] = var = nodes.Name(name.value, 'load')
Armin Ronacherb98dad92010-05-29 22:31:17 +0200246
Armin Ronacherb5124e62008-04-25 00:36:14 +0200247 if plural_expr is None:
248 plural_expr = var
Armin Ronacherb98dad92010-05-29 22:31:17 +0200249 num_called_num = name.value == 'num'
Armin Ronacher023b5e92008-05-08 11:03:10 +0200250
Armin Ronacherb5124e62008-04-25 00:36:14 +0200251 parser.stream.expect('block_end')
252
253 plural = plural_names = None
254 have_plural = False
255 referenced = set()
256
257 # now parse until endtrans or pluralize
258 singular_names, singular = self._parse_block(parser, True)
259 if singular_names:
260 referenced.update(singular_names)
261 if plural_expr is None:
262 plural_expr = nodes.Name(singular_names[0], 'load')
Armin Ronacherb98dad92010-05-29 22:31:17 +0200263 num_called_num = singular_names[0] == 'num'
Armin Ronacherb5124e62008-04-25 00:36:14 +0200264
265 # if we have a pluralize block, we parse that too
266 if parser.stream.current.test('name:pluralize'):
267 have_plural = True
Thomas Waldmann7d295622013-05-18 00:06:22 +0200268 six.advance_iterator(parser.stream)
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100269 if parser.stream.current.type != 'block_end':
Armin Ronacher4720c362008-09-06 16:15:38 +0200270 name = parser.stream.expect('name')
271 if name.value not in variables:
272 parser.fail('unknown variable %r for pluralization' %
273 name.value, name.lineno,
274 exc=TemplateAssertionError)
275 plural_expr = variables[name.value]
Armin Ronacherb98dad92010-05-29 22:31:17 +0200276 num_called_num = name.value == 'num'
Armin Ronacherb5124e62008-04-25 00:36:14 +0200277 parser.stream.expect('block_end')
278 plural_names, plural = self._parse_block(parser, False)
Thomas Waldmann7d295622013-05-18 00:06:22 +0200279 six.advance_iterator(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200280 referenced.update(plural_names)
281 else:
Thomas Waldmann7d295622013-05-18 00:06:22 +0200282 six.advance_iterator(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200283
284 # register free names as simple name expressions
285 for var in referenced:
286 if var not in variables:
287 variables[var] = nodes.Name(var, 'load')
288
Armin Ronacherb5124e62008-04-25 00:36:14 +0200289 if not have_plural:
290 plural_expr = None
291 elif plural_expr is None:
Armin Ronacher7f15ef82008-05-16 09:11:39 +0200292 parser.fail('pluralize without variables', lineno)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200293
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200294 node = self._make_node(singular, plural, variables, plural_expr,
Armin Ronacher4f77a302010-07-01 12:15:39 +0200295 bool(referenced),
296 num_called_num and have_plural)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200297 node.set_lineno(lineno)
298 return node
299
300 def _parse_block(self, parser, allow_pluralize):
301 """Parse until the next block tag with a given name."""
302 referenced = []
303 buf = []
304 while 1:
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100305 if parser.stream.current.type == 'data':
Armin Ronacherb5124e62008-04-25 00:36:14 +0200306 buf.append(parser.stream.current.value.replace('%', '%%'))
Thomas Waldmann7d295622013-05-18 00:06:22 +0200307 six.advance_iterator(parser.stream)
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100308 elif parser.stream.current.type == 'variable_begin':
Thomas Waldmann7d295622013-05-18 00:06:22 +0200309 six.advance_iterator(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200310 name = parser.stream.expect('name').value
311 referenced.append(name)
312 buf.append('%%(%s)s' % name)
313 parser.stream.expect('variable_end')
Armin Ronacher7647d1c2009-01-05 12:16:46 +0100314 elif parser.stream.current.type == 'block_begin':
Thomas Waldmann7d295622013-05-18 00:06:22 +0200315 six.advance_iterator(parser.stream)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200316 if parser.stream.current.test('name:endtrans'):
317 break
318 elif parser.stream.current.test('name:pluralize'):
319 if allow_pluralize:
320 break
Armin Ronacher7f15ef82008-05-16 09:11:39 +0200321 parser.fail('a translatable section can have only one '
322 'pluralize section')
323 parser.fail('control structures in translatable sections are '
324 'not allowed')
Armin Ronacherd02fc7d2008-06-14 14:19:47 +0200325 elif parser.stream.eos:
326 parser.fail('unclosed translation block')
Armin Ronacherb5124e62008-04-25 00:36:14 +0200327 else:
328 assert False, 'internal parser error'
329
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200330 return referenced, concat(buf)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200331
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200332 def _make_node(self, singular, plural, variables, plural_expr,
Armin Ronacherb98dad92010-05-29 22:31:17 +0200333 vars_referenced, num_called_num):
Armin Ronacherb5124e62008-04-25 00:36:14 +0200334 """Generates a useful node from the data provided."""
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200335 # no variables referenced? no need to escape for old style
Armin Ronacher4cccc222010-07-06 11:37:45 +0200336 # gettext invocations only if there are vars.
Armin Ronacherffaa2e72010-05-29 20:57:16 +0200337 if not vars_referenced and not self.environment.newstyle_gettext:
338 singular = singular.replace('%%', '%')
339 if plural:
340 plural = plural.replace('%%', '%')
341
Armin Ronacherb5124e62008-04-25 00:36:14 +0200342 # singular only:
343 if plural_expr is None:
344 gettext = nodes.Name('gettext', 'load')
345 node = nodes.Call(gettext, [nodes.Const(singular)],
346 [], None, None)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200347
348 # singular and plural
349 else:
350 ngettext = nodes.Name('ngettext', 'load')
351 node = nodes.Call(ngettext, [
352 nodes.Const(singular),
353 nodes.Const(plural),
354 plural_expr
355 ], [], None, None)
Armin Ronacherd84ec462008-04-29 13:43:16 +0200356
Armin Ronacher4da90342010-05-29 17:35:10 +0200357 # in case newstyle gettext is used, the method is powerful
358 # enough to handle the variable expansion and autoescape
359 # handling itself
360 if self.environment.newstyle_gettext:
Thomas Waldmanne0003552013-05-17 23:52:14 +0200361 for key, value in six.iteritems(variables):
Armin Ronacherb98dad92010-05-29 22:31:17 +0200362 # the function adds that later anyways in case num was
363 # called num, so just skip it.
364 if num_called_num and key == 'num':
365 continue
Armin Ronacherb8892e72010-05-29 17:58:06 +0200366 node.kwargs.append(nodes.Keyword(key, value))
Armin Ronacherd84ec462008-04-29 13:43:16 +0200367
Armin Ronacher4da90342010-05-29 17:35:10 +0200368 # otherwise do that here
369 else:
370 # mark the return value as safe if we are in an
371 # environment with autoescaping turned on
372 node = nodes.MarkSafeIfAutoescape(node)
373 if variables:
Armin Ronacherb8892e72010-05-29 17:58:06 +0200374 node = nodes.Mod(node, nodes.Dict([
375 nodes.Pair(nodes.Const(key), value)
376 for key, value in variables.items()
377 ]))
Armin Ronacherb5124e62008-04-25 00:36:14 +0200378 return nodes.Output([node])
379
380
Armin Ronacher5d2733f2008-05-15 23:26:52 +0200381class ExprStmtExtension(Extension):
382 """Adds a `do` tag to Jinja2 that works like the print statement just
383 that it doesn't print the return value.
384 """
385 tags = set(['do'])
386
387 def parse(self, parser):
Thomas Waldmann7d295622013-05-18 00:06:22 +0200388 node = nodes.ExprStmt(lineno=six.advance_iterator(parser.stream).lineno)
Armin Ronacher5d2733f2008-05-15 23:26:52 +0200389 node.node = parser.parse_tuple()
390 return node
391
392
Armin Ronacher3da90312008-05-23 16:37:28 +0200393class LoopControlExtension(Extension):
394 """Adds break and continue to the template engine."""
395 tags = set(['break', 'continue'])
396
397 def parse(self, parser):
Thomas Waldmann7d295622013-05-18 00:06:22 +0200398 token = six.advance_iterator(parser.stream)
Armin Ronacher3da90312008-05-23 16:37:28 +0200399 if token.value == 'break':
400 return nodes.Break(lineno=token.lineno)
401 return nodes.Continue(lineno=token.lineno)
402
403
Armin Ronacher9b4cc9f2010-02-07 03:55:15 +0100404class WithExtension(Extension):
405 """Adds support for a django-like with block."""
406 tags = set(['with'])
407
408 def parse(self, parser):
Thomas Waldmann7d295622013-05-18 00:06:22 +0200409 node = nodes.Scope(lineno=six.advance_iterator(parser.stream).lineno)
Armin Ronacher9b4cc9f2010-02-07 03:55:15 +0100410 assignments = []
411 while parser.stream.current.type != 'block_end':
412 lineno = parser.stream.current.lineno
413 if assignments:
414 parser.stream.expect('comma')
415 target = parser.parse_assign_target()
416 parser.stream.expect('assign')
417 expr = parser.parse_expression()
418 assignments.append(nodes.Assign(target, expr, lineno=lineno))
419 node.body = assignments + \
420 list(parser.parse_statements(('name:endwith',),
421 drop_needle=True))
422 return node
423
424
Armin Ronacher8346bd72010-03-14 19:43:47 +0100425class AutoEscapeExtension(Extension):
426 """Changes auto escape rules for a scope."""
427 tags = set(['autoescape'])
428
429 def parse(self, parser):
Thomas Waldmann7d295622013-05-18 00:06:22 +0200430 node = nodes.ScopedEvalContextModifier(lineno=six.advance_iterator(parser.stream).lineno)
Armin Ronacher8346bd72010-03-14 19:43:47 +0100431 node.options = [
432 nodes.Keyword('autoescape', parser.parse_expression())
433 ]
434 node.body = parser.parse_statements(('name:endautoescape',),
435 drop_needle=True)
436 return nodes.Scope([node])
437
438
Armin Ronacherabd36572008-06-27 08:45:19 +0200439def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
440 babel_style=True):
441 """Extract localizable strings from the given template node. Per
442 default this function returns matches in babel style that means non string
443 parameters as well as keyword arguments are returned as `None`. This
444 allows Babel to figure out what you really meant if you are using
445 gettext functions that allow keyword arguments for placeholder expansion.
446 If you don't want that behavior set the `babel_style` parameter to `False`
447 which causes only strings to be returned and parameters are always stored
448 in tuples. As a consequence invalid gettext calls (calls without a single
449 string parameter or string parameters after non-string parameters) are
450 skipped.
451
452 This example explains the behavior:
453
454 >>> from jinja2 import Environment
455 >>> env = Environment()
456 >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
457 >>> list(extract_from_ast(node))
458 [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
459 >>> list(extract_from_ast(node, babel_style=False))
460 [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
Armin Ronacherb5124e62008-04-25 00:36:14 +0200461
462 For every string found this function yields a ``(lineno, function,
463 message)`` tuple, where:
464
465 * ``lineno`` is the number of the line on which the string was found,
466 * ``function`` is the name of the ``gettext`` function used (if the
467 string was extracted from embedded Python code), and
468 * ``message`` is the string itself (a ``unicode`` object, or a tuple
469 of ``unicode`` objects for functions with multiple string arguments).
Armin Ronacher531578d2010-02-06 16:34:54 +0100470
471 This extraction function operates on the AST and is because of that unable
472 to extract any comments. For comment support you have to use the babel
473 extraction interface or extract comments yourself.
Armin Ronacherb5124e62008-04-25 00:36:14 +0200474 """
475 for node in node.find_all(nodes.Call):
476 if not isinstance(node.node, nodes.Name) or \
477 node.node.name not in gettext_functions:
478 continue
479
480 strings = []
481 for arg in node.args:
482 if isinstance(arg, nodes.Const) and \
Thomas Waldmann7d295622013-05-18 00:06:22 +0200483 isinstance(arg.value, six.string_types):
Armin Ronacherb5124e62008-04-25 00:36:14 +0200484 strings.append(arg.value)
485 else:
486 strings.append(None)
487
Armin Ronacherabd36572008-06-27 08:45:19 +0200488 for arg in node.kwargs:
489 strings.append(None)
490 if node.dyn_args is not None:
491 strings.append(None)
492 if node.dyn_kwargs is not None:
493 strings.append(None)
494
495 if not babel_style:
496 strings = tuple(x for x in strings if x is not None)
497 if not strings:
498 continue
Armin Ronacherb5124e62008-04-25 00:36:14 +0200499 else:
Armin Ronacherabd36572008-06-27 08:45:19 +0200500 if len(strings) == 1:
501 strings = strings[0]
502 else:
503 strings = tuple(strings)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200504 yield node.lineno, node.node.name, strings
505
506
Armin Ronacher531578d2010-02-06 16:34:54 +0100507class _CommentFinder(object):
508 """Helper class to find comments in a token stream. Can only
509 find comments for gettext calls forwards. Once the comment
510 from line 4 is found, a comment for line 1 will not return a
511 usable value.
512 """
513
514 def __init__(self, tokens, comment_tags):
515 self.tokens = tokens
516 self.comment_tags = comment_tags
517 self.offset = 0
518 self.last_lineno = 0
519
520 def find_backwards(self, offset):
521 try:
522 for _, token_type, token_value in \
523 reversed(self.tokens[self.offset:offset]):
524 if token_type in ('comment', 'linecomment'):
525 try:
526 prefix, comment = token_value.split(None, 1)
527 except ValueError:
528 continue
529 if prefix in self.comment_tags:
530 return [comment.rstrip()]
531 return []
532 finally:
533 self.offset = offset
534
535 def find_comments(self, lineno):
536 if not self.comment_tags or self.last_lineno > lineno:
537 return []
538 for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]):
539 if token_lineno > lineno:
540 return self.find_backwards(self.offset + idx)
541 return self.find_backwards(len(self.tokens))
542
543
Armin Ronacherb5124e62008-04-25 00:36:14 +0200544def babel_extract(fileobj, keywords, comment_tags, options):
545 """Babel extraction method for Jinja templates.
546
Armin Ronacher531578d2010-02-06 16:34:54 +0100547 .. versionchanged:: 2.3
548 Basic support for translation comments was added. If `comment_tags`
549 is now set to a list of keywords for extraction, the extractor will
550 try to find the best preceeding comment that begins with one of the
551 keywords. For best results, make sure to not have more than one
552 gettext call in one line of code and the matching comment in the
553 same line or the line before.
554
Armin Ronacher4f77a302010-07-01 12:15:39 +0200555 .. versionchanged:: 2.5.1
556 The `newstyle_gettext` flag can be set to `True` to enable newstyle
557 gettext calls.
558
Armin Ronacher11619152011-12-15 11:50:27 +0100559 .. versionchanged:: 2.7
560 A `silent` option can now be provided. If set to `False` template
561 syntax errors are propagated instead of being ignored.
562
Armin Ronacherb5124e62008-04-25 00:36:14 +0200563 :param fileobj: the file-like object the messages should be extracted from
564 :param keywords: a list of keywords (i.e. function names) that should be
565 recognized as translation functions
566 :param comment_tags: a list of translator tags to search for and include
Armin Ronacher531578d2010-02-06 16:34:54 +0100567 in the results.
Armin Ronacherb5124e62008-04-25 00:36:14 +0200568 :param options: a dictionary of additional options (optional)
569 :return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
570 (comments will be empty currently)
571 """
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200572 extensions = set()
Armin Ronacherb5124e62008-04-25 00:36:14 +0200573 for extension in options.get('extensions', '').split(','):
574 extension = extension.strip()
575 if not extension:
576 continue
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200577 extensions.add(import_string(extension))
578 if InternationalizationExtension not in extensions:
579 extensions.add(InternationalizationExtension)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200580
Armin Ronacher4f77a302010-07-01 12:15:39 +0200581 def getbool(options, key, default=False):
Armin Ronacher11619152011-12-15 11:50:27 +0100582 return options.get(key, str(default)).lower() in \
583 ('1', 'on', 'yes', 'true')
Armin Ronacher4f77a302010-07-01 12:15:39 +0200584
Armin Ronacher11619152011-12-15 11:50:27 +0100585 silent = getbool(options, 'silent', True)
Armin Ronacher4f77a302010-07-01 12:15:39 +0200586 environment = Environment(
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200587 options.get('block_start_string', BLOCK_START_STRING),
588 options.get('block_end_string', BLOCK_END_STRING),
589 options.get('variable_start_string', VARIABLE_START_STRING),
590 options.get('variable_end_string', VARIABLE_END_STRING),
591 options.get('comment_start_string', COMMENT_START_STRING),
592 options.get('comment_end_string', COMMENT_END_STRING),
593 options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
Armin Ronacher59b6bd52009-03-30 21:00:16 +0200594 options.get('line_comment_prefix') or LINE_COMMENT_PREFIX,
Armin Ronacher4f77a302010-07-01 12:15:39 +0200595 getbool(options, 'trim_blocks', TRIM_BLOCKS),
Armin Ronachera65f1eb2013-05-19 11:18:19 +0100596 getbool(options, 'lstrip_blocks', LSTRIP_BLOCKS),
W. Trevor King7e912c62013-01-11 08:23:24 -0500597 NEWLINE_SEQUENCE,
598 getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE),
599 frozenset(extensions),
Armin Ronacher4f77a302010-07-01 12:15:39 +0200600 cache_size=0,
601 auto_reload=False
Armin Ronacherb5124e62008-04-25 00:36:14 +0200602 )
603
Armin Ronacher4f77a302010-07-01 12:15:39 +0200604 if getbool(options, 'newstyle_gettext'):
605 environment.newstyle_gettext = True
606
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200607 source = fileobj.read().decode(options.get('encoding', 'utf-8'))
Armin Ronacherc670b112008-06-29 17:23:04 +0200608 try:
609 node = environment.parse(source)
Armin Ronacher531578d2010-02-06 16:34:54 +0100610 tokens = list(environment.lex(environment.preprocess(source)))
Thomas Waldmanne0003552013-05-17 23:52:14 +0200611 except TemplateSyntaxError as e:
Armin Ronacher11619152011-12-15 11:50:27 +0100612 if not silent:
613 raise
Armin Ronacherc670b112008-06-29 17:23:04 +0200614 # skip templates with syntax errors
615 return
Armin Ronacher531578d2010-02-06 16:34:54 +0100616
617 finder = _CommentFinder(tokens, comment_tags)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200618 for lineno, func, message in extract_from_ast(node, keywords):
Armin Ronacher531578d2010-02-06 16:34:54 +0100619 yield lineno, func, message, finder.find_comments(lineno)
Armin Ronachered98cac2008-05-07 08:42:11 +0200620
621
622#: nicer import names
623i18n = InternationalizationExtension
Armin Ronacher5d2733f2008-05-15 23:26:52 +0200624do = ExprStmtExtension
Armin Ronacher3da90312008-05-23 16:37:28 +0200625loopcontrols = LoopControlExtension
Armin Ronacher9b4cc9f2010-02-07 03:55:15 +0100626with_ = WithExtension
Armin Ronacher8346bd72010-03-14 19:43:47 +0100627autoescape = AutoEscapeExtension