OLD | NEW |
| (Empty) |
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: (c) 2010 by the Jinja Team. | |
11 :license: BSD. | |
12 """ | |
13 from jinja2 import nodes | |
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 | |
19 from jinja2.environment import Environment | |
20 from jinja2.runtime import concat | |
21 from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError | |
22 from jinja2.utils import contextfunction, import_string, Markup | |
23 from jinja2._compat import next, with_metaclass, string_types, iteritems | |
24 | |
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. | |
29 GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext') | |
30 | |
31 | |
32 class ExtensionRegistry(type): | |
33 """Gives the extension an unique identifier.""" | |
34 | |
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 | |
41 class Extension(with_metaclass(ExtensionRegistry, object)): | |
42 """Extensions can be used to add extra functionality to the Jinja template | |
43 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. | |
47 | |
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). | |
58 """ | |
59 | |
60 #: if this extension parses this is the list of tags it's listening to. | |
61 tags = set() | |
62 | |
63 #: the priority of that extension. This is especially useful for | |
64 #: extensions that preprocess values. A lower value means higher | |
65 #: priority. | |
66 #: | |
67 #: .. versionadded:: 2.4 | |
68 priority = 100 | |
69 | |
70 def __init__(self, environment): | |
71 self.environment = environment | |
72 | |
73 def bind(self, environment): | |
74 """Create a copy of this extension bound to another environment.""" | |
75 rv = object.__new__(self.__class__) | |
76 rv.__dict__.update(self.__dict__) | |
77 rv.environment = environment | |
78 return rv | |
79 | |
80 def preprocess(self, source, name, filename=None): | |
81 """This method is called before the actual lexing and can be used to | |
82 preprocess the source. The `filename` is optional. The return value | |
83 must be the preprocessed source. | |
84 """ | |
85 return source | |
86 | |
87 def filter_stream(self, stream): | |
88 """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used | |
89 to filter tokens returned. This method has to return an iterable of | |
90 :class:`~jinja2.lexer.Token`\s, but it doesn't have to return a | |
91 :class:`~jinja2.lexer.TokenStream`. | |
92 | |
93 In the `ext` folder of the Jinja2 source distribution there is a file | |
94 called `inlinegettext.py` which implements a filter that utilizes this | |
95 method. | |
96 """ | |
97 return stream | |
98 | |
99 def parse(self, parser): | |
100 """If any of the :attr:`tags` matched this method is called with the | |
101 parser as first argument. The token the parser stream is pointing at | |
102 is the name token that matched. This method has to return one or a | |
103 list of multiple nodes. | |
104 """ | |
105 raise NotImplementedError() | |
106 | |
107 def attr(self, name, lineno=None): | |
108 """Return an attribute node for the current extension. This is useful | |
109 to pass constants on extensions to generated template code. | |
110 | |
111 :: | |
112 | |
113 self.attr('_my_attribute', lineno=lineno) | |
114 """ | |
115 return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno) | |
116 | |
117 def call_method(self, name, args=None, kwargs=None, dyn_args=None, | |
118 dyn_kwargs=None, lineno=None): | |
119 """Call a method of the extension. This is a shortcut for | |
120 :meth:`attr` + :class:`jinja2.nodes.Call`. | |
121 """ | |
122 if args is None: | |
123 args = [] | |
124 if kwargs is None: | |
125 kwargs = [] | |
126 return nodes.Call(self.attr(name, lineno=lineno), args, kwargs, | |
127 dyn_args, dyn_kwargs, lineno=lineno) | |
128 | |
129 | |
130 @contextfunction | |
131 def _gettext_alias(__context, *args, **kwargs): | |
132 return __context.call(__context.resolve('gettext'), *args, **kwargs) | |
133 | |
134 | |
135 def _make_new_gettext(func): | |
136 @contextfunction | |
137 def gettext(__context, __string, **variables): | |
138 rv = __context.call(func, __string) | |
139 if __context.eval_ctx.autoescape: | |
140 rv = Markup(rv) | |
141 return rv % variables | |
142 return gettext | |
143 | |
144 | |
145 def _make_new_ngettext(func): | |
146 @contextfunction | |
147 def ngettext(__context, __singular, __plural, __num, **variables): | |
148 variables.setdefault('num', __num) | |
149 rv = __context.call(func, __singular, __plural, __num) | |
150 if __context.eval_ctx.autoescape: | |
151 rv = Markup(rv) | |
152 return rv % variables | |
153 return ngettext | |
154 | |
155 | |
156 class InternationalizationExtension(Extension): | |
157 """This extension adds gettext support to Jinja2.""" | |
158 tags = set(['trans']) | |
159 | |
160 # TODO: the i18n extension is currently reevaluating values in a few | |
161 # situations. Take this example: | |
162 # {% trans count=something() %}{{ count }} foo{% pluralize | |
163 # %}{{ count }} fooss{% endtrans %} | |
164 # something is called twice here. One time for the gettext value and | |
165 # the other time for the n-parameter of the ngettext function. | |
166 | |
167 def __init__(self, environment): | |
168 Extension.__init__(self, environment) | |
169 environment.globals['_'] = _gettext_alias | |
170 environment.extend( | |
171 install_gettext_translations=self._install, | |
172 install_null_translations=self._install_null, | |
173 install_gettext_callables=self._install_callables, | |
174 uninstall_gettext_translations=self._uninstall, | |
175 extract_translations=self._extract, | |
176 newstyle_gettext=False | |
177 ) | |
178 | |
179 def _install(self, translations, newstyle=None): | |
180 gettext = getattr(translations, 'ugettext', None) | |
181 if gettext is None: | |
182 gettext = translations.gettext | |
183 ngettext = getattr(translations, 'ungettext', None) | |
184 if ngettext is None: | |
185 ngettext = translations.ngettext | |
186 self._install_callables(gettext, ngettext, newstyle) | |
187 | |
188 def _install_null(self, newstyle=None): | |
189 self._install_callables( | |
190 lambda x: x, | |
191 lambda s, p, n: (n != 1 and (p,) or (s,))[0], | |
192 newstyle | |
193 ) | |
194 | |
195 def _install_callables(self, gettext, ngettext, newstyle=None): | |
196 if newstyle is not None: | |
197 self.environment.newstyle_gettext = newstyle | |
198 if self.environment.newstyle_gettext: | |
199 gettext = _make_new_gettext(gettext) | |
200 ngettext = _make_new_ngettext(ngettext) | |
201 self.environment.globals.update( | |
202 gettext=gettext, | |
203 ngettext=ngettext | |
204 ) | |
205 | |
206 def _uninstall(self, translations): | |
207 for key in 'gettext', 'ngettext': | |
208 self.environment.globals.pop(key, None) | |
209 | |
210 def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS): | |
211 if isinstance(source, string_types): | |
212 source = self.environment.parse(source) | |
213 return extract_from_ast(source, gettext_functions) | |
214 | |
215 def parse(self, parser): | |
216 """Parse a translatable tag.""" | |
217 lineno = next(parser.stream).lineno | |
218 num_called_num = False | |
219 | |
220 # find all the variables referenced. Additionally a variable can be | |
221 # defined in the body of the trans block too, but this is checked at | |
222 # a later state. | |
223 plural_expr = None | |
224 plural_expr_assignment = None | |
225 variables = {} | |
226 while parser.stream.current.type != 'block_end': | |
227 if variables: | |
228 parser.stream.expect('comma') | |
229 | |
230 # skip colon for python compatibility | |
231 if parser.stream.skip_if('colon'): | |
232 break | |
233 | |
234 name = parser.stream.expect('name') | |
235 if name.value in variables: | |
236 parser.fail('translatable variable %r defined twice.' % | |
237 name.value, name.lineno, | |
238 exc=TemplateAssertionError) | |
239 | |
240 # expressions | |
241 if parser.stream.current.type == 'assign': | |
242 next(parser.stream) | |
243 variables[name.value] = var = parser.parse_expression() | |
244 else: | |
245 variables[name.value] = var = nodes.Name(name.value, 'load') | |
246 | |
247 if plural_expr is None: | |
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 | |
255 num_called_num = name.value == 'num' | |
256 | |
257 parser.stream.expect('block_end') | |
258 | |
259 plural = plural_names = None | |
260 have_plural = False | |
261 referenced = set() | |
262 | |
263 # now parse until endtrans or pluralize | |
264 singular_names, singular = self._parse_block(parser, True) | |
265 if singular_names: | |
266 referenced.update(singular_names) | |
267 if plural_expr is None: | |
268 plural_expr = nodes.Name(singular_names[0], 'load') | |
269 num_called_num = singular_names[0] == 'num' | |
270 | |
271 # if we have a pluralize block, we parse that too | |
272 if parser.stream.current.test('name:pluralize'): | |
273 have_plural = True | |
274 next(parser.stream) | |
275 if parser.stream.current.type != 'block_end': | |
276 name = parser.stream.expect('name') | |
277 if name.value not in variables: | |
278 parser.fail('unknown variable %r for pluralization' % | |
279 name.value, name.lineno, | |
280 exc=TemplateAssertionError) | |
281 plural_expr = variables[name.value] | |
282 num_called_num = name.value == 'num' | |
283 parser.stream.expect('block_end') | |
284 plural_names, plural = self._parse_block(parser, False) | |
285 next(parser.stream) | |
286 referenced.update(plural_names) | |
287 else: | |
288 next(parser.stream) | |
289 | |
290 # register free names as simple name expressions | |
291 for var in referenced: | |
292 if var not in variables: | |
293 variables[var] = nodes.Name(var, 'load') | |
294 | |
295 if not have_plural: | |
296 plural_expr = None | |
297 elif plural_expr is None: | |
298 parser.fail('pluralize without variables', lineno) | |
299 | |
300 node = self._make_node(singular, plural, variables, plural_expr, | |
301 bool(referenced), | |
302 num_called_num and have_plural) | |
303 node.set_lineno(lineno) | |
304 if plural_expr_assignment is not None: | |
305 return [plural_expr_assignment, node] | |
306 else: | |
307 return node | |
308 | |
309 def _parse_block(self, parser, allow_pluralize): | |
310 """Parse until the next block tag with a given name.""" | |
311 referenced = [] | |
312 buf = [] | |
313 while 1: | |
314 if parser.stream.current.type == 'data': | |
315 buf.append(parser.stream.current.value.replace('%', '%%')) | |
316 next(parser.stream) | |
317 elif parser.stream.current.type == 'variable_begin': | |
318 next(parser.stream) | |
319 name = parser.stream.expect('name').value | |
320 referenced.append(name) | |
321 buf.append('%%(%s)s' % name) | |
322 parser.stream.expect('variable_end') | |
323 elif parser.stream.current.type == 'block_begin': | |
324 next(parser.stream) | |
325 if parser.stream.current.test('name:endtrans'): | |
326 break | |
327 elif parser.stream.current.test('name:pluralize'): | |
328 if allow_pluralize: | |
329 break | |
330 parser.fail('a translatable section can have only one ' | |
331 'pluralize section') | |
332 parser.fail('control structures in translatable sections are ' | |
333 'not allowed') | |
334 elif parser.stream.eos: | |
335 parser.fail('unclosed translation block') | |
336 else: | |
337 assert False, 'internal parser error' | |
338 | |
339 return referenced, concat(buf) | |
340 | |
341 def _make_node(self, singular, plural, variables, plural_expr, | |
342 vars_referenced, num_called_num): | |
343 """Generates a useful node from the data provided.""" | |
344 # no variables referenced? no need to escape for old style | |
345 # gettext invocations only if there are vars. | |
346 if not vars_referenced and not self.environment.newstyle_gettext: | |
347 singular = singular.replace('%%', '%') | |
348 if plural: | |
349 plural = plural.replace('%%', '%') | |
350 | |
351 # singular only: | |
352 if plural_expr is None: | |
353 gettext = nodes.Name('gettext', 'load') | |
354 node = nodes.Call(gettext, [nodes.Const(singular)], | |
355 [], None, None) | |
356 | |
357 # singular and plural | |
358 else: | |
359 ngettext = nodes.Name('ngettext', 'load') | |
360 node = nodes.Call(ngettext, [ | |
361 nodes.Const(singular), | |
362 nodes.Const(plural), | |
363 plural_expr | |
364 ], [], None, None) | |
365 | |
366 # in case newstyle gettext is used, the method is powerful | |
367 # enough to handle the variable expansion and autoescape | |
368 # handling itself | |
369 if self.environment.newstyle_gettext: | |
370 for key, value in iteritems(variables): | |
371 # the function adds that later anyways in case num was | |
372 # called num, so just skip it. | |
373 if num_called_num and key == 'num': | |
374 continue | |
375 node.kwargs.append(nodes.Keyword(key, value)) | |
376 | |
377 # otherwise do that here | |
378 else: | |
379 # mark the return value as safe if we are in an | |
380 # environment with autoescaping turned on | |
381 node = nodes.MarkSafeIfAutoescape(node) | |
382 if variables: | |
383 node = nodes.Mod(node, nodes.Dict([ | |
384 nodes.Pair(nodes.Const(key), value) | |
385 for key, value in variables.items() | |
386 ])) | |
387 return nodes.Output([node]) | |
388 | |
389 | |
390 class ExprStmtExtension(Extension): | |
391 """Adds a `do` tag to Jinja2 that works like the print statement just | |
392 that it doesn't print the return value. | |
393 """ | |
394 tags = set(['do']) | |
395 | |
396 def parse(self, parser): | |
397 node = nodes.ExprStmt(lineno=next(parser.stream).lineno) | |
398 node.node = parser.parse_tuple() | |
399 return node | |
400 | |
401 | |
402 class LoopControlExtension(Extension): | |
403 """Adds break and continue to the template engine.""" | |
404 tags = set(['break', 'continue']) | |
405 | |
406 def parse(self, parser): | |
407 token = next(parser.stream) | |
408 if token.value == 'break': | |
409 return nodes.Break(lineno=token.lineno) | |
410 return nodes.Continue(lineno=token.lineno) | |
411 | |
412 | |
413 class WithExtension(Extension): | |
414 """Adds support for a django-like with block.""" | |
415 tags = set(['with']) | |
416 | |
417 def parse(self, parser): | |
418 node = nodes.Scope(lineno=next(parser.stream).lineno) | |
419 assignments = [] | |
420 while parser.stream.current.type != 'block_end': | |
421 lineno = parser.stream.current.lineno | |
422 if assignments: | |
423 parser.stream.expect('comma') | |
424 target = parser.parse_assign_target() | |
425 parser.stream.expect('assign') | |
426 expr = parser.parse_expression() | |
427 assignments.append(nodes.Assign(target, expr, lineno=lineno)) | |
428 node.body = assignments + \ | |
429 list(parser.parse_statements(('name:endwith',), | |
430 drop_needle=True)) | |
431 return node | |
432 | |
433 | |
434 class AutoEscapeExtension(Extension): | |
435 """Changes auto escape rules for a scope.""" | |
436 tags = set(['autoescape']) | |
437 | |
438 def parse(self, parser): | |
439 node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno
) | |
440 node.options = [ | |
441 nodes.Keyword('autoescape', parser.parse_expression()) | |
442 ] | |
443 node.body = parser.parse_statements(('name:endautoescape',), | |
444 drop_needle=True) | |
445 return nodes.Scope([node]) | |
446 | |
447 | |
448 def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS, | |
449 babel_style=True): | |
450 """Extract localizable strings from the given template node. Per | |
451 default this function returns matches in babel style that means non string | |
452 parameters as well as keyword arguments are returned as `None`. This | |
453 allows Babel to figure out what you really meant if you are using | |
454 gettext functions that allow keyword arguments for placeholder expansion. | |
455 If you don't want that behavior set the `babel_style` parameter to `False` | |
456 which causes only strings to be returned and parameters are always stored | |
457 in tuples. As a consequence invalid gettext calls (calls without a single | |
458 string parameter or string parameters after non-string parameters) are | |
459 skipped. | |
460 | |
461 This example explains the behavior: | |
462 | |
463 >>> from jinja2 import Environment | |
464 >>> env = Environment() | |
465 >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}') | |
466 >>> list(extract_from_ast(node)) | |
467 [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))] | |
468 >>> list(extract_from_ast(node, babel_style=False)) | |
469 [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))] | |
470 | |
471 For every string found this function yields a ``(lineno, function, | |
472 message)`` tuple, where: | |
473 | |
474 * ``lineno`` is the number of the line on which the string was found, | |
475 * ``function`` is the name of the ``gettext`` function used (if the | |
476 string was extracted from embedded Python code), and | |
477 * ``message`` is the string itself (a ``unicode`` object, or a tuple | |
478 of ``unicode`` objects for functions with multiple string arguments). | |
479 | |
480 This extraction function operates on the AST and is because of that unable | |
481 to extract any comments. For comment support you have to use the babel | |
482 extraction interface or extract comments yourself. | |
483 """ | |
484 for node in node.find_all(nodes.Call): | |
485 if not isinstance(node.node, nodes.Name) or \ | |
486 node.node.name not in gettext_functions: | |
487 continue | |
488 | |
489 strings = [] | |
490 for arg in node.args: | |
491 if isinstance(arg, nodes.Const) and \ | |
492 isinstance(arg.value, string_types): | |
493 strings.append(arg.value) | |
494 else: | |
495 strings.append(None) | |
496 | |
497 for arg in node.kwargs: | |
498 strings.append(None) | |
499 if node.dyn_args is not None: | |
500 strings.append(None) | |
501 if node.dyn_kwargs is not None: | |
502 strings.append(None) | |
503 | |
504 if not babel_style: | |
505 strings = tuple(x for x in strings if x is not None) | |
506 if not strings: | |
507 continue | |
508 else: | |
509 if len(strings) == 1: | |
510 strings = strings[0] | |
511 else: | |
512 strings = tuple(strings) | |
513 yield node.lineno, node.node.name, strings | |
514 | |
515 | |
516 class _CommentFinder(object): | |
517 """Helper class to find comments in a token stream. Can only | |
518 find comments for gettext calls forwards. Once the comment | |
519 from line 4 is found, a comment for line 1 will not return a | |
520 usable value. | |
521 """ | |
522 | |
523 def __init__(self, tokens, comment_tags): | |
524 self.tokens = tokens | |
525 self.comment_tags = comment_tags | |
526 self.offset = 0 | |
527 self.last_lineno = 0 | |
528 | |
529 def find_backwards(self, offset): | |
530 try: | |
531 for _, token_type, token_value in \ | |
532 reversed(self.tokens[self.offset:offset]): | |
533 if token_type in ('comment', 'linecomment'): | |
534 try: | |
535 prefix, comment = token_value.split(None, 1) | |
536 except ValueError: | |
537 continue | |
538 if prefix in self.comment_tags: | |
539 return [comment.rstrip()] | |
540 return [] | |
541 finally: | |
542 self.offset = offset | |
543 | |
544 def find_comments(self, lineno): | |
545 if not self.comment_tags or self.last_lineno > lineno: | |
546 return [] | |
547 for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]): | |
548 if token_lineno > lineno: | |
549 return self.find_backwards(self.offset + idx) | |
550 return self.find_backwards(len(self.tokens)) | |
551 | |
552 | |
553 def babel_extract(fileobj, keywords, comment_tags, options): | |
554 """Babel extraction method for Jinja templates. | |
555 | |
556 .. versionchanged:: 2.3 | |
557 Basic support for translation comments was added. If `comment_tags` | |
558 is now set to a list of keywords for extraction, the extractor will | |
559 try to find the best preceeding comment that begins with one of the | |
560 keywords. For best results, make sure to not have more than one | |
561 gettext call in one line of code and the matching comment in the | |
562 same line or the line before. | |
563 | |
564 .. versionchanged:: 2.5.1 | |
565 The `newstyle_gettext` flag can be set to `True` to enable newstyle | |
566 gettext calls. | |
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 | |
572 :param fileobj: the file-like object the messages should be extracted from | |
573 :param keywords: a list of keywords (i.e. function names) that should be | |
574 recognized as translation functions | |
575 :param comment_tags: a list of translator tags to search for and include | |
576 in the results. | |
577 :param options: a dictionary of additional options (optional) | |
578 :return: an iterator over ``(lineno, funcname, message, comments)`` tuples. | |
579 (comments will be empty currently) | |
580 """ | |
581 extensions = set() | |
582 for extension in options.get('extensions', '').split(','): | |
583 extension = extension.strip() | |
584 if not extension: | |
585 continue | |
586 extensions.add(import_string(extension)) | |
587 if InternationalizationExtension not in extensions: | |
588 extensions.add(InternationalizationExtension) | |
589 | |
590 def getbool(options, key, default=False): | |
591 return options.get(key, str(default)).lower() in \ | |
592 ('1', 'on', 'yes', 'true') | |
593 | |
594 silent = getbool(options, 'silent', True) | |
595 environment = Environment( | |
596 options.get('block_start_string', BLOCK_START_STRING), | |
597 options.get('block_end_string', BLOCK_END_STRING), | |
598 options.get('variable_start_string', VARIABLE_START_STRING), | |
599 options.get('variable_end_string', VARIABLE_END_STRING), | |
600 options.get('comment_start_string', COMMENT_START_STRING), | |
601 options.get('comment_end_string', COMMENT_END_STRING), | |
602 options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX, | |
603 options.get('line_comment_prefix') or LINE_COMMENT_PREFIX, | |
604 getbool(options, 'trim_blocks', TRIM_BLOCKS), | |
605 getbool(options, 'lstrip_blocks', LSTRIP_BLOCKS), | |
606 NEWLINE_SEQUENCE, | |
607 getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE), | |
608 frozenset(extensions), | |
609 cache_size=0, | |
610 auto_reload=False | |
611 ) | |
612 | |
613 if getbool(options, 'newstyle_gettext'): | |
614 environment.newstyle_gettext = True | |
615 | |
616 source = fileobj.read().decode(options.get('encoding', 'utf-8')) | |
617 try: | |
618 node = environment.parse(source) | |
619 tokens = list(environment.lex(environment.preprocess(source))) | |
620 except TemplateSyntaxError as e: | |
621 if not silent: | |
622 raise | |
623 # skip templates with syntax errors | |
624 return | |
625 | |
626 finder = _CommentFinder(tokens, comment_tags) | |
627 for lineno, func, message in extract_from_ast(node, keywords): | |
628 yield lineno, func, message, finder.find_comments(lineno) | |
629 | |
630 | |
631 #: nicer import names | |
632 i18n = InternationalizationExtension | |
633 do = ExprStmtExtension | |
634 loopcontrols = LoopControlExtension | |
635 with_ = WithExtension | |
636 autoescape = AutoEscapeExtension | |
OLD | NEW |