1e31aef6aSopenharmony_ci"""Extension API for adding custom tags and behavior."""
2e31aef6aSopenharmony_ciimport pprint
3e31aef6aSopenharmony_ciimport re
4e31aef6aSopenharmony_ciimport typing as t
5e31aef6aSopenharmony_ci
6e31aef6aSopenharmony_cifrom markupsafe import Markup
7e31aef6aSopenharmony_ci
8e31aef6aSopenharmony_cifrom . import defaults
9e31aef6aSopenharmony_cifrom . import nodes
10e31aef6aSopenharmony_cifrom .environment import Environment
11e31aef6aSopenharmony_cifrom .exceptions import TemplateAssertionError
12e31aef6aSopenharmony_cifrom .exceptions import TemplateSyntaxError
13e31aef6aSopenharmony_cifrom .runtime import concat  # type: ignore
14e31aef6aSopenharmony_cifrom .runtime import Context
15e31aef6aSopenharmony_cifrom .runtime import Undefined
16e31aef6aSopenharmony_cifrom .utils import import_string
17e31aef6aSopenharmony_cifrom .utils import pass_context
18e31aef6aSopenharmony_ci
19e31aef6aSopenharmony_ciif t.TYPE_CHECKING:
20e31aef6aSopenharmony_ci    import typing_extensions as te
21e31aef6aSopenharmony_ci    from .lexer import Token
22e31aef6aSopenharmony_ci    from .lexer import TokenStream
23e31aef6aSopenharmony_ci    from .parser import Parser
24e31aef6aSopenharmony_ci
25e31aef6aSopenharmony_ci    class _TranslationsBasic(te.Protocol):
26e31aef6aSopenharmony_ci        def gettext(self, message: str) -> str:
27e31aef6aSopenharmony_ci            ...
28e31aef6aSopenharmony_ci
29e31aef6aSopenharmony_ci        def ngettext(self, singular: str, plural: str, n: int) -> str:
30e31aef6aSopenharmony_ci            pass
31e31aef6aSopenharmony_ci
32e31aef6aSopenharmony_ci    class _TranslationsContext(_TranslationsBasic):
33e31aef6aSopenharmony_ci        def pgettext(self, context: str, message: str) -> str:
34e31aef6aSopenharmony_ci            ...
35e31aef6aSopenharmony_ci
36e31aef6aSopenharmony_ci        def npgettext(self, context: str, singular: str, plural: str, n: int) -> str:
37e31aef6aSopenharmony_ci            ...
38e31aef6aSopenharmony_ci
39e31aef6aSopenharmony_ci    _SupportedTranslations = t.Union[_TranslationsBasic, _TranslationsContext]
40e31aef6aSopenharmony_ci
41e31aef6aSopenharmony_ci
42e31aef6aSopenharmony_ci# I18N functions available in Jinja templates. If the I18N library
43e31aef6aSopenharmony_ci# provides ugettext, it will be assigned to gettext.
44e31aef6aSopenharmony_ciGETTEXT_FUNCTIONS: t.Tuple[str, ...] = (
45e31aef6aSopenharmony_ci    "_",
46e31aef6aSopenharmony_ci    "gettext",
47e31aef6aSopenharmony_ci    "ngettext",
48e31aef6aSopenharmony_ci    "pgettext",
49e31aef6aSopenharmony_ci    "npgettext",
50e31aef6aSopenharmony_ci)
51e31aef6aSopenharmony_ci_ws_re = re.compile(r"\s*\n\s*")
52e31aef6aSopenharmony_ci
53e31aef6aSopenharmony_ci
54e31aef6aSopenharmony_ciclass Extension:
55e31aef6aSopenharmony_ci    """Extensions can be used to add extra functionality to the Jinja template
56e31aef6aSopenharmony_ci    system at the parser level.  Custom extensions are bound to an environment
57e31aef6aSopenharmony_ci    but may not store environment specific data on `self`.  The reason for
58e31aef6aSopenharmony_ci    this is that an extension can be bound to another environment (for
59e31aef6aSopenharmony_ci    overlays) by creating a copy and reassigning the `environment` attribute.
60e31aef6aSopenharmony_ci
61e31aef6aSopenharmony_ci    As extensions are created by the environment they cannot accept any
62e31aef6aSopenharmony_ci    arguments for configuration.  One may want to work around that by using
63e31aef6aSopenharmony_ci    a factory function, but that is not possible as extensions are identified
64e31aef6aSopenharmony_ci    by their import name.  The correct way to configure the extension is
65e31aef6aSopenharmony_ci    storing the configuration values on the environment.  Because this way the
66e31aef6aSopenharmony_ci    environment ends up acting as central configuration storage the
67e31aef6aSopenharmony_ci    attributes may clash which is why extensions have to ensure that the names
68e31aef6aSopenharmony_ci    they choose for configuration are not too generic.  ``prefix`` for example
69e31aef6aSopenharmony_ci    is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
70e31aef6aSopenharmony_ci    name as includes the name of the extension (fragment cache).
71e31aef6aSopenharmony_ci    """
72e31aef6aSopenharmony_ci
73e31aef6aSopenharmony_ci    identifier: t.ClassVar[str]
74e31aef6aSopenharmony_ci
75e31aef6aSopenharmony_ci    def __init_subclass__(cls) -> None:
76e31aef6aSopenharmony_ci        cls.identifier = f"{cls.__module__}.{cls.__name__}"
77e31aef6aSopenharmony_ci
78e31aef6aSopenharmony_ci    #: if this extension parses this is the list of tags it's listening to.
79e31aef6aSopenharmony_ci    tags: t.Set[str] = set()
80e31aef6aSopenharmony_ci
81e31aef6aSopenharmony_ci    #: the priority of that extension.  This is especially useful for
82e31aef6aSopenharmony_ci    #: extensions that preprocess values.  A lower value means higher
83e31aef6aSopenharmony_ci    #: priority.
84e31aef6aSopenharmony_ci    #:
85e31aef6aSopenharmony_ci    #: .. versionadded:: 2.4
86e31aef6aSopenharmony_ci    priority = 100
87e31aef6aSopenharmony_ci
88e31aef6aSopenharmony_ci    def __init__(self, environment: Environment) -> None:
89e31aef6aSopenharmony_ci        self.environment = environment
90e31aef6aSopenharmony_ci
91e31aef6aSopenharmony_ci    def bind(self, environment: Environment) -> "Extension":
92e31aef6aSopenharmony_ci        """Create a copy of this extension bound to another environment."""
93e31aef6aSopenharmony_ci        rv = object.__new__(self.__class__)
94e31aef6aSopenharmony_ci        rv.__dict__.update(self.__dict__)
95e31aef6aSopenharmony_ci        rv.environment = environment
96e31aef6aSopenharmony_ci        return rv
97e31aef6aSopenharmony_ci
98e31aef6aSopenharmony_ci    def preprocess(
99e31aef6aSopenharmony_ci        self, source: str, name: t.Optional[str], filename: t.Optional[str] = None
100e31aef6aSopenharmony_ci    ) -> str:
101e31aef6aSopenharmony_ci        """This method is called before the actual lexing and can be used to
102e31aef6aSopenharmony_ci        preprocess the source.  The `filename` is optional.  The return value
103e31aef6aSopenharmony_ci        must be the preprocessed source.
104e31aef6aSopenharmony_ci        """
105e31aef6aSopenharmony_ci        return source
106e31aef6aSopenharmony_ci
107e31aef6aSopenharmony_ci    def filter_stream(
108e31aef6aSopenharmony_ci        self, stream: "TokenStream"
109e31aef6aSopenharmony_ci    ) -> t.Union["TokenStream", t.Iterable["Token"]]:
110e31aef6aSopenharmony_ci        """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
111e31aef6aSopenharmony_ci        to filter tokens returned.  This method has to return an iterable of
112e31aef6aSopenharmony_ci        :class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a
113e31aef6aSopenharmony_ci        :class:`~jinja2.lexer.TokenStream`.
114e31aef6aSopenharmony_ci        """
115e31aef6aSopenharmony_ci        return stream
116e31aef6aSopenharmony_ci
117e31aef6aSopenharmony_ci    def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
118e31aef6aSopenharmony_ci        """If any of the :attr:`tags` matched this method is called with the
119e31aef6aSopenharmony_ci        parser as first argument.  The token the parser stream is pointing at
120e31aef6aSopenharmony_ci        is the name token that matched.  This method has to return one or a
121e31aef6aSopenharmony_ci        list of multiple nodes.
122e31aef6aSopenharmony_ci        """
123e31aef6aSopenharmony_ci        raise NotImplementedError()
124e31aef6aSopenharmony_ci
125e31aef6aSopenharmony_ci    def attr(
126e31aef6aSopenharmony_ci        self, name: str, lineno: t.Optional[int] = None
127e31aef6aSopenharmony_ci    ) -> nodes.ExtensionAttribute:
128e31aef6aSopenharmony_ci        """Return an attribute node for the current extension.  This is useful
129e31aef6aSopenharmony_ci        to pass constants on extensions to generated template code.
130e31aef6aSopenharmony_ci
131e31aef6aSopenharmony_ci        ::
132e31aef6aSopenharmony_ci
133e31aef6aSopenharmony_ci            self.attr('_my_attribute', lineno=lineno)
134e31aef6aSopenharmony_ci        """
135e31aef6aSopenharmony_ci        return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
136e31aef6aSopenharmony_ci
137e31aef6aSopenharmony_ci    def call_method(
138e31aef6aSopenharmony_ci        self,
139e31aef6aSopenharmony_ci        name: str,
140e31aef6aSopenharmony_ci        args: t.Optional[t.List[nodes.Expr]] = None,
141e31aef6aSopenharmony_ci        kwargs: t.Optional[t.List[nodes.Keyword]] = None,
142e31aef6aSopenharmony_ci        dyn_args: t.Optional[nodes.Expr] = None,
143e31aef6aSopenharmony_ci        dyn_kwargs: t.Optional[nodes.Expr] = None,
144e31aef6aSopenharmony_ci        lineno: t.Optional[int] = None,
145e31aef6aSopenharmony_ci    ) -> nodes.Call:
146e31aef6aSopenharmony_ci        """Call a method of the extension.  This is a shortcut for
147e31aef6aSopenharmony_ci        :meth:`attr` + :class:`jinja2.nodes.Call`.
148e31aef6aSopenharmony_ci        """
149e31aef6aSopenharmony_ci        if args is None:
150e31aef6aSopenharmony_ci            args = []
151e31aef6aSopenharmony_ci        if kwargs is None:
152e31aef6aSopenharmony_ci            kwargs = []
153e31aef6aSopenharmony_ci        return nodes.Call(
154e31aef6aSopenharmony_ci            self.attr(name, lineno=lineno),
155e31aef6aSopenharmony_ci            args,
156e31aef6aSopenharmony_ci            kwargs,
157e31aef6aSopenharmony_ci            dyn_args,
158e31aef6aSopenharmony_ci            dyn_kwargs,
159e31aef6aSopenharmony_ci            lineno=lineno,
160e31aef6aSopenharmony_ci        )
161e31aef6aSopenharmony_ci
162e31aef6aSopenharmony_ci
163e31aef6aSopenharmony_ci@pass_context
164e31aef6aSopenharmony_cidef _gettext_alias(
165e31aef6aSopenharmony_ci    __context: Context, *args: t.Any, **kwargs: t.Any
166e31aef6aSopenharmony_ci) -> t.Union[t.Any, Undefined]:
167e31aef6aSopenharmony_ci    return __context.call(__context.resolve("gettext"), *args, **kwargs)
168e31aef6aSopenharmony_ci
169e31aef6aSopenharmony_ci
170e31aef6aSopenharmony_cidef _make_new_gettext(func: t.Callable[[str], str]) -> t.Callable[..., str]:
171e31aef6aSopenharmony_ci    @pass_context
172e31aef6aSopenharmony_ci    def gettext(__context: Context, __string: str, **variables: t.Any) -> str:
173e31aef6aSopenharmony_ci        rv = __context.call(func, __string)
174e31aef6aSopenharmony_ci        if __context.eval_ctx.autoescape:
175e31aef6aSopenharmony_ci            rv = Markup(rv)
176e31aef6aSopenharmony_ci        # Always treat as a format string, even if there are no
177e31aef6aSopenharmony_ci        # variables. This makes translation strings more consistent
178e31aef6aSopenharmony_ci        # and predictable. This requires escaping
179e31aef6aSopenharmony_ci        return rv % variables  # type: ignore
180e31aef6aSopenharmony_ci
181e31aef6aSopenharmony_ci    return gettext
182e31aef6aSopenharmony_ci
183e31aef6aSopenharmony_ci
184e31aef6aSopenharmony_cidef _make_new_ngettext(func: t.Callable[[str, str, int], str]) -> t.Callable[..., str]:
185e31aef6aSopenharmony_ci    @pass_context
186e31aef6aSopenharmony_ci    def ngettext(
187e31aef6aSopenharmony_ci        __context: Context,
188e31aef6aSopenharmony_ci        __singular: str,
189e31aef6aSopenharmony_ci        __plural: str,
190e31aef6aSopenharmony_ci        __num: int,
191e31aef6aSopenharmony_ci        **variables: t.Any,
192e31aef6aSopenharmony_ci    ) -> str:
193e31aef6aSopenharmony_ci        variables.setdefault("num", __num)
194e31aef6aSopenharmony_ci        rv = __context.call(func, __singular, __plural, __num)
195e31aef6aSopenharmony_ci        if __context.eval_ctx.autoescape:
196e31aef6aSopenharmony_ci            rv = Markup(rv)
197e31aef6aSopenharmony_ci        # Always treat as a format string, see gettext comment above.
198e31aef6aSopenharmony_ci        return rv % variables  # type: ignore
199e31aef6aSopenharmony_ci
200e31aef6aSopenharmony_ci    return ngettext
201e31aef6aSopenharmony_ci
202e31aef6aSopenharmony_ci
203e31aef6aSopenharmony_cidef _make_new_pgettext(func: t.Callable[[str, str], str]) -> t.Callable[..., str]:
204e31aef6aSopenharmony_ci    @pass_context
205e31aef6aSopenharmony_ci    def pgettext(
206e31aef6aSopenharmony_ci        __context: Context, __string_ctx: str, __string: str, **variables: t.Any
207e31aef6aSopenharmony_ci    ) -> str:
208e31aef6aSopenharmony_ci        variables.setdefault("context", __string_ctx)
209e31aef6aSopenharmony_ci        rv = __context.call(func, __string_ctx, __string)
210e31aef6aSopenharmony_ci
211e31aef6aSopenharmony_ci        if __context.eval_ctx.autoescape:
212e31aef6aSopenharmony_ci            rv = Markup(rv)
213e31aef6aSopenharmony_ci
214e31aef6aSopenharmony_ci        # Always treat as a format string, see gettext comment above.
215e31aef6aSopenharmony_ci        return rv % variables  # type: ignore
216e31aef6aSopenharmony_ci
217e31aef6aSopenharmony_ci    return pgettext
218e31aef6aSopenharmony_ci
219e31aef6aSopenharmony_ci
220e31aef6aSopenharmony_cidef _make_new_npgettext(
221e31aef6aSopenharmony_ci    func: t.Callable[[str, str, str, int], str]
222e31aef6aSopenharmony_ci) -> t.Callable[..., str]:
223e31aef6aSopenharmony_ci    @pass_context
224e31aef6aSopenharmony_ci    def npgettext(
225e31aef6aSopenharmony_ci        __context: Context,
226e31aef6aSopenharmony_ci        __string_ctx: str,
227e31aef6aSopenharmony_ci        __singular: str,
228e31aef6aSopenharmony_ci        __plural: str,
229e31aef6aSopenharmony_ci        __num: int,
230e31aef6aSopenharmony_ci        **variables: t.Any,
231e31aef6aSopenharmony_ci    ) -> str:
232e31aef6aSopenharmony_ci        variables.setdefault("context", __string_ctx)
233e31aef6aSopenharmony_ci        variables.setdefault("num", __num)
234e31aef6aSopenharmony_ci        rv = __context.call(func, __string_ctx, __singular, __plural, __num)
235e31aef6aSopenharmony_ci
236e31aef6aSopenharmony_ci        if __context.eval_ctx.autoescape:
237e31aef6aSopenharmony_ci            rv = Markup(rv)
238e31aef6aSopenharmony_ci
239e31aef6aSopenharmony_ci        # Always treat as a format string, see gettext comment above.
240e31aef6aSopenharmony_ci        return rv % variables  # type: ignore
241e31aef6aSopenharmony_ci
242e31aef6aSopenharmony_ci    return npgettext
243e31aef6aSopenharmony_ci
244e31aef6aSopenharmony_ci
245e31aef6aSopenharmony_ciclass InternationalizationExtension(Extension):
246e31aef6aSopenharmony_ci    """This extension adds gettext support to Jinja."""
247e31aef6aSopenharmony_ci
248e31aef6aSopenharmony_ci    tags = {"trans"}
249e31aef6aSopenharmony_ci
250e31aef6aSopenharmony_ci    # TODO: the i18n extension is currently reevaluating values in a few
251e31aef6aSopenharmony_ci    # situations.  Take this example:
252e31aef6aSopenharmony_ci    #   {% trans count=something() %}{{ count }} foo{% pluralize
253e31aef6aSopenharmony_ci    #     %}{{ count }} fooss{% endtrans %}
254e31aef6aSopenharmony_ci    # something is called twice here.  One time for the gettext value and
255e31aef6aSopenharmony_ci    # the other time for the n-parameter of the ngettext function.
256e31aef6aSopenharmony_ci
257e31aef6aSopenharmony_ci    def __init__(self, environment: Environment) -> None:
258e31aef6aSopenharmony_ci        super().__init__(environment)
259e31aef6aSopenharmony_ci        environment.globals["_"] = _gettext_alias
260e31aef6aSopenharmony_ci        environment.extend(
261e31aef6aSopenharmony_ci            install_gettext_translations=self._install,
262e31aef6aSopenharmony_ci            install_null_translations=self._install_null,
263e31aef6aSopenharmony_ci            install_gettext_callables=self._install_callables,
264e31aef6aSopenharmony_ci            uninstall_gettext_translations=self._uninstall,
265e31aef6aSopenharmony_ci            extract_translations=self._extract,
266e31aef6aSopenharmony_ci            newstyle_gettext=False,
267e31aef6aSopenharmony_ci        )
268e31aef6aSopenharmony_ci
269e31aef6aSopenharmony_ci    def _install(
270e31aef6aSopenharmony_ci        self, translations: "_SupportedTranslations", newstyle: t.Optional[bool] = None
271e31aef6aSopenharmony_ci    ) -> None:
272e31aef6aSopenharmony_ci        # ugettext and ungettext are preferred in case the I18N library
273e31aef6aSopenharmony_ci        # is providing compatibility with older Python versions.
274e31aef6aSopenharmony_ci        gettext = getattr(translations, "ugettext", None)
275e31aef6aSopenharmony_ci        if gettext is None:
276e31aef6aSopenharmony_ci            gettext = translations.gettext
277e31aef6aSopenharmony_ci        ngettext = getattr(translations, "ungettext", None)
278e31aef6aSopenharmony_ci        if ngettext is None:
279e31aef6aSopenharmony_ci            ngettext = translations.ngettext
280e31aef6aSopenharmony_ci
281e31aef6aSopenharmony_ci        pgettext = getattr(translations, "pgettext", None)
282e31aef6aSopenharmony_ci        npgettext = getattr(translations, "npgettext", None)
283e31aef6aSopenharmony_ci        self._install_callables(
284e31aef6aSopenharmony_ci            gettext, ngettext, newstyle=newstyle, pgettext=pgettext, npgettext=npgettext
285e31aef6aSopenharmony_ci        )
286e31aef6aSopenharmony_ci
287e31aef6aSopenharmony_ci    def _install_null(self, newstyle: t.Optional[bool] = None) -> None:
288e31aef6aSopenharmony_ci        import gettext
289e31aef6aSopenharmony_ci
290e31aef6aSopenharmony_ci        translations = gettext.NullTranslations()
291e31aef6aSopenharmony_ci
292e31aef6aSopenharmony_ci        if hasattr(translations, "pgettext"):
293e31aef6aSopenharmony_ci            # Python < 3.8
294e31aef6aSopenharmony_ci            pgettext = translations.pgettext
295e31aef6aSopenharmony_ci        else:
296e31aef6aSopenharmony_ci
297e31aef6aSopenharmony_ci            def pgettext(c: str, s: str) -> str:
298e31aef6aSopenharmony_ci                return s
299e31aef6aSopenharmony_ci
300e31aef6aSopenharmony_ci        if hasattr(translations, "npgettext"):
301e31aef6aSopenharmony_ci            npgettext = translations.npgettext
302e31aef6aSopenharmony_ci        else:
303e31aef6aSopenharmony_ci
304e31aef6aSopenharmony_ci            def npgettext(c: str, s: str, p: str, n: int) -> str:
305e31aef6aSopenharmony_ci                return s if n == 1 else p
306e31aef6aSopenharmony_ci
307e31aef6aSopenharmony_ci        self._install_callables(
308e31aef6aSopenharmony_ci            gettext=translations.gettext,
309e31aef6aSopenharmony_ci            ngettext=translations.ngettext,
310e31aef6aSopenharmony_ci            newstyle=newstyle,
311e31aef6aSopenharmony_ci            pgettext=pgettext,
312e31aef6aSopenharmony_ci            npgettext=npgettext,
313e31aef6aSopenharmony_ci        )
314e31aef6aSopenharmony_ci
315e31aef6aSopenharmony_ci    def _install_callables(
316e31aef6aSopenharmony_ci        self,
317e31aef6aSopenharmony_ci        gettext: t.Callable[[str], str],
318e31aef6aSopenharmony_ci        ngettext: t.Callable[[str, str, int], str],
319e31aef6aSopenharmony_ci        newstyle: t.Optional[bool] = None,
320e31aef6aSopenharmony_ci        pgettext: t.Optional[t.Callable[[str, str], str]] = None,
321e31aef6aSopenharmony_ci        npgettext: t.Optional[t.Callable[[str, str, str, int], str]] = None,
322e31aef6aSopenharmony_ci    ) -> None:
323e31aef6aSopenharmony_ci        if newstyle is not None:
324e31aef6aSopenharmony_ci            self.environment.newstyle_gettext = newstyle  # type: ignore
325e31aef6aSopenharmony_ci        if self.environment.newstyle_gettext:  # type: ignore
326e31aef6aSopenharmony_ci            gettext = _make_new_gettext(gettext)
327e31aef6aSopenharmony_ci            ngettext = _make_new_ngettext(ngettext)
328e31aef6aSopenharmony_ci
329e31aef6aSopenharmony_ci            if pgettext is not None:
330e31aef6aSopenharmony_ci                pgettext = _make_new_pgettext(pgettext)
331e31aef6aSopenharmony_ci
332e31aef6aSopenharmony_ci            if npgettext is not None:
333e31aef6aSopenharmony_ci                npgettext = _make_new_npgettext(npgettext)
334e31aef6aSopenharmony_ci
335e31aef6aSopenharmony_ci        self.environment.globals.update(
336e31aef6aSopenharmony_ci            gettext=gettext, ngettext=ngettext, pgettext=pgettext, npgettext=npgettext
337e31aef6aSopenharmony_ci        )
338e31aef6aSopenharmony_ci
339e31aef6aSopenharmony_ci    def _uninstall(self, translations: "_SupportedTranslations") -> None:
340e31aef6aSopenharmony_ci        for key in ("gettext", "ngettext", "pgettext", "npgettext"):
341e31aef6aSopenharmony_ci            self.environment.globals.pop(key, None)
342e31aef6aSopenharmony_ci
343e31aef6aSopenharmony_ci    def _extract(
344e31aef6aSopenharmony_ci        self,
345e31aef6aSopenharmony_ci        source: t.Union[str, nodes.Template],
346e31aef6aSopenharmony_ci        gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
347e31aef6aSopenharmony_ci    ) -> t.Iterator[
348e31aef6aSopenharmony_ci        t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
349e31aef6aSopenharmony_ci    ]:
350e31aef6aSopenharmony_ci        if isinstance(source, str):
351e31aef6aSopenharmony_ci            source = self.environment.parse(source)
352e31aef6aSopenharmony_ci        return extract_from_ast(source, gettext_functions)
353e31aef6aSopenharmony_ci
354e31aef6aSopenharmony_ci    def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
355e31aef6aSopenharmony_ci        """Parse a translatable tag."""
356e31aef6aSopenharmony_ci        lineno = next(parser.stream).lineno
357e31aef6aSopenharmony_ci
358e31aef6aSopenharmony_ci        context = None
359e31aef6aSopenharmony_ci        context_token = parser.stream.next_if("string")
360e31aef6aSopenharmony_ci
361e31aef6aSopenharmony_ci        if context_token is not None:
362e31aef6aSopenharmony_ci            context = context_token.value
363e31aef6aSopenharmony_ci
364e31aef6aSopenharmony_ci        # find all the variables referenced.  Additionally a variable can be
365e31aef6aSopenharmony_ci        # defined in the body of the trans block too, but this is checked at
366e31aef6aSopenharmony_ci        # a later state.
367e31aef6aSopenharmony_ci        plural_expr: t.Optional[nodes.Expr] = None
368e31aef6aSopenharmony_ci        plural_expr_assignment: t.Optional[nodes.Assign] = None
369e31aef6aSopenharmony_ci        num_called_num = False
370e31aef6aSopenharmony_ci        variables: t.Dict[str, nodes.Expr] = {}
371e31aef6aSopenharmony_ci        trimmed = None
372e31aef6aSopenharmony_ci        while parser.stream.current.type != "block_end":
373e31aef6aSopenharmony_ci            if variables:
374e31aef6aSopenharmony_ci                parser.stream.expect("comma")
375e31aef6aSopenharmony_ci
376e31aef6aSopenharmony_ci            # skip colon for python compatibility
377e31aef6aSopenharmony_ci            if parser.stream.skip_if("colon"):
378e31aef6aSopenharmony_ci                break
379e31aef6aSopenharmony_ci
380e31aef6aSopenharmony_ci            token = parser.stream.expect("name")
381e31aef6aSopenharmony_ci            if token.value in variables:
382e31aef6aSopenharmony_ci                parser.fail(
383e31aef6aSopenharmony_ci                    f"translatable variable {token.value!r} defined twice.",
384e31aef6aSopenharmony_ci                    token.lineno,
385e31aef6aSopenharmony_ci                    exc=TemplateAssertionError,
386e31aef6aSopenharmony_ci                )
387e31aef6aSopenharmony_ci
388e31aef6aSopenharmony_ci            # expressions
389e31aef6aSopenharmony_ci            if parser.stream.current.type == "assign":
390e31aef6aSopenharmony_ci                next(parser.stream)
391e31aef6aSopenharmony_ci                variables[token.value] = var = parser.parse_expression()
392e31aef6aSopenharmony_ci            elif trimmed is None and token.value in ("trimmed", "notrimmed"):
393e31aef6aSopenharmony_ci                trimmed = token.value == "trimmed"
394e31aef6aSopenharmony_ci                continue
395e31aef6aSopenharmony_ci            else:
396e31aef6aSopenharmony_ci                variables[token.value] = var = nodes.Name(token.value, "load")
397e31aef6aSopenharmony_ci
398e31aef6aSopenharmony_ci            if plural_expr is None:
399e31aef6aSopenharmony_ci                if isinstance(var, nodes.Call):
400e31aef6aSopenharmony_ci                    plural_expr = nodes.Name("_trans", "load")
401e31aef6aSopenharmony_ci                    variables[token.value] = plural_expr
402e31aef6aSopenharmony_ci                    plural_expr_assignment = nodes.Assign(
403e31aef6aSopenharmony_ci                        nodes.Name("_trans", "store"), var
404e31aef6aSopenharmony_ci                    )
405e31aef6aSopenharmony_ci                else:
406e31aef6aSopenharmony_ci                    plural_expr = var
407e31aef6aSopenharmony_ci                num_called_num = token.value == "num"
408e31aef6aSopenharmony_ci
409e31aef6aSopenharmony_ci        parser.stream.expect("block_end")
410e31aef6aSopenharmony_ci
411e31aef6aSopenharmony_ci        plural = None
412e31aef6aSopenharmony_ci        have_plural = False
413e31aef6aSopenharmony_ci        referenced = set()
414e31aef6aSopenharmony_ci
415e31aef6aSopenharmony_ci        # now parse until endtrans or pluralize
416e31aef6aSopenharmony_ci        singular_names, singular = self._parse_block(parser, True)
417e31aef6aSopenharmony_ci        if singular_names:
418e31aef6aSopenharmony_ci            referenced.update(singular_names)
419e31aef6aSopenharmony_ci            if plural_expr is None:
420e31aef6aSopenharmony_ci                plural_expr = nodes.Name(singular_names[0], "load")
421e31aef6aSopenharmony_ci                num_called_num = singular_names[0] == "num"
422e31aef6aSopenharmony_ci
423e31aef6aSopenharmony_ci        # if we have a pluralize block, we parse that too
424e31aef6aSopenharmony_ci        if parser.stream.current.test("name:pluralize"):
425e31aef6aSopenharmony_ci            have_plural = True
426e31aef6aSopenharmony_ci            next(parser.stream)
427e31aef6aSopenharmony_ci            if parser.stream.current.type != "block_end":
428e31aef6aSopenharmony_ci                token = parser.stream.expect("name")
429e31aef6aSopenharmony_ci                if token.value not in variables:
430e31aef6aSopenharmony_ci                    parser.fail(
431e31aef6aSopenharmony_ci                        f"unknown variable {token.value!r} for pluralization",
432e31aef6aSopenharmony_ci                        token.lineno,
433e31aef6aSopenharmony_ci                        exc=TemplateAssertionError,
434e31aef6aSopenharmony_ci                    )
435e31aef6aSopenharmony_ci                plural_expr = variables[token.value]
436e31aef6aSopenharmony_ci                num_called_num = token.value == "num"
437e31aef6aSopenharmony_ci            parser.stream.expect("block_end")
438e31aef6aSopenharmony_ci            plural_names, plural = self._parse_block(parser, False)
439e31aef6aSopenharmony_ci            next(parser.stream)
440e31aef6aSopenharmony_ci            referenced.update(plural_names)
441e31aef6aSopenharmony_ci        else:
442e31aef6aSopenharmony_ci            next(parser.stream)
443e31aef6aSopenharmony_ci
444e31aef6aSopenharmony_ci        # register free names as simple name expressions
445e31aef6aSopenharmony_ci        for name in referenced:
446e31aef6aSopenharmony_ci            if name not in variables:
447e31aef6aSopenharmony_ci                variables[name] = nodes.Name(name, "load")
448e31aef6aSopenharmony_ci
449e31aef6aSopenharmony_ci        if not have_plural:
450e31aef6aSopenharmony_ci            plural_expr = None
451e31aef6aSopenharmony_ci        elif plural_expr is None:
452e31aef6aSopenharmony_ci            parser.fail("pluralize without variables", lineno)
453e31aef6aSopenharmony_ci
454e31aef6aSopenharmony_ci        if trimmed is None:
455e31aef6aSopenharmony_ci            trimmed = self.environment.policies["ext.i18n.trimmed"]
456e31aef6aSopenharmony_ci        if trimmed:
457e31aef6aSopenharmony_ci            singular = self._trim_whitespace(singular)
458e31aef6aSopenharmony_ci            if plural:
459e31aef6aSopenharmony_ci                plural = self._trim_whitespace(plural)
460e31aef6aSopenharmony_ci
461e31aef6aSopenharmony_ci        node = self._make_node(
462e31aef6aSopenharmony_ci            singular,
463e31aef6aSopenharmony_ci            plural,
464e31aef6aSopenharmony_ci            context,
465e31aef6aSopenharmony_ci            variables,
466e31aef6aSopenharmony_ci            plural_expr,
467e31aef6aSopenharmony_ci            bool(referenced),
468e31aef6aSopenharmony_ci            num_called_num and have_plural,
469e31aef6aSopenharmony_ci        )
470e31aef6aSopenharmony_ci        node.set_lineno(lineno)
471e31aef6aSopenharmony_ci        if plural_expr_assignment is not None:
472e31aef6aSopenharmony_ci            return [plural_expr_assignment, node]
473e31aef6aSopenharmony_ci        else:
474e31aef6aSopenharmony_ci            return node
475e31aef6aSopenharmony_ci
476e31aef6aSopenharmony_ci    def _trim_whitespace(self, string: str, _ws_re: t.Pattern[str] = _ws_re) -> str:
477e31aef6aSopenharmony_ci        return _ws_re.sub(" ", string.strip())
478e31aef6aSopenharmony_ci
479e31aef6aSopenharmony_ci    def _parse_block(
480e31aef6aSopenharmony_ci        self, parser: "Parser", allow_pluralize: bool
481e31aef6aSopenharmony_ci    ) -> t.Tuple[t.List[str], str]:
482e31aef6aSopenharmony_ci        """Parse until the next block tag with a given name."""
483e31aef6aSopenharmony_ci        referenced = []
484e31aef6aSopenharmony_ci        buf = []
485e31aef6aSopenharmony_ci
486e31aef6aSopenharmony_ci        while True:
487e31aef6aSopenharmony_ci            if parser.stream.current.type == "data":
488e31aef6aSopenharmony_ci                buf.append(parser.stream.current.value.replace("%", "%%"))
489e31aef6aSopenharmony_ci                next(parser.stream)
490e31aef6aSopenharmony_ci            elif parser.stream.current.type == "variable_begin":
491e31aef6aSopenharmony_ci                next(parser.stream)
492e31aef6aSopenharmony_ci                name = parser.stream.expect("name").value
493e31aef6aSopenharmony_ci                referenced.append(name)
494e31aef6aSopenharmony_ci                buf.append(f"%({name})s")
495e31aef6aSopenharmony_ci                parser.stream.expect("variable_end")
496e31aef6aSopenharmony_ci            elif parser.stream.current.type == "block_begin":
497e31aef6aSopenharmony_ci                next(parser.stream)
498e31aef6aSopenharmony_ci                block_name = (
499e31aef6aSopenharmony_ci                    parser.stream.current.value
500e31aef6aSopenharmony_ci                    if parser.stream.current.type == "name"
501e31aef6aSopenharmony_ci                    else None
502e31aef6aSopenharmony_ci                )
503e31aef6aSopenharmony_ci                if block_name == "endtrans":
504e31aef6aSopenharmony_ci                    break
505e31aef6aSopenharmony_ci                elif block_name == "pluralize":
506e31aef6aSopenharmony_ci                    if allow_pluralize:
507e31aef6aSopenharmony_ci                        break
508e31aef6aSopenharmony_ci                    parser.fail(
509e31aef6aSopenharmony_ci                        "a translatable section can have only one pluralize section"
510e31aef6aSopenharmony_ci                    )
511e31aef6aSopenharmony_ci                elif block_name == "trans":
512e31aef6aSopenharmony_ci                    parser.fail(
513e31aef6aSopenharmony_ci                        "trans blocks can't be nested; did you mean `endtrans`?"
514e31aef6aSopenharmony_ci                    )
515e31aef6aSopenharmony_ci                parser.fail(
516e31aef6aSopenharmony_ci                    f"control structures in translatable sections are not allowed; "
517e31aef6aSopenharmony_ci                    f"saw `{block_name}`"
518e31aef6aSopenharmony_ci                )
519e31aef6aSopenharmony_ci            elif parser.stream.eos:
520e31aef6aSopenharmony_ci                parser.fail("unclosed translation block")
521e31aef6aSopenharmony_ci            else:
522e31aef6aSopenharmony_ci                raise RuntimeError("internal parser error")
523e31aef6aSopenharmony_ci
524e31aef6aSopenharmony_ci        return referenced, concat(buf)
525e31aef6aSopenharmony_ci
526e31aef6aSopenharmony_ci    def _make_node(
527e31aef6aSopenharmony_ci        self,
528e31aef6aSopenharmony_ci        singular: str,
529e31aef6aSopenharmony_ci        plural: t.Optional[str],
530e31aef6aSopenharmony_ci        context: t.Optional[str],
531e31aef6aSopenharmony_ci        variables: t.Dict[str, nodes.Expr],
532e31aef6aSopenharmony_ci        plural_expr: t.Optional[nodes.Expr],
533e31aef6aSopenharmony_ci        vars_referenced: bool,
534e31aef6aSopenharmony_ci        num_called_num: bool,
535e31aef6aSopenharmony_ci    ) -> nodes.Output:
536e31aef6aSopenharmony_ci        """Generates a useful node from the data provided."""
537e31aef6aSopenharmony_ci        newstyle = self.environment.newstyle_gettext  # type: ignore
538e31aef6aSopenharmony_ci        node: nodes.Expr
539e31aef6aSopenharmony_ci
540e31aef6aSopenharmony_ci        # no variables referenced?  no need to escape for old style
541e31aef6aSopenharmony_ci        # gettext invocations only if there are vars.
542e31aef6aSopenharmony_ci        if not vars_referenced and not newstyle:
543e31aef6aSopenharmony_ci            singular = singular.replace("%%", "%")
544e31aef6aSopenharmony_ci            if plural:
545e31aef6aSopenharmony_ci                plural = plural.replace("%%", "%")
546e31aef6aSopenharmony_ci
547e31aef6aSopenharmony_ci        func_name = "gettext"
548e31aef6aSopenharmony_ci        func_args: t.List[nodes.Expr] = [nodes.Const(singular)]
549e31aef6aSopenharmony_ci
550e31aef6aSopenharmony_ci        if context is not None:
551e31aef6aSopenharmony_ci            func_args.insert(0, nodes.Const(context))
552e31aef6aSopenharmony_ci            func_name = f"p{func_name}"
553e31aef6aSopenharmony_ci
554e31aef6aSopenharmony_ci        if plural_expr is not None:
555e31aef6aSopenharmony_ci            func_name = f"n{func_name}"
556e31aef6aSopenharmony_ci            func_args.extend((nodes.Const(plural), plural_expr))
557e31aef6aSopenharmony_ci
558e31aef6aSopenharmony_ci        node = nodes.Call(nodes.Name(func_name, "load"), func_args, [], None, None)
559e31aef6aSopenharmony_ci
560e31aef6aSopenharmony_ci        # in case newstyle gettext is used, the method is powerful
561e31aef6aSopenharmony_ci        # enough to handle the variable expansion and autoescape
562e31aef6aSopenharmony_ci        # handling itself
563e31aef6aSopenharmony_ci        if newstyle:
564e31aef6aSopenharmony_ci            for key, value in variables.items():
565e31aef6aSopenharmony_ci                # the function adds that later anyways in case num was
566e31aef6aSopenharmony_ci                # called num, so just skip it.
567e31aef6aSopenharmony_ci                if num_called_num and key == "num":
568e31aef6aSopenharmony_ci                    continue
569e31aef6aSopenharmony_ci                node.kwargs.append(nodes.Keyword(key, value))
570e31aef6aSopenharmony_ci
571e31aef6aSopenharmony_ci        # otherwise do that here
572e31aef6aSopenharmony_ci        else:
573e31aef6aSopenharmony_ci            # mark the return value as safe if we are in an
574e31aef6aSopenharmony_ci            # environment with autoescaping turned on
575e31aef6aSopenharmony_ci            node = nodes.MarkSafeIfAutoescape(node)
576e31aef6aSopenharmony_ci            if variables:
577e31aef6aSopenharmony_ci                node = nodes.Mod(
578e31aef6aSopenharmony_ci                    node,
579e31aef6aSopenharmony_ci                    nodes.Dict(
580e31aef6aSopenharmony_ci                        [
581e31aef6aSopenharmony_ci                            nodes.Pair(nodes.Const(key), value)
582e31aef6aSopenharmony_ci                            for key, value in variables.items()
583e31aef6aSopenharmony_ci                        ]
584e31aef6aSopenharmony_ci                    ),
585e31aef6aSopenharmony_ci                )
586e31aef6aSopenharmony_ci        return nodes.Output([node])
587e31aef6aSopenharmony_ci
588e31aef6aSopenharmony_ci
589e31aef6aSopenharmony_ciclass ExprStmtExtension(Extension):
590e31aef6aSopenharmony_ci    """Adds a `do` tag to Jinja that works like the print statement just
591e31aef6aSopenharmony_ci    that it doesn't print the return value.
592e31aef6aSopenharmony_ci    """
593e31aef6aSopenharmony_ci
594e31aef6aSopenharmony_ci    tags = {"do"}
595e31aef6aSopenharmony_ci
596e31aef6aSopenharmony_ci    def parse(self, parser: "Parser") -> nodes.ExprStmt:
597e31aef6aSopenharmony_ci        node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
598e31aef6aSopenharmony_ci        node.node = parser.parse_tuple()
599e31aef6aSopenharmony_ci        return node
600e31aef6aSopenharmony_ci
601e31aef6aSopenharmony_ci
602e31aef6aSopenharmony_ciclass LoopControlExtension(Extension):
603e31aef6aSopenharmony_ci    """Adds break and continue to the template engine."""
604e31aef6aSopenharmony_ci
605e31aef6aSopenharmony_ci    tags = {"break", "continue"}
606e31aef6aSopenharmony_ci
607e31aef6aSopenharmony_ci    def parse(self, parser: "Parser") -> t.Union[nodes.Break, nodes.Continue]:
608e31aef6aSopenharmony_ci        token = next(parser.stream)
609e31aef6aSopenharmony_ci        if token.value == "break":
610e31aef6aSopenharmony_ci            return nodes.Break(lineno=token.lineno)
611e31aef6aSopenharmony_ci        return nodes.Continue(lineno=token.lineno)
612e31aef6aSopenharmony_ci
613e31aef6aSopenharmony_ci
614e31aef6aSopenharmony_ciclass DebugExtension(Extension):
615e31aef6aSopenharmony_ci    """A ``{% debug %}`` tag that dumps the available variables,
616e31aef6aSopenharmony_ci    filters, and tests.
617e31aef6aSopenharmony_ci
618e31aef6aSopenharmony_ci    .. code-block:: html+jinja
619e31aef6aSopenharmony_ci
620e31aef6aSopenharmony_ci        <pre>{% debug %}</pre>
621e31aef6aSopenharmony_ci
622e31aef6aSopenharmony_ci    .. code-block:: text
623e31aef6aSopenharmony_ci
624e31aef6aSopenharmony_ci        {'context': {'cycler': <class 'jinja2.utils.Cycler'>,
625e31aef6aSopenharmony_ci                     ...,
626e31aef6aSopenharmony_ci                     'namespace': <class 'jinja2.utils.Namespace'>},
627e31aef6aSopenharmony_ci         'filters': ['abs', 'attr', 'batch', 'capitalize', 'center', 'count', 'd',
628e31aef6aSopenharmony_ci                     ..., 'urlencode', 'urlize', 'wordcount', 'wordwrap', 'xmlattr'],
629e31aef6aSopenharmony_ci         'tests': ['!=', '<', '<=', '==', '>', '>=', 'callable', 'defined',
630e31aef6aSopenharmony_ci                   ..., 'odd', 'sameas', 'sequence', 'string', 'undefined', 'upper']}
631e31aef6aSopenharmony_ci
632e31aef6aSopenharmony_ci    .. versionadded:: 2.11.0
633e31aef6aSopenharmony_ci    """
634e31aef6aSopenharmony_ci
635e31aef6aSopenharmony_ci    tags = {"debug"}
636e31aef6aSopenharmony_ci
637e31aef6aSopenharmony_ci    def parse(self, parser: "Parser") -> nodes.Output:
638e31aef6aSopenharmony_ci        lineno = parser.stream.expect("name:debug").lineno
639e31aef6aSopenharmony_ci        context = nodes.ContextReference()
640e31aef6aSopenharmony_ci        result = self.call_method("_render", [context], lineno=lineno)
641e31aef6aSopenharmony_ci        return nodes.Output([result], lineno=lineno)
642e31aef6aSopenharmony_ci
643e31aef6aSopenharmony_ci    def _render(self, context: Context) -> str:
644e31aef6aSopenharmony_ci        result = {
645e31aef6aSopenharmony_ci            "context": context.get_all(),
646e31aef6aSopenharmony_ci            "filters": sorted(self.environment.filters.keys()),
647e31aef6aSopenharmony_ci            "tests": sorted(self.environment.tests.keys()),
648e31aef6aSopenharmony_ci        }
649e31aef6aSopenharmony_ci
650e31aef6aSopenharmony_ci        # Set the depth since the intent is to show the top few names.
651e31aef6aSopenharmony_ci        return pprint.pformat(result, depth=3, compact=True)
652e31aef6aSopenharmony_ci
653e31aef6aSopenharmony_ci
654e31aef6aSopenharmony_cidef extract_from_ast(
655e31aef6aSopenharmony_ci    ast: nodes.Template,
656e31aef6aSopenharmony_ci    gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
657e31aef6aSopenharmony_ci    babel_style: bool = True,
658e31aef6aSopenharmony_ci) -> t.Iterator[
659e31aef6aSopenharmony_ci    t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
660e31aef6aSopenharmony_ci]:
661e31aef6aSopenharmony_ci    """Extract localizable strings from the given template node.  Per
662e31aef6aSopenharmony_ci    default this function returns matches in babel style that means non string
663e31aef6aSopenharmony_ci    parameters as well as keyword arguments are returned as `None`.  This
664e31aef6aSopenharmony_ci    allows Babel to figure out what you really meant if you are using
665e31aef6aSopenharmony_ci    gettext functions that allow keyword arguments for placeholder expansion.
666e31aef6aSopenharmony_ci    If you don't want that behavior set the `babel_style` parameter to `False`
667e31aef6aSopenharmony_ci    which causes only strings to be returned and parameters are always stored
668e31aef6aSopenharmony_ci    in tuples.  As a consequence invalid gettext calls (calls without a single
669e31aef6aSopenharmony_ci    string parameter or string parameters after non-string parameters) are
670e31aef6aSopenharmony_ci    skipped.
671e31aef6aSopenharmony_ci
672e31aef6aSopenharmony_ci    This example explains the behavior:
673e31aef6aSopenharmony_ci
674e31aef6aSopenharmony_ci    >>> from jinja2 import Environment
675e31aef6aSopenharmony_ci    >>> env = Environment()
676e31aef6aSopenharmony_ci    >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
677e31aef6aSopenharmony_ci    >>> list(extract_from_ast(node))
678e31aef6aSopenharmony_ci    [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
679e31aef6aSopenharmony_ci    >>> list(extract_from_ast(node, babel_style=False))
680e31aef6aSopenharmony_ci    [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
681e31aef6aSopenharmony_ci
682e31aef6aSopenharmony_ci    For every string found this function yields a ``(lineno, function,
683e31aef6aSopenharmony_ci    message)`` tuple, where:
684e31aef6aSopenharmony_ci
685e31aef6aSopenharmony_ci    * ``lineno`` is the number of the line on which the string was found,
686e31aef6aSopenharmony_ci    * ``function`` is the name of the ``gettext`` function used (if the
687e31aef6aSopenharmony_ci      string was extracted from embedded Python code), and
688e31aef6aSopenharmony_ci    *   ``message`` is the string, or a tuple of strings for functions
689e31aef6aSopenharmony_ci         with multiple string arguments.
690e31aef6aSopenharmony_ci
691e31aef6aSopenharmony_ci    This extraction function operates on the AST and is because of that unable
692e31aef6aSopenharmony_ci    to extract any comments.  For comment support you have to use the babel
693e31aef6aSopenharmony_ci    extraction interface or extract comments yourself.
694e31aef6aSopenharmony_ci    """
695e31aef6aSopenharmony_ci    out: t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]
696e31aef6aSopenharmony_ci
697e31aef6aSopenharmony_ci    for node in ast.find_all(nodes.Call):
698e31aef6aSopenharmony_ci        if (
699e31aef6aSopenharmony_ci            not isinstance(node.node, nodes.Name)
700e31aef6aSopenharmony_ci            or node.node.name not in gettext_functions
701e31aef6aSopenharmony_ci        ):
702e31aef6aSopenharmony_ci            continue
703e31aef6aSopenharmony_ci
704e31aef6aSopenharmony_ci        strings: t.List[t.Optional[str]] = []
705e31aef6aSopenharmony_ci
706e31aef6aSopenharmony_ci        for arg in node.args:
707e31aef6aSopenharmony_ci            if isinstance(arg, nodes.Const) and isinstance(arg.value, str):
708e31aef6aSopenharmony_ci                strings.append(arg.value)
709e31aef6aSopenharmony_ci            else:
710e31aef6aSopenharmony_ci                strings.append(None)
711e31aef6aSopenharmony_ci
712e31aef6aSopenharmony_ci        for _ in node.kwargs:
713e31aef6aSopenharmony_ci            strings.append(None)
714e31aef6aSopenharmony_ci        if node.dyn_args is not None:
715e31aef6aSopenharmony_ci            strings.append(None)
716e31aef6aSopenharmony_ci        if node.dyn_kwargs is not None:
717e31aef6aSopenharmony_ci            strings.append(None)
718e31aef6aSopenharmony_ci
719e31aef6aSopenharmony_ci        if not babel_style:
720e31aef6aSopenharmony_ci            out = tuple(x for x in strings if x is not None)
721e31aef6aSopenharmony_ci
722e31aef6aSopenharmony_ci            if not out:
723e31aef6aSopenharmony_ci                continue
724e31aef6aSopenharmony_ci        else:
725e31aef6aSopenharmony_ci            if len(strings) == 1:
726e31aef6aSopenharmony_ci                out = strings[0]
727e31aef6aSopenharmony_ci            else:
728e31aef6aSopenharmony_ci                out = tuple(strings)
729e31aef6aSopenharmony_ci
730e31aef6aSopenharmony_ci        yield node.lineno, node.node.name, out
731e31aef6aSopenharmony_ci
732e31aef6aSopenharmony_ci
733e31aef6aSopenharmony_ciclass _CommentFinder:
734e31aef6aSopenharmony_ci    """Helper class to find comments in a token stream.  Can only
735e31aef6aSopenharmony_ci    find comments for gettext calls forwards.  Once the comment
736e31aef6aSopenharmony_ci    from line 4 is found, a comment for line 1 will not return a
737e31aef6aSopenharmony_ci    usable value.
738e31aef6aSopenharmony_ci    """
739e31aef6aSopenharmony_ci
740e31aef6aSopenharmony_ci    def __init__(
741e31aef6aSopenharmony_ci        self, tokens: t.Sequence[t.Tuple[int, str, str]], comment_tags: t.Sequence[str]
742e31aef6aSopenharmony_ci    ) -> None:
743e31aef6aSopenharmony_ci        self.tokens = tokens
744e31aef6aSopenharmony_ci        self.comment_tags = comment_tags
745e31aef6aSopenharmony_ci        self.offset = 0
746e31aef6aSopenharmony_ci        self.last_lineno = 0
747e31aef6aSopenharmony_ci
748e31aef6aSopenharmony_ci    def find_backwards(self, offset: int) -> t.List[str]:
749e31aef6aSopenharmony_ci        try:
750e31aef6aSopenharmony_ci            for _, token_type, token_value in reversed(
751e31aef6aSopenharmony_ci                self.tokens[self.offset : offset]
752e31aef6aSopenharmony_ci            ):
753e31aef6aSopenharmony_ci                if token_type in ("comment", "linecomment"):
754e31aef6aSopenharmony_ci                    try:
755e31aef6aSopenharmony_ci                        prefix, comment = token_value.split(None, 1)
756e31aef6aSopenharmony_ci                    except ValueError:
757e31aef6aSopenharmony_ci                        continue
758e31aef6aSopenharmony_ci                    if prefix in self.comment_tags:
759e31aef6aSopenharmony_ci                        return [comment.rstrip()]
760e31aef6aSopenharmony_ci            return []
761e31aef6aSopenharmony_ci        finally:
762e31aef6aSopenharmony_ci            self.offset = offset
763e31aef6aSopenharmony_ci
764e31aef6aSopenharmony_ci    def find_comments(self, lineno: int) -> t.List[str]:
765e31aef6aSopenharmony_ci        if not self.comment_tags or self.last_lineno > lineno:
766e31aef6aSopenharmony_ci            return []
767e31aef6aSopenharmony_ci        for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset :]):
768e31aef6aSopenharmony_ci            if token_lineno > lineno:
769e31aef6aSopenharmony_ci                return self.find_backwards(self.offset + idx)
770e31aef6aSopenharmony_ci        return self.find_backwards(len(self.tokens))
771e31aef6aSopenharmony_ci
772e31aef6aSopenharmony_ci
773e31aef6aSopenharmony_cidef babel_extract(
774e31aef6aSopenharmony_ci    fileobj: t.BinaryIO,
775e31aef6aSopenharmony_ci    keywords: t.Sequence[str],
776e31aef6aSopenharmony_ci    comment_tags: t.Sequence[str],
777e31aef6aSopenharmony_ci    options: t.Dict[str, t.Any],
778e31aef6aSopenharmony_ci) -> t.Iterator[
779e31aef6aSopenharmony_ci    t.Tuple[
780e31aef6aSopenharmony_ci        int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]], t.List[str]
781e31aef6aSopenharmony_ci    ]
782e31aef6aSopenharmony_ci]:
783e31aef6aSopenharmony_ci    """Babel extraction method for Jinja templates.
784e31aef6aSopenharmony_ci
785e31aef6aSopenharmony_ci    .. versionchanged:: 2.3
786e31aef6aSopenharmony_ci       Basic support for translation comments was added.  If `comment_tags`
787e31aef6aSopenharmony_ci       is now set to a list of keywords for extraction, the extractor will
788e31aef6aSopenharmony_ci       try to find the best preceding comment that begins with one of the
789e31aef6aSopenharmony_ci       keywords.  For best results, make sure to not have more than one
790e31aef6aSopenharmony_ci       gettext call in one line of code and the matching comment in the
791e31aef6aSopenharmony_ci       same line or the line before.
792e31aef6aSopenharmony_ci
793e31aef6aSopenharmony_ci    .. versionchanged:: 2.5.1
794e31aef6aSopenharmony_ci       The `newstyle_gettext` flag can be set to `True` to enable newstyle
795e31aef6aSopenharmony_ci       gettext calls.
796e31aef6aSopenharmony_ci
797e31aef6aSopenharmony_ci    .. versionchanged:: 2.7
798e31aef6aSopenharmony_ci       A `silent` option can now be provided.  If set to `False` template
799e31aef6aSopenharmony_ci       syntax errors are propagated instead of being ignored.
800e31aef6aSopenharmony_ci
801e31aef6aSopenharmony_ci    :param fileobj: the file-like object the messages should be extracted from
802e31aef6aSopenharmony_ci    :param keywords: a list of keywords (i.e. function names) that should be
803e31aef6aSopenharmony_ci                     recognized as translation functions
804e31aef6aSopenharmony_ci    :param comment_tags: a list of translator tags to search for and include
805e31aef6aSopenharmony_ci                         in the results.
806e31aef6aSopenharmony_ci    :param options: a dictionary of additional options (optional)
807e31aef6aSopenharmony_ci    :return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
808e31aef6aSopenharmony_ci             (comments will be empty currently)
809e31aef6aSopenharmony_ci    """
810e31aef6aSopenharmony_ci    extensions: t.Dict[t.Type[Extension], None] = {}
811e31aef6aSopenharmony_ci
812e31aef6aSopenharmony_ci    for extension_name in options.get("extensions", "").split(","):
813e31aef6aSopenharmony_ci        extension_name = extension_name.strip()
814e31aef6aSopenharmony_ci
815e31aef6aSopenharmony_ci        if not extension_name:
816e31aef6aSopenharmony_ci            continue
817e31aef6aSopenharmony_ci
818e31aef6aSopenharmony_ci        extensions[import_string(extension_name)] = None
819e31aef6aSopenharmony_ci
820e31aef6aSopenharmony_ci    if InternationalizationExtension not in extensions:
821e31aef6aSopenharmony_ci        extensions[InternationalizationExtension] = None
822e31aef6aSopenharmony_ci
823e31aef6aSopenharmony_ci    def getbool(options: t.Mapping[str, str], key: str, default: bool = False) -> bool:
824e31aef6aSopenharmony_ci        return options.get(key, str(default)).lower() in {"1", "on", "yes", "true"}
825e31aef6aSopenharmony_ci
826e31aef6aSopenharmony_ci    silent = getbool(options, "silent", True)
827e31aef6aSopenharmony_ci    environment = Environment(
828e31aef6aSopenharmony_ci        options.get("block_start_string", defaults.BLOCK_START_STRING),
829e31aef6aSopenharmony_ci        options.get("block_end_string", defaults.BLOCK_END_STRING),
830e31aef6aSopenharmony_ci        options.get("variable_start_string", defaults.VARIABLE_START_STRING),
831e31aef6aSopenharmony_ci        options.get("variable_end_string", defaults.VARIABLE_END_STRING),
832e31aef6aSopenharmony_ci        options.get("comment_start_string", defaults.COMMENT_START_STRING),
833e31aef6aSopenharmony_ci        options.get("comment_end_string", defaults.COMMENT_END_STRING),
834e31aef6aSopenharmony_ci        options.get("line_statement_prefix") or defaults.LINE_STATEMENT_PREFIX,
835e31aef6aSopenharmony_ci        options.get("line_comment_prefix") or defaults.LINE_COMMENT_PREFIX,
836e31aef6aSopenharmony_ci        getbool(options, "trim_blocks", defaults.TRIM_BLOCKS),
837e31aef6aSopenharmony_ci        getbool(options, "lstrip_blocks", defaults.LSTRIP_BLOCKS),
838e31aef6aSopenharmony_ci        defaults.NEWLINE_SEQUENCE,
839e31aef6aSopenharmony_ci        getbool(options, "keep_trailing_newline", defaults.KEEP_TRAILING_NEWLINE),
840e31aef6aSopenharmony_ci        tuple(extensions),
841e31aef6aSopenharmony_ci        cache_size=0,
842e31aef6aSopenharmony_ci        auto_reload=False,
843e31aef6aSopenharmony_ci    )
844e31aef6aSopenharmony_ci
845e31aef6aSopenharmony_ci    if getbool(options, "trimmed"):
846e31aef6aSopenharmony_ci        environment.policies["ext.i18n.trimmed"] = True
847e31aef6aSopenharmony_ci    if getbool(options, "newstyle_gettext"):
848e31aef6aSopenharmony_ci        environment.newstyle_gettext = True  # type: ignore
849e31aef6aSopenharmony_ci
850e31aef6aSopenharmony_ci    source = fileobj.read().decode(options.get("encoding", "utf-8"))
851e31aef6aSopenharmony_ci    try:
852e31aef6aSopenharmony_ci        node = environment.parse(source)
853e31aef6aSopenharmony_ci        tokens = list(environment.lex(environment.preprocess(source)))
854e31aef6aSopenharmony_ci    except TemplateSyntaxError:
855e31aef6aSopenharmony_ci        if not silent:
856e31aef6aSopenharmony_ci            raise
857e31aef6aSopenharmony_ci        # skip templates with syntax errors
858e31aef6aSopenharmony_ci        return
859e31aef6aSopenharmony_ci
860e31aef6aSopenharmony_ci    finder = _CommentFinder(tokens, comment_tags)
861e31aef6aSopenharmony_ci    for lineno, func, message in extract_from_ast(node, keywords):
862e31aef6aSopenharmony_ci        yield lineno, func, message, finder.find_comments(lineno)
863e31aef6aSopenharmony_ci
864e31aef6aSopenharmony_ci
865e31aef6aSopenharmony_ci#: nicer import names
866e31aef6aSopenharmony_cii18n = InternationalizationExtension
867e31aef6aSopenharmony_cido = ExprStmtExtension
868e31aef6aSopenharmony_ciloopcontrols = LoopControlExtension
869e31aef6aSopenharmony_cidebug = DebugExtension
870