11cb0ef41Sopenharmony_ci# -*- coding: utf-8 -*-
21cb0ef41Sopenharmony_ci"""
31cb0ef41Sopenharmony_ci    jinja2.ext
41cb0ef41Sopenharmony_ci    ~~~~~~~~~~
51cb0ef41Sopenharmony_ci
61cb0ef41Sopenharmony_ci    Jinja extensions allow to add custom tags similar to the way django custom
71cb0ef41Sopenharmony_ci    tags work.  By default two example extensions exist: an i18n and a cache
81cb0ef41Sopenharmony_ci    extension.
91cb0ef41Sopenharmony_ci
101cb0ef41Sopenharmony_ci    :copyright: (c) 2017 by the Jinja Team.
111cb0ef41Sopenharmony_ci    :license: BSD.
121cb0ef41Sopenharmony_ci"""
131cb0ef41Sopenharmony_ciimport re
141cb0ef41Sopenharmony_ci
151cb0ef41Sopenharmony_cifrom jinja2 import nodes
161cb0ef41Sopenharmony_cifrom jinja2.defaults import BLOCK_START_STRING, \
171cb0ef41Sopenharmony_ci     BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \
181cb0ef41Sopenharmony_ci     COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \
191cb0ef41Sopenharmony_ci     LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \
201cb0ef41Sopenharmony_ci     KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS
211cb0ef41Sopenharmony_cifrom jinja2.environment import Environment
221cb0ef41Sopenharmony_cifrom jinja2.runtime import concat
231cb0ef41Sopenharmony_cifrom jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
241cb0ef41Sopenharmony_cifrom jinja2.utils import contextfunction, import_string, Markup
251cb0ef41Sopenharmony_cifrom jinja2._compat import with_metaclass, string_types, iteritems
261cb0ef41Sopenharmony_ci
271cb0ef41Sopenharmony_ci
281cb0ef41Sopenharmony_ci# the only real useful gettext functions for a Jinja template.  Note
291cb0ef41Sopenharmony_ci# that ugettext must be assigned to gettext as Jinja doesn't support
301cb0ef41Sopenharmony_ci# non unicode strings.
311cb0ef41Sopenharmony_ciGETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
321cb0ef41Sopenharmony_ci
331cb0ef41Sopenharmony_ci
341cb0ef41Sopenharmony_ciclass ExtensionRegistry(type):
351cb0ef41Sopenharmony_ci    """Gives the extension an unique identifier."""
361cb0ef41Sopenharmony_ci
371cb0ef41Sopenharmony_ci    def __new__(cls, name, bases, d):
381cb0ef41Sopenharmony_ci        rv = type.__new__(cls, name, bases, d)
391cb0ef41Sopenharmony_ci        rv.identifier = rv.__module__ + '.' + rv.__name__
401cb0ef41Sopenharmony_ci        return rv
411cb0ef41Sopenharmony_ci
421cb0ef41Sopenharmony_ci
431cb0ef41Sopenharmony_ciclass Extension(with_metaclass(ExtensionRegistry, object)):
441cb0ef41Sopenharmony_ci    """Extensions can be used to add extra functionality to the Jinja template
451cb0ef41Sopenharmony_ci    system at the parser level.  Custom extensions are bound to an environment
461cb0ef41Sopenharmony_ci    but may not store environment specific data on `self`.  The reason for
471cb0ef41Sopenharmony_ci    this is that an extension can be bound to another environment (for
481cb0ef41Sopenharmony_ci    overlays) by creating a copy and reassigning the `environment` attribute.
491cb0ef41Sopenharmony_ci
501cb0ef41Sopenharmony_ci    As extensions are created by the environment they cannot accept any
511cb0ef41Sopenharmony_ci    arguments for configuration.  One may want to work around that by using
521cb0ef41Sopenharmony_ci    a factory function, but that is not possible as extensions are identified
531cb0ef41Sopenharmony_ci    by their import name.  The correct way to configure the extension is
541cb0ef41Sopenharmony_ci    storing the configuration values on the environment.  Because this way the
551cb0ef41Sopenharmony_ci    environment ends up acting as central configuration storage the
561cb0ef41Sopenharmony_ci    attributes may clash which is why extensions have to ensure that the names
571cb0ef41Sopenharmony_ci    they choose for configuration are not too generic.  ``prefix`` for example
581cb0ef41Sopenharmony_ci    is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
591cb0ef41Sopenharmony_ci    name as includes the name of the extension (fragment cache).
601cb0ef41Sopenharmony_ci    """
611cb0ef41Sopenharmony_ci
621cb0ef41Sopenharmony_ci    #: if this extension parses this is the list of tags it's listening to.
631cb0ef41Sopenharmony_ci    tags = set()
641cb0ef41Sopenharmony_ci
651cb0ef41Sopenharmony_ci    #: the priority of that extension.  This is especially useful for
661cb0ef41Sopenharmony_ci    #: extensions that preprocess values.  A lower value means higher
671cb0ef41Sopenharmony_ci    #: priority.
681cb0ef41Sopenharmony_ci    #:
691cb0ef41Sopenharmony_ci    #: .. versionadded:: 2.4
701cb0ef41Sopenharmony_ci    priority = 100
711cb0ef41Sopenharmony_ci
721cb0ef41Sopenharmony_ci    def __init__(self, environment):
731cb0ef41Sopenharmony_ci        self.environment = environment
741cb0ef41Sopenharmony_ci
751cb0ef41Sopenharmony_ci    def bind(self, environment):
761cb0ef41Sopenharmony_ci        """Create a copy of this extension bound to another environment."""
771cb0ef41Sopenharmony_ci        rv = object.__new__(self.__class__)
781cb0ef41Sopenharmony_ci        rv.__dict__.update(self.__dict__)
791cb0ef41Sopenharmony_ci        rv.environment = environment
801cb0ef41Sopenharmony_ci        return rv
811cb0ef41Sopenharmony_ci
821cb0ef41Sopenharmony_ci    def preprocess(self, source, name, filename=None):
831cb0ef41Sopenharmony_ci        """This method is called before the actual lexing and can be used to
841cb0ef41Sopenharmony_ci        preprocess the source.  The `filename` is optional.  The return value
851cb0ef41Sopenharmony_ci        must be the preprocessed source.
861cb0ef41Sopenharmony_ci        """
871cb0ef41Sopenharmony_ci        return source
881cb0ef41Sopenharmony_ci
891cb0ef41Sopenharmony_ci    def filter_stream(self, stream):
901cb0ef41Sopenharmony_ci        """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
911cb0ef41Sopenharmony_ci        to filter tokens returned.  This method has to return an iterable of
921cb0ef41Sopenharmony_ci        :class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a
931cb0ef41Sopenharmony_ci        :class:`~jinja2.lexer.TokenStream`.
941cb0ef41Sopenharmony_ci
951cb0ef41Sopenharmony_ci        In the `ext` folder of the Jinja2 source distribution there is a file
961cb0ef41Sopenharmony_ci        called `inlinegettext.py` which implements a filter that utilizes this
971cb0ef41Sopenharmony_ci        method.
981cb0ef41Sopenharmony_ci        """
991cb0ef41Sopenharmony_ci        return stream
1001cb0ef41Sopenharmony_ci
1011cb0ef41Sopenharmony_ci    def parse(self, parser):
1021cb0ef41Sopenharmony_ci        """If any of the :attr:`tags` matched this method is called with the
1031cb0ef41Sopenharmony_ci        parser as first argument.  The token the parser stream is pointing at
1041cb0ef41Sopenharmony_ci        is the name token that matched.  This method has to return one or a
1051cb0ef41Sopenharmony_ci        list of multiple nodes.
1061cb0ef41Sopenharmony_ci        """
1071cb0ef41Sopenharmony_ci        raise NotImplementedError()
1081cb0ef41Sopenharmony_ci
1091cb0ef41Sopenharmony_ci    def attr(self, name, lineno=None):
1101cb0ef41Sopenharmony_ci        """Return an attribute node for the current extension.  This is useful
1111cb0ef41Sopenharmony_ci        to pass constants on extensions to generated template code.
1121cb0ef41Sopenharmony_ci
1131cb0ef41Sopenharmony_ci        ::
1141cb0ef41Sopenharmony_ci
1151cb0ef41Sopenharmony_ci            self.attr('_my_attribute', lineno=lineno)
1161cb0ef41Sopenharmony_ci        """
1171cb0ef41Sopenharmony_ci        return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
1181cb0ef41Sopenharmony_ci
1191cb0ef41Sopenharmony_ci    def call_method(self, name, args=None, kwargs=None, dyn_args=None,
1201cb0ef41Sopenharmony_ci                    dyn_kwargs=None, lineno=None):
1211cb0ef41Sopenharmony_ci        """Call a method of the extension.  This is a shortcut for
1221cb0ef41Sopenharmony_ci        :meth:`attr` + :class:`jinja2.nodes.Call`.
1231cb0ef41Sopenharmony_ci        """
1241cb0ef41Sopenharmony_ci        if args is None:
1251cb0ef41Sopenharmony_ci            args = []
1261cb0ef41Sopenharmony_ci        if kwargs is None:
1271cb0ef41Sopenharmony_ci            kwargs = []
1281cb0ef41Sopenharmony_ci        return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
1291cb0ef41Sopenharmony_ci                          dyn_args, dyn_kwargs, lineno=lineno)
1301cb0ef41Sopenharmony_ci
1311cb0ef41Sopenharmony_ci
1321cb0ef41Sopenharmony_ci@contextfunction
1331cb0ef41Sopenharmony_cidef _gettext_alias(__context, *args, **kwargs):
1341cb0ef41Sopenharmony_ci    return __context.call(__context.resolve('gettext'), *args, **kwargs)
1351cb0ef41Sopenharmony_ci
1361cb0ef41Sopenharmony_ci
1371cb0ef41Sopenharmony_cidef _make_new_gettext(func):
1381cb0ef41Sopenharmony_ci    @contextfunction
1391cb0ef41Sopenharmony_ci    def gettext(__context, __string, **variables):
1401cb0ef41Sopenharmony_ci        rv = __context.call(func, __string)
1411cb0ef41Sopenharmony_ci        if __context.eval_ctx.autoescape:
1421cb0ef41Sopenharmony_ci            rv = Markup(rv)
1431cb0ef41Sopenharmony_ci        return rv % variables
1441cb0ef41Sopenharmony_ci    return gettext
1451cb0ef41Sopenharmony_ci
1461cb0ef41Sopenharmony_ci
1471cb0ef41Sopenharmony_cidef _make_new_ngettext(func):
1481cb0ef41Sopenharmony_ci    @contextfunction
1491cb0ef41Sopenharmony_ci    def ngettext(__context, __singular, __plural, __num, **variables):
1501cb0ef41Sopenharmony_ci        variables.setdefault('num', __num)
1511cb0ef41Sopenharmony_ci        rv = __context.call(func, __singular, __plural, __num)
1521cb0ef41Sopenharmony_ci        if __context.eval_ctx.autoescape:
1531cb0ef41Sopenharmony_ci            rv = Markup(rv)
1541cb0ef41Sopenharmony_ci        return rv % variables
1551cb0ef41Sopenharmony_ci    return ngettext
1561cb0ef41Sopenharmony_ci
1571cb0ef41Sopenharmony_ci
1581cb0ef41Sopenharmony_ciclass InternationalizationExtension(Extension):
1591cb0ef41Sopenharmony_ci    """This extension adds gettext support to Jinja2."""
1601cb0ef41Sopenharmony_ci    tags = set(['trans'])
1611cb0ef41Sopenharmony_ci
1621cb0ef41Sopenharmony_ci    # TODO: the i18n extension is currently reevaluating values in a few
1631cb0ef41Sopenharmony_ci    # situations.  Take this example:
1641cb0ef41Sopenharmony_ci    #   {% trans count=something() %}{{ count }} foo{% pluralize
1651cb0ef41Sopenharmony_ci    #     %}{{ count }} fooss{% endtrans %}
1661cb0ef41Sopenharmony_ci    # something is called twice here.  One time for the gettext value and
1671cb0ef41Sopenharmony_ci    # the other time for the n-parameter of the ngettext function.
1681cb0ef41Sopenharmony_ci
1691cb0ef41Sopenharmony_ci    def __init__(self, environment):
1701cb0ef41Sopenharmony_ci        Extension.__init__(self, environment)
1711cb0ef41Sopenharmony_ci        environment.globals['_'] = _gettext_alias
1721cb0ef41Sopenharmony_ci        environment.extend(
1731cb0ef41Sopenharmony_ci            install_gettext_translations=self._install,
1741cb0ef41Sopenharmony_ci            install_null_translations=self._install_null,
1751cb0ef41Sopenharmony_ci            install_gettext_callables=self._install_callables,
1761cb0ef41Sopenharmony_ci            uninstall_gettext_translations=self._uninstall,
1771cb0ef41Sopenharmony_ci            extract_translations=self._extract,
1781cb0ef41Sopenharmony_ci            newstyle_gettext=False
1791cb0ef41Sopenharmony_ci        )
1801cb0ef41Sopenharmony_ci
1811cb0ef41Sopenharmony_ci    def _install(self, translations, newstyle=None):
1821cb0ef41Sopenharmony_ci        gettext = getattr(translations, 'ugettext', None)
1831cb0ef41Sopenharmony_ci        if gettext is None:
1841cb0ef41Sopenharmony_ci            gettext = translations.gettext
1851cb0ef41Sopenharmony_ci        ngettext = getattr(translations, 'ungettext', None)
1861cb0ef41Sopenharmony_ci        if ngettext is None:
1871cb0ef41Sopenharmony_ci            ngettext = translations.ngettext
1881cb0ef41Sopenharmony_ci        self._install_callables(gettext, ngettext, newstyle)
1891cb0ef41Sopenharmony_ci
1901cb0ef41Sopenharmony_ci    def _install_null(self, newstyle=None):
1911cb0ef41Sopenharmony_ci        self._install_callables(
1921cb0ef41Sopenharmony_ci            lambda x: x,
1931cb0ef41Sopenharmony_ci            lambda s, p, n: (n != 1 and (p,) or (s,))[0],
1941cb0ef41Sopenharmony_ci            newstyle
1951cb0ef41Sopenharmony_ci        )
1961cb0ef41Sopenharmony_ci
1971cb0ef41Sopenharmony_ci    def _install_callables(self, gettext, ngettext, newstyle=None):
1981cb0ef41Sopenharmony_ci        if newstyle is not None:
1991cb0ef41Sopenharmony_ci            self.environment.newstyle_gettext = newstyle
2001cb0ef41Sopenharmony_ci        if self.environment.newstyle_gettext:
2011cb0ef41Sopenharmony_ci            gettext = _make_new_gettext(gettext)
2021cb0ef41Sopenharmony_ci            ngettext = _make_new_ngettext(ngettext)
2031cb0ef41Sopenharmony_ci        self.environment.globals.update(
2041cb0ef41Sopenharmony_ci            gettext=gettext,
2051cb0ef41Sopenharmony_ci            ngettext=ngettext
2061cb0ef41Sopenharmony_ci        )
2071cb0ef41Sopenharmony_ci
2081cb0ef41Sopenharmony_ci    def _uninstall(self, translations):
2091cb0ef41Sopenharmony_ci        for key in 'gettext', 'ngettext':
2101cb0ef41Sopenharmony_ci            self.environment.globals.pop(key, None)
2111cb0ef41Sopenharmony_ci
2121cb0ef41Sopenharmony_ci    def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
2131cb0ef41Sopenharmony_ci        if isinstance(source, string_types):
2141cb0ef41Sopenharmony_ci            source = self.environment.parse(source)
2151cb0ef41Sopenharmony_ci        return extract_from_ast(source, gettext_functions)
2161cb0ef41Sopenharmony_ci
2171cb0ef41Sopenharmony_ci    def parse(self, parser):
2181cb0ef41Sopenharmony_ci        """Parse a translatable tag."""
2191cb0ef41Sopenharmony_ci        lineno = next(parser.stream).lineno
2201cb0ef41Sopenharmony_ci        num_called_num = False
2211cb0ef41Sopenharmony_ci
2221cb0ef41Sopenharmony_ci        # find all the variables referenced.  Additionally a variable can be
2231cb0ef41Sopenharmony_ci        # defined in the body of the trans block too, but this is checked at
2241cb0ef41Sopenharmony_ci        # a later state.
2251cb0ef41Sopenharmony_ci        plural_expr = None
2261cb0ef41Sopenharmony_ci        plural_expr_assignment = None
2271cb0ef41Sopenharmony_ci        variables = {}
2281cb0ef41Sopenharmony_ci        trimmed = None
2291cb0ef41Sopenharmony_ci        while parser.stream.current.type != 'block_end':
2301cb0ef41Sopenharmony_ci            if variables:
2311cb0ef41Sopenharmony_ci                parser.stream.expect('comma')
2321cb0ef41Sopenharmony_ci
2331cb0ef41Sopenharmony_ci            # skip colon for python compatibility
2341cb0ef41Sopenharmony_ci            if parser.stream.skip_if('colon'):
2351cb0ef41Sopenharmony_ci                break
2361cb0ef41Sopenharmony_ci
2371cb0ef41Sopenharmony_ci            name = parser.stream.expect('name')
2381cb0ef41Sopenharmony_ci            if name.value in variables:
2391cb0ef41Sopenharmony_ci                parser.fail('translatable variable %r defined twice.' %
2401cb0ef41Sopenharmony_ci                            name.value, name.lineno,
2411cb0ef41Sopenharmony_ci                            exc=TemplateAssertionError)
2421cb0ef41Sopenharmony_ci
2431cb0ef41Sopenharmony_ci            # expressions
2441cb0ef41Sopenharmony_ci            if parser.stream.current.type == 'assign':
2451cb0ef41Sopenharmony_ci                next(parser.stream)
2461cb0ef41Sopenharmony_ci                variables[name.value] = var = parser.parse_expression()
2471cb0ef41Sopenharmony_ci            elif trimmed is None and name.value in ('trimmed', 'notrimmed'):
2481cb0ef41Sopenharmony_ci                trimmed = name.value == 'trimmed'
2491cb0ef41Sopenharmony_ci                continue
2501cb0ef41Sopenharmony_ci            else:
2511cb0ef41Sopenharmony_ci                variables[name.value] = var = nodes.Name(name.value, 'load')
2521cb0ef41Sopenharmony_ci
2531cb0ef41Sopenharmony_ci            if plural_expr is None:
2541cb0ef41Sopenharmony_ci                if isinstance(var, nodes.Call):
2551cb0ef41Sopenharmony_ci                    plural_expr = nodes.Name('_trans', 'load')
2561cb0ef41Sopenharmony_ci                    variables[name.value] = plural_expr
2571cb0ef41Sopenharmony_ci                    plural_expr_assignment = nodes.Assign(
2581cb0ef41Sopenharmony_ci                        nodes.Name('_trans', 'store'), var)
2591cb0ef41Sopenharmony_ci                else:
2601cb0ef41Sopenharmony_ci                    plural_expr = var
2611cb0ef41Sopenharmony_ci                num_called_num = name.value == 'num'
2621cb0ef41Sopenharmony_ci
2631cb0ef41Sopenharmony_ci        parser.stream.expect('block_end')
2641cb0ef41Sopenharmony_ci
2651cb0ef41Sopenharmony_ci        plural = None
2661cb0ef41Sopenharmony_ci        have_plural = False
2671cb0ef41Sopenharmony_ci        referenced = set()
2681cb0ef41Sopenharmony_ci
2691cb0ef41Sopenharmony_ci        # now parse until endtrans or pluralize
2701cb0ef41Sopenharmony_ci        singular_names, singular = self._parse_block(parser, True)
2711cb0ef41Sopenharmony_ci        if singular_names:
2721cb0ef41Sopenharmony_ci            referenced.update(singular_names)
2731cb0ef41Sopenharmony_ci            if plural_expr is None:
2741cb0ef41Sopenharmony_ci                plural_expr = nodes.Name(singular_names[0], 'load')
2751cb0ef41Sopenharmony_ci                num_called_num = singular_names[0] == 'num'
2761cb0ef41Sopenharmony_ci
2771cb0ef41Sopenharmony_ci        # if we have a pluralize block, we parse that too
2781cb0ef41Sopenharmony_ci        if parser.stream.current.test('name:pluralize'):
2791cb0ef41Sopenharmony_ci            have_plural = True
2801cb0ef41Sopenharmony_ci            next(parser.stream)
2811cb0ef41Sopenharmony_ci            if parser.stream.current.type != 'block_end':
2821cb0ef41Sopenharmony_ci                name = parser.stream.expect('name')
2831cb0ef41Sopenharmony_ci                if name.value not in variables:
2841cb0ef41Sopenharmony_ci                    parser.fail('unknown variable %r for pluralization' %
2851cb0ef41Sopenharmony_ci                                name.value, name.lineno,
2861cb0ef41Sopenharmony_ci                                exc=TemplateAssertionError)
2871cb0ef41Sopenharmony_ci                plural_expr = variables[name.value]
2881cb0ef41Sopenharmony_ci                num_called_num = name.value == 'num'
2891cb0ef41Sopenharmony_ci            parser.stream.expect('block_end')
2901cb0ef41Sopenharmony_ci            plural_names, plural = self._parse_block(parser, False)
2911cb0ef41Sopenharmony_ci            next(parser.stream)
2921cb0ef41Sopenharmony_ci            referenced.update(plural_names)
2931cb0ef41Sopenharmony_ci        else:
2941cb0ef41Sopenharmony_ci            next(parser.stream)
2951cb0ef41Sopenharmony_ci
2961cb0ef41Sopenharmony_ci        # register free names as simple name expressions
2971cb0ef41Sopenharmony_ci        for var in referenced:
2981cb0ef41Sopenharmony_ci            if var not in variables:
2991cb0ef41Sopenharmony_ci                variables[var] = nodes.Name(var, 'load')
3001cb0ef41Sopenharmony_ci
3011cb0ef41Sopenharmony_ci        if not have_plural:
3021cb0ef41Sopenharmony_ci            plural_expr = None
3031cb0ef41Sopenharmony_ci        elif plural_expr is None:
3041cb0ef41Sopenharmony_ci            parser.fail('pluralize without variables', lineno)
3051cb0ef41Sopenharmony_ci
3061cb0ef41Sopenharmony_ci        if trimmed is None:
3071cb0ef41Sopenharmony_ci            trimmed = self.environment.policies['ext.i18n.trimmed']
3081cb0ef41Sopenharmony_ci        if trimmed:
3091cb0ef41Sopenharmony_ci            singular = self._trim_whitespace(singular)
3101cb0ef41Sopenharmony_ci            if plural:
3111cb0ef41Sopenharmony_ci                plural = self._trim_whitespace(plural)
3121cb0ef41Sopenharmony_ci
3131cb0ef41Sopenharmony_ci        node = self._make_node(singular, plural, variables, plural_expr,
3141cb0ef41Sopenharmony_ci                               bool(referenced),
3151cb0ef41Sopenharmony_ci                               num_called_num and have_plural)
3161cb0ef41Sopenharmony_ci        node.set_lineno(lineno)
3171cb0ef41Sopenharmony_ci        if plural_expr_assignment is not None:
3181cb0ef41Sopenharmony_ci            return [plural_expr_assignment, node]
3191cb0ef41Sopenharmony_ci        else:
3201cb0ef41Sopenharmony_ci            return node
3211cb0ef41Sopenharmony_ci
3221cb0ef41Sopenharmony_ci    def _trim_whitespace(self, string, _ws_re=re.compile(r'\s*\n\s*')):
3231cb0ef41Sopenharmony_ci        return _ws_re.sub(' ', string.strip())
3241cb0ef41Sopenharmony_ci
3251cb0ef41Sopenharmony_ci    def _parse_block(self, parser, allow_pluralize):
3261cb0ef41Sopenharmony_ci        """Parse until the next block tag with a given name."""
3271cb0ef41Sopenharmony_ci        referenced = []
3281cb0ef41Sopenharmony_ci        buf = []
3291cb0ef41Sopenharmony_ci        while 1:
3301cb0ef41Sopenharmony_ci            if parser.stream.current.type == 'data':
3311cb0ef41Sopenharmony_ci                buf.append(parser.stream.current.value.replace('%', '%%'))
3321cb0ef41Sopenharmony_ci                next(parser.stream)
3331cb0ef41Sopenharmony_ci            elif parser.stream.current.type == 'variable_begin':
3341cb0ef41Sopenharmony_ci                next(parser.stream)
3351cb0ef41Sopenharmony_ci                name = parser.stream.expect('name').value
3361cb0ef41Sopenharmony_ci                referenced.append(name)
3371cb0ef41Sopenharmony_ci                buf.append('%%(%s)s' % name)
3381cb0ef41Sopenharmony_ci                parser.stream.expect('variable_end')
3391cb0ef41Sopenharmony_ci            elif parser.stream.current.type == 'block_begin':
3401cb0ef41Sopenharmony_ci                next(parser.stream)
3411cb0ef41Sopenharmony_ci                if parser.stream.current.test('name:endtrans'):
3421cb0ef41Sopenharmony_ci                    break
3431cb0ef41Sopenharmony_ci                elif parser.stream.current.test('name:pluralize'):
3441cb0ef41Sopenharmony_ci                    if allow_pluralize:
3451cb0ef41Sopenharmony_ci                        break
3461cb0ef41Sopenharmony_ci                    parser.fail('a translatable section can have only one '
3471cb0ef41Sopenharmony_ci                                'pluralize section')
3481cb0ef41Sopenharmony_ci                parser.fail('control structures in translatable sections are '
3491cb0ef41Sopenharmony_ci                            'not allowed')
3501cb0ef41Sopenharmony_ci            elif parser.stream.eos:
3511cb0ef41Sopenharmony_ci                parser.fail('unclosed translation block')
3521cb0ef41Sopenharmony_ci            else:
3531cb0ef41Sopenharmony_ci                assert False, 'internal parser error'
3541cb0ef41Sopenharmony_ci
3551cb0ef41Sopenharmony_ci        return referenced, concat(buf)
3561cb0ef41Sopenharmony_ci
3571cb0ef41Sopenharmony_ci    def _make_node(self, singular, plural, variables, plural_expr,
3581cb0ef41Sopenharmony_ci                   vars_referenced, num_called_num):
3591cb0ef41Sopenharmony_ci        """Generates a useful node from the data provided."""
3601cb0ef41Sopenharmony_ci        # no variables referenced?  no need to escape for old style
3611cb0ef41Sopenharmony_ci        # gettext invocations only if there are vars.
3621cb0ef41Sopenharmony_ci        if not vars_referenced and not self.environment.newstyle_gettext:
3631cb0ef41Sopenharmony_ci            singular = singular.replace('%%', '%')
3641cb0ef41Sopenharmony_ci            if plural:
3651cb0ef41Sopenharmony_ci                plural = plural.replace('%%', '%')
3661cb0ef41Sopenharmony_ci
3671cb0ef41Sopenharmony_ci        # singular only:
3681cb0ef41Sopenharmony_ci        if plural_expr is None:
3691cb0ef41Sopenharmony_ci            gettext = nodes.Name('gettext', 'load')
3701cb0ef41Sopenharmony_ci            node = nodes.Call(gettext, [nodes.Const(singular)],
3711cb0ef41Sopenharmony_ci                              [], None, None)
3721cb0ef41Sopenharmony_ci
3731cb0ef41Sopenharmony_ci        # singular and plural
3741cb0ef41Sopenharmony_ci        else:
3751cb0ef41Sopenharmony_ci            ngettext = nodes.Name('ngettext', 'load')
3761cb0ef41Sopenharmony_ci            node = nodes.Call(ngettext, [
3771cb0ef41Sopenharmony_ci                nodes.Const(singular),
3781cb0ef41Sopenharmony_ci                nodes.Const(plural),
3791cb0ef41Sopenharmony_ci                plural_expr
3801cb0ef41Sopenharmony_ci            ], [], None, None)
3811cb0ef41Sopenharmony_ci
3821cb0ef41Sopenharmony_ci        # in case newstyle gettext is used, the method is powerful
3831cb0ef41Sopenharmony_ci        # enough to handle the variable expansion and autoescape
3841cb0ef41Sopenharmony_ci        # handling itself
3851cb0ef41Sopenharmony_ci        if self.environment.newstyle_gettext:
3861cb0ef41Sopenharmony_ci            for key, value in iteritems(variables):
3871cb0ef41Sopenharmony_ci                # the function adds that later anyways in case num was
3881cb0ef41Sopenharmony_ci                # called num, so just skip it.
3891cb0ef41Sopenharmony_ci                if num_called_num and key == 'num':
3901cb0ef41Sopenharmony_ci                    continue
3911cb0ef41Sopenharmony_ci                node.kwargs.append(nodes.Keyword(key, value))
3921cb0ef41Sopenharmony_ci
3931cb0ef41Sopenharmony_ci        # otherwise do that here
3941cb0ef41Sopenharmony_ci        else:
3951cb0ef41Sopenharmony_ci            # mark the return value as safe if we are in an
3961cb0ef41Sopenharmony_ci            # environment with autoescaping turned on
3971cb0ef41Sopenharmony_ci            node = nodes.MarkSafeIfAutoescape(node)
3981cb0ef41Sopenharmony_ci            if variables:
3991cb0ef41Sopenharmony_ci                node = nodes.Mod(node, nodes.Dict([
4001cb0ef41Sopenharmony_ci                    nodes.Pair(nodes.Const(key), value)
4011cb0ef41Sopenharmony_ci                    for key, value in variables.items()
4021cb0ef41Sopenharmony_ci                ]))
4031cb0ef41Sopenharmony_ci        return nodes.Output([node])
4041cb0ef41Sopenharmony_ci
4051cb0ef41Sopenharmony_ci
4061cb0ef41Sopenharmony_ciclass ExprStmtExtension(Extension):
4071cb0ef41Sopenharmony_ci    """Adds a `do` tag to Jinja2 that works like the print statement just
4081cb0ef41Sopenharmony_ci    that it doesn't print the return value.
4091cb0ef41Sopenharmony_ci    """
4101cb0ef41Sopenharmony_ci    tags = set(['do'])
4111cb0ef41Sopenharmony_ci
4121cb0ef41Sopenharmony_ci    def parse(self, parser):
4131cb0ef41Sopenharmony_ci        node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
4141cb0ef41Sopenharmony_ci        node.node = parser.parse_tuple()
4151cb0ef41Sopenharmony_ci        return node
4161cb0ef41Sopenharmony_ci
4171cb0ef41Sopenharmony_ci
4181cb0ef41Sopenharmony_ciclass LoopControlExtension(Extension):
4191cb0ef41Sopenharmony_ci    """Adds break and continue to the template engine."""
4201cb0ef41Sopenharmony_ci    tags = set(['break', 'continue'])
4211cb0ef41Sopenharmony_ci
4221cb0ef41Sopenharmony_ci    def parse(self, parser):
4231cb0ef41Sopenharmony_ci        token = next(parser.stream)
4241cb0ef41Sopenharmony_ci        if token.value == 'break':
4251cb0ef41Sopenharmony_ci            return nodes.Break(lineno=token.lineno)
4261cb0ef41Sopenharmony_ci        return nodes.Continue(lineno=token.lineno)
4271cb0ef41Sopenharmony_ci
4281cb0ef41Sopenharmony_ci
4291cb0ef41Sopenharmony_ciclass WithExtension(Extension):
4301cb0ef41Sopenharmony_ci    pass
4311cb0ef41Sopenharmony_ci
4321cb0ef41Sopenharmony_ci
4331cb0ef41Sopenharmony_ciclass AutoEscapeExtension(Extension):
4341cb0ef41Sopenharmony_ci    pass
4351cb0ef41Sopenharmony_ci
4361cb0ef41Sopenharmony_ci
4371cb0ef41Sopenharmony_cidef extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
4381cb0ef41Sopenharmony_ci                     babel_style=True):
4391cb0ef41Sopenharmony_ci    """Extract localizable strings from the given template node.  Per
4401cb0ef41Sopenharmony_ci    default this function returns matches in babel style that means non string
4411cb0ef41Sopenharmony_ci    parameters as well as keyword arguments are returned as `None`.  This
4421cb0ef41Sopenharmony_ci    allows Babel to figure out what you really meant if you are using
4431cb0ef41Sopenharmony_ci    gettext functions that allow keyword arguments for placeholder expansion.
4441cb0ef41Sopenharmony_ci    If you don't want that behavior set the `babel_style` parameter to `False`
4451cb0ef41Sopenharmony_ci    which causes only strings to be returned and parameters are always stored
4461cb0ef41Sopenharmony_ci    in tuples.  As a consequence invalid gettext calls (calls without a single
4471cb0ef41Sopenharmony_ci    string parameter or string parameters after non-string parameters) are
4481cb0ef41Sopenharmony_ci    skipped.
4491cb0ef41Sopenharmony_ci
4501cb0ef41Sopenharmony_ci    This example explains the behavior:
4511cb0ef41Sopenharmony_ci
4521cb0ef41Sopenharmony_ci    >>> from jinja2 import Environment
4531cb0ef41Sopenharmony_ci    >>> env = Environment()
4541cb0ef41Sopenharmony_ci    >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
4551cb0ef41Sopenharmony_ci    >>> list(extract_from_ast(node))
4561cb0ef41Sopenharmony_ci    [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
4571cb0ef41Sopenharmony_ci    >>> list(extract_from_ast(node, babel_style=False))
4581cb0ef41Sopenharmony_ci    [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
4591cb0ef41Sopenharmony_ci
4601cb0ef41Sopenharmony_ci    For every string found this function yields a ``(lineno, function,
4611cb0ef41Sopenharmony_ci    message)`` tuple, where:
4621cb0ef41Sopenharmony_ci
4631cb0ef41Sopenharmony_ci    * ``lineno`` is the number of the line on which the string was found,
4641cb0ef41Sopenharmony_ci    * ``function`` is the name of the ``gettext`` function used (if the
4651cb0ef41Sopenharmony_ci      string was extracted from embedded Python code), and
4661cb0ef41Sopenharmony_ci    *  ``message`` is the string itself (a ``unicode`` object, or a tuple
4671cb0ef41Sopenharmony_ci       of ``unicode`` objects for functions with multiple string arguments).
4681cb0ef41Sopenharmony_ci
4691cb0ef41Sopenharmony_ci    This extraction function operates on the AST and is because of that unable
4701cb0ef41Sopenharmony_ci    to extract any comments.  For comment support you have to use the babel
4711cb0ef41Sopenharmony_ci    extraction interface or extract comments yourself.
4721cb0ef41Sopenharmony_ci    """
4731cb0ef41Sopenharmony_ci    for node in node.find_all(nodes.Call):
4741cb0ef41Sopenharmony_ci        if not isinstance(node.node, nodes.Name) or \
4751cb0ef41Sopenharmony_ci           node.node.name not in gettext_functions:
4761cb0ef41Sopenharmony_ci            continue
4771cb0ef41Sopenharmony_ci
4781cb0ef41Sopenharmony_ci        strings = []
4791cb0ef41Sopenharmony_ci        for arg in node.args:
4801cb0ef41Sopenharmony_ci            if isinstance(arg, nodes.Const) and \
4811cb0ef41Sopenharmony_ci               isinstance(arg.value, string_types):
4821cb0ef41Sopenharmony_ci                strings.append(arg.value)
4831cb0ef41Sopenharmony_ci            else:
4841cb0ef41Sopenharmony_ci                strings.append(None)
4851cb0ef41Sopenharmony_ci
4861cb0ef41Sopenharmony_ci        for arg in node.kwargs:
4871cb0ef41Sopenharmony_ci            strings.append(None)
4881cb0ef41Sopenharmony_ci        if node.dyn_args is not None:
4891cb0ef41Sopenharmony_ci            strings.append(None)
4901cb0ef41Sopenharmony_ci        if node.dyn_kwargs is not None:
4911cb0ef41Sopenharmony_ci            strings.append(None)
4921cb0ef41Sopenharmony_ci
4931cb0ef41Sopenharmony_ci        if not babel_style:
4941cb0ef41Sopenharmony_ci            strings = tuple(x for x in strings if x is not None)
4951cb0ef41Sopenharmony_ci            if not strings:
4961cb0ef41Sopenharmony_ci                continue
4971cb0ef41Sopenharmony_ci        else:
4981cb0ef41Sopenharmony_ci            if len(strings) == 1:
4991cb0ef41Sopenharmony_ci                strings = strings[0]
5001cb0ef41Sopenharmony_ci            else:
5011cb0ef41Sopenharmony_ci                strings = tuple(strings)
5021cb0ef41Sopenharmony_ci        yield node.lineno, node.node.name, strings
5031cb0ef41Sopenharmony_ci
5041cb0ef41Sopenharmony_ci
5051cb0ef41Sopenharmony_ciclass _CommentFinder(object):
5061cb0ef41Sopenharmony_ci    """Helper class to find comments in a token stream.  Can only
5071cb0ef41Sopenharmony_ci    find comments for gettext calls forwards.  Once the comment
5081cb0ef41Sopenharmony_ci    from line 4 is found, a comment for line 1 will not return a
5091cb0ef41Sopenharmony_ci    usable value.
5101cb0ef41Sopenharmony_ci    """
5111cb0ef41Sopenharmony_ci
5121cb0ef41Sopenharmony_ci    def __init__(self, tokens, comment_tags):
5131cb0ef41Sopenharmony_ci        self.tokens = tokens
5141cb0ef41Sopenharmony_ci        self.comment_tags = comment_tags
5151cb0ef41Sopenharmony_ci        self.offset = 0
5161cb0ef41Sopenharmony_ci        self.last_lineno = 0
5171cb0ef41Sopenharmony_ci
5181cb0ef41Sopenharmony_ci    def find_backwards(self, offset):
5191cb0ef41Sopenharmony_ci        try:
5201cb0ef41Sopenharmony_ci            for _, token_type, token_value in \
5211cb0ef41Sopenharmony_ci                    reversed(self.tokens[self.offset:offset]):
5221cb0ef41Sopenharmony_ci                if token_type in ('comment', 'linecomment'):
5231cb0ef41Sopenharmony_ci                    try:
5241cb0ef41Sopenharmony_ci                        prefix, comment = token_value.split(None, 1)
5251cb0ef41Sopenharmony_ci                    except ValueError:
5261cb0ef41Sopenharmony_ci                        continue
5271cb0ef41Sopenharmony_ci                    if prefix in self.comment_tags:
5281cb0ef41Sopenharmony_ci                        return [comment.rstrip()]
5291cb0ef41Sopenharmony_ci            return []
5301cb0ef41Sopenharmony_ci        finally:
5311cb0ef41Sopenharmony_ci            self.offset = offset
5321cb0ef41Sopenharmony_ci
5331cb0ef41Sopenharmony_ci    def find_comments(self, lineno):
5341cb0ef41Sopenharmony_ci        if not self.comment_tags or self.last_lineno > lineno:
5351cb0ef41Sopenharmony_ci            return []
5361cb0ef41Sopenharmony_ci        for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]):
5371cb0ef41Sopenharmony_ci            if token_lineno > lineno:
5381cb0ef41Sopenharmony_ci                return self.find_backwards(self.offset + idx)
5391cb0ef41Sopenharmony_ci        return self.find_backwards(len(self.tokens))
5401cb0ef41Sopenharmony_ci
5411cb0ef41Sopenharmony_ci
5421cb0ef41Sopenharmony_cidef babel_extract(fileobj, keywords, comment_tags, options):
5431cb0ef41Sopenharmony_ci    """Babel extraction method for Jinja templates.
5441cb0ef41Sopenharmony_ci
5451cb0ef41Sopenharmony_ci    .. versionchanged:: 2.3
5461cb0ef41Sopenharmony_ci       Basic support for translation comments was added.  If `comment_tags`
5471cb0ef41Sopenharmony_ci       is now set to a list of keywords for extraction, the extractor will
5481cb0ef41Sopenharmony_ci       try to find the best preceeding comment that begins with one of the
5491cb0ef41Sopenharmony_ci       keywords.  For best results, make sure to not have more than one
5501cb0ef41Sopenharmony_ci       gettext call in one line of code and the matching comment in the
5511cb0ef41Sopenharmony_ci       same line or the line before.
5521cb0ef41Sopenharmony_ci
5531cb0ef41Sopenharmony_ci    .. versionchanged:: 2.5.1
5541cb0ef41Sopenharmony_ci       The `newstyle_gettext` flag can be set to `True` to enable newstyle
5551cb0ef41Sopenharmony_ci       gettext calls.
5561cb0ef41Sopenharmony_ci
5571cb0ef41Sopenharmony_ci    .. versionchanged:: 2.7
5581cb0ef41Sopenharmony_ci       A `silent` option can now be provided.  If set to `False` template
5591cb0ef41Sopenharmony_ci       syntax errors are propagated instead of being ignored.
5601cb0ef41Sopenharmony_ci
5611cb0ef41Sopenharmony_ci    :param fileobj: the file-like object the messages should be extracted from
5621cb0ef41Sopenharmony_ci    :param keywords: a list of keywords (i.e. function names) that should be
5631cb0ef41Sopenharmony_ci                     recognized as translation functions
5641cb0ef41Sopenharmony_ci    :param comment_tags: a list of translator tags to search for and include
5651cb0ef41Sopenharmony_ci                         in the results.
5661cb0ef41Sopenharmony_ci    :param options: a dictionary of additional options (optional)
5671cb0ef41Sopenharmony_ci    :return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
5681cb0ef41Sopenharmony_ci             (comments will be empty currently)
5691cb0ef41Sopenharmony_ci    """
5701cb0ef41Sopenharmony_ci    extensions = set()
5711cb0ef41Sopenharmony_ci    for extension in options.get('extensions', '').split(','):
5721cb0ef41Sopenharmony_ci        extension = extension.strip()
5731cb0ef41Sopenharmony_ci        if not extension:
5741cb0ef41Sopenharmony_ci            continue
5751cb0ef41Sopenharmony_ci        extensions.add(import_string(extension))
5761cb0ef41Sopenharmony_ci    if InternationalizationExtension not in extensions:
5771cb0ef41Sopenharmony_ci        extensions.add(InternationalizationExtension)
5781cb0ef41Sopenharmony_ci
5791cb0ef41Sopenharmony_ci    def getbool(options, key, default=False):
5801cb0ef41Sopenharmony_ci        return options.get(key, str(default)).lower() in \
5811cb0ef41Sopenharmony_ci            ('1', 'on', 'yes', 'true')
5821cb0ef41Sopenharmony_ci
5831cb0ef41Sopenharmony_ci    silent = getbool(options, 'silent', True)
5841cb0ef41Sopenharmony_ci    environment = Environment(
5851cb0ef41Sopenharmony_ci        options.get('block_start_string', BLOCK_START_STRING),
5861cb0ef41Sopenharmony_ci        options.get('block_end_string', BLOCK_END_STRING),
5871cb0ef41Sopenharmony_ci        options.get('variable_start_string', VARIABLE_START_STRING),
5881cb0ef41Sopenharmony_ci        options.get('variable_end_string', VARIABLE_END_STRING),
5891cb0ef41Sopenharmony_ci        options.get('comment_start_string', COMMENT_START_STRING),
5901cb0ef41Sopenharmony_ci        options.get('comment_end_string', COMMENT_END_STRING),
5911cb0ef41Sopenharmony_ci        options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
5921cb0ef41Sopenharmony_ci        options.get('line_comment_prefix') or LINE_COMMENT_PREFIX,
5931cb0ef41Sopenharmony_ci        getbool(options, 'trim_blocks', TRIM_BLOCKS),
5941cb0ef41Sopenharmony_ci        getbool(options, 'lstrip_blocks', LSTRIP_BLOCKS),
5951cb0ef41Sopenharmony_ci        NEWLINE_SEQUENCE,
5961cb0ef41Sopenharmony_ci        getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE),
5971cb0ef41Sopenharmony_ci        frozenset(extensions),
5981cb0ef41Sopenharmony_ci        cache_size=0,
5991cb0ef41Sopenharmony_ci        auto_reload=False
6001cb0ef41Sopenharmony_ci    )
6011cb0ef41Sopenharmony_ci
6021cb0ef41Sopenharmony_ci    if getbool(options, 'trimmed'):
6031cb0ef41Sopenharmony_ci        environment.policies['ext.i18n.trimmed'] = True
6041cb0ef41Sopenharmony_ci    if getbool(options, 'newstyle_gettext'):
6051cb0ef41Sopenharmony_ci        environment.newstyle_gettext = True
6061cb0ef41Sopenharmony_ci
6071cb0ef41Sopenharmony_ci    source = fileobj.read().decode(options.get('encoding', 'utf-8'))
6081cb0ef41Sopenharmony_ci    try:
6091cb0ef41Sopenharmony_ci        node = environment.parse(source)
6101cb0ef41Sopenharmony_ci        tokens = list(environment.lex(environment.preprocess(source)))
6111cb0ef41Sopenharmony_ci    except TemplateSyntaxError as e:
6121cb0ef41Sopenharmony_ci        if not silent:
6131cb0ef41Sopenharmony_ci            raise
6141cb0ef41Sopenharmony_ci        # skip templates with syntax errors
6151cb0ef41Sopenharmony_ci        return
6161cb0ef41Sopenharmony_ci
6171cb0ef41Sopenharmony_ci    finder = _CommentFinder(tokens, comment_tags)
6181cb0ef41Sopenharmony_ci    for lineno, func, message in extract_from_ast(node, keywords):
6191cb0ef41Sopenharmony_ci        yield lineno, func, message, finder.find_comments(lineno)
6201cb0ef41Sopenharmony_ci
6211cb0ef41Sopenharmony_ci
6221cb0ef41Sopenharmony_ci#: nicer import names
6231cb0ef41Sopenharmony_cii18n = InternationalizationExtension
6241cb0ef41Sopenharmony_cido = ExprStmtExtension
6251cb0ef41Sopenharmony_ciloopcontrols = LoopControlExtension
6261cb0ef41Sopenharmony_ciwith_ = WithExtension
6271cb0ef41Sopenharmony_ciautoescape = AutoEscapeExtension
628