OLD | NEW |
1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
2 """ | 2 """ |
3 jinja2.ext | 3 jinja2.ext |
4 ~~~~~~~~~~ | 4 ~~~~~~~~~~ |
5 | 5 |
6 Jinja extensions allow to add custom tags similar to the way django custom | 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 | 7 tags work. By default two example extensions exist: an i18n and a cache |
8 extension. | 8 extension. |
9 | 9 |
10 :copyright: (c) 2010 by the Jinja Team. | 10 :copyright: (c) 2010 by the Jinja Team. |
11 :license: BSD. | 11 :license: BSD. |
12 """ | 12 """ |
13 from collections import deque | |
14 from jinja2 import nodes | 13 from jinja2 import nodes |
15 from jinja2.defaults import * | 14 from 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 |
16 from jinja2.environment import Environment | 19 from jinja2.environment import Environment |
17 from jinja2.runtime import Undefined, concat | 20 from jinja2.runtime import concat |
18 from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError | 21 from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError |
19 from jinja2.utils import contextfunction, import_string, Markup, next | 22 from jinja2.utils import contextfunction, import_string, Markup |
| 23 from jinja2._compat import next, with_metaclass, string_types, iteritems |
20 | 24 |
21 | 25 |
22 # the only real useful gettext functions for a Jinja template. Note | 26 # the only real useful gettext functions for a Jinja template. Note |
23 # that ugettext must be assigned to gettext as Jinja doesn't support | 27 # that ugettext must be assigned to gettext as Jinja doesn't support |
24 # non unicode strings. | 28 # non unicode strings. |
25 GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext') | 29 GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext') |
26 | 30 |
27 | 31 |
28 class ExtensionRegistry(type): | 32 class ExtensionRegistry(type): |
29 """Gives the extension an unique identifier.""" | 33 """Gives the extension an unique identifier.""" |
30 | 34 |
31 def __new__(cls, name, bases, d): | 35 def __new__(cls, name, bases, d): |
32 rv = type.__new__(cls, name, bases, d) | 36 rv = type.__new__(cls, name, bases, d) |
33 rv.identifier = rv.__module__ + '.' + rv.__name__ | 37 rv.identifier = rv.__module__ + '.' + rv.__name__ |
34 return rv | 38 return rv |
35 | 39 |
36 | 40 |
37 class Extension(object): | 41 class Extension(with_metaclass(ExtensionRegistry, object)): |
38 """Extensions can be used to add extra functionality to the Jinja template | 42 """Extensions can be used to add extra functionality to the Jinja template |
39 system at the parser level. Custom extensions are bound to an environment | 43 system at the parser level. Custom extensions are bound to an environment |
40 but may not store environment specific data on `self`. The reason for | 44 but may not store environment specific data on `self`. The reason for |
41 this is that an extension can be bound to another environment (for | 45 this is that an extension can be bound to another environment (for |
42 overlays) by creating a copy and reassigning the `environment` attribute. | 46 overlays) by creating a copy and reassigning the `environment` attribute. |
43 | 47 |
44 As extensions are created by the environment they cannot accept any | 48 As extensions are created by the environment they cannot accept any |
45 arguments for configuration. One may want to work around that by using | 49 arguments for configuration. One may want to work around that by using |
46 a factory function, but that is not possible as extensions are identified | 50 a factory function, but that is not possible as extensions are identified |
47 by their import name. The correct way to configure the extension is | 51 by their import name. The correct way to configure the extension is |
48 storing the configuration values on the environment. Because this way the | 52 storing the configuration values on the environment. Because this way the |
49 environment ends up acting as central configuration storage the | 53 environment ends up acting as central configuration storage the |
50 attributes may clash which is why extensions have to ensure that the names | 54 attributes may clash which is why extensions have to ensure that the names |
51 they choose for configuration are not too generic. ``prefix`` for example | 55 they choose for configuration are not too generic. ``prefix`` for example |
52 is a terrible name, ``fragment_cache_prefix`` on the other hand is a good | 56 is a terrible name, ``fragment_cache_prefix`` on the other hand is a good |
53 name as includes the name of the extension (fragment cache). | 57 name as includes the name of the extension (fragment cache). |
54 """ | 58 """ |
55 __metaclass__ = ExtensionRegistry | |
56 | 59 |
57 #: if this extension parses this is the list of tags it's listening to. | 60 #: if this extension parses this is the list of tags it's listening to. |
58 tags = set() | 61 tags = set() |
59 | 62 |
60 #: the priority of that extension. This is especially useful for | 63 #: the priority of that extension. This is especially useful for |
61 #: extensions that preprocess values. A lower value means higher | 64 #: extensions that preprocess values. A lower value means higher |
62 #: priority. | 65 #: priority. |
63 #: | 66 #: |
64 #: .. versionadded:: 2.4 | 67 #: .. versionadded:: 2.4 |
65 priority = 100 | 68 priority = 100 |
(...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
198 self.environment.globals.update( | 201 self.environment.globals.update( |
199 gettext=gettext, | 202 gettext=gettext, |
200 ngettext=ngettext | 203 ngettext=ngettext |
201 ) | 204 ) |
202 | 205 |
203 def _uninstall(self, translations): | 206 def _uninstall(self, translations): |
204 for key in 'gettext', 'ngettext': | 207 for key in 'gettext', 'ngettext': |
205 self.environment.globals.pop(key, None) | 208 self.environment.globals.pop(key, None) |
206 | 209 |
207 def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS): | 210 def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS): |
208 if isinstance(source, basestring): | 211 if isinstance(source, string_types): |
209 source = self.environment.parse(source) | 212 source = self.environment.parse(source) |
210 return extract_from_ast(source, gettext_functions) | 213 return extract_from_ast(source, gettext_functions) |
211 | 214 |
212 def parse(self, parser): | 215 def parse(self, parser): |
213 """Parse a translatable tag.""" | 216 """Parse a translatable tag.""" |
214 lineno = next(parser.stream).lineno | 217 lineno = next(parser.stream).lineno |
215 num_called_num = False | 218 num_called_num = False |
216 | 219 |
217 # find all the variables referenced. Additionally a variable can be | 220 # find all the variables referenced. Additionally a variable can be |
218 # defined in the body of the trans block too, but this is checked at | 221 # defined in the body of the trans block too, but this is checked at |
219 # a later state. | 222 # a later state. |
220 plural_expr = None | 223 plural_expr = None |
| 224 plural_expr_assignment = None |
221 variables = {} | 225 variables = {} |
222 while parser.stream.current.type != 'block_end': | 226 while parser.stream.current.type != 'block_end': |
223 if variables: | 227 if variables: |
224 parser.stream.expect('comma') | 228 parser.stream.expect('comma') |
225 | 229 |
226 # skip colon for python compatibility | 230 # skip colon for python compatibility |
227 if parser.stream.skip_if('colon'): | 231 if parser.stream.skip_if('colon'): |
228 break | 232 break |
229 | 233 |
230 name = parser.stream.expect('name') | 234 name = parser.stream.expect('name') |
231 if name.value in variables: | 235 if name.value in variables: |
232 parser.fail('translatable variable %r defined twice.' % | 236 parser.fail('translatable variable %r defined twice.' % |
233 name.value, name.lineno, | 237 name.value, name.lineno, |
234 exc=TemplateAssertionError) | 238 exc=TemplateAssertionError) |
235 | 239 |
236 # expressions | 240 # expressions |
237 if parser.stream.current.type == 'assign': | 241 if parser.stream.current.type == 'assign': |
238 next(parser.stream) | 242 next(parser.stream) |
239 variables[name.value] = var = parser.parse_expression() | 243 variables[name.value] = var = parser.parse_expression() |
240 else: | 244 else: |
241 variables[name.value] = var = nodes.Name(name.value, 'load') | 245 variables[name.value] = var = nodes.Name(name.value, 'load') |
242 | 246 |
243 if plural_expr is None: | 247 if plural_expr is None: |
244 plural_expr = var | 248 if isinstance(var, nodes.Call): |
| 249 plural_expr = nodes.Name('_trans', 'load') |
| 250 variables[name.value] = plural_expr |
| 251 plural_expr_assignment = nodes.Assign( |
| 252 nodes.Name('_trans', 'store'), var) |
| 253 else: |
| 254 plural_expr = var |
245 num_called_num = name.value == 'num' | 255 num_called_num = name.value == 'num' |
246 | 256 |
247 parser.stream.expect('block_end') | 257 parser.stream.expect('block_end') |
248 | 258 |
249 plural = plural_names = None | 259 plural = plural_names = None |
250 have_plural = False | 260 have_plural = False |
251 referenced = set() | 261 referenced = set() |
252 | 262 |
253 # now parse until endtrans or pluralize | 263 # now parse until endtrans or pluralize |
254 singular_names, singular = self._parse_block(parser, True) | 264 singular_names, singular = self._parse_block(parser, True) |
(...skipping 29 matching lines...) Expand all Loading... |
284 | 294 |
285 if not have_plural: | 295 if not have_plural: |
286 plural_expr = None | 296 plural_expr = None |
287 elif plural_expr is None: | 297 elif plural_expr is None: |
288 parser.fail('pluralize without variables', lineno) | 298 parser.fail('pluralize without variables', lineno) |
289 | 299 |
290 node = self._make_node(singular, plural, variables, plural_expr, | 300 node = self._make_node(singular, plural, variables, plural_expr, |
291 bool(referenced), | 301 bool(referenced), |
292 num_called_num and have_plural) | 302 num_called_num and have_plural) |
293 node.set_lineno(lineno) | 303 node.set_lineno(lineno) |
294 return node | 304 if plural_expr_assignment is not None: |
| 305 return [plural_expr_assignment, node] |
| 306 else: |
| 307 return node |
295 | 308 |
296 def _parse_block(self, parser, allow_pluralize): | 309 def _parse_block(self, parser, allow_pluralize): |
297 """Parse until the next block tag with a given name.""" | 310 """Parse until the next block tag with a given name.""" |
298 referenced = [] | 311 referenced = [] |
299 buf = [] | 312 buf = [] |
300 while 1: | 313 while 1: |
301 if parser.stream.current.type == 'data': | 314 if parser.stream.current.type == 'data': |
302 buf.append(parser.stream.current.value.replace('%', '%%')) | 315 buf.append(parser.stream.current.value.replace('%', '%%')) |
303 next(parser.stream) | 316 next(parser.stream) |
304 elif parser.stream.current.type == 'variable_begin': | 317 elif parser.stream.current.type == 'variable_begin': |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
347 node = nodes.Call(ngettext, [ | 360 node = nodes.Call(ngettext, [ |
348 nodes.Const(singular), | 361 nodes.Const(singular), |
349 nodes.Const(plural), | 362 nodes.Const(plural), |
350 plural_expr | 363 plural_expr |
351 ], [], None, None) | 364 ], [], None, None) |
352 | 365 |
353 # in case newstyle gettext is used, the method is powerful | 366 # in case newstyle gettext is used, the method is powerful |
354 # enough to handle the variable expansion and autoescape | 367 # enough to handle the variable expansion and autoescape |
355 # handling itself | 368 # handling itself |
356 if self.environment.newstyle_gettext: | 369 if self.environment.newstyle_gettext: |
357 for key, value in variables.iteritems(): | 370 for key, value in iteritems(variables): |
358 # the function adds that later anyways in case num was | 371 # the function adds that later anyways in case num was |
359 # called num, so just skip it. | 372 # called num, so just skip it. |
360 if num_called_num and key == 'num': | 373 if num_called_num and key == 'num': |
361 continue | 374 continue |
362 node.kwargs.append(nodes.Keyword(key, value)) | 375 node.kwargs.append(nodes.Keyword(key, value)) |
363 | 376 |
364 # otherwise do that here | 377 # otherwise do that here |
365 else: | 378 else: |
366 # mark the return value as safe if we are in an | 379 # mark the return value as safe if we are in an |
367 # environment with autoescaping turned on | 380 # environment with autoescaping turned on |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
469 extraction interface or extract comments yourself. | 482 extraction interface or extract comments yourself. |
470 """ | 483 """ |
471 for node in node.find_all(nodes.Call): | 484 for node in node.find_all(nodes.Call): |
472 if not isinstance(node.node, nodes.Name) or \ | 485 if not isinstance(node.node, nodes.Name) or \ |
473 node.node.name not in gettext_functions: | 486 node.node.name not in gettext_functions: |
474 continue | 487 continue |
475 | 488 |
476 strings = [] | 489 strings = [] |
477 for arg in node.args: | 490 for arg in node.args: |
478 if isinstance(arg, nodes.Const) and \ | 491 if isinstance(arg, nodes.Const) and \ |
479 isinstance(arg.value, basestring): | 492 isinstance(arg.value, string_types): |
480 strings.append(arg.value) | 493 strings.append(arg.value) |
481 else: | 494 else: |
482 strings.append(None) | 495 strings.append(None) |
483 | 496 |
484 for arg in node.kwargs: | 497 for arg in node.kwargs: |
485 strings.append(None) | 498 strings.append(None) |
486 if node.dyn_args is not None: | 499 if node.dyn_args is not None: |
487 strings.append(None) | 500 strings.append(None) |
488 if node.dyn_kwargs is not None: | 501 if node.dyn_kwargs is not None: |
489 strings.append(None) | 502 strings.append(None) |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
545 is now set to a list of keywords for extraction, the extractor will | 558 is now set to a list of keywords for extraction, the extractor will |
546 try to find the best preceeding comment that begins with one of the | 559 try to find the best preceeding comment that begins with one of the |
547 keywords. For best results, make sure to not have more than one | 560 keywords. For best results, make sure to not have more than one |
548 gettext call in one line of code and the matching comment in the | 561 gettext call in one line of code and the matching comment in the |
549 same line or the line before. | 562 same line or the line before. |
550 | 563 |
551 .. versionchanged:: 2.5.1 | 564 .. versionchanged:: 2.5.1 |
552 The `newstyle_gettext` flag can be set to `True` to enable newstyle | 565 The `newstyle_gettext` flag can be set to `True` to enable newstyle |
553 gettext calls. | 566 gettext calls. |
554 | 567 |
| 568 .. versionchanged:: 2.7 |
| 569 A `silent` option can now be provided. If set to `False` template |
| 570 syntax errors are propagated instead of being ignored. |
| 571 |
555 :param fileobj: the file-like object the messages should be extracted from | 572 :param fileobj: the file-like object the messages should be extracted from |
556 :param keywords: a list of keywords (i.e. function names) that should be | 573 :param keywords: a list of keywords (i.e. function names) that should be |
557 recognized as translation functions | 574 recognized as translation functions |
558 :param comment_tags: a list of translator tags to search for and include | 575 :param comment_tags: a list of translator tags to search for and include |
559 in the results. | 576 in the results. |
560 :param options: a dictionary of additional options (optional) | 577 :param options: a dictionary of additional options (optional) |
561 :return: an iterator over ``(lineno, funcname, message, comments)`` tuples. | 578 :return: an iterator over ``(lineno, funcname, message, comments)`` tuples. |
562 (comments will be empty currently) | 579 (comments will be empty currently) |
563 """ | 580 """ |
564 extensions = set() | 581 extensions = set() |
565 for extension in options.get('extensions', '').split(','): | 582 for extension in options.get('extensions', '').split(','): |
566 extension = extension.strip() | 583 extension = extension.strip() |
567 if not extension: | 584 if not extension: |
568 continue | 585 continue |
569 extensions.add(import_string(extension)) | 586 extensions.add(import_string(extension)) |
570 if InternationalizationExtension not in extensions: | 587 if InternationalizationExtension not in extensions: |
571 extensions.add(InternationalizationExtension) | 588 extensions.add(InternationalizationExtension) |
572 | 589 |
573 def getbool(options, key, default=False): | 590 def getbool(options, key, default=False): |
574 options.get(key, str(default)).lower() in ('1', 'on', 'yes', 'true') | 591 return options.get(key, str(default)).lower() in \ |
| 592 ('1', 'on', 'yes', 'true') |
575 | 593 |
| 594 silent = getbool(options, 'silent', True) |
576 environment = Environment( | 595 environment = Environment( |
577 options.get('block_start_string', BLOCK_START_STRING), | 596 options.get('block_start_string', BLOCK_START_STRING), |
578 options.get('block_end_string', BLOCK_END_STRING), | 597 options.get('block_end_string', BLOCK_END_STRING), |
579 options.get('variable_start_string', VARIABLE_START_STRING), | 598 options.get('variable_start_string', VARIABLE_START_STRING), |
580 options.get('variable_end_string', VARIABLE_END_STRING), | 599 options.get('variable_end_string', VARIABLE_END_STRING), |
581 options.get('comment_start_string', COMMENT_START_STRING), | 600 options.get('comment_start_string', COMMENT_START_STRING), |
582 options.get('comment_end_string', COMMENT_END_STRING), | 601 options.get('comment_end_string', COMMENT_END_STRING), |
583 options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX, | 602 options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX, |
584 options.get('line_comment_prefix') or LINE_COMMENT_PREFIX, | 603 options.get('line_comment_prefix') or LINE_COMMENT_PREFIX, |
585 getbool(options, 'trim_blocks', TRIM_BLOCKS), | 604 getbool(options, 'trim_blocks', TRIM_BLOCKS), |
586 NEWLINE_SEQUENCE, frozenset(extensions), | 605 getbool(options, 'lstrip_blocks', LSTRIP_BLOCKS), |
| 606 NEWLINE_SEQUENCE, |
| 607 getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE), |
| 608 frozenset(extensions), |
587 cache_size=0, | 609 cache_size=0, |
588 auto_reload=False | 610 auto_reload=False |
589 ) | 611 ) |
590 | 612 |
591 if getbool(options, 'newstyle_gettext'): | 613 if getbool(options, 'newstyle_gettext'): |
592 environment.newstyle_gettext = True | 614 environment.newstyle_gettext = True |
593 | 615 |
594 source = fileobj.read().decode(options.get('encoding', 'utf-8')) | 616 source = fileobj.read().decode(options.get('encoding', 'utf-8')) |
595 try: | 617 try: |
596 node = environment.parse(source) | 618 node = environment.parse(source) |
597 tokens = list(environment.lex(environment.preprocess(source))) | 619 tokens = list(environment.lex(environment.preprocess(source))) |
598 except TemplateSyntaxError, e: | 620 except TemplateSyntaxError as e: |
| 621 if not silent: |
| 622 raise |
599 # skip templates with syntax errors | 623 # skip templates with syntax errors |
600 return | 624 return |
601 | 625 |
602 finder = _CommentFinder(tokens, comment_tags) | 626 finder = _CommentFinder(tokens, comment_tags) |
603 for lineno, func, message in extract_from_ast(node, keywords): | 627 for lineno, func, message in extract_from_ast(node, keywords): |
604 yield lineno, func, message, finder.find_comments(lineno) | 628 yield lineno, func, message, finder.find_comments(lineno) |
605 | 629 |
606 | 630 |
607 #: nicer import names | 631 #: nicer import names |
608 i18n = InternationalizationExtension | 632 i18n = InternationalizationExtension |
609 do = ExprStmtExtension | 633 do = ExprStmtExtension |
610 loopcontrols = LoopControlExtension | 634 loopcontrols = LoopControlExtension |
611 with_ = WithExtension | 635 with_ = WithExtension |
612 autoescape = AutoEscapeExtension | 636 autoescape = AutoEscapeExtension |
OLD | NEW |