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