python-liquid 2.3.6__py3-none-any.whl → 3.0.0__py3-none-any.whl

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 (212) hide show
  1. liquid/__about__.py +1 -0
  2. liquid/__init__.py +161 -187
  3. liquid/_base_tag.py +66 -0
  4. liquid/{utils/chain_map.py → _chain_map.py} +18 -19
  5. liquid/_context.py +465 -0
  6. liquid/_drop.py +108 -0
  7. liquid/_filter.py +174 -0
  8. liquid/_io.py +18 -0
  9. liquid/_lax_parser_mixin.py +100 -0
  10. liquid/_lexer.py +93 -0
  11. liquid/_lexer_extended.py +623 -0
  12. liquid/_lexer_legacy.py +536 -0
  13. liquid/_loader.py +84 -0
  14. liquid/{utils/lru_cache.py → _lru_cache.py} +28 -36
  15. liquid/_markup.py +253 -0
  16. liquid/_messages.py +292 -0
  17. liquid/_missing.py +3 -0
  18. liquid/_nothing.py +28 -0
  19. liquid/_parser.py +460 -0
  20. liquid/_parser_extended.py +293 -0
  21. liquid/_parser_legacy.py +819 -0
  22. liquid/_resource_limits.py +50 -0
  23. liquid/_slice.py +41 -0
  24. liquid/_static_analysis.py +503 -0
  25. liquid/_strftime.py +186 -0
  26. liquid/_template.py +536 -0
  27. liquid/drops/__init__.py +20 -0
  28. liquid/drops/forloop.py +78 -0
  29. liquid/drops/iterable.py +25 -0
  30. liquid/drops/range.py +76 -0
  31. liquid/drops/self_.py +45 -0
  32. liquid/drops/tablerowloop.py +142 -0
  33. liquid/drops/undefined.py +228 -0
  34. liquid/environment.py +717 -551
  35. liquid/exceptions.py +67 -214
  36. liquid/expressions.py +1184 -0
  37. liquid/filters/__init__.py +134 -0
  38. liquid/filters/abs.py +7 -0
  39. liquid/filters/append.py +5 -0
  40. liquid/filters/at_least.py +9 -0
  41. liquid/filters/at_most.py +9 -0
  42. liquid/filters/babel.py +560 -0
  43. liquid/filters/base64.py +26 -0
  44. liquid/filters/capitalize.py +5 -0
  45. liquid/filters/ceil.py +8 -0
  46. liquid/filters/compact.py +25 -0
  47. liquid/filters/concat.py +11 -0
  48. liquid/filters/date.py +27 -0
  49. liquid/filters/default.py +25 -0
  50. liquid/filters/divided_by.py +21 -0
  51. liquid/filters/downcase.py +5 -0
  52. liquid/filters/escape.py +56 -0
  53. liquid/filters/find.py +56 -0
  54. liquid/filters/first.py +19 -0
  55. liquid/filters/floor.py +8 -0
  56. liquid/filters/html.py +23 -0
  57. liquid/filters/index.py +12 -0
  58. liquid/filters/join.py +13 -0
  59. liquid/filters/json_.py +30 -0
  60. liquid/filters/last.py +17 -0
  61. liquid/filters/map.py +5 -0
  62. liquid/filters/minus.py +13 -0
  63. liquid/filters/modulo.py +18 -0
  64. liquid/filters/newline_to_br.py +16 -0
  65. liquid/filters/plus.py +13 -0
  66. liquid/filters/prepend.py +5 -0
  67. liquid/filters/reject.py +19 -0
  68. liquid/filters/remove.py +36 -0
  69. liquid/filters/replace.py +32 -0
  70. liquid/filters/reverse.py +5 -0
  71. liquid/filters/round.py +35 -0
  72. liquid/filters/size.py +25 -0
  73. liquid/filters/slice.py +39 -0
  74. liquid/filters/sort.py +148 -0
  75. liquid/filters/split.py +34 -0
  76. liquid/filters/squish.py +9 -0
  77. liquid/filters/strip.py +13 -0
  78. liquid/filters/strip_html.py +19 -0
  79. liquid/filters/strip_newlines.py +17 -0
  80. liquid/filters/sum.py +21 -0
  81. liquid/filters/times.py +13 -0
  82. liquid/filters/translate.py +380 -0
  83. liquid/filters/truncate.py +39 -0
  84. liquid/filters/uniq.py +11 -0
  85. liquid/filters/upcase.py +5 -0
  86. liquid/filters/url.py +18 -0
  87. liquid/filters/where.py +18 -0
  88. liquid/loaders/__init__.py +17 -0
  89. liquid/loaders/caching_file_system_loader.py +64 -0
  90. liquid/loaders/choice_loader.py +93 -0
  91. liquid/loaders/dict_loader.py +56 -0
  92. liquid/{builtin/loaders → loaders}/file_system_loader.py +51 -55
  93. liquid/{builtin/loaders → loaders}/mixins.py +39 -51
  94. liquid/loaders/package_loader.py +110 -0
  95. liquid/tags/__init__.py +61 -0
  96. liquid/tags/assign.py +68 -0
  97. liquid/tags/capture.py +83 -0
  98. liquid/tags/case.py +199 -0
  99. liquid/tags/comment.py +56 -0
  100. liquid/tags/cycle.py +131 -0
  101. liquid/tags/decrement.py +55 -0
  102. liquid/tags/doc.py +56 -0
  103. liquid/tags/echo.py +65 -0
  104. liquid/tags/else_.py +53 -0
  105. liquid/tags/error.py +51 -0
  106. liquid/tags/extends.py +448 -0
  107. liquid/tags/for_.py +421 -0
  108. liquid/tags/if_.py +156 -0
  109. liquid/tags/ifchanged.py +69 -0
  110. liquid/tags/include.py +223 -0
  111. liquid/tags/increment.py +55 -0
  112. liquid/tags/inline_comment.py +70 -0
  113. liquid/tags/liquid.py +56 -0
  114. liquid/tags/macro.py +281 -0
  115. liquid/tags/output.py +58 -0
  116. liquid/tags/raw.py +60 -0
  117. liquid/tags/render.py +245 -0
  118. liquid/tags/tablerow.py +254 -0
  119. liquid/tags/template_comment.py +56 -0
  120. liquid/tags/translate.py +316 -0
  121. liquid/tags/unless.py +121 -0
  122. liquid/tags/with_.py +83 -0
  123. liquid/tokens.py +370 -0
  124. {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/METADATA +32 -39
  125. python_liquid-3.0.0.dist-info/RECORD +128 -0
  126. {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/WHEEL +1 -1
  127. {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/licenses/LICENSE +1 -1
  128. liquid/analyze_tags.py +0 -214
  129. liquid/ast.py +0 -252
  130. liquid/builtin/__init__.py +0 -203
  131. liquid/builtin/content.py +0 -41
  132. liquid/builtin/drops/__init__.py +0 -3
  133. liquid/builtin/drops/drops.py +0 -13
  134. liquid/builtin/expressions/__init__.py +0 -47
  135. liquid/builtin/expressions/_tokenize.py +0 -186
  136. liquid/builtin/expressions/arguments.py +0 -207
  137. liquid/builtin/expressions/filtered.py +0 -387
  138. liquid/builtin/expressions/logical.py +0 -672
  139. liquid/builtin/expressions/loop.py +0 -275
  140. liquid/builtin/expressions/path.py +0 -163
  141. liquid/builtin/expressions/primitive.py +0 -417
  142. liquid/builtin/filters/__init__.py +0 -0
  143. liquid/builtin/filters/array.py +0 -329
  144. liquid/builtin/filters/extra.py +0 -83
  145. liquid/builtin/filters/math.py +0 -129
  146. liquid/builtin/filters/misc.py +0 -137
  147. liquid/builtin/filters/string.py +0 -416
  148. liquid/builtin/illegal.py +0 -28
  149. liquid/builtin/loaders/__init__.py +0 -0
  150. liquid/builtin/loaders/caching_file_system_loader.py +0 -61
  151. liquid/builtin/loaders/choice_loader.py +0 -94
  152. liquid/builtin/loaders/dict_loader.py +0 -64
  153. liquid/builtin/loaders/package_loader.py +0 -132
  154. liquid/builtin/output.py +0 -74
  155. liquid/builtin/tags/__init__.py +0 -0
  156. liquid/builtin/tags/assign_tag.py +0 -75
  157. liquid/builtin/tags/capture_tag.py +0 -98
  158. liquid/builtin/tags/case_tag.py +0 -287
  159. liquid/builtin/tags/comment_tag.py +0 -77
  160. liquid/builtin/tags/cycle_tag.py +0 -132
  161. liquid/builtin/tags/decrement_tag.py +0 -59
  162. liquid/builtin/tags/doc_tag.py +0 -68
  163. liquid/builtin/tags/echo_tag.py +0 -45
  164. liquid/builtin/tags/for_tag.py +0 -337
  165. liquid/builtin/tags/if_tag.py +0 -209
  166. liquid/builtin/tags/ifchanged_tag.py +0 -94
  167. liquid/builtin/tags/include_tag.py +0 -251
  168. liquid/builtin/tags/increment_tag.py +0 -60
  169. liquid/builtin/tags/inline_comment_tag.py +0 -47
  170. liquid/builtin/tags/liquid_tag.py +0 -183
  171. liquid/builtin/tags/render_tag.py +0 -371
  172. liquid/builtin/tags/tablerow_tag.py +0 -320
  173. liquid/builtin/tags/unless_tag.py +0 -218
  174. liquid/context.py +0 -615
  175. liquid/expression.py +0 -41
  176. liquid/extra/__init__.py +0 -98
  177. liquid/extra/filters/__init__.py +0 -33
  178. liquid/extra/filters/_json.py +0 -33
  179. liquid/extra/filters/array.py +0 -60
  180. liquid/extra/filters/babel.py +0 -552
  181. liquid/extra/filters/html.py +0 -32
  182. liquid/extra/filters/translate.py +0 -448
  183. liquid/extra/tags/__init__.py +0 -17
  184. liquid/extra/tags/_with.py +0 -80
  185. liquid/extra/tags/extends_tag.py +0 -576
  186. liquid/extra/tags/macro_tag.py +0 -270
  187. liquid/extra/tags/snippet_tag.py +0 -107
  188. liquid/extra/tags/translate_tag.py +0 -388
  189. liquid/filter.py +0 -230
  190. liquid/future/__init__.py +0 -3
  191. liquid/future/environment.py +0 -30
  192. liquid/future/filters/__init__.py +0 -1
  193. liquid/future/tags/__init__.py +0 -1
  194. liquid/lex.py +0 -260
  195. liquid/limits.py +0 -66
  196. liquid/loader.py +0 -141
  197. liquid/messages.py +0 -357
  198. liquid/mode.py +0 -12
  199. liquid/output.py +0 -35
  200. liquid/parser.py +0 -119
  201. liquid/span.py +0 -35
  202. liquid/static_analysis.py +0 -443
  203. liquid/stream.py +0 -148
  204. liquid/stringify.py +0 -31
  205. liquid/tag.py +0 -47
  206. liquid/template.py +0 -658
  207. liquid/token.py +0 -145
  208. liquid/undefined.py +0 -218
  209. liquid/utils/__init__.py +0 -15
  210. liquid/utils/html.py +0 -57
  211. liquid/utils/text.py +0 -28
  212. python_liquid-2.3.6.dist-info/RECORD +0 -96
@@ -1,576 +0,0 @@
1
- """The standard _extends_ and _block_ tags."""
2
-
3
- from __future__ import annotations
4
-
5
- from dataclasses import dataclass
6
- from typing import TYPE_CHECKING
7
- from typing import DefaultDict
8
- from typing import Iterable
9
- from typing import Iterator
10
- from typing import Mapping
11
- from typing import Optional
12
- from typing import Sequence
13
- from typing import TextIO
14
-
15
- from markupsafe import Markup as Markupsafe
16
-
17
- from liquid.ast import BlockNode as TemplateBlock
18
- from liquid.ast import Node
19
- from liquid.ast import Partial
20
- from liquid.ast import PartialScope
21
- from liquid.builtin.expressions import Identifier
22
- from liquid.builtin.expressions import parse_name
23
- from liquid.exceptions import RequiredBlockError
24
- from liquid.exceptions import StopRender
25
- from liquid.exceptions import TemplateInheritanceError
26
- from liquid.exceptions import TemplateNotFoundError
27
- from liquid.parser import get_parser
28
- from liquid.tag import Tag
29
- from liquid.token import TOKEN_EOF
30
- from liquid.token import TOKEN_EXPRESSION
31
- from liquid.token import TOKEN_REQUIRED
32
- from liquid.token import TOKEN_TAG
33
-
34
- if TYPE_CHECKING:
35
- from liquid import BoundTemplate
36
- from liquid import RenderContext
37
- from liquid import Token
38
- from liquid import TokenStream
39
-
40
-
41
- class ExtendsNode(Node):
42
- """The standard _extends_ tag."""
43
-
44
- __slots__ = ("name",)
45
- tag = "extends"
46
-
47
- def __init__(self, token: Token, name: Identifier) -> None:
48
- super().__init__(token)
49
- self.name = name
50
- self.blank = False
51
-
52
- def __str__(self) -> str:
53
- return f"{{% extends {self.name} %}}"
54
-
55
- def render_to_output(self, context: RenderContext, buffer: TextIO) -> int:
56
- """Render the node to the output buffer."""
57
- base_template = _build_block_stacks(context, context.template, "extends")
58
-
59
- base_template.render_with_context(context, buffer)
60
- context.tag_namespace["extends"].clear()
61
- raise StopRender
62
-
63
- async def render_to_output_async(
64
- self, context: RenderContext, buffer: TextIO
65
- ) -> int:
66
- """Render the node to the output buffer."""
67
- base_template = await _build_block_stacks_async(
68
- context, context.template, "extends"
69
- )
70
-
71
- await base_template.render_with_context_async(context, buffer)
72
- context.tag_namespace["extends"].clear()
73
- raise StopRender
74
-
75
- def children(
76
- self, static_context: RenderContext, *, include_partials: bool = True
77
- ) -> Iterable[Node]:
78
- """Return this node's children."""
79
- if include_partials:
80
- try:
81
- parent = static_context.env.get_template(
82
- self.name, context=static_context, tag=self.tag
83
- )
84
- yield from parent.nodes
85
- except TemplateNotFoundError as err:
86
- err.token = self.name.token
87
- err.template_name = static_context.template.full_name()
88
- raise
89
-
90
- async def children_async(
91
- self, static_context: RenderContext, *, include_partials: bool = True
92
- ) -> Iterable[Node]:
93
- """Return this node's children."""
94
- if include_partials:
95
- try:
96
- parent = await static_context.env.get_template_async(
97
- self.name, context=static_context, tag=self.tag
98
- )
99
- return parent.nodes
100
- except TemplateNotFoundError as err:
101
- err.token = self.name.token
102
- err.template_name = static_context.template.full_name()
103
- raise
104
- return []
105
-
106
- def partial_scope(self) -> Optional[Partial]:
107
- """Return information about a partial template loaded by this node."""
108
- return Partial(
109
- name=self.name,
110
- scope=PartialScope.INHERITED,
111
- in_scope=[],
112
- )
113
-
114
-
115
- class ExtendsTag(Tag):
116
- """The standard _extends_ tag."""
117
-
118
- name = "extends"
119
- block = False
120
- node_class = ExtendsNode
121
-
122
- def parse(self, stream: TokenStream) -> Node:
123
- """Parse tokens from _stream_ into an AST node."""
124
- token = stream.eat(TOKEN_TAG)
125
- tokens = stream.into_inner(tag=token, eat=False)
126
- name = parse_name(self.env, tokens)
127
- tokens.expect_eos()
128
- return self.node_class(token=token, name=name)
129
-
130
-
131
- class BlockNode(Node):
132
- """The standard _block_ tag."""
133
-
134
- __slots__ = ("name", "block", "required")
135
- tag = "block"
136
-
137
- def __init__(
138
- self,
139
- token: Token,
140
- name: str,
141
- block: TemplateBlock,
142
- *,
143
- required: bool,
144
- ) -> None:
145
- super().__init__(token)
146
- self.name = name
147
- self.block = block
148
- self.required = required
149
- self.blank = False
150
-
151
- def __str__(self) -> str:
152
- required = " required" if self.required else ""
153
- return (
154
- f"{{% block {self.name}{required} %}}"
155
- f"{self.block}"
156
- f"{{% endblock {self.name} %}}"
157
- )
158
-
159
- def render_to_output(self, context: RenderContext, buffer: TextIO) -> int:
160
- """Render the node to the output buffer."""
161
- # We should be in a base template. Render the block at the top of the "stack".
162
- block_stack: Sequence[_BlockStackItem] = context.tag_namespace.get(
163
- "extends", {}
164
- ).get(self.name)
165
-
166
- if not block_stack:
167
- # This base template is being rendered directly.
168
- if self.required:
169
- raise RequiredBlockError(
170
- f"block {self.name!r} must be overridden", token=self.token
171
- )
172
- with context.extend(
173
- {
174
- "block": BlockDrop(
175
- token=self.token,
176
- context=context,
177
- buffer=buffer,
178
- name=self.name,
179
- parent=None,
180
- )
181
- }
182
- ):
183
- return self.block.render(context, buffer)
184
-
185
- stack_item = block_stack[0]
186
-
187
- if stack_item.required:
188
- raise RequiredBlockError(
189
- f"block {self.name!r} must be overridden",
190
- token=self.token,
191
- template_name=stack_item.source_name,
192
- )
193
-
194
- ctx = context.copy(
195
- namespace={
196
- "block": BlockDrop(
197
- token=self.token,
198
- context=context,
199
- buffer=buffer,
200
- name=self.name,
201
- parent=stack_item.parent,
202
- )
203
- },
204
- carry_loop_iterations=True,
205
- block_scope=True,
206
- )
207
-
208
- return stack_item.block.block.render(ctx, buffer)
209
-
210
- async def render_to_output_async(
211
- self, context: RenderContext, buffer: TextIO
212
- ) -> int:
213
- """Render the node to the output buffer."""
214
- # We should be in a base template. Render the block at the top of the "stack".
215
- block_stack: Sequence[_BlockStackItem] = context.tag_namespace.get(
216
- "extends", {}
217
- ).get(self.name)
218
-
219
- if not block_stack:
220
- # This base template is being rendered directly.
221
- if self.required:
222
- raise RequiredBlockError(
223
- f"block {self.name!r} must be overridden", token=self.token
224
- )
225
- with context.extend(
226
- {
227
- "block": BlockDrop(
228
- token=self.token,
229
- context=context,
230
- buffer=buffer,
231
- name=self.name,
232
- parent=None,
233
- )
234
- }
235
- ):
236
- return await self.block.render_async(context, buffer)
237
-
238
- stack_item = block_stack[0]
239
-
240
- if stack_item.required:
241
- raise RequiredBlockError(
242
- f"block {self.name!r} must be overridden",
243
- token=self.token,
244
- template_name=stack_item.source_name,
245
- )
246
-
247
- ctx = context.copy(
248
- namespace={
249
- "block": BlockDrop(
250
- token=self.token,
251
- context=context,
252
- buffer=buffer,
253
- name=self.name,
254
- parent=stack_item.parent,
255
- )
256
- },
257
- carry_loop_iterations=True,
258
- block_scope=True,
259
- )
260
- return await stack_item.block.block.render_async(ctx, buffer)
261
-
262
- def children(
263
- self,
264
- static_context: RenderContext, # noqa: ARG002
265
- *,
266
- include_partials: bool = True, # noqa: ARG002
267
- ) -> Iterable[Node]:
268
- """Return this node's expressions."""
269
- yield self.block
270
-
271
- def block_scope(self) -> Iterable[Identifier]:
272
- """Return variables this node adds to the node's block scope."""
273
- yield Identifier("block", token=self.token)
274
-
275
-
276
- class BlockTag(Tag):
277
- """The standard _extends_ tag."""
278
-
279
- name = "block"
280
- block = True
281
- node_class = BlockNode
282
- end_block = frozenset(["endblock"])
283
-
284
- def parse(self, stream: TokenStream) -> Node:
285
- """Parse tokens from _stream_ into an AST node."""
286
- token = stream.eat(TOKEN_TAG)
287
- tokens = stream.into_inner(tag=token)
288
- block_name = parse_name(self.env, tokens)
289
-
290
- if tokens.current.kind == TOKEN_REQUIRED:
291
- required = True
292
- next(tokens)
293
- else:
294
- required = False
295
-
296
- tokens.expect_eos()
297
-
298
- parse_block = get_parser(self.env).parse_block
299
- block = parse_block(stream, self.end_block)
300
-
301
- stream.expect(TOKEN_TAG, "endblock")
302
- end_block_token = stream.current
303
-
304
- if stream.peek.kind == TOKEN_EXPRESSION:
305
- next(stream)
306
- tokens = stream.into_inner(tag=token, eat=False)
307
- if tokens.current.kind != TOKEN_EOF:
308
- end_block_name = parse_name(self.env, tokens)
309
- if end_block_name != block_name:
310
- raise TemplateInheritanceError(
311
- f"expected endblock for '{block_name}, "
312
- f"found '{end_block_name}'",
313
- token=end_block_token,
314
- )
315
- next(tokens)
316
- tokens.expect_eos()
317
-
318
- return self.node_class(
319
- token=token,
320
- name=block_name,
321
- block=block,
322
- required=required,
323
- )
324
-
325
-
326
- @dataclass
327
- class _BlockStackItem:
328
- token: Token
329
- block: BlockNode
330
- required: bool
331
- source_name: str
332
- parent: Optional[_BlockStackItem] = None
333
-
334
-
335
- class BlockDrop(Mapping[str, object]):
336
- """A `block` object with a `super` property."""
337
-
338
- __slots__ = ("token", "buffer", "context", "name", "parent")
339
-
340
- def __init__(
341
- self,
342
- *,
343
- token: Token,
344
- context: RenderContext,
345
- buffer: TextIO,
346
- name: str,
347
- parent: Optional[_BlockStackItem],
348
- ) -> None:
349
- self.token = token
350
- self.buffer = buffer
351
- self.context = context
352
- self.name = name
353
- self.parent = parent
354
-
355
- def __str__(self) -> str: # pragma: no cover
356
- return f"BlockDrop({self.name})"
357
-
358
- def __getitem__(self, key: str) -> object:
359
- if key != "super":
360
- raise KeyError(key)
361
-
362
- if not self.parent:
363
- return self.context.env.undefined("super", token=self.token)
364
-
365
- # NOTE: We're not allowing chaining of references to `super` for now.
366
- # Just the immediate parent.
367
- buf = self.context.get_buffer(self.buffer)
368
- with self.context.extend(
369
- {
370
- "block": BlockDrop(
371
- token=self.parent.token,
372
- context=self.context,
373
- buffer=buf,
374
- name=self.parent.source_name,
375
- parent=self.parent.parent,
376
- )
377
- }
378
- ):
379
- self.parent.block.block.render(self.context, buf)
380
-
381
- if self.context.autoescape:
382
- return Markupsafe(buf.getvalue())
383
- return buf.getvalue()
384
-
385
- def __len__(self) -> int: # pragma: no cover
386
- return 1
387
-
388
- def __iter__(self) -> Iterator[str]: # pragma: no cover
389
- return iter(["super"])
390
-
391
-
392
- def _build_block_stacks(
393
- context: RenderContext,
394
- template: BoundTemplate,
395
- tag: str,
396
- ) -> BoundTemplate:
397
- """Build a stack for each `{% block %}` in the inheritance chain.
398
-
399
- Blocks defined in the base template will be at the top of the stack.
400
-
401
- Args:
402
- context: A render context to build the block stacks in.
403
- template: A leaf template with an `extends` tag.
404
- parent_name: The name of the immediate parent template as a string literal.
405
- tag: The name of the `extends` tag, if it is overridden.
406
- """
407
- # Guard against recursive `extends`.
408
- seen: set[str] = set()
409
-
410
- def _stack_template_blocks(template: BoundTemplate) -> Optional[BoundTemplate]:
411
- extends_node, _ = _stack_blocks(context, template)
412
-
413
- if not extends_node:
414
- return None
415
-
416
- if extends_node.name in seen:
417
- raise TemplateInheritanceError(
418
- f"circular extends {extends_node.name!r}",
419
- token=extends_node.token,
420
- template_name=template.name,
421
- )
422
-
423
- seen.add(extends_node.name)
424
-
425
- try:
426
- return context.env.get_template(extends_node.name, context=context, tag=tag)
427
- except TemplateNotFoundError as err:
428
- err.token = extends_node.name.token
429
- err.template_name = template.full_name()
430
- raise
431
-
432
- base = next_template = _stack_template_blocks(template)
433
-
434
- while next_template:
435
- next_template = _stack_template_blocks(next_template)
436
- if next_template:
437
- base = next_template
438
-
439
- assert base
440
- return base
441
-
442
-
443
- async def _build_block_stacks_async(
444
- context: RenderContext,
445
- template: BoundTemplate,
446
- tag: str,
447
- ) -> BoundTemplate:
448
- """Build a stack for each `{% block %}` in the inheritance chain.
449
-
450
- Blocks defined in the base template will be at the top of the stack.
451
-
452
- Args:
453
- context: A render context to build the block stacks in.
454
- template: A leaf template with an `extends` tag.
455
- parent_name: The name of the immediate parent template as a string.
456
- tag: The name of the `extends` tag, if it is overridden.
457
- """
458
- # Guard against recursive `extends`.
459
- seen: set[str] = set()
460
-
461
- async def _stack_template_blocks(
462
- template: BoundTemplate,
463
- ) -> Optional[BoundTemplate]:
464
- extends_node, _ = _stack_blocks(context, template)
465
-
466
- if not extends_node:
467
- return None
468
-
469
- if extends_node.name in seen:
470
- raise TemplateInheritanceError(
471
- f"circular extends {extends_node.name!r}",
472
- token=extends_node.token,
473
- template_name=template.name,
474
- )
475
-
476
- seen.add(extends_node.name)
477
-
478
- try:
479
- return await context.env.get_template_async(
480
- extends_node.name, context=context, tag=tag
481
- )
482
- except TemplateNotFoundError as err:
483
- err.token = extends_node.name.token
484
- err.template_name = template.full_name()
485
- raise
486
-
487
- base = next_template = await _stack_template_blocks(template)
488
-
489
- while next_template:
490
- next_template = await _stack_template_blocks(next_template)
491
- if next_template:
492
- base = next_template
493
-
494
- assert base
495
- return base
496
-
497
-
498
- def _find_inheritance_nodes(
499
- template: BoundTemplate, context: RenderContext
500
- ) -> tuple[list["ExtendsNode"], list[BlockNode]]:
501
- """Return lists of `extends` and `block` nodes from the given template."""
502
- extends_nodes: list["ExtendsNode"] = []
503
- block_nodes: list[BlockNode] = []
504
-
505
- def _visit_node(node: Node, context: RenderContext) -> None:
506
- if isinstance(node, BlockNode):
507
- block_nodes.append(node)
508
-
509
- if isinstance(node, ExtendsNode):
510
- extends_nodes.append(node)
511
-
512
- for child in node.children(context, include_partials=False):
513
- _visit_node(child, context=context)
514
-
515
- for node in template.nodes:
516
- _visit_node(node, context=context)
517
-
518
- return extends_nodes, block_nodes
519
-
520
-
521
- def _stack_blocks(
522
- context: RenderContext, template: BoundTemplate
523
- ) -> tuple[Optional[ExtendsNode], list[BlockNode]]:
524
- """Find template inheritance nodes in `template`.
525
-
526
- Each node found is pushed on to the appropriate block stack.
527
- """
528
- extends, blocks = _find_inheritance_nodes(template, context)
529
- template_name = str(template.path or template.name)
530
-
531
- if len(extends) > 1:
532
- raise TemplateInheritanceError(
533
- "too many 'extends' tags",
534
- token=extends[1].token,
535
- template_name=template_name,
536
- )
537
-
538
- seen_block_names: set[str] = set()
539
- for block in blocks:
540
- if block.name in seen_block_names:
541
- raise TemplateInheritanceError(
542
- f"duplicate block {block.name}",
543
- token=block.token,
544
- )
545
- seen_block_names.add(block.name)
546
-
547
- _store_blocks(context, blocks, template_name)
548
-
549
- if not extends:
550
- return None, blocks
551
- # return extends[0].name.evaluate(context), blocks
552
- return extends[0], blocks
553
-
554
-
555
- def _store_blocks(
556
- context: RenderContext, blocks: list[BlockNode], source_name: str
557
- ) -> None:
558
- block_stacks: DefaultDict[str, list[_BlockStackItem]] = context.tag_namespace[
559
- "extends"
560
- ]
561
-
562
- for block in blocks:
563
- stack = block_stacks[block.name]
564
- required = False if stack and not block.required else block.required
565
-
566
- stack.append(
567
- _BlockStackItem(
568
- token=block.token,
569
- block=block,
570
- required=required,
571
- source_name=source_name,
572
- )
573
- )
574
-
575
- if len(stack) > 1:
576
- stack[-2].parent = stack[-1]