Environment.lex returns unicode tokens now, even if the input data was a bytestring.
[jinja2.git] / jinja2 / ext.py
1 # -*- coding: utf-8 -*-
2 """
3     jinja2.ext
4     ~~~~~~~~~~
5
6     Jinja extensions allow to add custom tags similar to the way django custom
7     tags work.  By default two example extensions exist: an i18n and a cache
8     extension.
9
10     :copyright: Copyright 2008 by Armin Ronacher.
11     :license: BSD.
12 """
13 from collections import deque
14 from jinja2 import nodes
15 from jinja2.environment import get_spontaneous_environment
16 from jinja2.runtime import Undefined, concat
17 from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
18 from jinja2.utils import contextfunction, import_string, Markup
19
20
21 # the only real useful gettext functions for a Jinja template.  Note
22 # that ugettext must be assigned to gettext as Jinja doesn't support
23 # non unicode strings.
24 GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
25
26
27 class ExtensionRegistry(type):
28     """Gives the extension an unique identifier."""
29
30     def __new__(cls, name, bases, d):
31         rv = type.__new__(cls, name, bases, d)
32         rv.identifier = rv.__module__ + '.' + rv.__name__
33         return rv
34
35
36 class Extension(object):
37     """Extensions can be used to add extra functionality to the Jinja template
38     system at the parser level.  Custom extensions are bound to an environment
39     but may not store environment specific data on `self`.  The reason for
40     this is that an extension can be bound to another environment (for
41     overlays) by creating a copy and reassigning the `environment` attribute.
42
43     As extensions are created by the environment they cannot accept any
44     arguments for configuration.  One may want to work around that by using
45     a factory function, but that is not possible as extensions are identified
46     by their import name.  The correct way to configure the extension is
47     storing the configuration values on the environment.  Because this way the
48     environment ends up acting as central configuration storage the
49     attributes may clash which is why extensions have to ensure that the names
50     they choose for configuration are not too generic.  ``prefix`` for example
51     is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
52     name as includes the name of the extension (fragment cache).
53     """
54     __metaclass__ = ExtensionRegistry
55
56     #: if this extension parses this is the list of tags it's listening to.
57     tags = set()
58
59     def __init__(self, environment):
60         self.environment = environment
61
62     def bind(self, environment):
63         """Create a copy of this extension bound to another environment."""
64         rv = object.__new__(self.__class__)
65         rv.__dict__.update(self.__dict__)
66         rv.environment = environment
67         return rv
68
69     def parse(self, parser):
70         """If any of the :attr:`tags` matched this method is called with the
71         parser as first argument.  The token the parser stream is pointing at
72         is the name token that matched.  This method has to return one or a
73         list of multiple nodes.
74         """
75         raise NotImplementedError()
76
77     def attr(self, name, lineno=None):
78         """Return an attribute node for the current extension.  This is useful
79         to pass constants on extensions to generated template code::
80
81             self.attr('_my_attribute', lineno=lineno)
82         """
83         return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
84
85     def call_method(self, name, args=None, kwargs=None, dyn_args=None,
86                     dyn_kwargs=None, lineno=None):
87         """Call a method of the extension.  This is a shortcut for
88         :meth:`attr` + :class:`jinja2.nodes.Call`.
89         """
90         if args is None:
91             args = []
92         if kwargs is None:
93             kwargs = []
94         return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
95                           dyn_args, dyn_kwargs, lineno=lineno)
96
97
98 @contextfunction
99 def _gettext_alias(context, string):
100     return context.resolve('gettext')(string)
101
102
103 class InternationalizationExtension(Extension):
104     """This extension adds gettext support to Jinja2."""
105     tags = set(['trans'])
106
107     def __init__(self, environment):
108         Extension.__init__(self, environment)
109         environment.globals['_'] = _gettext_alias
110         environment.extend(
111             install_gettext_translations=self._install,
112             install_null_translations=self._install_null,
113             uninstall_gettext_translations=self._uninstall,
114             extract_translations=self._extract
115         )
116
117     def _install(self, translations):
118         self.environment.globals.update(
119             gettext=translations.ugettext,
120             ngettext=translations.ungettext
121         )
122
123     def _install_null(self):
124         self.environment.globals.update(
125             gettext=lambda x: x,
126             ngettext=lambda s, p, n: (n != 1 and (p,) or (s,))[0]
127         )
128
129     def _uninstall(self, translations):
130         for key in 'gettext', 'ngettext':
131             self.environment.globals.pop(key, None)
132
133     def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
134         if isinstance(source, basestring):
135             source = self.environment.parse(source)
136         return extract_from_ast(source, gettext_functions)
137
138     def parse(self, parser):
139         """Parse a translatable tag."""
140         lineno = parser.stream.next().lineno
141
142         # find all the variables referenced.  Additionally a variable can be
143         # defined in the body of the trans block too, but this is checked at
144         # a later state.
145         plural_expr = None
146         variables = {}
147         while parser.stream.current.type is not 'block_end':
148             if variables:
149                 parser.stream.expect('comma')
150
151             # skip colon for python compatibility
152             if parser.stream.skip_if('colon'):
153                 break
154
155             name = parser.stream.expect('name')
156             if name.value in variables:
157                 parser.fail('translatable variable %r defined twice.' %
158                             name.value, name.lineno,
159                             exc=TemplateAssertionError)
160
161             # expressions
162             if parser.stream.current.type is 'assign':
163                 parser.stream.next()
164                 variables[name.value] = var = parser.parse_expression()
165             else:
166                 variables[name.value] = var = nodes.Name(name.value, 'load')
167             if plural_expr is None:
168                 plural_expr = var
169
170         parser.stream.expect('block_end')
171
172         plural = plural_names = None
173         have_plural = False
174         referenced = set()
175
176         # now parse until endtrans or pluralize
177         singular_names, singular = self._parse_block(parser, True)
178         if singular_names:
179             referenced.update(singular_names)
180             if plural_expr is None:
181                 plural_expr = nodes.Name(singular_names[0], 'load')
182
183         # if we have a pluralize block, we parse that too
184         if parser.stream.current.test('name:pluralize'):
185             have_plural = True
186             parser.stream.next()
187             if parser.stream.current.type is not 'block_end':
188                 plural_expr = parser.parse_expression()
189             parser.stream.expect('block_end')
190             plural_names, plural = self._parse_block(parser, False)
191             parser.stream.next()
192             referenced.update(plural_names)
193         else:
194             parser.stream.next()
195
196         # register free names as simple name expressions
197         for var in referenced:
198             if var not in variables:
199                 variables[var] = nodes.Name(var, 'load')
200
201         # no variables referenced?  no need to escape
202         if not referenced:
203             singular = singular.replace('%%', '%')
204             if plural:
205                 plural = plural.replace('%%', '%')
206
207         if not have_plural:
208             plural_expr = None
209         elif plural_expr is None:
210             parser.fail('pluralize without variables', lineno)
211
212         if variables:
213             variables = nodes.Dict([nodes.Pair(nodes.Const(x, lineno=lineno), y)
214                                     for x, y in variables.items()])
215         else:
216             variables = None
217
218         node = self._make_node(singular, plural, variables, plural_expr)
219         node.set_lineno(lineno)
220         return node
221
222     def _parse_block(self, parser, allow_pluralize):
223         """Parse until the next block tag with a given name."""
224         referenced = []
225         buf = []
226         while 1:
227             if parser.stream.current.type is 'data':
228                 buf.append(parser.stream.current.value.replace('%', '%%'))
229                 parser.stream.next()
230             elif parser.stream.current.type is 'variable_begin':
231                 parser.stream.next()
232                 name = parser.stream.expect('name').value
233                 referenced.append(name)
234                 buf.append('%%(%s)s' % name)
235                 parser.stream.expect('variable_end')
236             elif parser.stream.current.type is 'block_begin':
237                 parser.stream.next()
238                 if parser.stream.current.test('name:endtrans'):
239                     break
240                 elif parser.stream.current.test('name:pluralize'):
241                     if allow_pluralize:
242                         break
243                     parser.fail('a translatable section can have only one '
244                                 'pluralize section')
245                 parser.fail('control structures in translatable sections are '
246                             'not allowed')
247             else:
248                 assert False, 'internal parser error'
249
250         return referenced, concat(buf)
251
252     def _make_node(self, singular, plural, variables, plural_expr):
253         """Generates a useful node from the data provided."""
254         # singular only:
255         if plural_expr is None:
256             gettext = nodes.Name('gettext', 'load')
257             node = nodes.Call(gettext, [nodes.Const(singular)],
258                               [], None, None)
259
260         # singular and plural
261         else:
262             ngettext = nodes.Name('ngettext', 'load')
263             node = nodes.Call(ngettext, [
264                 nodes.Const(singular),
265                 nodes.Const(plural),
266                 plural_expr
267             ], [], None, None)
268
269         # mark the return value as safe if we are in an
270         # environment with autoescaping turned on
271         if self.environment.autoescape:
272             node = nodes.MarkSafe(node)
273
274         if variables:
275             node = nodes.Mod(node, variables)
276         return nodes.Output([node])
277
278
279 class ExprStmtExtension(Extension):
280     """Adds a `do` tag to Jinja2 that works like the print statement just
281     that it doesn't print the return value.
282     """
283     tags = set(['do'])
284
285     def parse(self, parser):
286         node = nodes.ExprStmt(lineno=parser.stream.next().lineno)
287         node.node = parser.parse_tuple()
288         return node
289
290
291 class LoopControlExtension(Extension):
292     """Adds break and continue to the template engine."""
293     tags = set(['break', 'continue'])
294
295     def parse(self, parser):
296         token = parser.stream.next()
297         if token.value == 'break':
298             return nodes.Break(lineno=token.lineno)
299         return nodes.Continue(lineno=token.lineno)
300
301
302 def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS):
303     """Extract localizable strings from the given template node.
304
305     For every string found this function yields a ``(lineno, function,
306     message)`` tuple, where:
307
308     * ``lineno`` is the number of the line on which the string was found,
309     * ``function`` is the name of the ``gettext`` function used (if the
310       string was extracted from embedded Python code), and
311     *  ``message`` is the string itself (a ``unicode`` object, or a tuple
312        of ``unicode`` objects for functions with multiple string arguments).
313     """
314     for node in node.find_all(nodes.Call):
315         if not isinstance(node.node, nodes.Name) or \
316            node.node.name not in gettext_functions:
317             continue
318
319         strings = []
320         for arg in node.args:
321             if isinstance(arg, nodes.Const) and \
322                isinstance(arg.value, basestring):
323                 strings.append(arg.value)
324             else:
325                 strings.append(None)
326
327         if len(strings) == 1:
328             strings = strings[0]
329         else:
330             strings = tuple(strings)
331         yield node.lineno, node.node.name, strings
332
333
334 def babel_extract(fileobj, keywords, comment_tags, options):
335     """Babel extraction method for Jinja templates.
336
337     :param fileobj: the file-like object the messages should be extracted from
338     :param keywords: a list of keywords (i.e. function names) that should be
339                      recognized as translation functions
340     :param comment_tags: a list of translator tags to search for and include
341                          in the results.  (Unused)
342     :param options: a dictionary of additional options (optional)
343     :return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
344              (comments will be empty currently)
345     """
346     encoding = options.get('encoding', 'utf-8')
347
348     have_trans_extension = False
349     extensions = []
350     for extension in options.get('extensions', '').split(','):
351         extension = extension.strip()
352         if not extension:
353             continue
354         extension = import_string(extension)
355         if extension is InternationalizationExtension:
356             have_trans_extension = True
357         extensions.append(extension)
358     if not have_trans_extension:
359         extensions.append(InternationalizationExtension)
360
361     environment = get_spontaneous_environment(
362         options.get('block_start_string', '{%'),
363         options.get('block_end_string', '%}'),
364         options.get('variable_start_string', '{{'),
365         options.get('variable_end_string', '}}'),
366         options.get('comment_start_string', '{#'),
367         options.get('comment_end_string', '#}'),
368         options.get('line_statement_prefix') or None,
369         options.get('trim_blocks', '').lower() in ('1', 'on', 'yes', 'true'),
370         tuple(extensions),
371         # fill with defaults so that environments are shared
372         # with other spontaneus environments.  The rest of the
373         # arguments are optimizer, undefined, finalize, autoescape,
374         # loader, cache size and auto reloading setting
375         True, Undefined, None, False, None, 0, False
376     )
377
378     node = environment.parse(fileobj.read().decode(encoding))
379     for lineno, func, message in extract_from_ast(node, keywords):
380         yield lineno, func, message, []
381
382
383 #: nicer import names
384 i18n = InternationalizationExtension
385 do = ExprStmtExtension
386 loopcontrols = LoopControlExtension