okstra 0.31.0 → 0.32.0

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