blob: 9dfa87cc0c2df7af8a7d2088559ade310150b080 [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
10 :copyright: Copyright 2008 by Armin Ronacher.
11 :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 Ronacherb5124e62008-04-25 00:36:14 +020016from jinja2.environment import get_spontaneous_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 Ronacher9ad96e72008-06-13 22:44:01 +020019from jinja2.lexer import Token
Armin Ronachered98cac2008-05-07 08:42:11 +020020from jinja2.utils import contextfunction, import_string, Markup
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
61 def __init__(self, environment):
62 self.environment = environment
63
Armin Ronacher7259c762008-04-30 13:03:59 +020064 def bind(self, environment):
65 """Create a copy of this extension bound to another environment."""
66 rv = object.__new__(self.__class__)
67 rv.__dict__.update(self.__dict__)
68 rv.environment = environment
69 return rv
70
Armin Ronacher9ad96e72008-06-13 22:44:01 +020071 def preprocess(self, source, name, filename=None):
72 """This method is called before the actual lexing and can be used to
73 preprocess the source. The `filename` is optional. The return value
74 must be the preprocessed source.
75 """
76 return source
77
78 def filter_stream(self, stream):
79 """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
80 to filter tokens returned. This method has to return an iterable of
81 :class:`~jinja2.lexer.Token`\s, but it doesn't have to return a
82 :class:`~jinja2.lexer.TokenStream`.
83 """
84 return stream
85
Armin Ronacher05530932008-04-20 13:27:49 +020086 def parse(self, parser):
Armin Ronacher023b5e92008-05-08 11:03:10 +020087 """If any of the :attr:`tags` matched this method is called with the
88 parser as first argument. The token the parser stream is pointing at
89 is the name token that matched. This method has to return one or a
90 list of multiple nodes.
91 """
Armin Ronacher27069d72008-05-11 19:48:12 +020092 raise NotImplementedError()
Armin Ronacher023b5e92008-05-08 11:03:10 +020093
94 def attr(self, name, lineno=None):
95 """Return an attribute node for the current extension. This is useful
Armin Ronacher69e12db2008-05-12 09:00:03 +020096 to pass constants on extensions to generated template code::
Armin Ronacher023b5e92008-05-08 11:03:10 +020097
Armin Ronacher69e12db2008-05-12 09:00:03 +020098 self.attr('_my_attribute', lineno=lineno)
Armin Ronacher023b5e92008-05-08 11:03:10 +020099 """
100 return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
Armin Ronacher05530932008-04-20 13:27:49 +0200101
Armin Ronacher27069d72008-05-11 19:48:12 +0200102 def call_method(self, name, args=None, kwargs=None, dyn_args=None,
103 dyn_kwargs=None, lineno=None):
Armin Ronacher69e12db2008-05-12 09:00:03 +0200104 """Call a method of the extension. This is a shortcut for
105 :meth:`attr` + :class:`jinja2.nodes.Call`.
106 """
Armin Ronacher27069d72008-05-11 19:48:12 +0200107 if args is None:
108 args = []
109 if kwargs is None:
110 kwargs = []
111 return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
112 dyn_args, dyn_kwargs, lineno=lineno)
113
Armin Ronacher05530932008-04-20 13:27:49 +0200114
Armin Ronacher5c047ea2008-05-23 22:26:45 +0200115@contextfunction
116def _gettext_alias(context, string):
117 return context.resolve('gettext')(string)
118
119
Armin Ronachered98cac2008-05-07 08:42:11 +0200120class InternationalizationExtension(Extension):
Armin Ronacher762079c2008-05-08 23:57:56 +0200121 """This extension adds gettext support to Jinja2."""
Armin Ronacherb5124e62008-04-25 00:36:14 +0200122 tags = set(['trans'])
123
124 def __init__(self, environment):
125 Extension.__init__(self, environment)
Armin Ronacher5c047ea2008-05-23 22:26:45 +0200126 environment.globals['_'] = _gettext_alias
Armin Ronacher762079c2008-05-08 23:57:56 +0200127 environment.extend(
128 install_gettext_translations=self._install,
129 install_null_translations=self._install_null,
130 uninstall_gettext_translations=self._uninstall,
131 extract_translations=self._extract
132 )
133
134 def _install(self, translations):
135 self.environment.globals.update(
136 gettext=translations.ugettext,
137 ngettext=translations.ungettext
138 )
139
140 def _install_null(self):
141 self.environment.globals.update(
142 gettext=lambda x: x,
143 ngettext=lambda s, p, n: (n != 1 and (p,) or (s,))[0]
144 )
145
146 def _uninstall(self, translations):
147 for key in 'gettext', 'ngettext':
148 self.environment.globals.pop(key, None)
149
150 def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
151 if isinstance(source, basestring):
152 source = self.environment.parse(source)
153 return extract_from_ast(source, gettext_functions)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200154
155 def parse(self, parser):
156 """Parse a translatable tag."""
157 lineno = parser.stream.next().lineno
158
Armin Ronacherb5124e62008-04-25 00:36:14 +0200159 # find all the variables referenced. Additionally a variable can be
160 # defined in the body of the trans block too, but this is checked at
161 # a later state.
162 plural_expr = None
163 variables = {}
164 while parser.stream.current.type is not 'block_end':
165 if variables:
166 parser.stream.expect('comma')
Armin Ronacher023b5e92008-05-08 11:03:10 +0200167
168 # skip colon for python compatibility
Armin Ronacherfdf95302008-05-11 22:20:51 +0200169 if parser.stream.skip_if('colon'):
Armin Ronacher023b5e92008-05-08 11:03:10 +0200170 break
171
Armin Ronacherb5124e62008-04-25 00:36:14 +0200172 name = parser.stream.expect('name')
173 if name.value in variables:
Armin Ronacher7f15ef82008-05-16 09:11:39 +0200174 parser.fail('translatable variable %r defined twice.' %
175 name.value, name.lineno,
176 exc=TemplateAssertionError)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200177
178 # expressions
179 if parser.stream.current.type is 'assign':
180 parser.stream.next()
181 variables[name.value] = var = parser.parse_expression()
182 else:
183 variables[name.value] = var = nodes.Name(name.value, 'load')
184 if plural_expr is None:
185 plural_expr = var
Armin Ronacher023b5e92008-05-08 11:03:10 +0200186
Armin Ronacherb5124e62008-04-25 00:36:14 +0200187 parser.stream.expect('block_end')
188
189 plural = plural_names = None
190 have_plural = False
191 referenced = set()
192
193 # now parse until endtrans or pluralize
194 singular_names, singular = self._parse_block(parser, True)
195 if singular_names:
196 referenced.update(singular_names)
197 if plural_expr is None:
198 plural_expr = nodes.Name(singular_names[0], 'load')
199
200 # if we have a pluralize block, we parse that too
201 if parser.stream.current.test('name:pluralize'):
202 have_plural = True
203 parser.stream.next()
204 if parser.stream.current.type is not 'block_end':
205 plural_expr = parser.parse_expression()
206 parser.stream.expect('block_end')
207 plural_names, plural = self._parse_block(parser, False)
208 parser.stream.next()
209 referenced.update(plural_names)
210 else:
211 parser.stream.next()
212
213 # register free names as simple name expressions
214 for var in referenced:
215 if var not in variables:
216 variables[var] = nodes.Name(var, 'load')
217
218 # no variables referenced? no need to escape
219 if not referenced:
220 singular = singular.replace('%%', '%')
221 if plural:
222 plural = plural.replace('%%', '%')
223
224 if not have_plural:
225 plural_expr = None
226 elif plural_expr is None:
Armin Ronacher7f15ef82008-05-16 09:11:39 +0200227 parser.fail('pluralize without variables', lineno)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200228
229 if variables:
230 variables = nodes.Dict([nodes.Pair(nodes.Const(x, lineno=lineno), y)
231 for x, y in variables.items()])
232 else:
233 variables = None
234
235 node = self._make_node(singular, plural, variables, plural_expr)
236 node.set_lineno(lineno)
237 return node
238
239 def _parse_block(self, parser, allow_pluralize):
240 """Parse until the next block tag with a given name."""
241 referenced = []
242 buf = []
243 while 1:
244 if parser.stream.current.type is 'data':
245 buf.append(parser.stream.current.value.replace('%', '%%'))
246 parser.stream.next()
247 elif parser.stream.current.type is 'variable_begin':
248 parser.stream.next()
249 name = parser.stream.expect('name').value
250 referenced.append(name)
251 buf.append('%%(%s)s' % name)
252 parser.stream.expect('variable_end')
253 elif parser.stream.current.type is 'block_begin':
254 parser.stream.next()
255 if parser.stream.current.test('name:endtrans'):
256 break
257 elif parser.stream.current.test('name:pluralize'):
258 if allow_pluralize:
259 break
Armin Ronacher7f15ef82008-05-16 09:11:39 +0200260 parser.fail('a translatable section can have only one '
261 'pluralize section')
262 parser.fail('control structures in translatable sections are '
263 'not allowed')
Armin Ronacherb5124e62008-04-25 00:36:14 +0200264 else:
265 assert False, 'internal parser error'
266
Armin Ronacher2feed1d2008-04-26 16:26:52 +0200267 return referenced, concat(buf)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200268
269 def _make_node(self, singular, plural, variables, plural_expr):
270 """Generates a useful node from the data provided."""
271 # singular only:
272 if plural_expr is None:
273 gettext = nodes.Name('gettext', 'load')
274 node = nodes.Call(gettext, [nodes.Const(singular)],
275 [], None, None)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200276
277 # singular and plural
278 else:
279 ngettext = nodes.Name('ngettext', 'load')
280 node = nodes.Call(ngettext, [
281 nodes.Const(singular),
282 nodes.Const(plural),
283 plural_expr
284 ], [], None, None)
Armin Ronacherd84ec462008-04-29 13:43:16 +0200285
286 # mark the return value as safe if we are in an
287 # environment with autoescaping turned on
288 if self.environment.autoescape:
289 node = nodes.MarkSafe(node)
290
291 if variables:
292 node = nodes.Mod(node, variables)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200293 return nodes.Output([node])
294
295
Armin Ronacher5d2733f2008-05-15 23:26:52 +0200296class ExprStmtExtension(Extension):
297 """Adds a `do` tag to Jinja2 that works like the print statement just
298 that it doesn't print the return value.
299 """
300 tags = set(['do'])
301
302 def parse(self, parser):
303 node = nodes.ExprStmt(lineno=parser.stream.next().lineno)
304 node.node = parser.parse_tuple()
305 return node
306
307
Armin Ronacher3da90312008-05-23 16:37:28 +0200308class LoopControlExtension(Extension):
309 """Adds break and continue to the template engine."""
310 tags = set(['break', 'continue'])
311
312 def parse(self, parser):
313 token = parser.stream.next()
314 if token.value == 'break':
315 return nodes.Break(lineno=token.lineno)
316 return nodes.Continue(lineno=token.lineno)
317
318
Armin Ronacherb5124e62008-04-25 00:36:14 +0200319def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS):
320 """Extract localizable strings from the given template node.
321
322 For every string found this function yields a ``(lineno, function,
323 message)`` tuple, where:
324
325 * ``lineno`` is the number of the line on which the string was found,
326 * ``function`` is the name of the ``gettext`` function used (if the
327 string was extracted from embedded Python code), and
328 * ``message`` is the string itself (a ``unicode`` object, or a tuple
329 of ``unicode`` objects for functions with multiple string arguments).
330 """
331 for node in node.find_all(nodes.Call):
332 if not isinstance(node.node, nodes.Name) or \
333 node.node.name not in gettext_functions:
334 continue
335
336 strings = []
337 for arg in node.args:
338 if isinstance(arg, nodes.Const) and \
339 isinstance(arg.value, basestring):
340 strings.append(arg.value)
341 else:
342 strings.append(None)
343
344 if len(strings) == 1:
345 strings = strings[0]
346 else:
347 strings = tuple(strings)
348 yield node.lineno, node.node.name, strings
349
350
351def babel_extract(fileobj, keywords, comment_tags, options):
352 """Babel extraction method for Jinja templates.
353
354 :param fileobj: the file-like object the messages should be extracted from
355 :param keywords: a list of keywords (i.e. function names) that should be
356 recognized as translation functions
357 :param comment_tags: a list of translator tags to search for and include
358 in the results. (Unused)
359 :param options: a dictionary of additional options (optional)
360 :return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
361 (comments will be empty currently)
362 """
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200363 extensions = set()
Armin Ronacherb5124e62008-04-25 00:36:14 +0200364 for extension in options.get('extensions', '').split(','):
365 extension = extension.strip()
366 if not extension:
367 continue
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200368 extensions.add(import_string(extension))
369 if InternationalizationExtension not in extensions:
370 extensions.add(InternationalizationExtension)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200371
372 environment = get_spontaneous_environment(
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200373 options.get('block_start_string', BLOCK_START_STRING),
374 options.get('block_end_string', BLOCK_END_STRING),
375 options.get('variable_start_string', VARIABLE_START_STRING),
376 options.get('variable_end_string', VARIABLE_END_STRING),
377 options.get('comment_start_string', COMMENT_START_STRING),
378 options.get('comment_end_string', COMMENT_END_STRING),
379 options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
380 str(options.get('trim_blocks', TRIM_BLOCKS)).lower() in \
381 ('1', 'on', 'yes', 'true'),
382 NEWLINE_SEQUENCE, frozenset(extensions),
Armin Ronacherb5124e62008-04-25 00:36:14 +0200383 # fill with defaults so that environments are shared
Armin Ronacher7259c762008-04-30 13:03:59 +0200384 # with other spontaneus environments. The rest of the
385 # arguments are optimizer, undefined, finalize, autoescape,
386 # loader, cache size and auto reloading setting
387 True, Undefined, None, False, None, 0, False
Armin Ronacherb5124e62008-04-25 00:36:14 +0200388 )
389
Armin Ronacher4f5008f2008-05-23 23:36:07 +0200390 source = fileobj.read().decode(options.get('encoding', 'utf-8'))
391 node = environment.parse(source)
Armin Ronacherb5124e62008-04-25 00:36:14 +0200392 for lineno, func, message in extract_from_ast(node, keywords):
393 yield lineno, func, message, []
Armin Ronachered98cac2008-05-07 08:42:11 +0200394
395
396#: nicer import names
397i18n = InternationalizationExtension
Armin Ronacher5d2733f2008-05-15 23:26:52 +0200398do = ExprStmtExtension
Armin Ronacher3da90312008-05-23 16:37:28 +0200399loopcontrols = LoopControlExtension