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