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.
- liquid/__about__.py +1 -0
- liquid/__init__.py +161 -187
- liquid/_base_tag.py +66 -0
- liquid/{utils/chain_map.py → _chain_map.py} +18 -19
- liquid/_context.py +465 -0
- liquid/_drop.py +108 -0
- liquid/_filter.py +174 -0
- liquid/_io.py +18 -0
- liquid/_lax_parser_mixin.py +100 -0
- liquid/_lexer.py +93 -0
- liquid/_lexer_extended.py +623 -0
- liquid/_lexer_legacy.py +536 -0
- liquid/_loader.py +84 -0
- liquid/{utils/lru_cache.py → _lru_cache.py} +28 -36
- liquid/_markup.py +253 -0
- liquid/_messages.py +292 -0
- liquid/_missing.py +3 -0
- liquid/_nothing.py +28 -0
- liquid/_parser.py +460 -0
- liquid/_parser_extended.py +293 -0
- liquid/_parser_legacy.py +819 -0
- liquid/_resource_limits.py +50 -0
- liquid/_slice.py +41 -0
- liquid/_static_analysis.py +503 -0
- liquid/_strftime.py +186 -0
- liquid/_template.py +536 -0
- liquid/drops/__init__.py +20 -0
- liquid/drops/forloop.py +78 -0
- liquid/drops/iterable.py +25 -0
- liquid/drops/range.py +76 -0
- liquid/drops/self_.py +45 -0
- liquid/drops/tablerowloop.py +142 -0
- liquid/drops/undefined.py +228 -0
- liquid/environment.py +717 -551
- liquid/exceptions.py +67 -214
- liquid/expressions.py +1184 -0
- liquid/filters/__init__.py +134 -0
- liquid/filters/abs.py +7 -0
- liquid/filters/append.py +5 -0
- liquid/filters/at_least.py +9 -0
- liquid/filters/at_most.py +9 -0
- liquid/filters/babel.py +560 -0
- liquid/filters/base64.py +26 -0
- liquid/filters/capitalize.py +5 -0
- liquid/filters/ceil.py +8 -0
- liquid/filters/compact.py +25 -0
- liquid/filters/concat.py +11 -0
- liquid/filters/date.py +27 -0
- liquid/filters/default.py +25 -0
- liquid/filters/divided_by.py +21 -0
- liquid/filters/downcase.py +5 -0
- liquid/filters/escape.py +56 -0
- liquid/filters/find.py +56 -0
- liquid/filters/first.py +19 -0
- liquid/filters/floor.py +8 -0
- liquid/filters/html.py +23 -0
- liquid/filters/index.py +12 -0
- liquid/filters/join.py +13 -0
- liquid/filters/json_.py +30 -0
- liquid/filters/last.py +17 -0
- liquid/filters/map.py +5 -0
- liquid/filters/minus.py +13 -0
- liquid/filters/modulo.py +18 -0
- liquid/filters/newline_to_br.py +16 -0
- liquid/filters/plus.py +13 -0
- liquid/filters/prepend.py +5 -0
- liquid/filters/reject.py +19 -0
- liquid/filters/remove.py +36 -0
- liquid/filters/replace.py +32 -0
- liquid/filters/reverse.py +5 -0
- liquid/filters/round.py +35 -0
- liquid/filters/size.py +25 -0
- liquid/filters/slice.py +39 -0
- liquid/filters/sort.py +148 -0
- liquid/filters/split.py +34 -0
- liquid/filters/squish.py +9 -0
- liquid/filters/strip.py +13 -0
- liquid/filters/strip_html.py +19 -0
- liquid/filters/strip_newlines.py +17 -0
- liquid/filters/sum.py +21 -0
- liquid/filters/times.py +13 -0
- liquid/filters/translate.py +380 -0
- liquid/filters/truncate.py +39 -0
- liquid/filters/uniq.py +11 -0
- liquid/filters/upcase.py +5 -0
- liquid/filters/url.py +18 -0
- liquid/filters/where.py +18 -0
- liquid/loaders/__init__.py +17 -0
- liquid/loaders/caching_file_system_loader.py +64 -0
- liquid/loaders/choice_loader.py +93 -0
- liquid/loaders/dict_loader.py +56 -0
- liquid/{builtin/loaders → loaders}/file_system_loader.py +51 -55
- liquid/{builtin/loaders → loaders}/mixins.py +39 -51
- liquid/loaders/package_loader.py +110 -0
- liquid/tags/__init__.py +61 -0
- liquid/tags/assign.py +68 -0
- liquid/tags/capture.py +83 -0
- liquid/tags/case.py +199 -0
- liquid/tags/comment.py +56 -0
- liquid/tags/cycle.py +131 -0
- liquid/tags/decrement.py +55 -0
- liquid/tags/doc.py +56 -0
- liquid/tags/echo.py +65 -0
- liquid/tags/else_.py +53 -0
- liquid/tags/error.py +51 -0
- liquid/tags/extends.py +448 -0
- liquid/tags/for_.py +421 -0
- liquid/tags/if_.py +156 -0
- liquid/tags/ifchanged.py +69 -0
- liquid/tags/include.py +223 -0
- liquid/tags/increment.py +55 -0
- liquid/tags/inline_comment.py +70 -0
- liquid/tags/liquid.py +56 -0
- liquid/tags/macro.py +281 -0
- liquid/tags/output.py +58 -0
- liquid/tags/raw.py +60 -0
- liquid/tags/render.py +245 -0
- liquid/tags/tablerow.py +254 -0
- liquid/tags/template_comment.py +56 -0
- liquid/tags/translate.py +316 -0
- liquid/tags/unless.py +121 -0
- liquid/tags/with_.py +83 -0
- liquid/tokens.py +370 -0
- {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/METADATA +32 -39
- python_liquid-3.0.0.dist-info/RECORD +128 -0
- {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/WHEEL +1 -1
- {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/licenses/LICENSE +1 -1
- liquid/analyze_tags.py +0 -214
- liquid/ast.py +0 -252
- liquid/builtin/__init__.py +0 -203
- liquid/builtin/content.py +0 -41
- liquid/builtin/drops/__init__.py +0 -3
- liquid/builtin/drops/drops.py +0 -13
- liquid/builtin/expressions/__init__.py +0 -47
- liquid/builtin/expressions/_tokenize.py +0 -186
- liquid/builtin/expressions/arguments.py +0 -207
- liquid/builtin/expressions/filtered.py +0 -387
- liquid/builtin/expressions/logical.py +0 -672
- liquid/builtin/expressions/loop.py +0 -275
- liquid/builtin/expressions/path.py +0 -163
- liquid/builtin/expressions/primitive.py +0 -417
- liquid/builtin/filters/__init__.py +0 -0
- liquid/builtin/filters/array.py +0 -329
- liquid/builtin/filters/extra.py +0 -83
- liquid/builtin/filters/math.py +0 -129
- liquid/builtin/filters/misc.py +0 -137
- liquid/builtin/filters/string.py +0 -416
- liquid/builtin/illegal.py +0 -28
- liquid/builtin/loaders/__init__.py +0 -0
- liquid/builtin/loaders/caching_file_system_loader.py +0 -61
- liquid/builtin/loaders/choice_loader.py +0 -94
- liquid/builtin/loaders/dict_loader.py +0 -64
- liquid/builtin/loaders/package_loader.py +0 -132
- liquid/builtin/output.py +0 -74
- liquid/builtin/tags/__init__.py +0 -0
- liquid/builtin/tags/assign_tag.py +0 -75
- liquid/builtin/tags/capture_tag.py +0 -98
- liquid/builtin/tags/case_tag.py +0 -287
- liquid/builtin/tags/comment_tag.py +0 -77
- liquid/builtin/tags/cycle_tag.py +0 -132
- liquid/builtin/tags/decrement_tag.py +0 -59
- liquid/builtin/tags/doc_tag.py +0 -68
- liquid/builtin/tags/echo_tag.py +0 -45
- liquid/builtin/tags/for_tag.py +0 -337
- liquid/builtin/tags/if_tag.py +0 -209
- liquid/builtin/tags/ifchanged_tag.py +0 -94
- liquid/builtin/tags/include_tag.py +0 -251
- liquid/builtin/tags/increment_tag.py +0 -60
- liquid/builtin/tags/inline_comment_tag.py +0 -47
- liquid/builtin/tags/liquid_tag.py +0 -183
- liquid/builtin/tags/render_tag.py +0 -371
- liquid/builtin/tags/tablerow_tag.py +0 -320
- liquid/builtin/tags/unless_tag.py +0 -218
- liquid/context.py +0 -615
- liquid/expression.py +0 -41
- liquid/extra/__init__.py +0 -98
- liquid/extra/filters/__init__.py +0 -33
- liquid/extra/filters/_json.py +0 -33
- liquid/extra/filters/array.py +0 -60
- liquid/extra/filters/babel.py +0 -552
- liquid/extra/filters/html.py +0 -32
- liquid/extra/filters/translate.py +0 -448
- liquid/extra/tags/__init__.py +0 -17
- liquid/extra/tags/_with.py +0 -80
- liquid/extra/tags/extends_tag.py +0 -576
- liquid/extra/tags/macro_tag.py +0 -270
- liquid/extra/tags/snippet_tag.py +0 -107
- liquid/extra/tags/translate_tag.py +0 -388
- liquid/filter.py +0 -230
- liquid/future/__init__.py +0 -3
- liquid/future/environment.py +0 -30
- liquid/future/filters/__init__.py +0 -1
- liquid/future/tags/__init__.py +0 -1
- liquid/lex.py +0 -260
- liquid/limits.py +0 -66
- liquid/loader.py +0 -141
- liquid/messages.py +0 -357
- liquid/mode.py +0 -12
- liquid/output.py +0 -35
- liquid/parser.py +0 -119
- liquid/span.py +0 -35
- liquid/static_analysis.py +0 -443
- liquid/stream.py +0 -148
- liquid/stringify.py +0 -31
- liquid/tag.py +0 -47
- liquid/template.py +0 -658
- liquid/token.py +0 -145
- liquid/undefined.py +0 -218
- liquid/utils/__init__.py +0 -15
- liquid/utils/html.py +0 -57
- liquid/utils/text.py +0 -28
- python_liquid-2.3.6.dist-info/RECORD +0 -96
liquid/tags/extends.py
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from .. import expressions
|
|
8
|
+
from .._markup import SCOPE_INHERITED, Partial, render_block, render_block_async
|
|
9
|
+
from .._nothing import NOTHING
|
|
10
|
+
from ..exceptions import (
|
|
11
|
+
LiquidArgumentError,
|
|
12
|
+
LiquidSyntaxError,
|
|
13
|
+
NoSuchTemplateError,
|
|
14
|
+
RequiredBlockError,
|
|
15
|
+
TemplateInheritanceError,
|
|
16
|
+
TemplateNotFoundError,
|
|
17
|
+
)
|
|
18
|
+
from ..expressions import Name
|
|
19
|
+
from ..tokens import (
|
|
20
|
+
TOKEN_DOUBLE_QUOTE,
|
|
21
|
+
TOKEN_IDENT,
|
|
22
|
+
TOKEN_SINGLE_QUOTE,
|
|
23
|
+
TOKEN_TAG_END,
|
|
24
|
+
TOKEN_TAG_NAME,
|
|
25
|
+
TOKEN_TAG_START,
|
|
26
|
+
token_value,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from .._context import RenderContext
|
|
31
|
+
from .._io import StringBuffer
|
|
32
|
+
from .._markup import Block, Expression, Markup
|
|
33
|
+
from .._parser import Parser
|
|
34
|
+
from .._template import Template
|
|
35
|
+
from ..tokens import Token
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ExtendsTag:
|
|
39
|
+
blank = False
|
|
40
|
+
tag = "extend"
|
|
41
|
+
|
|
42
|
+
__slots__ = ("name", "token")
|
|
43
|
+
|
|
44
|
+
def __init__(self, token: Token, name: Name) -> None:
|
|
45
|
+
self.token = token
|
|
46
|
+
self.name = name
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def parse(token: Token, parser: Parser) -> Markup:
|
|
50
|
+
name = parser.parse_name()
|
|
51
|
+
parser.carry_whitespace_control()
|
|
52
|
+
parser.eat(TOKEN_TAG_END)
|
|
53
|
+
return ExtendsTag(token, name)
|
|
54
|
+
|
|
55
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
56
|
+
stacks: defaultdict[str, list[ExtendsStackItem]] = context.get_register(
|
|
57
|
+
"extends", lambda: defaultdict(list)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
base_template = self._stack_blocks(context, stacks)
|
|
61
|
+
|
|
62
|
+
with context.extend({}, template=base_template):
|
|
63
|
+
base_template.render_with_context(context, buffer)
|
|
64
|
+
|
|
65
|
+
stacks.clear()
|
|
66
|
+
context.interrupts.append("stop")
|
|
67
|
+
|
|
68
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
69
|
+
stacks: defaultdict[str, list[ExtendsStackItem]] = context.get_register(
|
|
70
|
+
"extends", lambda: defaultdict(list)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
base_template = self._stack_blocks(context, stacks)
|
|
74
|
+
|
|
75
|
+
with context.extend({}, template=base_template):
|
|
76
|
+
await base_template.render_with_context_async(context, buffer)
|
|
77
|
+
|
|
78
|
+
stacks.clear()
|
|
79
|
+
context.interrupts.append("stop")
|
|
80
|
+
|
|
81
|
+
def children(self) -> list[Markup]:
|
|
82
|
+
return []
|
|
83
|
+
|
|
84
|
+
def expressions(self) -> list[Expression]:
|
|
85
|
+
return []
|
|
86
|
+
|
|
87
|
+
def block_scope(self) -> list[Name]:
|
|
88
|
+
return []
|
|
89
|
+
|
|
90
|
+
def template_scope(self) -> list[Name]:
|
|
91
|
+
return []
|
|
92
|
+
|
|
93
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
94
|
+
template = context.env.get_template(
|
|
95
|
+
self.name.value, context=context, tag="extends"
|
|
96
|
+
)
|
|
97
|
+
return [
|
|
98
|
+
Partial(
|
|
99
|
+
template=template,
|
|
100
|
+
scope=SCOPE_INHERITED,
|
|
101
|
+
in_scope=[],
|
|
102
|
+
key=hash(self.name.value),
|
|
103
|
+
)
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
107
|
+
template = await context.env.get_template_async(
|
|
108
|
+
self.name.value, context=context, tag="extends"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return [
|
|
112
|
+
Partial(
|
|
113
|
+
template=template,
|
|
114
|
+
scope=SCOPE_INHERITED,
|
|
115
|
+
in_scope=[],
|
|
116
|
+
key=hash(self.name.value),
|
|
117
|
+
)
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
def _stack_blocks(
|
|
121
|
+
self, context: RenderContext, stacks: defaultdict[str, list[ExtendsStackItem]]
|
|
122
|
+
) -> Template:
|
|
123
|
+
"""Build a stack for each named block by visiting templates in the inheritance chain."""
|
|
124
|
+
seen_extends: set[str] = set()
|
|
125
|
+
|
|
126
|
+
def visit(template_: Template) -> Template | None:
|
|
127
|
+
extends_nodes, block_nodes = self._find_extends_nodes(context, template_)
|
|
128
|
+
|
|
129
|
+
seen_blocks: set[str] = set()
|
|
130
|
+
|
|
131
|
+
for block in block_nodes:
|
|
132
|
+
block_name = block.name.value
|
|
133
|
+
if block_name in seen_blocks:
|
|
134
|
+
raise TemplateInheritanceError(
|
|
135
|
+
f"duplicate block {block_name!r}",
|
|
136
|
+
token=block.token,
|
|
137
|
+
source=template_.source,
|
|
138
|
+
name=template_.name,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
seen_blocks.add(block_name)
|
|
142
|
+
stack = stacks[block_name]
|
|
143
|
+
required = len(stack) > 1 and block.required
|
|
144
|
+
|
|
145
|
+
stack_item = ExtendsStackItem(
|
|
146
|
+
block=block,
|
|
147
|
+
required=required,
|
|
148
|
+
template=template_,
|
|
149
|
+
parent=None,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if stack:
|
|
153
|
+
stack[-1].parent = stack_item
|
|
154
|
+
|
|
155
|
+
stack.append(stack_item)
|
|
156
|
+
|
|
157
|
+
if not extends_nodes:
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
extends_node = extends_nodes[0]
|
|
161
|
+
extends_name = extends_node.name.value
|
|
162
|
+
|
|
163
|
+
if len(extends_nodes) > 1:
|
|
164
|
+
raise TemplateInheritanceError(
|
|
165
|
+
"too many 'extends' tags",
|
|
166
|
+
token=extends_node.token,
|
|
167
|
+
source=template_.source,
|
|
168
|
+
name=template_.name,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
if extends_name in seen_extends:
|
|
172
|
+
raise TemplateInheritanceError(
|
|
173
|
+
f"circular extends {extends_name!r}",
|
|
174
|
+
token=extends_node.token,
|
|
175
|
+
source=template_.source,
|
|
176
|
+
name=template_.name,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
seen_extends.add(extends_name)
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
return context.env.get_template(
|
|
183
|
+
extends_name, context=context, tag="extends"
|
|
184
|
+
)
|
|
185
|
+
except TemplateNotFoundError as err:
|
|
186
|
+
raise NoSuchTemplateError(
|
|
187
|
+
str(err),
|
|
188
|
+
token=extends_node.name.span,
|
|
189
|
+
source=template_.source,
|
|
190
|
+
name=template_.name,
|
|
191
|
+
) from None
|
|
192
|
+
|
|
193
|
+
next_template = visit(context.template)
|
|
194
|
+
base = next_template
|
|
195
|
+
|
|
196
|
+
while next_template:
|
|
197
|
+
next_template = visit(next_template)
|
|
198
|
+
if next_template:
|
|
199
|
+
base = next_template
|
|
200
|
+
|
|
201
|
+
assert base
|
|
202
|
+
return base
|
|
203
|
+
|
|
204
|
+
def _find_extends_nodes(
|
|
205
|
+
self, context: RenderContext, template: Template
|
|
206
|
+
) -> tuple[list[ExtendsTag], list[BlockTag]]:
|
|
207
|
+
extends_nodes: list[ExtendsTag] = []
|
|
208
|
+
block_nodes: list[BlockTag] = []
|
|
209
|
+
|
|
210
|
+
def visit(node: Markup) -> None:
|
|
211
|
+
if isinstance(node, ExtendsTag):
|
|
212
|
+
extends_nodes.append(node)
|
|
213
|
+
elif isinstance(node, BlockTag):
|
|
214
|
+
block_nodes.append(node)
|
|
215
|
+
|
|
216
|
+
for child in node.children():
|
|
217
|
+
visit(child)
|
|
218
|
+
|
|
219
|
+
for node in template.nodes:
|
|
220
|
+
if not isinstance(node, str):
|
|
221
|
+
visit(node)
|
|
222
|
+
|
|
223
|
+
return extends_nodes, block_nodes
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class BlockTag:
|
|
227
|
+
blank = False
|
|
228
|
+
tag = "block"
|
|
229
|
+
|
|
230
|
+
__slots__ = ("block", "name", "required", "token")
|
|
231
|
+
|
|
232
|
+
def __init__(self, token: Token, name: Name, block: Block, required: bool) -> None:
|
|
233
|
+
self.token = token
|
|
234
|
+
self.name = name
|
|
235
|
+
self.block = block
|
|
236
|
+
self.required = required
|
|
237
|
+
|
|
238
|
+
@classmethod
|
|
239
|
+
def parse(cls, token: Token, parser: Parser) -> Markup:
|
|
240
|
+
name = parser.parse_name()
|
|
241
|
+
required = cls.unpack_args(parser, parser.parse_arguments(require_commas=True))
|
|
242
|
+
parser.carry_whitespace_control()
|
|
243
|
+
parser.eat(TOKEN_TAG_END)
|
|
244
|
+
block = parser.parse_block(("endblock",))
|
|
245
|
+
|
|
246
|
+
# Consume `{% endblock %}` with optional name: `{% endblock name %}`.
|
|
247
|
+
parser.eat(
|
|
248
|
+
TOKEN_TAG_START, message=f"expected tag 'endblock' for {name.value!r}"
|
|
249
|
+
)
|
|
250
|
+
parser.skip_whitespace_control()
|
|
251
|
+
token_ = parser.eat(TOKEN_TAG_NAME, message="expected 'endblock'")
|
|
252
|
+
|
|
253
|
+
if value := token_value(token_, parser.source) != "endblock":
|
|
254
|
+
raise LiquidSyntaxError(
|
|
255
|
+
f"expected endblock for {name!r}, found {value!r}",
|
|
256
|
+
token=token,
|
|
257
|
+
source=parser.source,
|
|
258
|
+
name=parser.template_name,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
if parser.kind() in (TOKEN_IDENT, TOKEN_SINGLE_QUOTE, TOKEN_DOUBLE_QUOTE):
|
|
262
|
+
end_name = parser.parse_name()
|
|
263
|
+
if end_name.value != name.value:
|
|
264
|
+
raise TemplateInheritanceError(
|
|
265
|
+
f"expected endblock for {name!r}, found {end_name.value!r}",
|
|
266
|
+
token=end_name.span,
|
|
267
|
+
source=parser.source,
|
|
268
|
+
name=parser.template_name,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
parser.carry_whitespace_control()
|
|
272
|
+
parser.eat(TOKEN_TAG_END, message="expected a closing tag delimiter")
|
|
273
|
+
|
|
274
|
+
return BlockTag(token, name, block, required)
|
|
275
|
+
|
|
276
|
+
@classmethod
|
|
277
|
+
def unpack_args(
|
|
278
|
+
cls, parser: Parser, args: list[Expression | expressions.KeywordArgument]
|
|
279
|
+
) -> bool:
|
|
280
|
+
required = False
|
|
281
|
+
|
|
282
|
+
for arg in args:
|
|
283
|
+
if (
|
|
284
|
+
isinstance(arg, expressions.Variable)
|
|
285
|
+
and isinstance(arg.root, Name)
|
|
286
|
+
and arg.root.value == "required"
|
|
287
|
+
and arg.selectors is None
|
|
288
|
+
):
|
|
289
|
+
required = True
|
|
290
|
+
else:
|
|
291
|
+
raise LiquidArgumentError(
|
|
292
|
+
"unexpected argument",
|
|
293
|
+
token=arg.span,
|
|
294
|
+
source=parser.source,
|
|
295
|
+
name=parser.template_name,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
return required
|
|
299
|
+
|
|
300
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
301
|
+
stacks: defaultdict[str, list[ExtendsStackItem]] = context.get_register(
|
|
302
|
+
"extends", lambda: defaultdict(list)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
stack = stacks[self.name.value]
|
|
306
|
+
|
|
307
|
+
if not stack:
|
|
308
|
+
# This is a base block being rendered directly
|
|
309
|
+
if self.required:
|
|
310
|
+
raise RequiredBlockError(
|
|
311
|
+
f"block {self.name.value!r} is required",
|
|
312
|
+
token=self.name.span,
|
|
313
|
+
source=context.template.source,
|
|
314
|
+
name=context.template.name,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
with context.extend({"block": BlockDrop(self.name.value)}):
|
|
318
|
+
render_block(self.block, context, buffer)
|
|
319
|
+
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
itm = stack[0]
|
|
323
|
+
|
|
324
|
+
if itm.block.required:
|
|
325
|
+
raise RequiredBlockError(
|
|
326
|
+
f"block {self.name.value!r} is required",
|
|
327
|
+
token=self.name.span,
|
|
328
|
+
source=context.template.source,
|
|
329
|
+
name=context.template.name,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
scope: dict[str, object] = {"block": BlockDrop(self.name.value, itm.parent)}
|
|
333
|
+
block_context = context.copy(scope, block_scope=True, template=itm.template)
|
|
334
|
+
render_block(itm.block.block, block_context, buffer)
|
|
335
|
+
context.render_score_cumulative += block_context.render_score
|
|
336
|
+
|
|
337
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
338
|
+
stacks: defaultdict[str, list[ExtendsStackItem]] = context.get_register(
|
|
339
|
+
"extends", lambda: defaultdict(list)
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
stack = stacks[self.name.value]
|
|
343
|
+
|
|
344
|
+
if not stack:
|
|
345
|
+
# This is a base block being rendered directly
|
|
346
|
+
if self.required:
|
|
347
|
+
raise RequiredBlockError(
|
|
348
|
+
f"block {self.name.value!r} is required",
|
|
349
|
+
token=self.name.span,
|
|
350
|
+
source=context.template.source,
|
|
351
|
+
name=context.template.name,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
with context.extend({"block": BlockDrop(self.name.value)}):
|
|
355
|
+
await render_block_async(self.block, context, buffer)
|
|
356
|
+
|
|
357
|
+
return
|
|
358
|
+
|
|
359
|
+
itm = stack[0]
|
|
360
|
+
|
|
361
|
+
if itm.block.required:
|
|
362
|
+
raise RequiredBlockError(
|
|
363
|
+
f"block {self.name.value!r} is required",
|
|
364
|
+
token=self.name.span,
|
|
365
|
+
source=context.template.source,
|
|
366
|
+
name=context.template.name,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
scope: dict[str, object] = {"block": BlockDrop(self.name.value, itm.parent)}
|
|
370
|
+
block_context = context.copy(scope, block_scope=True, template=itm.template)
|
|
371
|
+
await render_block_async(itm.block.block, block_context, buffer)
|
|
372
|
+
context.render_score_cumulative += block_context.render_score
|
|
373
|
+
|
|
374
|
+
def children(self) -> list[Markup]:
|
|
375
|
+
return [node for node in self.block if not isinstance(node, str)]
|
|
376
|
+
|
|
377
|
+
def expressions(self) -> list[Expression]:
|
|
378
|
+
return []
|
|
379
|
+
|
|
380
|
+
def block_scope(self) -> list[Name]:
|
|
381
|
+
return [Name(self.token, "block")]
|
|
382
|
+
|
|
383
|
+
def template_scope(self) -> list[Name]:
|
|
384
|
+
return []
|
|
385
|
+
|
|
386
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
387
|
+
return []
|
|
388
|
+
|
|
389
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
390
|
+
return []
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@dataclass(slots=True, kw_only=True)
|
|
394
|
+
class ExtendsStackItem:
|
|
395
|
+
block: BlockTag
|
|
396
|
+
required: bool
|
|
397
|
+
template: Template
|
|
398
|
+
parent: ExtendsStackItem | None
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class BlockDrop:
|
|
402
|
+
__slots__ = ("name", "parent")
|
|
403
|
+
|
|
404
|
+
def __init__(self, name: str, parent: ExtendsStackItem | None = None) -> None:
|
|
405
|
+
super().__init__()
|
|
406
|
+
self.name = name
|
|
407
|
+
self.parent = parent
|
|
408
|
+
|
|
409
|
+
def __liquid_hasattr__(self, name: str, context: RenderContext) -> bool:
|
|
410
|
+
return name == "super"
|
|
411
|
+
|
|
412
|
+
def __liquid_getattr__(self, name: str, context: RenderContext) -> object:
|
|
413
|
+
if self.parent is None or name != "super":
|
|
414
|
+
return NOTHING
|
|
415
|
+
|
|
416
|
+
buffer = context.env.buffer_factory()
|
|
417
|
+
|
|
418
|
+
scope: dict[str, object] = {
|
|
419
|
+
"block": BlockDrop(self.parent.template.name, self.parent.parent)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
with context.extend(scope, template=self.parent.template):
|
|
423
|
+
render_block(self.parent.block.block, context, buffer)
|
|
424
|
+
|
|
425
|
+
return buffer.getvalue()
|
|
426
|
+
|
|
427
|
+
async def __liquid_hasattr_async__(self, name: str, context: RenderContext) -> bool:
|
|
428
|
+
return name == "super"
|
|
429
|
+
|
|
430
|
+
async def __liquid_getattr_async__(
|
|
431
|
+
self, name: str, context: RenderContext
|
|
432
|
+
) -> object:
|
|
433
|
+
if self.parent is None or name != "super":
|
|
434
|
+
return NOTHING
|
|
435
|
+
|
|
436
|
+
buffer = context.env.buffer_factory()
|
|
437
|
+
|
|
438
|
+
scope: dict[str, object] = {
|
|
439
|
+
"block": BlockDrop(self.parent.template.name, self.parent.parent)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
with context.extend(scope, template=self.parent.template):
|
|
443
|
+
await render_block_async(self.parent.block.block, context, buffer)
|
|
444
|
+
|
|
445
|
+
return buffer.getvalue()
|
|
446
|
+
|
|
447
|
+
def __str__(self) -> str:
|
|
448
|
+
return f"BlockDrop({self.name})"
|