Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/jinja2/compiler.py @ 0:26e78fe6e8c4 draft
"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author | shellac |
---|---|
date | Sat, 02 May 2020 07:14:21 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:26e78fe6e8c4 |
---|---|
1 # -*- coding: utf-8 -*- | |
2 """Compiles nodes from the parser into Python code.""" | |
3 from collections import namedtuple | |
4 from functools import update_wrapper | |
5 from itertools import chain | |
6 from keyword import iskeyword as is_python_keyword | |
7 | |
8 from markupsafe import escape | |
9 from markupsafe import Markup | |
10 | |
11 from . import nodes | |
12 from ._compat import imap | |
13 from ._compat import iteritems | |
14 from ._compat import izip | |
15 from ._compat import NativeStringIO | |
16 from ._compat import range_type | |
17 from ._compat import string_types | |
18 from ._compat import text_type | |
19 from .exceptions import TemplateAssertionError | |
20 from .idtracking import Symbols | |
21 from .idtracking import VAR_LOAD_ALIAS | |
22 from .idtracking import VAR_LOAD_PARAMETER | |
23 from .idtracking import VAR_LOAD_RESOLVE | |
24 from .idtracking import VAR_LOAD_UNDEFINED | |
25 from .nodes import EvalContext | |
26 from .optimizer import Optimizer | |
27 from .utils import concat | |
28 from .visitor import NodeVisitor | |
29 | |
30 operators = { | |
31 "eq": "==", | |
32 "ne": "!=", | |
33 "gt": ">", | |
34 "gteq": ">=", | |
35 "lt": "<", | |
36 "lteq": "<=", | |
37 "in": "in", | |
38 "notin": "not in", | |
39 } | |
40 | |
41 # what method to iterate over items do we want to use for dict iteration | |
42 # in generated code? on 2.x let's go with iteritems, on 3.x with items | |
43 if hasattr(dict, "iteritems"): | |
44 dict_item_iter = "iteritems" | |
45 else: | |
46 dict_item_iter = "items" | |
47 | |
48 code_features = ["division"] | |
49 | |
50 # does this python version support generator stops? (PEP 0479) | |
51 try: | |
52 exec("from __future__ import generator_stop") | |
53 code_features.append("generator_stop") | |
54 except SyntaxError: | |
55 pass | |
56 | |
57 # does this python version support yield from? | |
58 try: | |
59 exec("def f(): yield from x()") | |
60 except SyntaxError: | |
61 supports_yield_from = False | |
62 else: | |
63 supports_yield_from = True | |
64 | |
65 | |
66 def optimizeconst(f): | |
67 def new_func(self, node, frame, **kwargs): | |
68 # Only optimize if the frame is not volatile | |
69 if self.optimized and not frame.eval_ctx.volatile: | |
70 new_node = self.optimizer.visit(node, frame.eval_ctx) | |
71 if new_node != node: | |
72 return self.visit(new_node, frame) | |
73 return f(self, node, frame, **kwargs) | |
74 | |
75 return update_wrapper(new_func, f) | |
76 | |
77 | |
78 def generate( | |
79 node, environment, name, filename, stream=None, defer_init=False, optimized=True | |
80 ): | |
81 """Generate the python source for a node tree.""" | |
82 if not isinstance(node, nodes.Template): | |
83 raise TypeError("Can't compile non template nodes") | |
84 generator = environment.code_generator_class( | |
85 environment, name, filename, stream, defer_init, optimized | |
86 ) | |
87 generator.visit(node) | |
88 if stream is None: | |
89 return generator.stream.getvalue() | |
90 | |
91 | |
92 def has_safe_repr(value): | |
93 """Does the node have a safe representation?""" | |
94 if value is None or value is NotImplemented or value is Ellipsis: | |
95 return True | |
96 if type(value) in (bool, int, float, complex, range_type, Markup) + string_types: | |
97 return True | |
98 if type(value) in (tuple, list, set, frozenset): | |
99 for item in value: | |
100 if not has_safe_repr(item): | |
101 return False | |
102 return True | |
103 elif type(value) is dict: | |
104 for key, value in iteritems(value): | |
105 if not has_safe_repr(key): | |
106 return False | |
107 if not has_safe_repr(value): | |
108 return False | |
109 return True | |
110 return False | |
111 | |
112 | |
113 def find_undeclared(nodes, names): | |
114 """Check if the names passed are accessed undeclared. The return value | |
115 is a set of all the undeclared names from the sequence of names found. | |
116 """ | |
117 visitor = UndeclaredNameVisitor(names) | |
118 try: | |
119 for node in nodes: | |
120 visitor.visit(node) | |
121 except VisitorExit: | |
122 pass | |
123 return visitor.undeclared | |
124 | |
125 | |
126 class MacroRef(object): | |
127 def __init__(self, node): | |
128 self.node = node | |
129 self.accesses_caller = False | |
130 self.accesses_kwargs = False | |
131 self.accesses_varargs = False | |
132 | |
133 | |
134 class Frame(object): | |
135 """Holds compile time information for us.""" | |
136 | |
137 def __init__(self, eval_ctx, parent=None, level=None): | |
138 self.eval_ctx = eval_ctx | |
139 self.symbols = Symbols(parent and parent.symbols or None, level=level) | |
140 | |
141 # a toplevel frame is the root + soft frames such as if conditions. | |
142 self.toplevel = False | |
143 | |
144 # the root frame is basically just the outermost frame, so no if | |
145 # conditions. This information is used to optimize inheritance | |
146 # situations. | |
147 self.rootlevel = False | |
148 | |
149 # in some dynamic inheritance situations the compiler needs to add | |
150 # write tests around output statements. | |
151 self.require_output_check = parent and parent.require_output_check | |
152 | |
153 # inside some tags we are using a buffer rather than yield statements. | |
154 # this for example affects {% filter %} or {% macro %}. If a frame | |
155 # is buffered this variable points to the name of the list used as | |
156 # buffer. | |
157 self.buffer = None | |
158 | |
159 # the name of the block we're in, otherwise None. | |
160 self.block = parent and parent.block or None | |
161 | |
162 # the parent of this frame | |
163 self.parent = parent | |
164 | |
165 if parent is not None: | |
166 self.buffer = parent.buffer | |
167 | |
168 def copy(self): | |
169 """Create a copy of the current one.""" | |
170 rv = object.__new__(self.__class__) | |
171 rv.__dict__.update(self.__dict__) | |
172 rv.symbols = self.symbols.copy() | |
173 return rv | |
174 | |
175 def inner(self, isolated=False): | |
176 """Return an inner frame.""" | |
177 if isolated: | |
178 return Frame(self.eval_ctx, level=self.symbols.level + 1) | |
179 return Frame(self.eval_ctx, self) | |
180 | |
181 def soft(self): | |
182 """Return a soft frame. A soft frame may not be modified as | |
183 standalone thing as it shares the resources with the frame it | |
184 was created of, but it's not a rootlevel frame any longer. | |
185 | |
186 This is only used to implement if-statements. | |
187 """ | |
188 rv = self.copy() | |
189 rv.rootlevel = False | |
190 return rv | |
191 | |
192 __copy__ = copy | |
193 | |
194 | |
195 class VisitorExit(RuntimeError): | |
196 """Exception used by the `UndeclaredNameVisitor` to signal a stop.""" | |
197 | |
198 | |
199 class DependencyFinderVisitor(NodeVisitor): | |
200 """A visitor that collects filter and test calls.""" | |
201 | |
202 def __init__(self): | |
203 self.filters = set() | |
204 self.tests = set() | |
205 | |
206 def visit_Filter(self, node): | |
207 self.generic_visit(node) | |
208 self.filters.add(node.name) | |
209 | |
210 def visit_Test(self, node): | |
211 self.generic_visit(node) | |
212 self.tests.add(node.name) | |
213 | |
214 def visit_Block(self, node): | |
215 """Stop visiting at blocks.""" | |
216 | |
217 | |
218 class UndeclaredNameVisitor(NodeVisitor): | |
219 """A visitor that checks if a name is accessed without being | |
220 declared. This is different from the frame visitor as it will | |
221 not stop at closure frames. | |
222 """ | |
223 | |
224 def __init__(self, names): | |
225 self.names = set(names) | |
226 self.undeclared = set() | |
227 | |
228 def visit_Name(self, node): | |
229 if node.ctx == "load" and node.name in self.names: | |
230 self.undeclared.add(node.name) | |
231 if self.undeclared == self.names: | |
232 raise VisitorExit() | |
233 else: | |
234 self.names.discard(node.name) | |
235 | |
236 def visit_Block(self, node): | |
237 """Stop visiting a blocks.""" | |
238 | |
239 | |
240 class CompilerExit(Exception): | |
241 """Raised if the compiler encountered a situation where it just | |
242 doesn't make sense to further process the code. Any block that | |
243 raises such an exception is not further processed. | |
244 """ | |
245 | |
246 | |
247 class CodeGenerator(NodeVisitor): | |
248 def __init__( | |
249 self, environment, name, filename, stream=None, defer_init=False, optimized=True | |
250 ): | |
251 if stream is None: | |
252 stream = NativeStringIO() | |
253 self.environment = environment | |
254 self.name = name | |
255 self.filename = filename | |
256 self.stream = stream | |
257 self.created_block_context = False | |
258 self.defer_init = defer_init | |
259 self.optimized = optimized | |
260 if optimized: | |
261 self.optimizer = Optimizer(environment) | |
262 | |
263 # aliases for imports | |
264 self.import_aliases = {} | |
265 | |
266 # a registry for all blocks. Because blocks are moved out | |
267 # into the global python scope they are registered here | |
268 self.blocks = {} | |
269 | |
270 # the number of extends statements so far | |
271 self.extends_so_far = 0 | |
272 | |
273 # some templates have a rootlevel extends. In this case we | |
274 # can safely assume that we're a child template and do some | |
275 # more optimizations. | |
276 self.has_known_extends = False | |
277 | |
278 # the current line number | |
279 self.code_lineno = 1 | |
280 | |
281 # registry of all filters and tests (global, not block local) | |
282 self.tests = {} | |
283 self.filters = {} | |
284 | |
285 # the debug information | |
286 self.debug_info = [] | |
287 self._write_debug_info = None | |
288 | |
289 # the number of new lines before the next write() | |
290 self._new_lines = 0 | |
291 | |
292 # the line number of the last written statement | |
293 self._last_line = 0 | |
294 | |
295 # true if nothing was written so far. | |
296 self._first_write = True | |
297 | |
298 # used by the `temporary_identifier` method to get new | |
299 # unique, temporary identifier | |
300 self._last_identifier = 0 | |
301 | |
302 # the current indentation | |
303 self._indentation = 0 | |
304 | |
305 # Tracks toplevel assignments | |
306 self._assign_stack = [] | |
307 | |
308 # Tracks parameter definition blocks | |
309 self._param_def_block = [] | |
310 | |
311 # Tracks the current context. | |
312 self._context_reference_stack = ["context"] | |
313 | |
314 # -- Various compilation helpers | |
315 | |
316 def fail(self, msg, lineno): | |
317 """Fail with a :exc:`TemplateAssertionError`.""" | |
318 raise TemplateAssertionError(msg, lineno, self.name, self.filename) | |
319 | |
320 def temporary_identifier(self): | |
321 """Get a new unique identifier.""" | |
322 self._last_identifier += 1 | |
323 return "t_%d" % self._last_identifier | |
324 | |
325 def buffer(self, frame): | |
326 """Enable buffering for the frame from that point onwards.""" | |
327 frame.buffer = self.temporary_identifier() | |
328 self.writeline("%s = []" % frame.buffer) | |
329 | |
330 def return_buffer_contents(self, frame, force_unescaped=False): | |
331 """Return the buffer contents of the frame.""" | |
332 if not force_unescaped: | |
333 if frame.eval_ctx.volatile: | |
334 self.writeline("if context.eval_ctx.autoescape:") | |
335 self.indent() | |
336 self.writeline("return Markup(concat(%s))" % frame.buffer) | |
337 self.outdent() | |
338 self.writeline("else:") | |
339 self.indent() | |
340 self.writeline("return concat(%s)" % frame.buffer) | |
341 self.outdent() | |
342 return | |
343 elif frame.eval_ctx.autoescape: | |
344 self.writeline("return Markup(concat(%s))" % frame.buffer) | |
345 return | |
346 self.writeline("return concat(%s)" % frame.buffer) | |
347 | |
348 def indent(self): | |
349 """Indent by one.""" | |
350 self._indentation += 1 | |
351 | |
352 def outdent(self, step=1): | |
353 """Outdent by step.""" | |
354 self._indentation -= step | |
355 | |
356 def start_write(self, frame, node=None): | |
357 """Yield or write into the frame buffer.""" | |
358 if frame.buffer is None: | |
359 self.writeline("yield ", node) | |
360 else: | |
361 self.writeline("%s.append(" % frame.buffer, node) | |
362 | |
363 def end_write(self, frame): | |
364 """End the writing process started by `start_write`.""" | |
365 if frame.buffer is not None: | |
366 self.write(")") | |
367 | |
368 def simple_write(self, s, frame, node=None): | |
369 """Simple shortcut for start_write + write + end_write.""" | |
370 self.start_write(frame, node) | |
371 self.write(s) | |
372 self.end_write(frame) | |
373 | |
374 def blockvisit(self, nodes, frame): | |
375 """Visit a list of nodes as block in a frame. If the current frame | |
376 is no buffer a dummy ``if 0: yield None`` is written automatically. | |
377 """ | |
378 try: | |
379 self.writeline("pass") | |
380 for node in nodes: | |
381 self.visit(node, frame) | |
382 except CompilerExit: | |
383 pass | |
384 | |
385 def write(self, x): | |
386 """Write a string into the output stream.""" | |
387 if self._new_lines: | |
388 if not self._first_write: | |
389 self.stream.write("\n" * self._new_lines) | |
390 self.code_lineno += self._new_lines | |
391 if self._write_debug_info is not None: | |
392 self.debug_info.append((self._write_debug_info, self.code_lineno)) | |
393 self._write_debug_info = None | |
394 self._first_write = False | |
395 self.stream.write(" " * self._indentation) | |
396 self._new_lines = 0 | |
397 self.stream.write(x) | |
398 | |
399 def writeline(self, x, node=None, extra=0): | |
400 """Combination of newline and write.""" | |
401 self.newline(node, extra) | |
402 self.write(x) | |
403 | |
404 def newline(self, node=None, extra=0): | |
405 """Add one or more newlines before the next write.""" | |
406 self._new_lines = max(self._new_lines, 1 + extra) | |
407 if node is not None and node.lineno != self._last_line: | |
408 self._write_debug_info = node.lineno | |
409 self._last_line = node.lineno | |
410 | |
411 def signature(self, node, frame, extra_kwargs=None): | |
412 """Writes a function call to the stream for the current node. | |
413 A leading comma is added automatically. The extra keyword | |
414 arguments may not include python keywords otherwise a syntax | |
415 error could occur. The extra keyword arguments should be given | |
416 as python dict. | |
417 """ | |
418 # if any of the given keyword arguments is a python keyword | |
419 # we have to make sure that no invalid call is created. | |
420 kwarg_workaround = False | |
421 for kwarg in chain((x.key for x in node.kwargs), extra_kwargs or ()): | |
422 if is_python_keyword(kwarg): | |
423 kwarg_workaround = True | |
424 break | |
425 | |
426 for arg in node.args: | |
427 self.write(", ") | |
428 self.visit(arg, frame) | |
429 | |
430 if not kwarg_workaround: | |
431 for kwarg in node.kwargs: | |
432 self.write(", ") | |
433 self.visit(kwarg, frame) | |
434 if extra_kwargs is not None: | |
435 for key, value in iteritems(extra_kwargs): | |
436 self.write(", %s=%s" % (key, value)) | |
437 if node.dyn_args: | |
438 self.write(", *") | |
439 self.visit(node.dyn_args, frame) | |
440 | |
441 if kwarg_workaround: | |
442 if node.dyn_kwargs is not None: | |
443 self.write(", **dict({") | |
444 else: | |
445 self.write(", **{") | |
446 for kwarg in node.kwargs: | |
447 self.write("%r: " % kwarg.key) | |
448 self.visit(kwarg.value, frame) | |
449 self.write(", ") | |
450 if extra_kwargs is not None: | |
451 for key, value in iteritems(extra_kwargs): | |
452 self.write("%r: %s, " % (key, value)) | |
453 if node.dyn_kwargs is not None: | |
454 self.write("}, **") | |
455 self.visit(node.dyn_kwargs, frame) | |
456 self.write(")") | |
457 else: | |
458 self.write("}") | |
459 | |
460 elif node.dyn_kwargs is not None: | |
461 self.write(", **") | |
462 self.visit(node.dyn_kwargs, frame) | |
463 | |
464 def pull_dependencies(self, nodes): | |
465 """Pull all the dependencies.""" | |
466 visitor = DependencyFinderVisitor() | |
467 for node in nodes: | |
468 visitor.visit(node) | |
469 for dependency in "filters", "tests": | |
470 mapping = getattr(self, dependency) | |
471 for name in getattr(visitor, dependency): | |
472 if name not in mapping: | |
473 mapping[name] = self.temporary_identifier() | |
474 self.writeline( | |
475 "%s = environment.%s[%r]" % (mapping[name], dependency, name) | |
476 ) | |
477 | |
478 def enter_frame(self, frame): | |
479 undefs = [] | |
480 for target, (action, param) in iteritems(frame.symbols.loads): | |
481 if action == VAR_LOAD_PARAMETER: | |
482 pass | |
483 elif action == VAR_LOAD_RESOLVE: | |
484 self.writeline("%s = %s(%r)" % (target, self.get_resolve_func(), param)) | |
485 elif action == VAR_LOAD_ALIAS: | |
486 self.writeline("%s = %s" % (target, param)) | |
487 elif action == VAR_LOAD_UNDEFINED: | |
488 undefs.append(target) | |
489 else: | |
490 raise NotImplementedError("unknown load instruction") | |
491 if undefs: | |
492 self.writeline("%s = missing" % " = ".join(undefs)) | |
493 | |
494 def leave_frame(self, frame, with_python_scope=False): | |
495 if not with_python_scope: | |
496 undefs = [] | |
497 for target, _ in iteritems(frame.symbols.loads): | |
498 undefs.append(target) | |
499 if undefs: | |
500 self.writeline("%s = missing" % " = ".join(undefs)) | |
501 | |
502 def func(self, name): | |
503 if self.environment.is_async: | |
504 return "async def %s" % name | |
505 return "def %s" % name | |
506 | |
507 def macro_body(self, node, frame): | |
508 """Dump the function def of a macro or call block.""" | |
509 frame = frame.inner() | |
510 frame.symbols.analyze_node(node) | |
511 macro_ref = MacroRef(node) | |
512 | |
513 explicit_caller = None | |
514 skip_special_params = set() | |
515 args = [] | |
516 for idx, arg in enumerate(node.args): | |
517 if arg.name == "caller": | |
518 explicit_caller = idx | |
519 if arg.name in ("kwargs", "varargs"): | |
520 skip_special_params.add(arg.name) | |
521 args.append(frame.symbols.ref(arg.name)) | |
522 | |
523 undeclared = find_undeclared(node.body, ("caller", "kwargs", "varargs")) | |
524 | |
525 if "caller" in undeclared: | |
526 # In older Jinja versions there was a bug that allowed caller | |
527 # to retain the special behavior even if it was mentioned in | |
528 # the argument list. However thankfully this was only really | |
529 # working if it was the last argument. So we are explicitly | |
530 # checking this now and error out if it is anywhere else in | |
531 # the argument list. | |
532 if explicit_caller is not None: | |
533 try: | |
534 node.defaults[explicit_caller - len(node.args)] | |
535 except IndexError: | |
536 self.fail( | |
537 "When defining macros or call blocks the " | |
538 'special "caller" argument must be omitted ' | |
539 "or be given a default.", | |
540 node.lineno, | |
541 ) | |
542 else: | |
543 args.append(frame.symbols.declare_parameter("caller")) | |
544 macro_ref.accesses_caller = True | |
545 if "kwargs" in undeclared and "kwargs" not in skip_special_params: | |
546 args.append(frame.symbols.declare_parameter("kwargs")) | |
547 macro_ref.accesses_kwargs = True | |
548 if "varargs" in undeclared and "varargs" not in skip_special_params: | |
549 args.append(frame.symbols.declare_parameter("varargs")) | |
550 macro_ref.accesses_varargs = True | |
551 | |
552 # macros are delayed, they never require output checks | |
553 frame.require_output_check = False | |
554 frame.symbols.analyze_node(node) | |
555 self.writeline("%s(%s):" % (self.func("macro"), ", ".join(args)), node) | |
556 self.indent() | |
557 | |
558 self.buffer(frame) | |
559 self.enter_frame(frame) | |
560 | |
561 self.push_parameter_definitions(frame) | |
562 for idx, arg in enumerate(node.args): | |
563 ref = frame.symbols.ref(arg.name) | |
564 self.writeline("if %s is missing:" % ref) | |
565 self.indent() | |
566 try: | |
567 default = node.defaults[idx - len(node.args)] | |
568 except IndexError: | |
569 self.writeline( | |
570 "%s = undefined(%r, name=%r)" | |
571 % (ref, "parameter %r was not provided" % arg.name, arg.name) | |
572 ) | |
573 else: | |
574 self.writeline("%s = " % ref) | |
575 self.visit(default, frame) | |
576 self.mark_parameter_stored(ref) | |
577 self.outdent() | |
578 self.pop_parameter_definitions() | |
579 | |
580 self.blockvisit(node.body, frame) | |
581 self.return_buffer_contents(frame, force_unescaped=True) | |
582 self.leave_frame(frame, with_python_scope=True) | |
583 self.outdent() | |
584 | |
585 return frame, macro_ref | |
586 | |
587 def macro_def(self, macro_ref, frame): | |
588 """Dump the macro definition for the def created by macro_body.""" | |
589 arg_tuple = ", ".join(repr(x.name) for x in macro_ref.node.args) | |
590 name = getattr(macro_ref.node, "name", None) | |
591 if len(macro_ref.node.args) == 1: | |
592 arg_tuple += "," | |
593 self.write( | |
594 "Macro(environment, macro, %r, (%s), %r, %r, %r, " | |
595 "context.eval_ctx.autoescape)" | |
596 % ( | |
597 name, | |
598 arg_tuple, | |
599 macro_ref.accesses_kwargs, | |
600 macro_ref.accesses_varargs, | |
601 macro_ref.accesses_caller, | |
602 ) | |
603 ) | |
604 | |
605 def position(self, node): | |
606 """Return a human readable position for the node.""" | |
607 rv = "line %d" % node.lineno | |
608 if self.name is not None: | |
609 rv += " in " + repr(self.name) | |
610 return rv | |
611 | |
612 def dump_local_context(self, frame): | |
613 return "{%s}" % ", ".join( | |
614 "%r: %s" % (name, target) | |
615 for name, target in iteritems(frame.symbols.dump_stores()) | |
616 ) | |
617 | |
618 def write_commons(self): | |
619 """Writes a common preamble that is used by root and block functions. | |
620 Primarily this sets up common local helpers and enforces a generator | |
621 through a dead branch. | |
622 """ | |
623 self.writeline("resolve = context.resolve_or_missing") | |
624 self.writeline("undefined = environment.undefined") | |
625 # always use the standard Undefined class for the implicit else of | |
626 # conditional expressions | |
627 self.writeline("cond_expr_undefined = Undefined") | |
628 self.writeline("if 0: yield None") | |
629 | |
630 def push_parameter_definitions(self, frame): | |
631 """Pushes all parameter targets from the given frame into a local | |
632 stack that permits tracking of yet to be assigned parameters. In | |
633 particular this enables the optimization from `visit_Name` to skip | |
634 undefined expressions for parameters in macros as macros can reference | |
635 otherwise unbound parameters. | |
636 """ | |
637 self._param_def_block.append(frame.symbols.dump_param_targets()) | |
638 | |
639 def pop_parameter_definitions(self): | |
640 """Pops the current parameter definitions set.""" | |
641 self._param_def_block.pop() | |
642 | |
643 def mark_parameter_stored(self, target): | |
644 """Marks a parameter in the current parameter definitions as stored. | |
645 This will skip the enforced undefined checks. | |
646 """ | |
647 if self._param_def_block: | |
648 self._param_def_block[-1].discard(target) | |
649 | |
650 def push_context_reference(self, target): | |
651 self._context_reference_stack.append(target) | |
652 | |
653 def pop_context_reference(self): | |
654 self._context_reference_stack.pop() | |
655 | |
656 def get_context_ref(self): | |
657 return self._context_reference_stack[-1] | |
658 | |
659 def get_resolve_func(self): | |
660 target = self._context_reference_stack[-1] | |
661 if target == "context": | |
662 return "resolve" | |
663 return "%s.resolve" % target | |
664 | |
665 def derive_context(self, frame): | |
666 return "%s.derived(%s)" % ( | |
667 self.get_context_ref(), | |
668 self.dump_local_context(frame), | |
669 ) | |
670 | |
671 def parameter_is_undeclared(self, target): | |
672 """Checks if a given target is an undeclared parameter.""" | |
673 if not self._param_def_block: | |
674 return False | |
675 return target in self._param_def_block[-1] | |
676 | |
677 def push_assign_tracking(self): | |
678 """Pushes a new layer for assignment tracking.""" | |
679 self._assign_stack.append(set()) | |
680 | |
681 def pop_assign_tracking(self, frame): | |
682 """Pops the topmost level for assignment tracking and updates the | |
683 context variables if necessary. | |
684 """ | |
685 vars = self._assign_stack.pop() | |
686 if not frame.toplevel or not vars: | |
687 return | |
688 public_names = [x for x in vars if x[:1] != "_"] | |
689 if len(vars) == 1: | |
690 name = next(iter(vars)) | |
691 ref = frame.symbols.ref(name) | |
692 self.writeline("context.vars[%r] = %s" % (name, ref)) | |
693 else: | |
694 self.writeline("context.vars.update({") | |
695 for idx, name in enumerate(vars): | |
696 if idx: | |
697 self.write(", ") | |
698 ref = frame.symbols.ref(name) | |
699 self.write("%r: %s" % (name, ref)) | |
700 self.write("})") | |
701 if public_names: | |
702 if len(public_names) == 1: | |
703 self.writeline("context.exported_vars.add(%r)" % public_names[0]) | |
704 else: | |
705 self.writeline( | |
706 "context.exported_vars.update((%s))" | |
707 % ", ".join(imap(repr, public_names)) | |
708 ) | |
709 | |
710 # -- Statement Visitors | |
711 | |
712 def visit_Template(self, node, frame=None): | |
713 assert frame is None, "no root frame allowed" | |
714 eval_ctx = EvalContext(self.environment, self.name) | |
715 | |
716 from .runtime import exported | |
717 | |
718 self.writeline("from __future__ import %s" % ", ".join(code_features)) | |
719 self.writeline("from jinja2.runtime import " + ", ".join(exported)) | |
720 | |
721 if self.environment.is_async: | |
722 self.writeline( | |
723 "from jinja2.asyncsupport import auto_await, " | |
724 "auto_aiter, AsyncLoopContext" | |
725 ) | |
726 | |
727 # if we want a deferred initialization we cannot move the | |
728 # environment into a local name | |
729 envenv = not self.defer_init and ", environment=environment" or "" | |
730 | |
731 # do we have an extends tag at all? If not, we can save some | |
732 # overhead by just not processing any inheritance code. | |
733 have_extends = node.find(nodes.Extends) is not None | |
734 | |
735 # find all blocks | |
736 for block in node.find_all(nodes.Block): | |
737 if block.name in self.blocks: | |
738 self.fail("block %r defined twice" % block.name, block.lineno) | |
739 self.blocks[block.name] = block | |
740 | |
741 # find all imports and import them | |
742 for import_ in node.find_all(nodes.ImportedName): | |
743 if import_.importname not in self.import_aliases: | |
744 imp = import_.importname | |
745 self.import_aliases[imp] = alias = self.temporary_identifier() | |
746 if "." in imp: | |
747 module, obj = imp.rsplit(".", 1) | |
748 self.writeline("from %s import %s as %s" % (module, obj, alias)) | |
749 else: | |
750 self.writeline("import %s as %s" % (imp, alias)) | |
751 | |
752 # add the load name | |
753 self.writeline("name = %r" % self.name) | |
754 | |
755 # generate the root render function. | |
756 self.writeline( | |
757 "%s(context, missing=missing%s):" % (self.func("root"), envenv), extra=1 | |
758 ) | |
759 self.indent() | |
760 self.write_commons() | |
761 | |
762 # process the root | |
763 frame = Frame(eval_ctx) | |
764 if "self" in find_undeclared(node.body, ("self",)): | |
765 ref = frame.symbols.declare_parameter("self") | |
766 self.writeline("%s = TemplateReference(context)" % ref) | |
767 frame.symbols.analyze_node(node) | |
768 frame.toplevel = frame.rootlevel = True | |
769 frame.require_output_check = have_extends and not self.has_known_extends | |
770 if have_extends: | |
771 self.writeline("parent_template = None") | |
772 self.enter_frame(frame) | |
773 self.pull_dependencies(node.body) | |
774 self.blockvisit(node.body, frame) | |
775 self.leave_frame(frame, with_python_scope=True) | |
776 self.outdent() | |
777 | |
778 # make sure that the parent root is called. | |
779 if have_extends: | |
780 if not self.has_known_extends: | |
781 self.indent() | |
782 self.writeline("if parent_template is not None:") | |
783 self.indent() | |
784 if supports_yield_from and not self.environment.is_async: | |
785 self.writeline("yield from parent_template.root_render_func(context)") | |
786 else: | |
787 self.writeline( | |
788 "%sfor event in parent_template." | |
789 "root_render_func(context):" | |
790 % (self.environment.is_async and "async " or "") | |
791 ) | |
792 self.indent() | |
793 self.writeline("yield event") | |
794 self.outdent() | |
795 self.outdent(1 + (not self.has_known_extends)) | |
796 | |
797 # at this point we now have the blocks collected and can visit them too. | |
798 for name, block in iteritems(self.blocks): | |
799 self.writeline( | |
800 "%s(context, missing=missing%s):" | |
801 % (self.func("block_" + name), envenv), | |
802 block, | |
803 1, | |
804 ) | |
805 self.indent() | |
806 self.write_commons() | |
807 # It's important that we do not make this frame a child of the | |
808 # toplevel template. This would cause a variety of | |
809 # interesting issues with identifier tracking. | |
810 block_frame = Frame(eval_ctx) | |
811 undeclared = find_undeclared(block.body, ("self", "super")) | |
812 if "self" in undeclared: | |
813 ref = block_frame.symbols.declare_parameter("self") | |
814 self.writeline("%s = TemplateReference(context)" % ref) | |
815 if "super" in undeclared: | |
816 ref = block_frame.symbols.declare_parameter("super") | |
817 self.writeline("%s = context.super(%r, block_%s)" % (ref, name, name)) | |
818 block_frame.symbols.analyze_node(block) | |
819 block_frame.block = name | |
820 self.enter_frame(block_frame) | |
821 self.pull_dependencies(block.body) | |
822 self.blockvisit(block.body, block_frame) | |
823 self.leave_frame(block_frame, with_python_scope=True) | |
824 self.outdent() | |
825 | |
826 self.writeline( | |
827 "blocks = {%s}" % ", ".join("%r: block_%s" % (x, x) for x in self.blocks), | |
828 extra=1, | |
829 ) | |
830 | |
831 # add a function that returns the debug info | |
832 self.writeline( | |
833 "debug_info = %r" % "&".join("%s=%s" % x for x in self.debug_info) | |
834 ) | |
835 | |
836 def visit_Block(self, node, frame): | |
837 """Call a block and register it for the template.""" | |
838 level = 0 | |
839 if frame.toplevel: | |
840 # if we know that we are a child template, there is no need to | |
841 # check if we are one | |
842 if self.has_known_extends: | |
843 return | |
844 if self.extends_so_far > 0: | |
845 self.writeline("if parent_template is None:") | |
846 self.indent() | |
847 level += 1 | |
848 | |
849 if node.scoped: | |
850 context = self.derive_context(frame) | |
851 else: | |
852 context = self.get_context_ref() | |
853 | |
854 if ( | |
855 supports_yield_from | |
856 and not self.environment.is_async | |
857 and frame.buffer is None | |
858 ): | |
859 self.writeline( | |
860 "yield from context.blocks[%r][0](%s)" % (node.name, context), node | |
861 ) | |
862 else: | |
863 loop = self.environment.is_async and "async for" or "for" | |
864 self.writeline( | |
865 "%s event in context.blocks[%r][0](%s):" % (loop, node.name, context), | |
866 node, | |
867 ) | |
868 self.indent() | |
869 self.simple_write("event", frame) | |
870 self.outdent() | |
871 | |
872 self.outdent(level) | |
873 | |
874 def visit_Extends(self, node, frame): | |
875 """Calls the extender.""" | |
876 if not frame.toplevel: | |
877 self.fail("cannot use extend from a non top-level scope", node.lineno) | |
878 | |
879 # if the number of extends statements in general is zero so | |
880 # far, we don't have to add a check if something extended | |
881 # the template before this one. | |
882 if self.extends_so_far > 0: | |
883 | |
884 # if we have a known extends we just add a template runtime | |
885 # error into the generated code. We could catch that at compile | |
886 # time too, but i welcome it not to confuse users by throwing the | |
887 # same error at different times just "because we can". | |
888 if not self.has_known_extends: | |
889 self.writeline("if parent_template is not None:") | |
890 self.indent() | |
891 self.writeline("raise TemplateRuntimeError(%r)" % "extended multiple times") | |
892 | |
893 # if we have a known extends already we don't need that code here | |
894 # as we know that the template execution will end here. | |
895 if self.has_known_extends: | |
896 raise CompilerExit() | |
897 else: | |
898 self.outdent() | |
899 | |
900 self.writeline("parent_template = environment.get_template(", node) | |
901 self.visit(node.template, frame) | |
902 self.write(", %r)" % self.name) | |
903 self.writeline( | |
904 "for name, parent_block in parent_template.blocks.%s():" % dict_item_iter | |
905 ) | |
906 self.indent() | |
907 self.writeline("context.blocks.setdefault(name, []).append(parent_block)") | |
908 self.outdent() | |
909 | |
910 # if this extends statement was in the root level we can take | |
911 # advantage of that information and simplify the generated code | |
912 # in the top level from this point onwards | |
913 if frame.rootlevel: | |
914 self.has_known_extends = True | |
915 | |
916 # and now we have one more | |
917 self.extends_so_far += 1 | |
918 | |
919 def visit_Include(self, node, frame): | |
920 """Handles includes.""" | |
921 if node.ignore_missing: | |
922 self.writeline("try:") | |
923 self.indent() | |
924 | |
925 func_name = "get_or_select_template" | |
926 if isinstance(node.template, nodes.Const): | |
927 if isinstance(node.template.value, string_types): | |
928 func_name = "get_template" | |
929 elif isinstance(node.template.value, (tuple, list)): | |
930 func_name = "select_template" | |
931 elif isinstance(node.template, (nodes.Tuple, nodes.List)): | |
932 func_name = "select_template" | |
933 | |
934 self.writeline("template = environment.%s(" % func_name, node) | |
935 self.visit(node.template, frame) | |
936 self.write(", %r)" % self.name) | |
937 if node.ignore_missing: | |
938 self.outdent() | |
939 self.writeline("except TemplateNotFound:") | |
940 self.indent() | |
941 self.writeline("pass") | |
942 self.outdent() | |
943 self.writeline("else:") | |
944 self.indent() | |
945 | |
946 skip_event_yield = False | |
947 if node.with_context: | |
948 loop = self.environment.is_async and "async for" or "for" | |
949 self.writeline( | |
950 "%s event in template.root_render_func(" | |
951 "template.new_context(context.get_all(), True, " | |
952 "%s)):" % (loop, self.dump_local_context(frame)) | |
953 ) | |
954 elif self.environment.is_async: | |
955 self.writeline( | |
956 "for event in (await " | |
957 "template._get_default_module_async())" | |
958 "._body_stream:" | |
959 ) | |
960 else: | |
961 if supports_yield_from: | |
962 self.writeline("yield from template._get_default_module()._body_stream") | |
963 skip_event_yield = True | |
964 else: | |
965 self.writeline( | |
966 "for event in template._get_default_module()._body_stream:" | |
967 ) | |
968 | |
969 if not skip_event_yield: | |
970 self.indent() | |
971 self.simple_write("event", frame) | |
972 self.outdent() | |
973 | |
974 if node.ignore_missing: | |
975 self.outdent() | |
976 | |
977 def visit_Import(self, node, frame): | |
978 """Visit regular imports.""" | |
979 self.writeline("%s = " % frame.symbols.ref(node.target), node) | |
980 if frame.toplevel: | |
981 self.write("context.vars[%r] = " % node.target) | |
982 if self.environment.is_async: | |
983 self.write("await ") | |
984 self.write("environment.get_template(") | |
985 self.visit(node.template, frame) | |
986 self.write(", %r)." % self.name) | |
987 if node.with_context: | |
988 self.write( | |
989 "make_module%s(context.get_all(), True, %s)" | |
990 % ( | |
991 self.environment.is_async and "_async" or "", | |
992 self.dump_local_context(frame), | |
993 ) | |
994 ) | |
995 elif self.environment.is_async: | |
996 self.write("_get_default_module_async()") | |
997 else: | |
998 self.write("_get_default_module()") | |
999 if frame.toplevel and not node.target.startswith("_"): | |
1000 self.writeline("context.exported_vars.discard(%r)" % node.target) | |
1001 | |
1002 def visit_FromImport(self, node, frame): | |
1003 """Visit named imports.""" | |
1004 self.newline(node) | |
1005 self.write( | |
1006 "included_template = %senvironment.get_template(" | |
1007 % (self.environment.is_async and "await " or "") | |
1008 ) | |
1009 self.visit(node.template, frame) | |
1010 self.write(", %r)." % self.name) | |
1011 if node.with_context: | |
1012 self.write( | |
1013 "make_module%s(context.get_all(), True, %s)" | |
1014 % ( | |
1015 self.environment.is_async and "_async" or "", | |
1016 self.dump_local_context(frame), | |
1017 ) | |
1018 ) | |
1019 elif self.environment.is_async: | |
1020 self.write("_get_default_module_async()") | |
1021 else: | |
1022 self.write("_get_default_module()") | |
1023 | |
1024 var_names = [] | |
1025 discarded_names = [] | |
1026 for name in node.names: | |
1027 if isinstance(name, tuple): | |
1028 name, alias = name | |
1029 else: | |
1030 alias = name | |
1031 self.writeline( | |
1032 "%s = getattr(included_template, " | |
1033 "%r, missing)" % (frame.symbols.ref(alias), name) | |
1034 ) | |
1035 self.writeline("if %s is missing:" % frame.symbols.ref(alias)) | |
1036 self.indent() | |
1037 self.writeline( | |
1038 "%s = undefined(%r %% " | |
1039 "included_template.__name__, " | |
1040 "name=%r)" | |
1041 % ( | |
1042 frame.symbols.ref(alias), | |
1043 "the template %%r (imported on %s) does " | |
1044 "not export the requested name %s" | |
1045 % (self.position(node), repr(name)), | |
1046 name, | |
1047 ) | |
1048 ) | |
1049 self.outdent() | |
1050 if frame.toplevel: | |
1051 var_names.append(alias) | |
1052 if not alias.startswith("_"): | |
1053 discarded_names.append(alias) | |
1054 | |
1055 if var_names: | |
1056 if len(var_names) == 1: | |
1057 name = var_names[0] | |
1058 self.writeline( | |
1059 "context.vars[%r] = %s" % (name, frame.symbols.ref(name)) | |
1060 ) | |
1061 else: | |
1062 self.writeline( | |
1063 "context.vars.update({%s})" | |
1064 % ", ".join( | |
1065 "%r: %s" % (name, frame.symbols.ref(name)) for name in var_names | |
1066 ) | |
1067 ) | |
1068 if discarded_names: | |
1069 if len(discarded_names) == 1: | |
1070 self.writeline("context.exported_vars.discard(%r)" % discarded_names[0]) | |
1071 else: | |
1072 self.writeline( | |
1073 "context.exported_vars.difference_" | |
1074 "update((%s))" % ", ".join(imap(repr, discarded_names)) | |
1075 ) | |
1076 | |
1077 def visit_For(self, node, frame): | |
1078 loop_frame = frame.inner() | |
1079 test_frame = frame.inner() | |
1080 else_frame = frame.inner() | |
1081 | |
1082 # try to figure out if we have an extended loop. An extended loop | |
1083 # is necessary if the loop is in recursive mode if the special loop | |
1084 # variable is accessed in the body. | |
1085 extended_loop = node.recursive or "loop" in find_undeclared( | |
1086 node.iter_child_nodes(only=("body",)), ("loop",) | |
1087 ) | |
1088 | |
1089 loop_ref = None | |
1090 if extended_loop: | |
1091 loop_ref = loop_frame.symbols.declare_parameter("loop") | |
1092 | |
1093 loop_frame.symbols.analyze_node(node, for_branch="body") | |
1094 if node.else_: | |
1095 else_frame.symbols.analyze_node(node, for_branch="else") | |
1096 | |
1097 if node.test: | |
1098 loop_filter_func = self.temporary_identifier() | |
1099 test_frame.symbols.analyze_node(node, for_branch="test") | |
1100 self.writeline("%s(fiter):" % self.func(loop_filter_func), node.test) | |
1101 self.indent() | |
1102 self.enter_frame(test_frame) | |
1103 self.writeline(self.environment.is_async and "async for " or "for ") | |
1104 self.visit(node.target, loop_frame) | |
1105 self.write(" in ") | |
1106 self.write(self.environment.is_async and "auto_aiter(fiter)" or "fiter") | |
1107 self.write(":") | |
1108 self.indent() | |
1109 self.writeline("if ", node.test) | |
1110 self.visit(node.test, test_frame) | |
1111 self.write(":") | |
1112 self.indent() | |
1113 self.writeline("yield ") | |
1114 self.visit(node.target, loop_frame) | |
1115 self.outdent(3) | |
1116 self.leave_frame(test_frame, with_python_scope=True) | |
1117 | |
1118 # if we don't have an recursive loop we have to find the shadowed | |
1119 # variables at that point. Because loops can be nested but the loop | |
1120 # variable is a special one we have to enforce aliasing for it. | |
1121 if node.recursive: | |
1122 self.writeline( | |
1123 "%s(reciter, loop_render_func, depth=0):" % self.func("loop"), node | |
1124 ) | |
1125 self.indent() | |
1126 self.buffer(loop_frame) | |
1127 | |
1128 # Use the same buffer for the else frame | |
1129 else_frame.buffer = loop_frame.buffer | |
1130 | |
1131 # make sure the loop variable is a special one and raise a template | |
1132 # assertion error if a loop tries to write to loop | |
1133 if extended_loop: | |
1134 self.writeline("%s = missing" % loop_ref) | |
1135 | |
1136 for name in node.find_all(nodes.Name): | |
1137 if name.ctx == "store" and name.name == "loop": | |
1138 self.fail( | |
1139 "Can't assign to special loop variable in for-loop target", | |
1140 name.lineno, | |
1141 ) | |
1142 | |
1143 if node.else_: | |
1144 iteration_indicator = self.temporary_identifier() | |
1145 self.writeline("%s = 1" % iteration_indicator) | |
1146 | |
1147 self.writeline(self.environment.is_async and "async for " or "for ", node) | |
1148 self.visit(node.target, loop_frame) | |
1149 if extended_loop: | |
1150 if self.environment.is_async: | |
1151 self.write(", %s in AsyncLoopContext(" % loop_ref) | |
1152 else: | |
1153 self.write(", %s in LoopContext(" % loop_ref) | |
1154 else: | |
1155 self.write(" in ") | |
1156 | |
1157 if node.test: | |
1158 self.write("%s(" % loop_filter_func) | |
1159 if node.recursive: | |
1160 self.write("reciter") | |
1161 else: | |
1162 if self.environment.is_async and not extended_loop: | |
1163 self.write("auto_aiter(") | |
1164 self.visit(node.iter, frame) | |
1165 if self.environment.is_async and not extended_loop: | |
1166 self.write(")") | |
1167 if node.test: | |
1168 self.write(")") | |
1169 | |
1170 if node.recursive: | |
1171 self.write(", undefined, loop_render_func, depth):") | |
1172 else: | |
1173 self.write(extended_loop and ", undefined):" or ":") | |
1174 | |
1175 self.indent() | |
1176 self.enter_frame(loop_frame) | |
1177 | |
1178 self.blockvisit(node.body, loop_frame) | |
1179 if node.else_: | |
1180 self.writeline("%s = 0" % iteration_indicator) | |
1181 self.outdent() | |
1182 self.leave_frame( | |
1183 loop_frame, with_python_scope=node.recursive and not node.else_ | |
1184 ) | |
1185 | |
1186 if node.else_: | |
1187 self.writeline("if %s:" % iteration_indicator) | |
1188 self.indent() | |
1189 self.enter_frame(else_frame) | |
1190 self.blockvisit(node.else_, else_frame) | |
1191 self.leave_frame(else_frame) | |
1192 self.outdent() | |
1193 | |
1194 # if the node was recursive we have to return the buffer contents | |
1195 # and start the iteration code | |
1196 if node.recursive: | |
1197 self.return_buffer_contents(loop_frame) | |
1198 self.outdent() | |
1199 self.start_write(frame, node) | |
1200 if self.environment.is_async: | |
1201 self.write("await ") | |
1202 self.write("loop(") | |
1203 if self.environment.is_async: | |
1204 self.write("auto_aiter(") | |
1205 self.visit(node.iter, frame) | |
1206 if self.environment.is_async: | |
1207 self.write(")") | |
1208 self.write(", loop)") | |
1209 self.end_write(frame) | |
1210 | |
1211 def visit_If(self, node, frame): | |
1212 if_frame = frame.soft() | |
1213 self.writeline("if ", node) | |
1214 self.visit(node.test, if_frame) | |
1215 self.write(":") | |
1216 self.indent() | |
1217 self.blockvisit(node.body, if_frame) | |
1218 self.outdent() | |
1219 for elif_ in node.elif_: | |
1220 self.writeline("elif ", elif_) | |
1221 self.visit(elif_.test, if_frame) | |
1222 self.write(":") | |
1223 self.indent() | |
1224 self.blockvisit(elif_.body, if_frame) | |
1225 self.outdent() | |
1226 if node.else_: | |
1227 self.writeline("else:") | |
1228 self.indent() | |
1229 self.blockvisit(node.else_, if_frame) | |
1230 self.outdent() | |
1231 | |
1232 def visit_Macro(self, node, frame): | |
1233 macro_frame, macro_ref = self.macro_body(node, frame) | |
1234 self.newline() | |
1235 if frame.toplevel: | |
1236 if not node.name.startswith("_"): | |
1237 self.write("context.exported_vars.add(%r)" % node.name) | |
1238 self.writeline("context.vars[%r] = " % node.name) | |
1239 self.write("%s = " % frame.symbols.ref(node.name)) | |
1240 self.macro_def(macro_ref, macro_frame) | |
1241 | |
1242 def visit_CallBlock(self, node, frame): | |
1243 call_frame, macro_ref = self.macro_body(node, frame) | |
1244 self.writeline("caller = ") | |
1245 self.macro_def(macro_ref, call_frame) | |
1246 self.start_write(frame, node) | |
1247 self.visit_Call(node.call, frame, forward_caller=True) | |
1248 self.end_write(frame) | |
1249 | |
1250 def visit_FilterBlock(self, node, frame): | |
1251 filter_frame = frame.inner() | |
1252 filter_frame.symbols.analyze_node(node) | |
1253 self.enter_frame(filter_frame) | |
1254 self.buffer(filter_frame) | |
1255 self.blockvisit(node.body, filter_frame) | |
1256 self.start_write(frame, node) | |
1257 self.visit_Filter(node.filter, filter_frame) | |
1258 self.end_write(frame) | |
1259 self.leave_frame(filter_frame) | |
1260 | |
1261 def visit_With(self, node, frame): | |
1262 with_frame = frame.inner() | |
1263 with_frame.symbols.analyze_node(node) | |
1264 self.enter_frame(with_frame) | |
1265 for target, expr in izip(node.targets, node.values): | |
1266 self.newline() | |
1267 self.visit(target, with_frame) | |
1268 self.write(" = ") | |
1269 self.visit(expr, frame) | |
1270 self.blockvisit(node.body, with_frame) | |
1271 self.leave_frame(with_frame) | |
1272 | |
1273 def visit_ExprStmt(self, node, frame): | |
1274 self.newline(node) | |
1275 self.visit(node.node, frame) | |
1276 | |
1277 _FinalizeInfo = namedtuple("_FinalizeInfo", ("const", "src")) | |
1278 #: The default finalize function if the environment isn't configured | |
1279 #: with one. Or if the environment has one, this is called on that | |
1280 #: function's output for constants. | |
1281 _default_finalize = text_type | |
1282 _finalize = None | |
1283 | |
1284 def _make_finalize(self): | |
1285 """Build the finalize function to be used on constants and at | |
1286 runtime. Cached so it's only created once for all output nodes. | |
1287 | |
1288 Returns a ``namedtuple`` with the following attributes: | |
1289 | |
1290 ``const`` | |
1291 A function to finalize constant data at compile time. | |
1292 | |
1293 ``src`` | |
1294 Source code to output around nodes to be evaluated at | |
1295 runtime. | |
1296 """ | |
1297 if self._finalize is not None: | |
1298 return self._finalize | |
1299 | |
1300 finalize = default = self._default_finalize | |
1301 src = None | |
1302 | |
1303 if self.environment.finalize: | |
1304 src = "environment.finalize(" | |
1305 env_finalize = self.environment.finalize | |
1306 | |
1307 def finalize(value): | |
1308 return default(env_finalize(value)) | |
1309 | |
1310 if getattr(env_finalize, "contextfunction", False) is True: | |
1311 src += "context, " | |
1312 finalize = None # noqa: F811 | |
1313 elif getattr(env_finalize, "evalcontextfunction", False) is True: | |
1314 src += "context.eval_ctx, " | |
1315 finalize = None | |
1316 elif getattr(env_finalize, "environmentfunction", False) is True: | |
1317 src += "environment, " | |
1318 | |
1319 def finalize(value): | |
1320 return default(env_finalize(self.environment, value)) | |
1321 | |
1322 self._finalize = self._FinalizeInfo(finalize, src) | |
1323 return self._finalize | |
1324 | |
1325 def _output_const_repr(self, group): | |
1326 """Given a group of constant values converted from ``Output`` | |
1327 child nodes, produce a string to write to the template module | |
1328 source. | |
1329 """ | |
1330 return repr(concat(group)) | |
1331 | |
1332 def _output_child_to_const(self, node, frame, finalize): | |
1333 """Try to optimize a child of an ``Output`` node by trying to | |
1334 convert it to constant, finalized data at compile time. | |
1335 | |
1336 If :exc:`Impossible` is raised, the node is not constant and | |
1337 will be evaluated at runtime. Any other exception will also be | |
1338 evaluated at runtime for easier debugging. | |
1339 """ | |
1340 const = node.as_const(frame.eval_ctx) | |
1341 | |
1342 if frame.eval_ctx.autoescape: | |
1343 const = escape(const) | |
1344 | |
1345 # Template data doesn't go through finalize. | |
1346 if isinstance(node, nodes.TemplateData): | |
1347 return text_type(const) | |
1348 | |
1349 return finalize.const(const) | |
1350 | |
1351 def _output_child_pre(self, node, frame, finalize): | |
1352 """Output extra source code before visiting a child of an | |
1353 ``Output`` node. | |
1354 """ | |
1355 if frame.eval_ctx.volatile: | |
1356 self.write("(escape if context.eval_ctx.autoescape else to_string)(") | |
1357 elif frame.eval_ctx.autoescape: | |
1358 self.write("escape(") | |
1359 else: | |
1360 self.write("to_string(") | |
1361 | |
1362 if finalize.src is not None: | |
1363 self.write(finalize.src) | |
1364 | |
1365 def _output_child_post(self, node, frame, finalize): | |
1366 """Output extra source code after visiting a child of an | |
1367 ``Output`` node. | |
1368 """ | |
1369 self.write(")") | |
1370 | |
1371 if finalize.src is not None: | |
1372 self.write(")") | |
1373 | |
1374 def visit_Output(self, node, frame): | |
1375 # If an extends is active, don't render outside a block. | |
1376 if frame.require_output_check: | |
1377 # A top-level extends is known to exist at compile time. | |
1378 if self.has_known_extends: | |
1379 return | |
1380 | |
1381 self.writeline("if parent_template is None:") | |
1382 self.indent() | |
1383 | |
1384 finalize = self._make_finalize() | |
1385 body = [] | |
1386 | |
1387 # Evaluate constants at compile time if possible. Each item in | |
1388 # body will be either a list of static data or a node to be | |
1389 # evaluated at runtime. | |
1390 for child in node.nodes: | |
1391 try: | |
1392 if not ( | |
1393 # If the finalize function requires runtime context, | |
1394 # constants can't be evaluated at compile time. | |
1395 finalize.const | |
1396 # Unless it's basic template data that won't be | |
1397 # finalized anyway. | |
1398 or isinstance(child, nodes.TemplateData) | |
1399 ): | |
1400 raise nodes.Impossible() | |
1401 | |
1402 const = self._output_child_to_const(child, frame, finalize) | |
1403 except (nodes.Impossible, Exception): | |
1404 # The node was not constant and needs to be evaluated at | |
1405 # runtime. Or another error was raised, which is easier | |
1406 # to debug at runtime. | |
1407 body.append(child) | |
1408 continue | |
1409 | |
1410 if body and isinstance(body[-1], list): | |
1411 body[-1].append(const) | |
1412 else: | |
1413 body.append([const]) | |
1414 | |
1415 if frame.buffer is not None: | |
1416 if len(body) == 1: | |
1417 self.writeline("%s.append(" % frame.buffer) | |
1418 else: | |
1419 self.writeline("%s.extend((" % frame.buffer) | |
1420 | |
1421 self.indent() | |
1422 | |
1423 for item in body: | |
1424 if isinstance(item, list): | |
1425 # A group of constant data to join and output. | |
1426 val = self._output_const_repr(item) | |
1427 | |
1428 if frame.buffer is None: | |
1429 self.writeline("yield " + val) | |
1430 else: | |
1431 self.writeline(val + ",") | |
1432 else: | |
1433 if frame.buffer is None: | |
1434 self.writeline("yield ", item) | |
1435 else: | |
1436 self.newline(item) | |
1437 | |
1438 # A node to be evaluated at runtime. | |
1439 self._output_child_pre(item, frame, finalize) | |
1440 self.visit(item, frame) | |
1441 self._output_child_post(item, frame, finalize) | |
1442 | |
1443 if frame.buffer is not None: | |
1444 self.write(",") | |
1445 | |
1446 if frame.buffer is not None: | |
1447 self.outdent() | |
1448 self.writeline(")" if len(body) == 1 else "))") | |
1449 | |
1450 if frame.require_output_check: | |
1451 self.outdent() | |
1452 | |
1453 def visit_Assign(self, node, frame): | |
1454 self.push_assign_tracking() | |
1455 self.newline(node) | |
1456 self.visit(node.target, frame) | |
1457 self.write(" = ") | |
1458 self.visit(node.node, frame) | |
1459 self.pop_assign_tracking(frame) | |
1460 | |
1461 def visit_AssignBlock(self, node, frame): | |
1462 self.push_assign_tracking() | |
1463 block_frame = frame.inner() | |
1464 # This is a special case. Since a set block always captures we | |
1465 # will disable output checks. This way one can use set blocks | |
1466 # toplevel even in extended templates. | |
1467 block_frame.require_output_check = False | |
1468 block_frame.symbols.analyze_node(node) | |
1469 self.enter_frame(block_frame) | |
1470 self.buffer(block_frame) | |
1471 self.blockvisit(node.body, block_frame) | |
1472 self.newline(node) | |
1473 self.visit(node.target, frame) | |
1474 self.write(" = (Markup if context.eval_ctx.autoescape else identity)(") | |
1475 if node.filter is not None: | |
1476 self.visit_Filter(node.filter, block_frame) | |
1477 else: | |
1478 self.write("concat(%s)" % block_frame.buffer) | |
1479 self.write(")") | |
1480 self.pop_assign_tracking(frame) | |
1481 self.leave_frame(block_frame) | |
1482 | |
1483 # -- Expression Visitors | |
1484 | |
1485 def visit_Name(self, node, frame): | |
1486 if node.ctx == "store" and frame.toplevel: | |
1487 if self._assign_stack: | |
1488 self._assign_stack[-1].add(node.name) | |
1489 ref = frame.symbols.ref(node.name) | |
1490 | |
1491 # If we are looking up a variable we might have to deal with the | |
1492 # case where it's undefined. We can skip that case if the load | |
1493 # instruction indicates a parameter which are always defined. | |
1494 if node.ctx == "load": | |
1495 load = frame.symbols.find_load(ref) | |
1496 if not ( | |
1497 load is not None | |
1498 and load[0] == VAR_LOAD_PARAMETER | |
1499 and not self.parameter_is_undeclared(ref) | |
1500 ): | |
1501 self.write( | |
1502 "(undefined(name=%r) if %s is missing else %s)" | |
1503 % (node.name, ref, ref) | |
1504 ) | |
1505 return | |
1506 | |
1507 self.write(ref) | |
1508 | |
1509 def visit_NSRef(self, node, frame): | |
1510 # NSRefs can only be used to store values; since they use the normal | |
1511 # `foo.bar` notation they will be parsed as a normal attribute access | |
1512 # when used anywhere but in a `set` context | |
1513 ref = frame.symbols.ref(node.name) | |
1514 self.writeline("if not isinstance(%s, Namespace):" % ref) | |
1515 self.indent() | |
1516 self.writeline( | |
1517 "raise TemplateRuntimeError(%r)" | |
1518 % "cannot assign attribute on non-namespace object" | |
1519 ) | |
1520 self.outdent() | |
1521 self.writeline("%s[%r]" % (ref, node.attr)) | |
1522 | |
1523 def visit_Const(self, node, frame): | |
1524 val = node.as_const(frame.eval_ctx) | |
1525 if isinstance(val, float): | |
1526 self.write(str(val)) | |
1527 else: | |
1528 self.write(repr(val)) | |
1529 | |
1530 def visit_TemplateData(self, node, frame): | |
1531 try: | |
1532 self.write(repr(node.as_const(frame.eval_ctx))) | |
1533 except nodes.Impossible: | |
1534 self.write( | |
1535 "(Markup if context.eval_ctx.autoescape else identity)(%r)" % node.data | |
1536 ) | |
1537 | |
1538 def visit_Tuple(self, node, frame): | |
1539 self.write("(") | |
1540 idx = -1 | |
1541 for idx, item in enumerate(node.items): | |
1542 if idx: | |
1543 self.write(", ") | |
1544 self.visit(item, frame) | |
1545 self.write(idx == 0 and ",)" or ")") | |
1546 | |
1547 def visit_List(self, node, frame): | |
1548 self.write("[") | |
1549 for idx, item in enumerate(node.items): | |
1550 if idx: | |
1551 self.write(", ") | |
1552 self.visit(item, frame) | |
1553 self.write("]") | |
1554 | |
1555 def visit_Dict(self, node, frame): | |
1556 self.write("{") | |
1557 for idx, item in enumerate(node.items): | |
1558 if idx: | |
1559 self.write(", ") | |
1560 self.visit(item.key, frame) | |
1561 self.write(": ") | |
1562 self.visit(item.value, frame) | |
1563 self.write("}") | |
1564 | |
1565 def binop(operator, interceptable=True): # noqa: B902 | |
1566 @optimizeconst | |
1567 def visitor(self, node, frame): | |
1568 if ( | |
1569 self.environment.sandboxed | |
1570 and operator in self.environment.intercepted_binops | |
1571 ): | |
1572 self.write("environment.call_binop(context, %r, " % operator) | |
1573 self.visit(node.left, frame) | |
1574 self.write(", ") | |
1575 self.visit(node.right, frame) | |
1576 else: | |
1577 self.write("(") | |
1578 self.visit(node.left, frame) | |
1579 self.write(" %s " % operator) | |
1580 self.visit(node.right, frame) | |
1581 self.write(")") | |
1582 | |
1583 return visitor | |
1584 | |
1585 def uaop(operator, interceptable=True): # noqa: B902 | |
1586 @optimizeconst | |
1587 def visitor(self, node, frame): | |
1588 if ( | |
1589 self.environment.sandboxed | |
1590 and operator in self.environment.intercepted_unops | |
1591 ): | |
1592 self.write("environment.call_unop(context, %r, " % operator) | |
1593 self.visit(node.node, frame) | |
1594 else: | |
1595 self.write("(" + operator) | |
1596 self.visit(node.node, frame) | |
1597 self.write(")") | |
1598 | |
1599 return visitor | |
1600 | |
1601 visit_Add = binop("+") | |
1602 visit_Sub = binop("-") | |
1603 visit_Mul = binop("*") | |
1604 visit_Div = binop("/") | |
1605 visit_FloorDiv = binop("//") | |
1606 visit_Pow = binop("**") | |
1607 visit_Mod = binop("%") | |
1608 visit_And = binop("and", interceptable=False) | |
1609 visit_Or = binop("or", interceptable=False) | |
1610 visit_Pos = uaop("+") | |
1611 visit_Neg = uaop("-") | |
1612 visit_Not = uaop("not ", interceptable=False) | |
1613 del binop, uaop | |
1614 | |
1615 @optimizeconst | |
1616 def visit_Concat(self, node, frame): | |
1617 if frame.eval_ctx.volatile: | |
1618 func_name = "(context.eval_ctx.volatile and markup_join or unicode_join)" | |
1619 elif frame.eval_ctx.autoescape: | |
1620 func_name = "markup_join" | |
1621 else: | |
1622 func_name = "unicode_join" | |
1623 self.write("%s((" % func_name) | |
1624 for arg in node.nodes: | |
1625 self.visit(arg, frame) | |
1626 self.write(", ") | |
1627 self.write("))") | |
1628 | |
1629 @optimizeconst | |
1630 def visit_Compare(self, node, frame): | |
1631 self.write("(") | |
1632 self.visit(node.expr, frame) | |
1633 for op in node.ops: | |
1634 self.visit(op, frame) | |
1635 self.write(")") | |
1636 | |
1637 def visit_Operand(self, node, frame): | |
1638 self.write(" %s " % operators[node.op]) | |
1639 self.visit(node.expr, frame) | |
1640 | |
1641 @optimizeconst | |
1642 def visit_Getattr(self, node, frame): | |
1643 if self.environment.is_async: | |
1644 self.write("(await auto_await(") | |
1645 | |
1646 self.write("environment.getattr(") | |
1647 self.visit(node.node, frame) | |
1648 self.write(", %r)" % node.attr) | |
1649 | |
1650 if self.environment.is_async: | |
1651 self.write("))") | |
1652 | |
1653 @optimizeconst | |
1654 def visit_Getitem(self, node, frame): | |
1655 # slices bypass the environment getitem method. | |
1656 if isinstance(node.arg, nodes.Slice): | |
1657 self.visit(node.node, frame) | |
1658 self.write("[") | |
1659 self.visit(node.arg, frame) | |
1660 self.write("]") | |
1661 else: | |
1662 if self.environment.is_async: | |
1663 self.write("(await auto_await(") | |
1664 | |
1665 self.write("environment.getitem(") | |
1666 self.visit(node.node, frame) | |
1667 self.write(", ") | |
1668 self.visit(node.arg, frame) | |
1669 self.write(")") | |
1670 | |
1671 if self.environment.is_async: | |
1672 self.write("))") | |
1673 | |
1674 def visit_Slice(self, node, frame): | |
1675 if node.start is not None: | |
1676 self.visit(node.start, frame) | |
1677 self.write(":") | |
1678 if node.stop is not None: | |
1679 self.visit(node.stop, frame) | |
1680 if node.step is not None: | |
1681 self.write(":") | |
1682 self.visit(node.step, frame) | |
1683 | |
1684 @optimizeconst | |
1685 def visit_Filter(self, node, frame): | |
1686 if self.environment.is_async: | |
1687 self.write("await auto_await(") | |
1688 self.write(self.filters[node.name] + "(") | |
1689 func = self.environment.filters.get(node.name) | |
1690 if func is None: | |
1691 self.fail("no filter named %r" % node.name, node.lineno) | |
1692 if getattr(func, "contextfilter", False) is True: | |
1693 self.write("context, ") | |
1694 elif getattr(func, "evalcontextfilter", False) is True: | |
1695 self.write("context.eval_ctx, ") | |
1696 elif getattr(func, "environmentfilter", False) is True: | |
1697 self.write("environment, ") | |
1698 | |
1699 # if the filter node is None we are inside a filter block | |
1700 # and want to write to the current buffer | |
1701 if node.node is not None: | |
1702 self.visit(node.node, frame) | |
1703 elif frame.eval_ctx.volatile: | |
1704 self.write( | |
1705 "(context.eval_ctx.autoescape and" | |
1706 " Markup(concat(%s)) or concat(%s))" % (frame.buffer, frame.buffer) | |
1707 ) | |
1708 elif frame.eval_ctx.autoescape: | |
1709 self.write("Markup(concat(%s))" % frame.buffer) | |
1710 else: | |
1711 self.write("concat(%s)" % frame.buffer) | |
1712 self.signature(node, frame) | |
1713 self.write(")") | |
1714 if self.environment.is_async: | |
1715 self.write(")") | |
1716 | |
1717 @optimizeconst | |
1718 def visit_Test(self, node, frame): | |
1719 self.write(self.tests[node.name] + "(") | |
1720 if node.name not in self.environment.tests: | |
1721 self.fail("no test named %r" % node.name, node.lineno) | |
1722 self.visit(node.node, frame) | |
1723 self.signature(node, frame) | |
1724 self.write(")") | |
1725 | |
1726 @optimizeconst | |
1727 def visit_CondExpr(self, node, frame): | |
1728 def write_expr2(): | |
1729 if node.expr2 is not None: | |
1730 return self.visit(node.expr2, frame) | |
1731 self.write( | |
1732 "cond_expr_undefined(%r)" | |
1733 % ( | |
1734 "the inline if-" | |
1735 "expression on %s evaluated to false and " | |
1736 "no else section was defined." % self.position(node) | |
1737 ) | |
1738 ) | |
1739 | |
1740 self.write("(") | |
1741 self.visit(node.expr1, frame) | |
1742 self.write(" if ") | |
1743 self.visit(node.test, frame) | |
1744 self.write(" else ") | |
1745 write_expr2() | |
1746 self.write(")") | |
1747 | |
1748 @optimizeconst | |
1749 def visit_Call(self, node, frame, forward_caller=False): | |
1750 if self.environment.is_async: | |
1751 self.write("await auto_await(") | |
1752 if self.environment.sandboxed: | |
1753 self.write("environment.call(context, ") | |
1754 else: | |
1755 self.write("context.call(") | |
1756 self.visit(node.node, frame) | |
1757 extra_kwargs = forward_caller and {"caller": "caller"} or None | |
1758 self.signature(node, frame, extra_kwargs) | |
1759 self.write(")") | |
1760 if self.environment.is_async: | |
1761 self.write(")") | |
1762 | |
1763 def visit_Keyword(self, node, frame): | |
1764 self.write(node.key + "=") | |
1765 self.visit(node.value, frame) | |
1766 | |
1767 # -- Unused nodes for extensions | |
1768 | |
1769 def visit_MarkSafe(self, node, frame): | |
1770 self.write("Markup(") | |
1771 self.visit(node.expr, frame) | |
1772 self.write(")") | |
1773 | |
1774 def visit_MarkSafeIfAutoescape(self, node, frame): | |
1775 self.write("(context.eval_ctx.autoescape and Markup or identity)(") | |
1776 self.visit(node.expr, frame) | |
1777 self.write(")") | |
1778 | |
1779 def visit_EnvironmentAttribute(self, node, frame): | |
1780 self.write("environment." + node.name) | |
1781 | |
1782 def visit_ExtensionAttribute(self, node, frame): | |
1783 self.write("environment.extensions[%r].%s" % (node.identifier, node.name)) | |
1784 | |
1785 def visit_ImportedName(self, node, frame): | |
1786 self.write(self.import_aliases[node.importname]) | |
1787 | |
1788 def visit_InternalName(self, node, frame): | |
1789 self.write(node.name) | |
1790 | |
1791 def visit_ContextReference(self, node, frame): | |
1792 self.write("context") | |
1793 | |
1794 def visit_DerivedContextReference(self, node, frame): | |
1795 self.write(self.derive_context(frame)) | |
1796 | |
1797 def visit_Continue(self, node, frame): | |
1798 self.writeline("continue", node) | |
1799 | |
1800 def visit_Break(self, node, frame): | |
1801 self.writeline("break", node) | |
1802 | |
1803 def visit_Scope(self, node, frame): | |
1804 scope_frame = frame.inner() | |
1805 scope_frame.symbols.analyze_node(node) | |
1806 self.enter_frame(scope_frame) | |
1807 self.blockvisit(node.body, scope_frame) | |
1808 self.leave_frame(scope_frame) | |
1809 | |
1810 def visit_OverlayScope(self, node, frame): | |
1811 ctx = self.temporary_identifier() | |
1812 self.writeline("%s = %s" % (ctx, self.derive_context(frame))) | |
1813 self.writeline("%s.vars = " % ctx) | |
1814 self.visit(node.context, frame) | |
1815 self.push_context_reference(ctx) | |
1816 | |
1817 scope_frame = frame.inner(isolated=True) | |
1818 scope_frame.symbols.analyze_node(node) | |
1819 self.enter_frame(scope_frame) | |
1820 self.blockvisit(node.body, scope_frame) | |
1821 self.leave_frame(scope_frame) | |
1822 self.pop_context_reference() | |
1823 | |
1824 def visit_EvalContextModifier(self, node, frame): | |
1825 for keyword in node.options: | |
1826 self.writeline("context.eval_ctx.%s = " % keyword.key) | |
1827 self.visit(keyword.value, frame) | |
1828 try: | |
1829 val = keyword.value.as_const(frame.eval_ctx) | |
1830 except nodes.Impossible: | |
1831 frame.eval_ctx.volatile = True | |
1832 else: | |
1833 setattr(frame.eval_ctx, keyword.key, val) | |
1834 | |
1835 def visit_ScopedEvalContextModifier(self, node, frame): | |
1836 old_ctx_name = self.temporary_identifier() | |
1837 saved_ctx = frame.eval_ctx.save() | |
1838 self.writeline("%s = context.eval_ctx.save()" % old_ctx_name) | |
1839 self.visit_EvalContextModifier(node, frame) | |
1840 for child in node.body: | |
1841 self.visit(child, frame) | |
1842 frame.eval_ctx.revert(saved_ctx) | |
1843 self.writeline("context.eval_ctx.revert(%s)" % old_ctx_name) |