1 """Extension API for adding custom tags and behavior."""
6 from markupsafe
import Markup
10 from .environment
import Environment
11 from .exceptions
import TemplateAssertionError
12 from .exceptions
import TemplateSyntaxError
13 from .runtime
import concat
# type: ignore
14 from .runtime
import Context
15 from .runtime
import Undefined
16 from .utils
import import_string
17 from .utils
import pass_context
20 import typing_extensions
as te
21 from .lexer
import Token
22 from .lexer
import TokenStream
23 from .parser
import Parser
25 class _TranslationsBasic(te
.Protocol
):
26 def gettext(self
, message
: str) -> str:
29 def ngettext(self
, singular
: str, plural
: str, n
: int) -> str:
32 class _TranslationsContext(_TranslationsBasic
):
33 def pgettext(self
, context
: str, message
: str) -> str:
36 def npgettext(self
, context
: str, singular
: str, plural
: str, n
: int) -> str:
39 _SupportedTranslations
= t
.Union
[_TranslationsBasic
, _TranslationsContext
]
42 # I18N functions available in Jinja templates. If the I18N library
43 # provides ugettext, it will be assigned to gettext.
44 GETTEXT_FUNCTIONS
: t
.Tuple
[str, ...] = (
51 _ws_re
= re
.compile(r
"\s*\n\s*")
55 """Extensions can be used to add extra functionality to the Jinja template
56 system at the parser level. Custom extensions are bound to an environment
57 but may not store environment specific data on `self`. The reason for
58 this is that an extension can be bound to another environment (for
59 overlays) by creating a copy and reassigning the `environment` attribute.
61 As extensions are created by the environment they cannot accept any
62 arguments for configuration. One may want to work around that by using
63 a factory function, but that is not possible as extensions are identified
64 by their import name. The correct way to configure the extension is
65 storing the configuration values on the environment. Because this way the
66 environment ends up acting as central configuration storage the
67 attributes may clash which is why extensions have to ensure that the names
68 they choose for configuration are not too generic. ``prefix`` for example
69 is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
70 name as includes the name of the extension (fragment cache).
73 identifier
: t
.ClassVar
[str]
75 def __init_subclass__(cls
) -> None:
76 cls
.identifier
= f
"{cls.__module__}.{cls.__name__}"
78 #: if this extension parses this is the list of tags it's listening to.
79 tags
: t
.Set
[str] = set()
81 #: the priority of that extension. This is especially useful for
82 #: extensions that preprocess values. A lower value means higher
85 #: .. versionadded:: 2.4
88 def __init__(self
, environment
: Environment
) -> None:
89 self
.environment
= environment
91 def bind(self
, environment
: Environment
) -> "Extension":
92 """Create a copy of this extension bound to another environment."""
93 rv
= object.__new
__(self
.__class
__)
94 rv
.__dict
__.update(self
.__dict
__)
95 rv
.environment
= environment
99 self
, source
: str, name
: t
.Optional
[str], filename
: t
.Optional
[str] = None
101 """This method is called before the actual lexing and can be used to
102 preprocess the source. The `filename` is optional. The return value
103 must be the preprocessed source.
108 self
, stream
: "TokenStream"
109 ) -> t
.Union
["TokenStream", t
.Iterable
["Token"]]:
110 """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
111 to filter tokens returned. This method has to return an iterable of
112 :class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a
113 :class:`~jinja2.lexer.TokenStream`.
117 def parse(self
, parser
: "Parser") -> t
.Union
[nodes
.Node
, t
.List
[nodes
.Node
]]:
118 """If any of the :attr:`tags` matched this method is called with the
119 parser as first argument. The token the parser stream is pointing at
120 is the name token that matched. This method has to return one or a
121 list of multiple nodes.
123 raise NotImplementedError()
126 self
, name
: str, lineno
: t
.Optional
[int] = None
127 ) -> nodes
.ExtensionAttribute
:
128 """Return an attribute node for the current extension. This is useful
129 to pass constants on extensions to generated template code.
133 self.attr('_my_attribute', lineno=lineno)
135 return nodes
.ExtensionAttribute(self
.identifier
, name
, lineno
=lineno
)
140 args
: t
.Optional
[t
.List
[nodes
.Expr
]] = None,
141 kwargs
: t
.Optional
[t
.List
[nodes
.Keyword
]] = None,
142 dyn_args
: t
.Optional
[nodes
.Expr
] = None,
143 dyn_kwargs
: t
.Optional
[nodes
.Expr
] = None,
144 lineno
: t
.Optional
[int] = None,
146 """Call a method of the extension. This is a shortcut for
147 :meth:`attr` + :class:`jinja2.nodes.Call`.
154 self
.attr(name
, lineno
=lineno
),
165 __context
: Context
, *args
: t
.Any
, **kwargs
: t
.Any
166 ) -> t
.Union
[t
.Any
, Undefined
]:
167 return __context
.call(__context
.resolve("gettext"), *args
, **kwargs
)
170 def _make_new_gettext(func
: t
.Callable
[[str], str]) -> t
.Callable
[..., str]:
172 def gettext(__context
: Context
, __string
: str, **variables
: t
.Any
) -> str:
173 rv
= __context
.call(func
, __string
)
174 if __context
.eval_ctx
.autoescape
:
176 # Always treat as a format string, even if there are no
177 # variables. This makes translation strings more consistent
178 # and predictable. This requires escaping
179 return rv
% variables
# type: ignore
184 def _make_new_ngettext(func
: t
.Callable
[[str, str, int], str]) -> t
.Callable
[..., str]:
193 variables
.setdefault("num", __num
)
194 rv
= __context
.call(func
, __singular
, __plural
, __num
)
195 if __context
.eval_ctx
.autoescape
:
197 # Always treat as a format string, see gettext comment above.
198 return rv
% variables
# type: ignore
203 def _make_new_pgettext(func
: t
.Callable
[[str, str], str]) -> t
.Callable
[..., str]:
206 __context
: Context
, __string_ctx
: str, __string
: str, **variables
: t
.Any
208 variables
.setdefault("context", __string_ctx
)
209 rv
= __context
.call(func
, __string_ctx
, __string
)
211 if __context
.eval_ctx
.autoescape
:
214 # Always treat as a format string, see gettext comment above.
215 return rv
% variables
# type: ignore
220 def _make_new_npgettext(
221 func
: t
.Callable
[[str, str, str, int], str]
222 ) -> t
.Callable
[..., str]:
232 variables
.setdefault("context", __string_ctx
)
233 variables
.setdefault("num", __num
)
234 rv
= __context
.call(func
, __string_ctx
, __singular
, __plural
, __num
)
236 if __context
.eval_ctx
.autoescape
:
239 # Always treat as a format string, see gettext comment above.
240 return rv
% variables
# type: ignore
245 class InternationalizationExtension(Extension
):
246 """This extension adds gettext support to Jinja."""
250 # TODO: the i18n extension is currently reevaluating values in a few
251 # situations. Take this example:
252 # {% trans count=something() %}{{ count }} foo{% pluralize
253 # %}{{ count }} fooss{% endtrans %}
254 # something is called twice here. One time for the gettext value and
255 # the other time for the n-parameter of the ngettext function.
257 def __init__(self
, environment
: Environment
) -> None:
258 super().__init
__(environment
)
259 environment
.globals["_"] = _gettext_alias
261 install_gettext_translations
=self
._install
,
262 install_null_translations
=self
._install
_null
,
263 install_gettext_callables
=self
._install
_callables
,
264 uninstall_gettext_translations
=self
._uninstall
,
265 extract_translations
=self
._extract
,
266 newstyle_gettext
=False,
270 self
, translations
: "_SupportedTranslations", newstyle
: t
.Optional
[bool] = None
272 # ugettext and ungettext are preferred in case the I18N library
273 # is providing compatibility with older Python versions.
274 gettext
= getattr(translations
, "ugettext", None)
276 gettext
= translations
.gettext
277 ngettext
= getattr(translations
, "ungettext", None)
279 ngettext
= translations
.ngettext
281 pgettext
= getattr(translations
, "pgettext", None)
282 npgettext
= getattr(translations
, "npgettext", None)
283 self
._install
_callables
(
284 gettext
, ngettext
, newstyle
=newstyle
, pgettext
=pgettext
, npgettext
=npgettext
287 def _install_null(self
, newstyle
: t
.Optional
[bool] = None) -> None:
290 translations
= gettext
.NullTranslations()
292 if hasattr(translations
, "pgettext"):
294 pgettext
= translations
.pgettext
# type: ignore
297 def pgettext(c
: str, s
: str) -> str:
300 if hasattr(translations
, "npgettext"):
301 npgettext
= translations
.npgettext
# type: ignore
304 def npgettext(c
: str, s
: str, p
: str, n
: int) -> str:
305 return s
if n
== 1 else p
307 self
._install
_callables
(
308 gettext
=translations
.gettext
,
309 ngettext
=translations
.ngettext
,
315 def _install_callables(
317 gettext
: t
.Callable
[[str], str],
318 ngettext
: t
.Callable
[[str, str, int], str],
319 newstyle
: t
.Optional
[bool] = None,
320 pgettext
: t
.Optional
[t
.Callable
[[str, str], str]] = None,
321 npgettext
: t
.Optional
[t
.Callable
[[str, str, str, int], str]] = None,
323 if newstyle
is not None:
324 self
.environment
.newstyle_gettext
= newstyle
# type: ignore
325 if self
.environment
.newstyle_gettext
: # type: ignore
326 gettext
= _make_new_gettext(gettext
)
327 ngettext
= _make_new_ngettext(ngettext
)
329 if pgettext
is not None:
330 pgettext
= _make_new_pgettext(pgettext
)
332 if npgettext
is not None:
333 npgettext
= _make_new_npgettext(npgettext
)
335 self
.environment
.globals.update(
336 gettext
=gettext
, ngettext
=ngettext
, pgettext
=pgettext
, npgettext
=npgettext
339 def _uninstall(self
, translations
: "_SupportedTranslations") -> None:
340 for key
in ("gettext", "ngettext", "pgettext", "npgettext"):
341 self
.environment
.globals.pop(key
, None)
345 source
: t
.Union
[str, nodes
.Template
],
346 gettext_functions
: t
.Sequence
[str] = GETTEXT_FUNCTIONS
,
348 t
.Tuple
[int, str, t
.Union
[t
.Optional
[str], t
.Tuple
[t
.Optional
[str], ...]]]
350 if isinstance(source
, str):
351 source
= self
.environment
.parse(source
)
352 return extract_from_ast(source
, gettext_functions
)
354 def parse(self
, parser
: "Parser") -> t
.Union
[nodes
.Node
, t
.List
[nodes
.Node
]]:
355 """Parse a translatable tag."""
356 lineno
= next(parser
.stream
).lineno
359 context_token
= parser
.stream
.next_if("string")
361 if context_token
is not None:
362 context
= context_token
.value
364 # find all the variables referenced. Additionally a variable can be
365 # defined in the body of the trans block too, but this is checked at
367 plural_expr
: t
.Optional
[nodes
.Expr
] = None
368 plural_expr_assignment
: t
.Optional
[nodes
.Assign
] = None
369 num_called_num
= False
370 variables
: t
.Dict
[str, nodes
.Expr
] = {}
372 while parser
.stream
.current
.type != "block_end":
374 parser
.stream
.expect("comma")
376 # skip colon for python compatibility
377 if parser
.stream
.skip_if("colon"):
380 token
= parser
.stream
.expect("name")
381 if token
.value
in variables
:
383 f
"translatable variable {token.value!r} defined twice.",
385 exc
=TemplateAssertionError
,
389 if parser
.stream
.current
.type == "assign":
391 variables
[token
.value
] = var
= parser
.parse_expression()
392 elif trimmed
is None and token
.value
in ("trimmed", "notrimmed"):
393 trimmed
= token
.value
== "trimmed"
396 variables
[token
.value
] = var
= nodes
.Name(token
.value
, "load")
398 if plural_expr
is None:
399 if isinstance(var
, nodes
.Call
):
400 plural_expr
= nodes
.Name("_trans", "load")
401 variables
[token
.value
] = plural_expr
402 plural_expr_assignment
= nodes
.Assign(
403 nodes
.Name("_trans", "store"), var
407 num_called_num
= token
.value
== "num"
409 parser
.stream
.expect("block_end")
415 # now parse until endtrans or pluralize
416 singular_names
, singular
= self
._parse
_block
(parser
, True)
418 referenced
.update(singular_names
)
419 if plural_expr
is None:
420 plural_expr
= nodes
.Name(singular_names
[0], "load")
421 num_called_num
= singular_names
[0] == "num"
423 # if we have a pluralize block, we parse that too
424 if parser
.stream
.current
.test("name:pluralize"):
427 if parser
.stream
.current
.type != "block_end":
428 token
= parser
.stream
.expect("name")
429 if token
.value
not in variables
:
431 f
"unknown variable {token.value!r} for pluralization",
433 exc
=TemplateAssertionError
,
435 plural_expr
= variables
[token
.value
]
436 num_called_num
= token
.value
== "num"
437 parser
.stream
.expect("block_end")
438 plural_names
, plural
= self
._parse
_block
(parser
, False)
440 referenced
.update(plural_names
)
444 # register free names as simple name expressions
445 for name
in referenced
:
446 if name
not in variables
:
447 variables
[name
] = nodes
.Name(name
, "load")
451 elif plural_expr
is None:
452 parser
.fail("pluralize without variables", lineno
)
455 trimmed
= self
.environment
.policies
["ext.i18n.trimmed"]
457 singular
= self
._trim
_whitespace
(singular
)
459 plural
= self
._trim
_whitespace
(plural
)
461 node
= self
._make
_node
(
468 num_called_num
and have_plural
,
470 node
.set_lineno(lineno
)
471 if plural_expr_assignment
is not None:
472 return [plural_expr_assignment
, node
]
476 def _trim_whitespace(self
, string
: str, _ws_re
: t
.Pattern
[str] = _ws_re
) -> str:
477 return _ws_re
.sub(" ", string
.strip())
480 self
, parser
: "Parser", allow_pluralize
: bool
481 ) -> t
.Tuple
[t
.List
[str], str]:
482 """Parse until the next block tag with a given name."""
487 if parser
.stream
.current
.type == "data":
488 buf
.append(parser
.stream
.current
.value
.replace("%", "%%"))
490 elif parser
.stream
.current
.type == "variable_begin":
492 name
= parser
.stream
.expect("name").value
493 referenced
.append(name
)
494 buf
.append(f
"%({name})s")
495 parser
.stream
.expect("variable_end")
496 elif parser
.stream
.current
.type == "block_begin":
498 if parser
.stream
.current
.test("name:endtrans"):
500 elif parser
.stream
.current
.test("name:pluralize"):
504 "a translatable section can have only one pluralize section"
507 "control structures in translatable sections are not allowed"
509 elif parser
.stream
.eos
:
510 parser
.fail("unclosed translation block")
512 raise RuntimeError("internal parser error")
514 return referenced
, concat(buf
)
519 plural
: t
.Optional
[str],
520 context
: t
.Optional
[str],
521 variables
: t
.Dict
[str, nodes
.Expr
],
522 plural_expr
: t
.Optional
[nodes
.Expr
],
523 vars_referenced
: bool,
524 num_called_num
: bool,
526 """Generates a useful node from the data provided."""
527 newstyle
= self
.environment
.newstyle_gettext
# type: ignore
530 # no variables referenced? no need to escape for old style
531 # gettext invocations only if there are vars.
532 if not vars_referenced
and not newstyle
:
533 singular
= singular
.replace("%%", "%")
535 plural
= plural
.replace("%%", "%")
537 func_name
= "gettext"
538 func_args
: t
.List
[nodes
.Expr
] = [nodes
.Const(singular
)]
540 if context
is not None:
541 func_args
.insert(0, nodes
.Const(context
))
542 func_name
= f
"p{func_name}"
544 if plural_expr
is not None:
545 func_name
= f
"n{func_name}"
546 func_args
.extend((nodes
.Const(plural
), plural_expr
))
548 node
= nodes
.Call(nodes
.Name(func_name
, "load"), func_args
, [], None, None)
550 # in case newstyle gettext is used, the method is powerful
551 # enough to handle the variable expansion and autoescape
554 for key
, value
in variables
.items():
555 # the function adds that later anyways in case num was
556 # called num, so just skip it.
557 if num_called_num
and key
== "num":
559 node
.kwargs
.append(nodes
.Keyword(key
, value
))
561 # otherwise do that here
563 # mark the return value as safe if we are in an
564 # environment with autoescaping turned on
565 node
= nodes
.MarkSafeIfAutoescape(node
)
571 nodes
.Pair(nodes
.Const(key
), value
)
572 for key
, value
in variables
.items()
576 return nodes
.Output([node
])
579 class ExprStmtExtension(Extension
):
580 """Adds a `do` tag to Jinja that works like the print statement just
581 that it doesn't print the return value.
586 def parse(self
, parser
: "Parser") -> nodes
.ExprStmt
:
587 node
= nodes
.ExprStmt(lineno
=next(parser
.stream
).lineno
)
588 node
.node
= parser
.parse_tuple()
592 class LoopControlExtension(Extension
):
593 """Adds break and continue to the template engine."""
595 tags
= {"break", "continue"}
597 def parse(self
, parser
: "Parser") -> t
.Union
[nodes
.Break
, nodes
.Continue
]:
598 token
= next(parser
.stream
)
599 if token
.value
== "break":
600 return nodes
.Break(lineno
=token
.lineno
)
601 return nodes
.Continue(lineno
=token
.lineno
)
604 class DebugExtension(Extension
):
605 """A ``{% debug %}`` tag that dumps the available variables,
608 .. code-block:: html+jinja
610 <pre>{% debug %}</pre>
614 {'context': {'cycler': <class 'jinja2.utils.Cycler'>,
616 'namespace': <class 'jinja2.utils.Namespace'>},
617 'filters': ['abs', 'attr', 'batch', 'capitalize', 'center', 'count', 'd',
618 ..., 'urlencode', 'urlize', 'wordcount', 'wordwrap', 'xmlattr'],
619 'tests': ['!=', '<', '<=', '==', '>', '>=', 'callable', 'defined',
620 ..., 'odd', 'sameas', 'sequence', 'string', 'undefined', 'upper']}
622 .. versionadded:: 2.11.0
627 def parse(self
, parser
: "Parser") -> nodes
.Output
:
628 lineno
= parser
.stream
.expect("name:debug").lineno
629 context
= nodes
.ContextReference()
630 result
= self
.call_method("_render", [context
], lineno
=lineno
)
631 return nodes
.Output([result
], lineno
=lineno
)
633 def _render(self
, context
: Context
) -> str:
635 "context": context
.get_all(),
636 "filters": sorted(self
.environment
.filters
.keys()),
637 "tests": sorted(self
.environment
.tests
.keys()),
640 # Set the depth since the intent is to show the top few names.
641 return pprint
.pformat(result
, depth
=3, compact
=True)
644 def extract_from_ast(
646 gettext_functions
: t
.Sequence
[str] = GETTEXT_FUNCTIONS
,
647 babel_style
: bool = True,
649 t
.Tuple
[int, str, t
.Union
[t
.Optional
[str], t
.Tuple
[t
.Optional
[str], ...]]]
651 """Extract localizable strings from the given template node. Per
652 default this function returns matches in babel style that means non string
653 parameters as well as keyword arguments are returned as `None`. This
654 allows Babel to figure out what you really meant if you are using
655 gettext functions that allow keyword arguments for placeholder expansion.
656 If you don't want that behavior set the `babel_style` parameter to `False`
657 which causes only strings to be returned and parameters are always stored
658 in tuples. As a consequence invalid gettext calls (calls without a single
659 string parameter or string parameters after non-string parameters) are
662 This example explains the behavior:
664 >>> from jinja2 import Environment
665 >>> env = Environment()
666 >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
667 >>> list(extract_from_ast(node))
668 [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
669 >>> list(extract_from_ast(node, babel_style=False))
670 [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
672 For every string found this function yields a ``(lineno, function,
673 message)`` tuple, where:
675 * ``lineno`` is the number of the line on which the string was found,
676 * ``function`` is the name of the ``gettext`` function used (if the
677 string was extracted from embedded Python code), and
678 * ``message`` is the string, or a tuple of strings for functions
679 with multiple string arguments.
681 This extraction function operates on the AST and is because of that unable
682 to extract any comments. For comment support you have to use the babel
683 extraction interface or extract comments yourself.
685 out
: t
.Union
[t
.Optional
[str], t
.Tuple
[t
.Optional
[str], ...]]
687 for node
in ast
.find_all(nodes
.Call
):
689 not isinstance(node
.node
, nodes
.Name
)
690 or node
.node
.name
not in gettext_functions
694 strings
: t
.List
[t
.Optional
[str]] = []
696 for arg
in node
.args
:
697 if isinstance(arg
, nodes
.Const
) and isinstance(arg
.value
, str):
698 strings
.append(arg
.value
)
702 for _
in node
.kwargs
:
704 if node
.dyn_args
is not None:
706 if node
.dyn_kwargs
is not None:
710 out
= tuple(x
for x
in strings
if x
is not None)
715 if len(strings
) == 1:
720 yield node
.lineno
, node
.node
.name
, out
723 class _CommentFinder
:
724 """Helper class to find comments in a token stream. Can only
725 find comments for gettext calls forwards. Once the comment
726 from line 4 is found, a comment for line 1 will not return a
731 self
, tokens
: t
.Sequence
[t
.Tuple
[int, str, str]], comment_tags
: t
.Sequence
[str]
734 self
.comment_tags
= comment_tags
738 def find_backwards(self
, offset
: int) -> t
.List
[str]:
740 for _
, token_type
, token_value
in reversed(
741 self
.tokens
[self
.offset
: offset
]
743 if token_type
in ("comment", "linecomment"):
745 prefix
, comment
= token_value
.split(None, 1)
748 if prefix
in self
.comment_tags
:
749 return [comment
.rstrip()]
754 def find_comments(self
, lineno
: int) -> t
.List
[str]:
755 if not self
.comment_tags
or self
.last_lineno
> lineno
:
757 for idx
, (token_lineno
, _
, _
) in enumerate(self
.tokens
[self
.offset
:]):
758 if token_lineno
> lineno
:
759 return self
.find_backwards(self
.offset
+ idx
)
760 return self
.find_backwards(len(self
.tokens
))
765 keywords
: t
.Sequence
[str],
766 comment_tags
: t
.Sequence
[str],
767 options
: t
.Dict
[str, t
.Any
],
770 int, str, t
.Union
[t
.Optional
[str], t
.Tuple
[t
.Optional
[str], ...]], t
.List
[str]
773 """Babel extraction method for Jinja templates.
775 .. versionchanged:: 2.3
776 Basic support for translation comments was added. If `comment_tags`
777 is now set to a list of keywords for extraction, the extractor will
778 try to find the best preceding comment that begins with one of the
779 keywords. For best results, make sure to not have more than one
780 gettext call in one line of code and the matching comment in the
781 same line or the line before.
783 .. versionchanged:: 2.5.1
784 The `newstyle_gettext` flag can be set to `True` to enable newstyle
787 .. versionchanged:: 2.7
788 A `silent` option can now be provided. If set to `False` template
789 syntax errors are propagated instead of being ignored.
791 :param fileobj: the file-like object the messages should be extracted from
792 :param keywords: a list of keywords (i.e. function names) that should be
793 recognized as translation functions
794 :param comment_tags: a list of translator tags to search for and include
796 :param options: a dictionary of additional options (optional)
797 :return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
798 (comments will be empty currently)
800 extensions
: t
.Dict
[t
.Type
[Extension
], None] = {}
802 for extension_name
in options
.get("extensions", "").split(","):
803 extension_name
= extension_name
.strip()
805 if not extension_name
:
808 extensions
[import_string(extension_name
)] = None
810 if InternationalizationExtension
not in extensions
:
811 extensions
[InternationalizationExtension
] = None
813 def getbool(options
: t
.Mapping
[str, str], key
: str, default
: bool = False) -> bool:
814 return options
.get(key
, str(default
)).lower() in {"1", "on", "yes", "true"}
816 silent
= getbool(options
, "silent", True)
817 environment
= Environment(
818 options
.get("block_start_string", defaults
.BLOCK_START_STRING
),
819 options
.get("block_end_string", defaults
.BLOCK_END_STRING
),
820 options
.get("variable_start_string", defaults
.VARIABLE_START_STRING
),
821 options
.get("variable_end_string", defaults
.VARIABLE_END_STRING
),
822 options
.get("comment_start_string", defaults
.COMMENT_START_STRING
),
823 options
.get("comment_end_string", defaults
.COMMENT_END_STRING
),
824 options
.get("line_statement_prefix") or defaults
.LINE_STATEMENT_PREFIX
,
825 options
.get("line_comment_prefix") or defaults
.LINE_COMMENT_PREFIX
,
826 getbool(options
, "trim_blocks", defaults
.TRIM_BLOCKS
),
827 getbool(options
, "lstrip_blocks", defaults
.LSTRIP_BLOCKS
),
828 defaults
.NEWLINE_SEQUENCE
,
829 getbool(options
, "keep_trailing_newline", defaults
.KEEP_TRAILING_NEWLINE
),
835 if getbool(options
, "trimmed"):
836 environment
.policies
["ext.i18n.trimmed"] = True
837 if getbool(options
, "newstyle_gettext"):
838 environment
.newstyle_gettext
= True # type: ignore
840 source
= fileobj
.read().decode(options
.get("encoding", "utf-8"))
842 node
= environment
.parse(source
)
843 tokens
= list(environment
.lex(environment
.preprocess(source
)))
844 except TemplateSyntaxError
:
847 # skip templates with syntax errors
850 finder
= _CommentFinder(tokens
, comment_tags
)
851 for lineno
, func
, message
in extract_from_ast(node
, keywords
):
852 yield lineno
, func
, message
, finder
.find_comments(lineno
)
855 #: nicer import names
856 i18n
= InternationalizationExtension
857 do
= ExprStmtExtension
858 loopcontrols
= LoopControlExtension
859 debug
= DebugExtension