1*635a8641SAndroid Build Coastguard Worker# -*- coding: utf-8 -*- 2*635a8641SAndroid Build Coastguard Worker""" 3*635a8641SAndroid Build Coastguard Worker jinja2.ext 4*635a8641SAndroid Build Coastguard Worker ~~~~~~~~~~ 5*635a8641SAndroid Build Coastguard Worker 6*635a8641SAndroid Build Coastguard Worker Jinja extensions allow to add custom tags similar to the way django custom 7*635a8641SAndroid Build Coastguard Worker tags work. By default two example extensions exist: an i18n and a cache 8*635a8641SAndroid Build Coastguard Worker extension. 9*635a8641SAndroid Build Coastguard Worker 10*635a8641SAndroid Build Coastguard Worker :copyright: (c) 2017 by the Jinja Team. 11*635a8641SAndroid Build Coastguard Worker :license: BSD. 12*635a8641SAndroid Build Coastguard Worker""" 13*635a8641SAndroid Build Coastguard Workerimport re 14*635a8641SAndroid Build Coastguard Worker 15*635a8641SAndroid Build Coastguard Workerfrom jinja2 import nodes 16*635a8641SAndroid Build Coastguard Workerfrom jinja2.defaults import BLOCK_START_STRING, \ 17*635a8641SAndroid Build Coastguard Worker BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \ 18*635a8641SAndroid Build Coastguard Worker COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \ 19*635a8641SAndroid Build Coastguard Worker LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \ 20*635a8641SAndroid Build Coastguard Worker KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS 21*635a8641SAndroid Build Coastguard Workerfrom jinja2.environment import Environment 22*635a8641SAndroid Build Coastguard Workerfrom jinja2.runtime import concat 23*635a8641SAndroid Build Coastguard Workerfrom jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError 24*635a8641SAndroid Build Coastguard Workerfrom jinja2.utils import contextfunction, import_string, Markup 25*635a8641SAndroid Build Coastguard Workerfrom jinja2._compat import with_metaclass, string_types, iteritems 26*635a8641SAndroid Build Coastguard Worker 27*635a8641SAndroid Build Coastguard Worker 28*635a8641SAndroid Build Coastguard Worker# the only real useful gettext functions for a Jinja template. Note 29*635a8641SAndroid Build Coastguard Worker# that ugettext must be assigned to gettext as Jinja doesn't support 30*635a8641SAndroid Build Coastguard Worker# non unicode strings. 31*635a8641SAndroid Build Coastguard WorkerGETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext') 32*635a8641SAndroid Build Coastguard Worker 33*635a8641SAndroid Build Coastguard Worker 34*635a8641SAndroid Build Coastguard Workerclass ExtensionRegistry(type): 35*635a8641SAndroid Build Coastguard Worker """Gives the extension an unique identifier.""" 36*635a8641SAndroid Build Coastguard Worker 37*635a8641SAndroid Build Coastguard Worker def __new__(cls, name, bases, d): 38*635a8641SAndroid Build Coastguard Worker rv = type.__new__(cls, name, bases, d) 39*635a8641SAndroid Build Coastguard Worker rv.identifier = rv.__module__ + '.' + rv.__name__ 40*635a8641SAndroid Build Coastguard Worker return rv 41*635a8641SAndroid Build Coastguard Worker 42*635a8641SAndroid Build Coastguard Worker 43*635a8641SAndroid Build Coastguard Workerclass Extension(with_metaclass(ExtensionRegistry, object)): 44*635a8641SAndroid Build Coastguard Worker """Extensions can be used to add extra functionality to the Jinja template 45*635a8641SAndroid Build Coastguard Worker system at the parser level. Custom extensions are bound to an environment 46*635a8641SAndroid Build Coastguard Worker but may not store environment specific data on `self`. The reason for 47*635a8641SAndroid Build Coastguard Worker this is that an extension can be bound to another environment (for 48*635a8641SAndroid Build Coastguard Worker overlays) by creating a copy and reassigning the `environment` attribute. 49*635a8641SAndroid Build Coastguard Worker 50*635a8641SAndroid Build Coastguard Worker As extensions are created by the environment they cannot accept any 51*635a8641SAndroid Build Coastguard Worker arguments for configuration. One may want to work around that by using 52*635a8641SAndroid Build Coastguard Worker a factory function, but that is not possible as extensions are identified 53*635a8641SAndroid Build Coastguard Worker by their import name. The correct way to configure the extension is 54*635a8641SAndroid Build Coastguard Worker storing the configuration values on the environment. Because this way the 55*635a8641SAndroid Build Coastguard Worker environment ends up acting as central configuration storage the 56*635a8641SAndroid Build Coastguard Worker attributes may clash which is why extensions have to ensure that the names 57*635a8641SAndroid Build Coastguard Worker they choose for configuration are not too generic. ``prefix`` for example 58*635a8641SAndroid Build Coastguard Worker is a terrible name, ``fragment_cache_prefix`` on the other hand is a good 59*635a8641SAndroid Build Coastguard Worker name as includes the name of the extension (fragment cache). 60*635a8641SAndroid Build Coastguard Worker """ 61*635a8641SAndroid Build Coastguard Worker 62*635a8641SAndroid Build Coastguard Worker #: if this extension parses this is the list of tags it's listening to. 63*635a8641SAndroid Build Coastguard Worker tags = set() 64*635a8641SAndroid Build Coastguard Worker 65*635a8641SAndroid Build Coastguard Worker #: the priority of that extension. This is especially useful for 66*635a8641SAndroid Build Coastguard Worker #: extensions that preprocess values. A lower value means higher 67*635a8641SAndroid Build Coastguard Worker #: priority. 68*635a8641SAndroid Build Coastguard Worker #: 69*635a8641SAndroid Build Coastguard Worker #: .. versionadded:: 2.4 70*635a8641SAndroid Build Coastguard Worker priority = 100 71*635a8641SAndroid Build Coastguard Worker 72*635a8641SAndroid Build Coastguard Worker def __init__(self, environment): 73*635a8641SAndroid Build Coastguard Worker self.environment = environment 74*635a8641SAndroid Build Coastguard Worker 75*635a8641SAndroid Build Coastguard Worker def bind(self, environment): 76*635a8641SAndroid Build Coastguard Worker """Create a copy of this extension bound to another environment.""" 77*635a8641SAndroid Build Coastguard Worker rv = object.__new__(self.__class__) 78*635a8641SAndroid Build Coastguard Worker rv.__dict__.update(self.__dict__) 79*635a8641SAndroid Build Coastguard Worker rv.environment = environment 80*635a8641SAndroid Build Coastguard Worker return rv 81*635a8641SAndroid Build Coastguard Worker 82*635a8641SAndroid Build Coastguard Worker def preprocess(self, source, name, filename=None): 83*635a8641SAndroid Build Coastguard Worker """This method is called before the actual lexing and can be used to 84*635a8641SAndroid Build Coastguard Worker preprocess the source. The `filename` is optional. The return value 85*635a8641SAndroid Build Coastguard Worker must be the preprocessed source. 86*635a8641SAndroid Build Coastguard Worker """ 87*635a8641SAndroid Build Coastguard Worker return source 88*635a8641SAndroid Build Coastguard Worker 89*635a8641SAndroid Build Coastguard Worker def filter_stream(self, stream): 90*635a8641SAndroid Build Coastguard Worker """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used 91*635a8641SAndroid Build Coastguard Worker to filter tokens returned. This method has to return an iterable of 92*635a8641SAndroid Build Coastguard Worker :class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a 93*635a8641SAndroid Build Coastguard Worker :class:`~jinja2.lexer.TokenStream`. 94*635a8641SAndroid Build Coastguard Worker 95*635a8641SAndroid Build Coastguard Worker In the `ext` folder of the Jinja2 source distribution there is a file 96*635a8641SAndroid Build Coastguard Worker called `inlinegettext.py` which implements a filter that utilizes this 97*635a8641SAndroid Build Coastguard Worker method. 98*635a8641SAndroid Build Coastguard Worker """ 99*635a8641SAndroid Build Coastguard Worker return stream 100*635a8641SAndroid Build Coastguard Worker 101*635a8641SAndroid Build Coastguard Worker def parse(self, parser): 102*635a8641SAndroid Build Coastguard Worker """If any of the :attr:`tags` matched this method is called with the 103*635a8641SAndroid Build Coastguard Worker parser as first argument. The token the parser stream is pointing at 104*635a8641SAndroid Build Coastguard Worker is the name token that matched. This method has to return one or a 105*635a8641SAndroid Build Coastguard Worker list of multiple nodes. 106*635a8641SAndroid Build Coastguard Worker """ 107*635a8641SAndroid Build Coastguard Worker raise NotImplementedError() 108*635a8641SAndroid Build Coastguard Worker 109*635a8641SAndroid Build Coastguard Worker def attr(self, name, lineno=None): 110*635a8641SAndroid Build Coastguard Worker """Return an attribute node for the current extension. This is useful 111*635a8641SAndroid Build Coastguard Worker to pass constants on extensions to generated template code. 112*635a8641SAndroid Build Coastguard Worker 113*635a8641SAndroid Build Coastguard Worker :: 114*635a8641SAndroid Build Coastguard Worker 115*635a8641SAndroid Build Coastguard Worker self.attr('_my_attribute', lineno=lineno) 116*635a8641SAndroid Build Coastguard Worker """ 117*635a8641SAndroid Build Coastguard Worker return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno) 118*635a8641SAndroid Build Coastguard Worker 119*635a8641SAndroid Build Coastguard Worker def call_method(self, name, args=None, kwargs=None, dyn_args=None, 120*635a8641SAndroid Build Coastguard Worker dyn_kwargs=None, lineno=None): 121*635a8641SAndroid Build Coastguard Worker """Call a method of the extension. This is a shortcut for 122*635a8641SAndroid Build Coastguard Worker :meth:`attr` + :class:`jinja2.nodes.Call`. 123*635a8641SAndroid Build Coastguard Worker """ 124*635a8641SAndroid Build Coastguard Worker if args is None: 125*635a8641SAndroid Build Coastguard Worker args = [] 126*635a8641SAndroid Build Coastguard Worker if kwargs is None: 127*635a8641SAndroid Build Coastguard Worker kwargs = [] 128*635a8641SAndroid Build Coastguard Worker return nodes.Call(self.attr(name, lineno=lineno), args, kwargs, 129*635a8641SAndroid Build Coastguard Worker dyn_args, dyn_kwargs, lineno=lineno) 130*635a8641SAndroid Build Coastguard Worker 131*635a8641SAndroid Build Coastguard Worker 132*635a8641SAndroid Build Coastguard Worker@contextfunction 133*635a8641SAndroid Build Coastguard Workerdef _gettext_alias(__context, *args, **kwargs): 134*635a8641SAndroid Build Coastguard Worker return __context.call(__context.resolve('gettext'), *args, **kwargs) 135*635a8641SAndroid Build Coastguard Worker 136*635a8641SAndroid Build Coastguard Worker 137*635a8641SAndroid Build Coastguard Workerdef _make_new_gettext(func): 138*635a8641SAndroid Build Coastguard Worker @contextfunction 139*635a8641SAndroid Build Coastguard Worker def gettext(__context, __string, **variables): 140*635a8641SAndroid Build Coastguard Worker rv = __context.call(func, __string) 141*635a8641SAndroid Build Coastguard Worker if __context.eval_ctx.autoescape: 142*635a8641SAndroid Build Coastguard Worker rv = Markup(rv) 143*635a8641SAndroid Build Coastguard Worker return rv % variables 144*635a8641SAndroid Build Coastguard Worker return gettext 145*635a8641SAndroid Build Coastguard Worker 146*635a8641SAndroid Build Coastguard Worker 147*635a8641SAndroid Build Coastguard Workerdef _make_new_ngettext(func): 148*635a8641SAndroid Build Coastguard Worker @contextfunction 149*635a8641SAndroid Build Coastguard Worker def ngettext(__context, __singular, __plural, __num, **variables): 150*635a8641SAndroid Build Coastguard Worker variables.setdefault('num', __num) 151*635a8641SAndroid Build Coastguard Worker rv = __context.call(func, __singular, __plural, __num) 152*635a8641SAndroid Build Coastguard Worker if __context.eval_ctx.autoescape: 153*635a8641SAndroid Build Coastguard Worker rv = Markup(rv) 154*635a8641SAndroid Build Coastguard Worker return rv % variables 155*635a8641SAndroid Build Coastguard Worker return ngettext 156*635a8641SAndroid Build Coastguard Worker 157*635a8641SAndroid Build Coastguard Worker 158*635a8641SAndroid Build Coastguard Workerclass InternationalizationExtension(Extension): 159*635a8641SAndroid Build Coastguard Worker """This extension adds gettext support to Jinja2.""" 160*635a8641SAndroid Build Coastguard Worker tags = set(['trans']) 161*635a8641SAndroid Build Coastguard Worker 162*635a8641SAndroid Build Coastguard Worker # TODO: the i18n extension is currently reevaluating values in a few 163*635a8641SAndroid Build Coastguard Worker # situations. Take this example: 164*635a8641SAndroid Build Coastguard Worker # {% trans count=something() %}{{ count }} foo{% pluralize 165*635a8641SAndroid Build Coastguard Worker # %}{{ count }} fooss{% endtrans %} 166*635a8641SAndroid Build Coastguard Worker # something is called twice here. One time for the gettext value and 167*635a8641SAndroid Build Coastguard Worker # the other time for the n-parameter of the ngettext function. 168*635a8641SAndroid Build Coastguard Worker 169*635a8641SAndroid Build Coastguard Worker def __init__(self, environment): 170*635a8641SAndroid Build Coastguard Worker Extension.__init__(self, environment) 171*635a8641SAndroid Build Coastguard Worker environment.globals['_'] = _gettext_alias 172*635a8641SAndroid Build Coastguard Worker environment.extend( 173*635a8641SAndroid Build Coastguard Worker install_gettext_translations=self._install, 174*635a8641SAndroid Build Coastguard Worker install_null_translations=self._install_null, 175*635a8641SAndroid Build Coastguard Worker install_gettext_callables=self._install_callables, 176*635a8641SAndroid Build Coastguard Worker uninstall_gettext_translations=self._uninstall, 177*635a8641SAndroid Build Coastguard Worker extract_translations=self._extract, 178*635a8641SAndroid Build Coastguard Worker newstyle_gettext=False 179*635a8641SAndroid Build Coastguard Worker ) 180*635a8641SAndroid Build Coastguard Worker 181*635a8641SAndroid Build Coastguard Worker def _install(self, translations, newstyle=None): 182*635a8641SAndroid Build Coastguard Worker gettext = getattr(translations, 'ugettext', None) 183*635a8641SAndroid Build Coastguard Worker if gettext is None: 184*635a8641SAndroid Build Coastguard Worker gettext = translations.gettext 185*635a8641SAndroid Build Coastguard Worker ngettext = getattr(translations, 'ungettext', None) 186*635a8641SAndroid Build Coastguard Worker if ngettext is None: 187*635a8641SAndroid Build Coastguard Worker ngettext = translations.ngettext 188*635a8641SAndroid Build Coastguard Worker self._install_callables(gettext, ngettext, newstyle) 189*635a8641SAndroid Build Coastguard Worker 190*635a8641SAndroid Build Coastguard Worker def _install_null(self, newstyle=None): 191*635a8641SAndroid Build Coastguard Worker self._install_callables( 192*635a8641SAndroid Build Coastguard Worker lambda x: x, 193*635a8641SAndroid Build Coastguard Worker lambda s, p, n: (n != 1 and (p,) or (s,))[0], 194*635a8641SAndroid Build Coastguard Worker newstyle 195*635a8641SAndroid Build Coastguard Worker ) 196*635a8641SAndroid Build Coastguard Worker 197*635a8641SAndroid Build Coastguard Worker def _install_callables(self, gettext, ngettext, newstyle=None): 198*635a8641SAndroid Build Coastguard Worker if newstyle is not None: 199*635a8641SAndroid Build Coastguard Worker self.environment.newstyle_gettext = newstyle 200*635a8641SAndroid Build Coastguard Worker if self.environment.newstyle_gettext: 201*635a8641SAndroid Build Coastguard Worker gettext = _make_new_gettext(gettext) 202*635a8641SAndroid Build Coastguard Worker ngettext = _make_new_ngettext(ngettext) 203*635a8641SAndroid Build Coastguard Worker self.environment.globals.update( 204*635a8641SAndroid Build Coastguard Worker gettext=gettext, 205*635a8641SAndroid Build Coastguard Worker ngettext=ngettext 206*635a8641SAndroid Build Coastguard Worker ) 207*635a8641SAndroid Build Coastguard Worker 208*635a8641SAndroid Build Coastguard Worker def _uninstall(self, translations): 209*635a8641SAndroid Build Coastguard Worker for key in 'gettext', 'ngettext': 210*635a8641SAndroid Build Coastguard Worker self.environment.globals.pop(key, None) 211*635a8641SAndroid Build Coastguard Worker 212*635a8641SAndroid Build Coastguard Worker def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS): 213*635a8641SAndroid Build Coastguard Worker if isinstance(source, string_types): 214*635a8641SAndroid Build Coastguard Worker source = self.environment.parse(source) 215*635a8641SAndroid Build Coastguard Worker return extract_from_ast(source, gettext_functions) 216*635a8641SAndroid Build Coastguard Worker 217*635a8641SAndroid Build Coastguard Worker def parse(self, parser): 218*635a8641SAndroid Build Coastguard Worker """Parse a translatable tag.""" 219*635a8641SAndroid Build Coastguard Worker lineno = next(parser.stream).lineno 220*635a8641SAndroid Build Coastguard Worker num_called_num = False 221*635a8641SAndroid Build Coastguard Worker 222*635a8641SAndroid Build Coastguard Worker # find all the variables referenced. Additionally a variable can be 223*635a8641SAndroid Build Coastguard Worker # defined in the body of the trans block too, but this is checked at 224*635a8641SAndroid Build Coastguard Worker # a later state. 225*635a8641SAndroid Build Coastguard Worker plural_expr = None 226*635a8641SAndroid Build Coastguard Worker plural_expr_assignment = None 227*635a8641SAndroid Build Coastguard Worker variables = {} 228*635a8641SAndroid Build Coastguard Worker trimmed = None 229*635a8641SAndroid Build Coastguard Worker while parser.stream.current.type != 'block_end': 230*635a8641SAndroid Build Coastguard Worker if variables: 231*635a8641SAndroid Build Coastguard Worker parser.stream.expect('comma') 232*635a8641SAndroid Build Coastguard Worker 233*635a8641SAndroid Build Coastguard Worker # skip colon for python compatibility 234*635a8641SAndroid Build Coastguard Worker if parser.stream.skip_if('colon'): 235*635a8641SAndroid Build Coastguard Worker break 236*635a8641SAndroid Build Coastguard Worker 237*635a8641SAndroid Build Coastguard Worker name = parser.stream.expect('name') 238*635a8641SAndroid Build Coastguard Worker if name.value in variables: 239*635a8641SAndroid Build Coastguard Worker parser.fail('translatable variable %r defined twice.' % 240*635a8641SAndroid Build Coastguard Worker name.value, name.lineno, 241*635a8641SAndroid Build Coastguard Worker exc=TemplateAssertionError) 242*635a8641SAndroid Build Coastguard Worker 243*635a8641SAndroid Build Coastguard Worker # expressions 244*635a8641SAndroid Build Coastguard Worker if parser.stream.current.type == 'assign': 245*635a8641SAndroid Build Coastguard Worker next(parser.stream) 246*635a8641SAndroid Build Coastguard Worker variables[name.value] = var = parser.parse_expression() 247*635a8641SAndroid Build Coastguard Worker elif trimmed is None and name.value in ('trimmed', 'notrimmed'): 248*635a8641SAndroid Build Coastguard Worker trimmed = name.value == 'trimmed' 249*635a8641SAndroid Build Coastguard Worker continue 250*635a8641SAndroid Build Coastguard Worker else: 251*635a8641SAndroid Build Coastguard Worker variables[name.value] = var = nodes.Name(name.value, 'load') 252*635a8641SAndroid Build Coastguard Worker 253*635a8641SAndroid Build Coastguard Worker if plural_expr is None: 254*635a8641SAndroid Build Coastguard Worker if isinstance(var, nodes.Call): 255*635a8641SAndroid Build Coastguard Worker plural_expr = nodes.Name('_trans', 'load') 256*635a8641SAndroid Build Coastguard Worker variables[name.value] = plural_expr 257*635a8641SAndroid Build Coastguard Worker plural_expr_assignment = nodes.Assign( 258*635a8641SAndroid Build Coastguard Worker nodes.Name('_trans', 'store'), var) 259*635a8641SAndroid Build Coastguard Worker else: 260*635a8641SAndroid Build Coastguard Worker plural_expr = var 261*635a8641SAndroid Build Coastguard Worker num_called_num = name.value == 'num' 262*635a8641SAndroid Build Coastguard Worker 263*635a8641SAndroid Build Coastguard Worker parser.stream.expect('block_end') 264*635a8641SAndroid Build Coastguard Worker 265*635a8641SAndroid Build Coastguard Worker plural = None 266*635a8641SAndroid Build Coastguard Worker have_plural = False 267*635a8641SAndroid Build Coastguard Worker referenced = set() 268*635a8641SAndroid Build Coastguard Worker 269*635a8641SAndroid Build Coastguard Worker # now parse until endtrans or pluralize 270*635a8641SAndroid Build Coastguard Worker singular_names, singular = self._parse_block(parser, True) 271*635a8641SAndroid Build Coastguard Worker if singular_names: 272*635a8641SAndroid Build Coastguard Worker referenced.update(singular_names) 273*635a8641SAndroid Build Coastguard Worker if plural_expr is None: 274*635a8641SAndroid Build Coastguard Worker plural_expr = nodes.Name(singular_names[0], 'load') 275*635a8641SAndroid Build Coastguard Worker num_called_num = singular_names[0] == 'num' 276*635a8641SAndroid Build Coastguard Worker 277*635a8641SAndroid Build Coastguard Worker # if we have a pluralize block, we parse that too 278*635a8641SAndroid Build Coastguard Worker if parser.stream.current.test('name:pluralize'): 279*635a8641SAndroid Build Coastguard Worker have_plural = True 280*635a8641SAndroid Build Coastguard Worker next(parser.stream) 281*635a8641SAndroid Build Coastguard Worker if parser.stream.current.type != 'block_end': 282*635a8641SAndroid Build Coastguard Worker name = parser.stream.expect('name') 283*635a8641SAndroid Build Coastguard Worker if name.value not in variables: 284*635a8641SAndroid Build Coastguard Worker parser.fail('unknown variable %r for pluralization' % 285*635a8641SAndroid Build Coastguard Worker name.value, name.lineno, 286*635a8641SAndroid Build Coastguard Worker exc=TemplateAssertionError) 287*635a8641SAndroid Build Coastguard Worker plural_expr = variables[name.value] 288*635a8641SAndroid Build Coastguard Worker num_called_num = name.value == 'num' 289*635a8641SAndroid Build Coastguard Worker parser.stream.expect('block_end') 290*635a8641SAndroid Build Coastguard Worker plural_names, plural = self._parse_block(parser, False) 291*635a8641SAndroid Build Coastguard Worker next(parser.stream) 292*635a8641SAndroid Build Coastguard Worker referenced.update(plural_names) 293*635a8641SAndroid Build Coastguard Worker else: 294*635a8641SAndroid Build Coastguard Worker next(parser.stream) 295*635a8641SAndroid Build Coastguard Worker 296*635a8641SAndroid Build Coastguard Worker # register free names as simple name expressions 297*635a8641SAndroid Build Coastguard Worker for var in referenced: 298*635a8641SAndroid Build Coastguard Worker if var not in variables: 299*635a8641SAndroid Build Coastguard Worker variables[var] = nodes.Name(var, 'load') 300*635a8641SAndroid Build Coastguard Worker 301*635a8641SAndroid Build Coastguard Worker if not have_plural: 302*635a8641SAndroid Build Coastguard Worker plural_expr = None 303*635a8641SAndroid Build Coastguard Worker elif plural_expr is None: 304*635a8641SAndroid Build Coastguard Worker parser.fail('pluralize without variables', lineno) 305*635a8641SAndroid Build Coastguard Worker 306*635a8641SAndroid Build Coastguard Worker if trimmed is None: 307*635a8641SAndroid Build Coastguard Worker trimmed = self.environment.policies['ext.i18n.trimmed'] 308*635a8641SAndroid Build Coastguard Worker if trimmed: 309*635a8641SAndroid Build Coastguard Worker singular = self._trim_whitespace(singular) 310*635a8641SAndroid Build Coastguard Worker if plural: 311*635a8641SAndroid Build Coastguard Worker plural = self._trim_whitespace(plural) 312*635a8641SAndroid Build Coastguard Worker 313*635a8641SAndroid Build Coastguard Worker node = self._make_node(singular, plural, variables, plural_expr, 314*635a8641SAndroid Build Coastguard Worker bool(referenced), 315*635a8641SAndroid Build Coastguard Worker num_called_num and have_plural) 316*635a8641SAndroid Build Coastguard Worker node.set_lineno(lineno) 317*635a8641SAndroid Build Coastguard Worker if plural_expr_assignment is not None: 318*635a8641SAndroid Build Coastguard Worker return [plural_expr_assignment, node] 319*635a8641SAndroid Build Coastguard Worker else: 320*635a8641SAndroid Build Coastguard Worker return node 321*635a8641SAndroid Build Coastguard Worker 322*635a8641SAndroid Build Coastguard Worker def _trim_whitespace(self, string, _ws_re=re.compile(r'\s*\n\s*')): 323*635a8641SAndroid Build Coastguard Worker return _ws_re.sub(' ', string.strip()) 324*635a8641SAndroid Build Coastguard Worker 325*635a8641SAndroid Build Coastguard Worker def _parse_block(self, parser, allow_pluralize): 326*635a8641SAndroid Build Coastguard Worker """Parse until the next block tag with a given name.""" 327*635a8641SAndroid Build Coastguard Worker referenced = [] 328*635a8641SAndroid Build Coastguard Worker buf = [] 329*635a8641SAndroid Build Coastguard Worker while 1: 330*635a8641SAndroid Build Coastguard Worker if parser.stream.current.type == 'data': 331*635a8641SAndroid Build Coastguard Worker buf.append(parser.stream.current.value.replace('%', '%%')) 332*635a8641SAndroid Build Coastguard Worker next(parser.stream) 333*635a8641SAndroid Build Coastguard Worker elif parser.stream.current.type == 'variable_begin': 334*635a8641SAndroid Build Coastguard Worker next(parser.stream) 335*635a8641SAndroid Build Coastguard Worker name = parser.stream.expect('name').value 336*635a8641SAndroid Build Coastguard Worker referenced.append(name) 337*635a8641SAndroid Build Coastguard Worker buf.append('%%(%s)s' % name) 338*635a8641SAndroid Build Coastguard Worker parser.stream.expect('variable_end') 339*635a8641SAndroid Build Coastguard Worker elif parser.stream.current.type == 'block_begin': 340*635a8641SAndroid Build Coastguard Worker next(parser.stream) 341*635a8641SAndroid Build Coastguard Worker if parser.stream.current.test('name:endtrans'): 342*635a8641SAndroid Build Coastguard Worker break 343*635a8641SAndroid Build Coastguard Worker elif parser.stream.current.test('name:pluralize'): 344*635a8641SAndroid Build Coastguard Worker if allow_pluralize: 345*635a8641SAndroid Build Coastguard Worker break 346*635a8641SAndroid Build Coastguard Worker parser.fail('a translatable section can have only one ' 347*635a8641SAndroid Build Coastguard Worker 'pluralize section') 348*635a8641SAndroid Build Coastguard Worker parser.fail('control structures in translatable sections are ' 349*635a8641SAndroid Build Coastguard Worker 'not allowed') 350*635a8641SAndroid Build Coastguard Worker elif parser.stream.eos: 351*635a8641SAndroid Build Coastguard Worker parser.fail('unclosed translation block') 352*635a8641SAndroid Build Coastguard Worker else: 353*635a8641SAndroid Build Coastguard Worker assert False, 'internal parser error' 354*635a8641SAndroid Build Coastguard Worker 355*635a8641SAndroid Build Coastguard Worker return referenced, concat(buf) 356*635a8641SAndroid Build Coastguard Worker 357*635a8641SAndroid Build Coastguard Worker def _make_node(self, singular, plural, variables, plural_expr, 358*635a8641SAndroid Build Coastguard Worker vars_referenced, num_called_num): 359*635a8641SAndroid Build Coastguard Worker """Generates a useful node from the data provided.""" 360*635a8641SAndroid Build Coastguard Worker # no variables referenced? no need to escape for old style 361*635a8641SAndroid Build Coastguard Worker # gettext invocations only if there are vars. 362*635a8641SAndroid Build Coastguard Worker if not vars_referenced and not self.environment.newstyle_gettext: 363*635a8641SAndroid Build Coastguard Worker singular = singular.replace('%%', '%') 364*635a8641SAndroid Build Coastguard Worker if plural: 365*635a8641SAndroid Build Coastguard Worker plural = plural.replace('%%', '%') 366*635a8641SAndroid Build Coastguard Worker 367*635a8641SAndroid Build Coastguard Worker # singular only: 368*635a8641SAndroid Build Coastguard Worker if plural_expr is None: 369*635a8641SAndroid Build Coastguard Worker gettext = nodes.Name('gettext', 'load') 370*635a8641SAndroid Build Coastguard Worker node = nodes.Call(gettext, [nodes.Const(singular)], 371*635a8641SAndroid Build Coastguard Worker [], None, None) 372*635a8641SAndroid Build Coastguard Worker 373*635a8641SAndroid Build Coastguard Worker # singular and plural 374*635a8641SAndroid Build Coastguard Worker else: 375*635a8641SAndroid Build Coastguard Worker ngettext = nodes.Name('ngettext', 'load') 376*635a8641SAndroid Build Coastguard Worker node = nodes.Call(ngettext, [ 377*635a8641SAndroid Build Coastguard Worker nodes.Const(singular), 378*635a8641SAndroid Build Coastguard Worker nodes.Const(plural), 379*635a8641SAndroid Build Coastguard Worker plural_expr 380*635a8641SAndroid Build Coastguard Worker ], [], None, None) 381*635a8641SAndroid Build Coastguard Worker 382*635a8641SAndroid Build Coastguard Worker # in case newstyle gettext is used, the method is powerful 383*635a8641SAndroid Build Coastguard Worker # enough to handle the variable expansion and autoescape 384*635a8641SAndroid Build Coastguard Worker # handling itself 385*635a8641SAndroid Build Coastguard Worker if self.environment.newstyle_gettext: 386*635a8641SAndroid Build Coastguard Worker for key, value in iteritems(variables): 387*635a8641SAndroid Build Coastguard Worker # the function adds that later anyways in case num was 388*635a8641SAndroid Build Coastguard Worker # called num, so just skip it. 389*635a8641SAndroid Build Coastguard Worker if num_called_num and key == 'num': 390*635a8641SAndroid Build Coastguard Worker continue 391*635a8641SAndroid Build Coastguard Worker node.kwargs.append(nodes.Keyword(key, value)) 392*635a8641SAndroid Build Coastguard Worker 393*635a8641SAndroid Build Coastguard Worker # otherwise do that here 394*635a8641SAndroid Build Coastguard Worker else: 395*635a8641SAndroid Build Coastguard Worker # mark the return value as safe if we are in an 396*635a8641SAndroid Build Coastguard Worker # environment with autoescaping turned on 397*635a8641SAndroid Build Coastguard Worker node = nodes.MarkSafeIfAutoescape(node) 398*635a8641SAndroid Build Coastguard Worker if variables: 399*635a8641SAndroid Build Coastguard Worker node = nodes.Mod(node, nodes.Dict([ 400*635a8641SAndroid Build Coastguard Worker nodes.Pair(nodes.Const(key), value) 401*635a8641SAndroid Build Coastguard Worker for key, value in variables.items() 402*635a8641SAndroid Build Coastguard Worker ])) 403*635a8641SAndroid Build Coastguard Worker return nodes.Output([node]) 404*635a8641SAndroid Build Coastguard Worker 405*635a8641SAndroid Build Coastguard Worker 406*635a8641SAndroid Build Coastguard Workerclass ExprStmtExtension(Extension): 407*635a8641SAndroid Build Coastguard Worker """Adds a `do` tag to Jinja2 that works like the print statement just 408*635a8641SAndroid Build Coastguard Worker that it doesn't print the return value. 409*635a8641SAndroid Build Coastguard Worker """ 410*635a8641SAndroid Build Coastguard Worker tags = set(['do']) 411*635a8641SAndroid Build Coastguard Worker 412*635a8641SAndroid Build Coastguard Worker def parse(self, parser): 413*635a8641SAndroid Build Coastguard Worker node = nodes.ExprStmt(lineno=next(parser.stream).lineno) 414*635a8641SAndroid Build Coastguard Worker node.node = parser.parse_tuple() 415*635a8641SAndroid Build Coastguard Worker return node 416*635a8641SAndroid Build Coastguard Worker 417*635a8641SAndroid Build Coastguard Worker 418*635a8641SAndroid Build Coastguard Workerclass LoopControlExtension(Extension): 419*635a8641SAndroid Build Coastguard Worker """Adds break and continue to the template engine.""" 420*635a8641SAndroid Build Coastguard Worker tags = set(['break', 'continue']) 421*635a8641SAndroid Build Coastguard Worker 422*635a8641SAndroid Build Coastguard Worker def parse(self, parser): 423*635a8641SAndroid Build Coastguard Worker token = next(parser.stream) 424*635a8641SAndroid Build Coastguard Worker if token.value == 'break': 425*635a8641SAndroid Build Coastguard Worker return nodes.Break(lineno=token.lineno) 426*635a8641SAndroid Build Coastguard Worker return nodes.Continue(lineno=token.lineno) 427*635a8641SAndroid Build Coastguard Worker 428*635a8641SAndroid Build Coastguard Worker 429*635a8641SAndroid Build Coastguard Workerclass WithExtension(Extension): 430*635a8641SAndroid Build Coastguard Worker pass 431*635a8641SAndroid Build Coastguard Worker 432*635a8641SAndroid Build Coastguard Worker 433*635a8641SAndroid Build Coastguard Workerclass AutoEscapeExtension(Extension): 434*635a8641SAndroid Build Coastguard Worker pass 435*635a8641SAndroid Build Coastguard Worker 436*635a8641SAndroid Build Coastguard Worker 437*635a8641SAndroid Build Coastguard Workerdef extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS, 438*635a8641SAndroid Build Coastguard Worker babel_style=True): 439*635a8641SAndroid Build Coastguard Worker """Extract localizable strings from the given template node. Per 440*635a8641SAndroid Build Coastguard Worker default this function returns matches in babel style that means non string 441*635a8641SAndroid Build Coastguard Worker parameters as well as keyword arguments are returned as `None`. This 442*635a8641SAndroid Build Coastguard Worker allows Babel to figure out what you really meant if you are using 443*635a8641SAndroid Build Coastguard Worker gettext functions that allow keyword arguments for placeholder expansion. 444*635a8641SAndroid Build Coastguard Worker If you don't want that behavior set the `babel_style` parameter to `False` 445*635a8641SAndroid Build Coastguard Worker which causes only strings to be returned and parameters are always stored 446*635a8641SAndroid Build Coastguard Worker in tuples. As a consequence invalid gettext calls (calls without a single 447*635a8641SAndroid Build Coastguard Worker string parameter or string parameters after non-string parameters) are 448*635a8641SAndroid Build Coastguard Worker skipped. 449*635a8641SAndroid Build Coastguard Worker 450*635a8641SAndroid Build Coastguard Worker This example explains the behavior: 451*635a8641SAndroid Build Coastguard Worker 452*635a8641SAndroid Build Coastguard Worker >>> from jinja2 import Environment 453*635a8641SAndroid Build Coastguard Worker >>> env = Environment() 454*635a8641SAndroid Build Coastguard Worker >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}') 455*635a8641SAndroid Build Coastguard Worker >>> list(extract_from_ast(node)) 456*635a8641SAndroid Build Coastguard Worker [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))] 457*635a8641SAndroid Build Coastguard Worker >>> list(extract_from_ast(node, babel_style=False)) 458*635a8641SAndroid Build Coastguard Worker [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))] 459*635a8641SAndroid Build Coastguard Worker 460*635a8641SAndroid Build Coastguard Worker For every string found this function yields a ``(lineno, function, 461*635a8641SAndroid Build Coastguard Worker message)`` tuple, where: 462*635a8641SAndroid Build Coastguard Worker 463*635a8641SAndroid Build Coastguard Worker * ``lineno`` is the number of the line on which the string was found, 464*635a8641SAndroid Build Coastguard Worker * ``function`` is the name of the ``gettext`` function used (if the 465*635a8641SAndroid Build Coastguard Worker string was extracted from embedded Python code), and 466*635a8641SAndroid Build Coastguard Worker * ``message`` is the string itself (a ``unicode`` object, or a tuple 467*635a8641SAndroid Build Coastguard Worker of ``unicode`` objects for functions with multiple string arguments). 468*635a8641SAndroid Build Coastguard Worker 469*635a8641SAndroid Build Coastguard Worker This extraction function operates on the AST and is because of that unable 470*635a8641SAndroid Build Coastguard Worker to extract any comments. For comment support you have to use the babel 471*635a8641SAndroid Build Coastguard Worker extraction interface or extract comments yourself. 472*635a8641SAndroid Build Coastguard Worker """ 473*635a8641SAndroid Build Coastguard Worker for node in node.find_all(nodes.Call): 474*635a8641SAndroid Build Coastguard Worker if not isinstance(node.node, nodes.Name) or \ 475*635a8641SAndroid Build Coastguard Worker node.node.name not in gettext_functions: 476*635a8641SAndroid Build Coastguard Worker continue 477*635a8641SAndroid Build Coastguard Worker 478*635a8641SAndroid Build Coastguard Worker strings = [] 479*635a8641SAndroid Build Coastguard Worker for arg in node.args: 480*635a8641SAndroid Build Coastguard Worker if isinstance(arg, nodes.Const) and \ 481*635a8641SAndroid Build Coastguard Worker isinstance(arg.value, string_types): 482*635a8641SAndroid Build Coastguard Worker strings.append(arg.value) 483*635a8641SAndroid Build Coastguard Worker else: 484*635a8641SAndroid Build Coastguard Worker strings.append(None) 485*635a8641SAndroid Build Coastguard Worker 486*635a8641SAndroid Build Coastguard Worker for arg in node.kwargs: 487*635a8641SAndroid Build Coastguard Worker strings.append(None) 488*635a8641SAndroid Build Coastguard Worker if node.dyn_args is not None: 489*635a8641SAndroid Build Coastguard Worker strings.append(None) 490*635a8641SAndroid Build Coastguard Worker if node.dyn_kwargs is not None: 491*635a8641SAndroid Build Coastguard Worker strings.append(None) 492*635a8641SAndroid Build Coastguard Worker 493*635a8641SAndroid Build Coastguard Worker if not babel_style: 494*635a8641SAndroid Build Coastguard Worker strings = tuple(x for x in strings if x is not None) 495*635a8641SAndroid Build Coastguard Worker if not strings: 496*635a8641SAndroid Build Coastguard Worker continue 497*635a8641SAndroid Build Coastguard Worker else: 498*635a8641SAndroid Build Coastguard Worker if len(strings) == 1: 499*635a8641SAndroid Build Coastguard Worker strings = strings[0] 500*635a8641SAndroid Build Coastguard Worker else: 501*635a8641SAndroid Build Coastguard Worker strings = tuple(strings) 502*635a8641SAndroid Build Coastguard Worker yield node.lineno, node.node.name, strings 503*635a8641SAndroid Build Coastguard Worker 504*635a8641SAndroid Build Coastguard Worker 505*635a8641SAndroid Build Coastguard Workerclass _CommentFinder(object): 506*635a8641SAndroid Build Coastguard Worker """Helper class to find comments in a token stream. Can only 507*635a8641SAndroid Build Coastguard Worker find comments for gettext calls forwards. Once the comment 508*635a8641SAndroid Build Coastguard Worker from line 4 is found, a comment for line 1 will not return a 509*635a8641SAndroid Build Coastguard Worker usable value. 510*635a8641SAndroid Build Coastguard Worker """ 511*635a8641SAndroid Build Coastguard Worker 512*635a8641SAndroid Build Coastguard Worker def __init__(self, tokens, comment_tags): 513*635a8641SAndroid Build Coastguard Worker self.tokens = tokens 514*635a8641SAndroid Build Coastguard Worker self.comment_tags = comment_tags 515*635a8641SAndroid Build Coastguard Worker self.offset = 0 516*635a8641SAndroid Build Coastguard Worker self.last_lineno = 0 517*635a8641SAndroid Build Coastguard Worker 518*635a8641SAndroid Build Coastguard Worker def find_backwards(self, offset): 519*635a8641SAndroid Build Coastguard Worker try: 520*635a8641SAndroid Build Coastguard Worker for _, token_type, token_value in \ 521*635a8641SAndroid Build Coastguard Worker reversed(self.tokens[self.offset:offset]): 522*635a8641SAndroid Build Coastguard Worker if token_type in ('comment', 'linecomment'): 523*635a8641SAndroid Build Coastguard Worker try: 524*635a8641SAndroid Build Coastguard Worker prefix, comment = token_value.split(None, 1) 525*635a8641SAndroid Build Coastguard Worker except ValueError: 526*635a8641SAndroid Build Coastguard Worker continue 527*635a8641SAndroid Build Coastguard Worker if prefix in self.comment_tags: 528*635a8641SAndroid Build Coastguard Worker return [comment.rstrip()] 529*635a8641SAndroid Build Coastguard Worker return [] 530*635a8641SAndroid Build Coastguard Worker finally: 531*635a8641SAndroid Build Coastguard Worker self.offset = offset 532*635a8641SAndroid Build Coastguard Worker 533*635a8641SAndroid Build Coastguard Worker def find_comments(self, lineno): 534*635a8641SAndroid Build Coastguard Worker if not self.comment_tags or self.last_lineno > lineno: 535*635a8641SAndroid Build Coastguard Worker return [] 536*635a8641SAndroid Build Coastguard Worker for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]): 537*635a8641SAndroid Build Coastguard Worker if token_lineno > lineno: 538*635a8641SAndroid Build Coastguard Worker return self.find_backwards(self.offset + idx) 539*635a8641SAndroid Build Coastguard Worker return self.find_backwards(len(self.tokens)) 540*635a8641SAndroid Build Coastguard Worker 541*635a8641SAndroid Build Coastguard Worker 542*635a8641SAndroid Build Coastguard Workerdef babel_extract(fileobj, keywords, comment_tags, options): 543*635a8641SAndroid Build Coastguard Worker """Babel extraction method for Jinja templates. 544*635a8641SAndroid Build Coastguard Worker 545*635a8641SAndroid Build Coastguard Worker .. versionchanged:: 2.3 546*635a8641SAndroid Build Coastguard Worker Basic support for translation comments was added. If `comment_tags` 547*635a8641SAndroid Build Coastguard Worker is now set to a list of keywords for extraction, the extractor will 548*635a8641SAndroid Build Coastguard Worker try to find the best preceeding comment that begins with one of the 549*635a8641SAndroid Build Coastguard Worker keywords. For best results, make sure to not have more than one 550*635a8641SAndroid Build Coastguard Worker gettext call in one line of code and the matching comment in the 551*635a8641SAndroid Build Coastguard Worker same line or the line before. 552*635a8641SAndroid Build Coastguard Worker 553*635a8641SAndroid Build Coastguard Worker .. versionchanged:: 2.5.1 554*635a8641SAndroid Build Coastguard Worker The `newstyle_gettext` flag can be set to `True` to enable newstyle 555*635a8641SAndroid Build Coastguard Worker gettext calls. 556*635a8641SAndroid Build Coastguard Worker 557*635a8641SAndroid Build Coastguard Worker .. versionchanged:: 2.7 558*635a8641SAndroid Build Coastguard Worker A `silent` option can now be provided. If set to `False` template 559*635a8641SAndroid Build Coastguard Worker syntax errors are propagated instead of being ignored. 560*635a8641SAndroid Build Coastguard Worker 561*635a8641SAndroid Build Coastguard Worker :param fileobj: the file-like object the messages should be extracted from 562*635a8641SAndroid Build Coastguard Worker :param keywords: a list of keywords (i.e. function names) that should be 563*635a8641SAndroid Build Coastguard Worker recognized as translation functions 564*635a8641SAndroid Build Coastguard Worker :param comment_tags: a list of translator tags to search for and include 565*635a8641SAndroid Build Coastguard Worker in the results. 566*635a8641SAndroid Build Coastguard Worker :param options: a dictionary of additional options (optional) 567*635a8641SAndroid Build Coastguard Worker :return: an iterator over ``(lineno, funcname, message, comments)`` tuples. 568*635a8641SAndroid Build Coastguard Worker (comments will be empty currently) 569*635a8641SAndroid Build Coastguard Worker """ 570*635a8641SAndroid Build Coastguard Worker extensions = set() 571*635a8641SAndroid Build Coastguard Worker for extension in options.get('extensions', '').split(','): 572*635a8641SAndroid Build Coastguard Worker extension = extension.strip() 573*635a8641SAndroid Build Coastguard Worker if not extension: 574*635a8641SAndroid Build Coastguard Worker continue 575*635a8641SAndroid Build Coastguard Worker extensions.add(import_string(extension)) 576*635a8641SAndroid Build Coastguard Worker if InternationalizationExtension not in extensions: 577*635a8641SAndroid Build Coastguard Worker extensions.add(InternationalizationExtension) 578*635a8641SAndroid Build Coastguard Worker 579*635a8641SAndroid Build Coastguard Worker def getbool(options, key, default=False): 580*635a8641SAndroid Build Coastguard Worker return options.get(key, str(default)).lower() in \ 581*635a8641SAndroid Build Coastguard Worker ('1', 'on', 'yes', 'true') 582*635a8641SAndroid Build Coastguard Worker 583*635a8641SAndroid Build Coastguard Worker silent = getbool(options, 'silent', True) 584*635a8641SAndroid Build Coastguard Worker environment = Environment( 585*635a8641SAndroid Build Coastguard Worker options.get('block_start_string', BLOCK_START_STRING), 586*635a8641SAndroid Build Coastguard Worker options.get('block_end_string', BLOCK_END_STRING), 587*635a8641SAndroid Build Coastguard Worker options.get('variable_start_string', VARIABLE_START_STRING), 588*635a8641SAndroid Build Coastguard Worker options.get('variable_end_string', VARIABLE_END_STRING), 589*635a8641SAndroid Build Coastguard Worker options.get('comment_start_string', COMMENT_START_STRING), 590*635a8641SAndroid Build Coastguard Worker options.get('comment_end_string', COMMENT_END_STRING), 591*635a8641SAndroid Build Coastguard Worker options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX, 592*635a8641SAndroid Build Coastguard Worker options.get('line_comment_prefix') or LINE_COMMENT_PREFIX, 593*635a8641SAndroid Build Coastguard Worker getbool(options, 'trim_blocks', TRIM_BLOCKS), 594*635a8641SAndroid Build Coastguard Worker getbool(options, 'lstrip_blocks', LSTRIP_BLOCKS), 595*635a8641SAndroid Build Coastguard Worker NEWLINE_SEQUENCE, 596*635a8641SAndroid Build Coastguard Worker getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE), 597*635a8641SAndroid Build Coastguard Worker frozenset(extensions), 598*635a8641SAndroid Build Coastguard Worker cache_size=0, 599*635a8641SAndroid Build Coastguard Worker auto_reload=False 600*635a8641SAndroid Build Coastguard Worker ) 601*635a8641SAndroid Build Coastguard Worker 602*635a8641SAndroid Build Coastguard Worker if getbool(options, 'trimmed'): 603*635a8641SAndroid Build Coastguard Worker environment.policies['ext.i18n.trimmed'] = True 604*635a8641SAndroid Build Coastguard Worker if getbool(options, 'newstyle_gettext'): 605*635a8641SAndroid Build Coastguard Worker environment.newstyle_gettext = True 606*635a8641SAndroid Build Coastguard Worker 607*635a8641SAndroid Build Coastguard Worker source = fileobj.read().decode(options.get('encoding', 'utf-8')) 608*635a8641SAndroid Build Coastguard Worker try: 609*635a8641SAndroid Build Coastguard Worker node = environment.parse(source) 610*635a8641SAndroid Build Coastguard Worker tokens = list(environment.lex(environment.preprocess(source))) 611*635a8641SAndroid Build Coastguard Worker except TemplateSyntaxError as e: 612*635a8641SAndroid Build Coastguard Worker if not silent: 613*635a8641SAndroid Build Coastguard Worker raise 614*635a8641SAndroid Build Coastguard Worker # skip templates with syntax errors 615*635a8641SAndroid Build Coastguard Worker return 616*635a8641SAndroid Build Coastguard Worker 617*635a8641SAndroid Build Coastguard Worker finder = _CommentFinder(tokens, comment_tags) 618*635a8641SAndroid Build Coastguard Worker for lineno, func, message in extract_from_ast(node, keywords): 619*635a8641SAndroid Build Coastguard Worker yield lineno, func, message, finder.find_comments(lineno) 620*635a8641SAndroid Build Coastguard Worker 621*635a8641SAndroid Build Coastguard Worker 622*635a8641SAndroid Build Coastguard Worker#: nicer import names 623*635a8641SAndroid Build Coastguard Workeri18n = InternationalizationExtension 624*635a8641SAndroid Build Coastguard Workerdo = ExprStmtExtension 625*635a8641SAndroid Build Coastguard Workerloopcontrols = LoopControlExtension 626*635a8641SAndroid Build Coastguard Workerwith_ = WithExtension 627*635a8641SAndroid Build Coastguard Workerautoescape = AutoEscapeExtension 628