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/builtin/tags/for_tag.py
DELETED
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
"""The built-in _for_ tag."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import sys
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
7
|
-
from typing import Any
|
|
8
|
-
from typing import Iterable
|
|
9
|
-
from typing import Iterator
|
|
10
|
-
from typing import Mapping
|
|
11
|
-
from typing import Optional
|
|
12
|
-
from typing import TextIO
|
|
13
|
-
|
|
14
|
-
from liquid.ast import BlockNode
|
|
15
|
-
from liquid.ast import Node
|
|
16
|
-
from liquid.builtin.expressions import Identifier
|
|
17
|
-
from liquid.builtin.expressions import LoopExpression
|
|
18
|
-
from liquid.exceptions import BreakLoop
|
|
19
|
-
from liquid.exceptions import ContinueLoop
|
|
20
|
-
from liquid.parser import get_parser
|
|
21
|
-
from liquid.tag import Tag
|
|
22
|
-
from liquid.token import TOKEN_TAG
|
|
23
|
-
from liquid.token import Token
|
|
24
|
-
|
|
25
|
-
if TYPE_CHECKING:
|
|
26
|
-
from liquid.context import RenderContext
|
|
27
|
-
from liquid.expression import Expression
|
|
28
|
-
from liquid.stream import TokenStream
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
TAG_FOR = sys.intern("for")
|
|
32
|
-
TAG_ENDFOR = sys.intern("endfor")
|
|
33
|
-
TAG_ELSE = sys.intern("else")
|
|
34
|
-
|
|
35
|
-
TAG_BREAK = sys.intern("break")
|
|
36
|
-
TAG_CONTINUE = sys.intern("continue")
|
|
37
|
-
|
|
38
|
-
ENDFORBLOCK = frozenset((TAG_ENDFOR, TAG_ELSE))
|
|
39
|
-
ENDFORELSEBLOCK = frozenset((TAG_ENDFOR,))
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class ForNode(Node):
|
|
43
|
-
"""The built-in _for_ tag."""
|
|
44
|
-
|
|
45
|
-
__slots__ = ("expression", "block", "default")
|
|
46
|
-
|
|
47
|
-
def __init__(
|
|
48
|
-
self,
|
|
49
|
-
token: Token,
|
|
50
|
-
expression: LoopExpression,
|
|
51
|
-
block: BlockNode,
|
|
52
|
-
default: Optional[BlockNode] = None,
|
|
53
|
-
):
|
|
54
|
-
super().__init__(token)
|
|
55
|
-
self.expression = expression
|
|
56
|
-
self.block = block
|
|
57
|
-
self.default = default
|
|
58
|
-
self.blank = block.blank and (not default or default.blank)
|
|
59
|
-
|
|
60
|
-
def __str__(self) -> str:
|
|
61
|
-
default = ""
|
|
62
|
-
|
|
63
|
-
if self.default:
|
|
64
|
-
default = f"{{% else %}}{self.default}"
|
|
65
|
-
|
|
66
|
-
return f"{{% for {self.expression} %}}{self.block}{default}{{% endfor %}}"
|
|
67
|
-
|
|
68
|
-
def render_to_output(self, context: RenderContext, buffer: TextIO) -> int:
|
|
69
|
-
"""Render the node to the output buffer."""
|
|
70
|
-
it, length = self.expression.evaluate(context)
|
|
71
|
-
|
|
72
|
-
if length:
|
|
73
|
-
character_count = 0
|
|
74
|
-
name = self.expression.identifier
|
|
75
|
-
|
|
76
|
-
forloop = ForLoop(
|
|
77
|
-
name=f"{name}-{self.expression.iterable}",
|
|
78
|
-
it=it,
|
|
79
|
-
length=length,
|
|
80
|
-
parentloop=context.parentloop(),
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
namespace = {
|
|
84
|
-
"forloop": forloop,
|
|
85
|
-
name: None,
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
# Extend the context. Essentially giving priority to `ForLoopDrop`, then
|
|
89
|
-
# delegating `get` and `assign` to the outer context.
|
|
90
|
-
with context.loop(namespace, forloop):
|
|
91
|
-
for itm in forloop:
|
|
92
|
-
namespace[name] = itm
|
|
93
|
-
try:
|
|
94
|
-
character_count += self.block.render(context, buffer)
|
|
95
|
-
except ContinueLoop:
|
|
96
|
-
continue
|
|
97
|
-
except BreakLoop:
|
|
98
|
-
break
|
|
99
|
-
|
|
100
|
-
return character_count
|
|
101
|
-
|
|
102
|
-
return self.default.render(context, buffer) if self.default else 0
|
|
103
|
-
|
|
104
|
-
async def render_to_output_async(
|
|
105
|
-
self, context: RenderContext, buffer: TextIO
|
|
106
|
-
) -> int:
|
|
107
|
-
"""Render the node to the output buffer."""
|
|
108
|
-
it, length = await self.expression.evaluate_async(context)
|
|
109
|
-
|
|
110
|
-
if length:
|
|
111
|
-
character_count = 0
|
|
112
|
-
name = self.expression.identifier
|
|
113
|
-
|
|
114
|
-
forloop = ForLoop(
|
|
115
|
-
name=f"{name}-{self.expression.iterable}",
|
|
116
|
-
it=it,
|
|
117
|
-
length=length,
|
|
118
|
-
parentloop=context.parentloop(),
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
namespace = {
|
|
122
|
-
"forloop": forloop,
|
|
123
|
-
name: None,
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
# Extend the context. Essentially giving priority to `ForLoopDrop`, then
|
|
127
|
-
# delegating `get` and `assign` to the outer context.
|
|
128
|
-
with context.loop(namespace, forloop):
|
|
129
|
-
for itm in forloop:
|
|
130
|
-
namespace[name] = itm
|
|
131
|
-
try:
|
|
132
|
-
character_count += await self.block.render_async(
|
|
133
|
-
context, buffer
|
|
134
|
-
)
|
|
135
|
-
except ContinueLoop:
|
|
136
|
-
continue
|
|
137
|
-
except BreakLoop:
|
|
138
|
-
break
|
|
139
|
-
|
|
140
|
-
return character_count
|
|
141
|
-
|
|
142
|
-
return await self.default.render_async(context, buffer) if self.default else 0
|
|
143
|
-
|
|
144
|
-
def children(
|
|
145
|
-
self,
|
|
146
|
-
static_context: RenderContext, # noqa: ARG002
|
|
147
|
-
*,
|
|
148
|
-
include_partials: bool = True, # noqa: ARG002
|
|
149
|
-
) -> Iterable[Node]:
|
|
150
|
-
"""Return this node's children."""
|
|
151
|
-
yield self.block
|
|
152
|
-
if self.default:
|
|
153
|
-
yield self.default
|
|
154
|
-
|
|
155
|
-
def expressions(self) -> Iterable[Expression]:
|
|
156
|
-
"""Return this node's expressions."""
|
|
157
|
-
yield self.expression
|
|
158
|
-
|
|
159
|
-
def block_scope(self) -> Iterable[Identifier]:
|
|
160
|
-
"""Return variables this node adds to the node's block scope."""
|
|
161
|
-
yield Identifier(self.expression.identifier, token=self.expression.token)
|
|
162
|
-
yield Identifier("forloop", token=self.token)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
class BreakNode(Node):
|
|
166
|
-
"""Parse tree node for the built-in "break" tag."""
|
|
167
|
-
|
|
168
|
-
def __str__(self) -> str:
|
|
169
|
-
return "{% break %}"
|
|
170
|
-
|
|
171
|
-
def render_to_output(
|
|
172
|
-
self,
|
|
173
|
-
_: RenderContext,
|
|
174
|
-
__: TextIO,
|
|
175
|
-
) -> int:
|
|
176
|
-
"""Render the node to the output buffer."""
|
|
177
|
-
raise BreakLoop("break")
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
class ContinueNode(Node):
|
|
181
|
-
"""Parse tree node for the built-in "continue" tag."""
|
|
182
|
-
|
|
183
|
-
def __str__(self) -> str:
|
|
184
|
-
return "{% continue %}"
|
|
185
|
-
|
|
186
|
-
def render_to_output(
|
|
187
|
-
self,
|
|
188
|
-
_: RenderContext,
|
|
189
|
-
__: TextIO,
|
|
190
|
-
) -> int:
|
|
191
|
-
"""Render the node to the output buffer."""
|
|
192
|
-
raise ContinueLoop("continue")
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
class ForTag(Tag):
|
|
196
|
-
"""The built-in _for_ tag."""
|
|
197
|
-
|
|
198
|
-
name = TAG_FOR
|
|
199
|
-
end = TAG_ENDFOR
|
|
200
|
-
node_class = ForNode
|
|
201
|
-
|
|
202
|
-
def parse(self, stream: TokenStream) -> Node:
|
|
203
|
-
"""Parse tokens from _stream_ into an AST node."""
|
|
204
|
-
token = stream.eat(TOKEN_TAG)
|
|
205
|
-
expr = LoopExpression.parse(self.env, stream.into_inner(tag=token))
|
|
206
|
-
|
|
207
|
-
parse_block = get_parser(self.env).parse_block
|
|
208
|
-
block = parse_block(stream, ENDFORBLOCK)
|
|
209
|
-
default: Optional[BlockNode] = None
|
|
210
|
-
|
|
211
|
-
if stream.current.is_tag(TAG_ELSE):
|
|
212
|
-
next(stream)
|
|
213
|
-
default = parse_block(stream, ENDFORELSEBLOCK)
|
|
214
|
-
|
|
215
|
-
stream.expect(TOKEN_TAG, value=TAG_ENDFOR)
|
|
216
|
-
return self.node_class(token, expression=expr, block=block, default=default)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
class BreakTag(Tag):
|
|
220
|
-
"""The built-in _break_ tag."""
|
|
221
|
-
|
|
222
|
-
name = TAG_BREAK
|
|
223
|
-
block = False
|
|
224
|
-
|
|
225
|
-
def parse(self, stream: TokenStream) -> BreakNode:
|
|
226
|
-
"""Parse tokens from _stream_ into an AST node."""
|
|
227
|
-
stream.expect(TOKEN_TAG, value=TAG_BREAK)
|
|
228
|
-
return BreakNode(stream.current)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
class ContinueTag(Tag):
|
|
232
|
-
"""The built-in _continue_ tag."""
|
|
233
|
-
|
|
234
|
-
name = TAG_CONTINUE
|
|
235
|
-
block = False
|
|
236
|
-
|
|
237
|
-
def parse(self, stream: TokenStream) -> ContinueNode:
|
|
238
|
-
"""Parse tokens from _stream_ into an AST node."""
|
|
239
|
-
stream.expect(TOKEN_TAG, value=TAG_CONTINUE)
|
|
240
|
-
return ContinueNode(stream.current)
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
class ForLoop(Mapping[str, object]):
|
|
244
|
-
"""Loop helper variables."""
|
|
245
|
-
|
|
246
|
-
__slots__ = (
|
|
247
|
-
"name",
|
|
248
|
-
"it",
|
|
249
|
-
"length",
|
|
250
|
-
"item",
|
|
251
|
-
"_index",
|
|
252
|
-
"parentloop",
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
_keys = frozenset(
|
|
256
|
-
[
|
|
257
|
-
"name",
|
|
258
|
-
"length",
|
|
259
|
-
"index",
|
|
260
|
-
"index0",
|
|
261
|
-
"rindex",
|
|
262
|
-
"rindex0",
|
|
263
|
-
"first",
|
|
264
|
-
"last",
|
|
265
|
-
"parentloop",
|
|
266
|
-
]
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
def __init__(
|
|
270
|
-
self,
|
|
271
|
-
name: str,
|
|
272
|
-
it: Iterator[Any],
|
|
273
|
-
length: int,
|
|
274
|
-
parentloop: object,
|
|
275
|
-
):
|
|
276
|
-
self.name = name
|
|
277
|
-
self.it = it
|
|
278
|
-
self.length = length
|
|
279
|
-
|
|
280
|
-
self.item = None
|
|
281
|
-
self._index = -1 # Step is called before `next(it)`
|
|
282
|
-
self.parentloop = parentloop
|
|
283
|
-
|
|
284
|
-
def __repr__(self) -> str: # pragma: no cover
|
|
285
|
-
return f"ForLoop(name='{self.name}', length={self.length})"
|
|
286
|
-
|
|
287
|
-
def __getitem__(self, key: str) -> object:
|
|
288
|
-
if key in self._keys:
|
|
289
|
-
return getattr(self, key)
|
|
290
|
-
raise KeyError(key)
|
|
291
|
-
|
|
292
|
-
def __len__(self) -> int:
|
|
293
|
-
return len(self._keys)
|
|
294
|
-
|
|
295
|
-
def __next__(self) -> object:
|
|
296
|
-
self.step()
|
|
297
|
-
return next(self.it)
|
|
298
|
-
|
|
299
|
-
def __iter__(self) -> Iterator[Any]:
|
|
300
|
-
return self
|
|
301
|
-
|
|
302
|
-
def __str__(self) -> str:
|
|
303
|
-
return "ForLoop"
|
|
304
|
-
|
|
305
|
-
@property
|
|
306
|
-
def index(self) -> int:
|
|
307
|
-
"""The 1-based index of the current loop iteration."""
|
|
308
|
-
return self._index + 1
|
|
309
|
-
|
|
310
|
-
@property
|
|
311
|
-
def index0(self) -> int:
|
|
312
|
-
"""The 0-based index of the current loop iteration."""
|
|
313
|
-
return self._index
|
|
314
|
-
|
|
315
|
-
@property
|
|
316
|
-
def rindex(self) -> int:
|
|
317
|
-
"""The 1-based index, counting from the right, of the current loop iteration."""
|
|
318
|
-
return self.length - self._index
|
|
319
|
-
|
|
320
|
-
@property
|
|
321
|
-
def rindex0(self) -> int:
|
|
322
|
-
"""The 0-based index, counting from the right, of the current loop iteration."""
|
|
323
|
-
return self.length - self._index - 1
|
|
324
|
-
|
|
325
|
-
@property
|
|
326
|
-
def first(self) -> bool:
|
|
327
|
-
"""True if this is the first iteration, false otherwise."""
|
|
328
|
-
return self._index == 0
|
|
329
|
-
|
|
330
|
-
@property
|
|
331
|
-
def last(self) -> bool:
|
|
332
|
-
"""True if this is the last iteration, false otherwise."""
|
|
333
|
-
return self._index == self.length - 1
|
|
334
|
-
|
|
335
|
-
def step(self) -> None:
|
|
336
|
-
"""Move the for loop helper forward to the next iteration."""
|
|
337
|
-
self._index += 1
|
liquid/builtin/tags/if_tag.py
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
"""The built-in _if_ tag."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import sys
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
7
|
-
from typing import Iterable
|
|
8
|
-
from typing import Optional
|
|
9
|
-
from typing import TextIO
|
|
10
|
-
|
|
11
|
-
from liquid.ast import BlockNode
|
|
12
|
-
from liquid.ast import ConditionalBlockNode
|
|
13
|
-
from liquid.ast import IllegalNode
|
|
14
|
-
from liquid.ast import Node
|
|
15
|
-
from liquid.builtin.expressions import BooleanExpression
|
|
16
|
-
from liquid.exceptions import LiquidSyntaxError
|
|
17
|
-
from liquid.mode import Mode
|
|
18
|
-
from liquid.parser import eat_block
|
|
19
|
-
from liquid.parser import get_parser
|
|
20
|
-
from liquid.tag import Tag
|
|
21
|
-
from liquid.token import TOKEN_EOF
|
|
22
|
-
from liquid.token import TOKEN_EXPRESSION
|
|
23
|
-
from liquid.token import TOKEN_TAG
|
|
24
|
-
from liquid.token import Token
|
|
25
|
-
|
|
26
|
-
if TYPE_CHECKING:
|
|
27
|
-
from liquid.context import RenderContext
|
|
28
|
-
from liquid.expression import Expression
|
|
29
|
-
from liquid.stream import TokenStream
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
TAG_IF = sys.intern("if")
|
|
33
|
-
TAG_ENDIF = sys.intern("endif")
|
|
34
|
-
TAG_ELSIF = sys.intern("elsif")
|
|
35
|
-
TAG_ELSE = sys.intern("else")
|
|
36
|
-
|
|
37
|
-
ENDIFBLOCK = frozenset((TAG_ENDIF, TAG_ELSIF, TAG_ELSE, TOKEN_EOF))
|
|
38
|
-
ENDELSIFBLOCK = frozenset((TAG_ENDIF, TAG_ELSIF, TAG_ELSE))
|
|
39
|
-
ENDIFELSEBLOCK = frozenset((TAG_ENDIF, TAG_ELSE, TAG_ELSIF))
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class IfNode(Node):
|
|
43
|
-
"""The built-in _if_ tag."""
|
|
44
|
-
|
|
45
|
-
__slots__ = (
|
|
46
|
-
"condition",
|
|
47
|
-
"consequence",
|
|
48
|
-
"alternatives",
|
|
49
|
-
"default",
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
def __init__(
|
|
53
|
-
self,
|
|
54
|
-
token: Token,
|
|
55
|
-
condition: Expression,
|
|
56
|
-
consequence: BlockNode,
|
|
57
|
-
alternatives: list[ConditionalBlockNode],
|
|
58
|
-
default: Optional[BlockNode],
|
|
59
|
-
):
|
|
60
|
-
super().__init__(token)
|
|
61
|
-
self.condition = condition
|
|
62
|
-
self.consequence = consequence
|
|
63
|
-
self.alternatives = alternatives
|
|
64
|
-
self.default = default
|
|
65
|
-
|
|
66
|
-
self.blank = (
|
|
67
|
-
consequence.blank
|
|
68
|
-
and all(node.blank for node in alternatives)
|
|
69
|
-
and (not default or default.blank)
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
self.consequence.blank = self.blank
|
|
73
|
-
|
|
74
|
-
for node in self.alternatives:
|
|
75
|
-
node.blank = self.blank
|
|
76
|
-
|
|
77
|
-
if self.default:
|
|
78
|
-
self.default.blank = self.blank
|
|
79
|
-
|
|
80
|
-
def __str__(self) -> str:
|
|
81
|
-
alts = "".join(str(alt) for alt in self.alternatives)
|
|
82
|
-
default = ""
|
|
83
|
-
|
|
84
|
-
if self.default:
|
|
85
|
-
default = f"{{% else %}}{self.default}"
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
f"{{% if {self.condition} %}}{self.consequence}{alts}{default}{{% endif %}}"
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
def render_to_output(self, context: RenderContext, buffer: TextIO) -> int:
|
|
92
|
-
"""Render the node to the output buffer."""
|
|
93
|
-
if self.condition.evaluate(context):
|
|
94
|
-
return self.consequence.render(context, buffer)
|
|
95
|
-
|
|
96
|
-
for alternative in self.alternatives:
|
|
97
|
-
if alternative.expression.evaluate(context):
|
|
98
|
-
return alternative.block.render(context, buffer)
|
|
99
|
-
|
|
100
|
-
if self.default:
|
|
101
|
-
return self.default.render(context, buffer)
|
|
102
|
-
|
|
103
|
-
return 0
|
|
104
|
-
|
|
105
|
-
async def render_to_output_async(
|
|
106
|
-
self, context: RenderContext, buffer: TextIO
|
|
107
|
-
) -> int:
|
|
108
|
-
"""Render the node to the output buffer."""
|
|
109
|
-
if await self.condition.evaluate_async(context):
|
|
110
|
-
return await self.consequence.render_async(context, buffer)
|
|
111
|
-
|
|
112
|
-
for alternative in self.alternatives:
|
|
113
|
-
if await alternative.expression.evaluate_async(context):
|
|
114
|
-
return await alternative.render_async(context, buffer)
|
|
115
|
-
|
|
116
|
-
if self.default:
|
|
117
|
-
return await self.default.render_async(context, buffer)
|
|
118
|
-
|
|
119
|
-
return 0
|
|
120
|
-
|
|
121
|
-
def children(
|
|
122
|
-
self,
|
|
123
|
-
static_context: RenderContext, # noqa: ARG002
|
|
124
|
-
*,
|
|
125
|
-
include_partials: bool = True, # noqa: ARG002
|
|
126
|
-
) -> Iterable[Node]:
|
|
127
|
-
"""Return this node's children."""
|
|
128
|
-
yield self.consequence
|
|
129
|
-
yield from self.alternatives
|
|
130
|
-
if self.default:
|
|
131
|
-
yield self.default
|
|
132
|
-
|
|
133
|
-
def expressions(self) -> Iterable[Expression]:
|
|
134
|
-
"""Return this node's expressions."""
|
|
135
|
-
yield self.condition
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
class IfTag(Tag):
|
|
139
|
-
"""The built-in _if_ tag."""
|
|
140
|
-
|
|
141
|
-
name = TAG_IF
|
|
142
|
-
end = TAG_ENDIF
|
|
143
|
-
node_class = IfNode
|
|
144
|
-
|
|
145
|
-
mode = Mode.LAX
|
|
146
|
-
"""Tag specific parsing mode, independent from environment tolerance mode."""
|
|
147
|
-
|
|
148
|
-
def parse(self, stream: TokenStream) -> Node:
|
|
149
|
-
"""Parse tokens from _stream_ into an AST node."""
|
|
150
|
-
token = stream.eat(TOKEN_TAG)
|
|
151
|
-
tokens = stream.into_inner(tag=token)
|
|
152
|
-
condition = BooleanExpression.parse(self.env, tokens)
|
|
153
|
-
parse_block = get_parser(self.env).parse_block
|
|
154
|
-
consequence = parse_block(stream, ENDIFBLOCK)
|
|
155
|
-
alternatives: list[ConditionalBlockNode] = []
|
|
156
|
-
|
|
157
|
-
while stream.current.is_tag(TAG_ELSIF):
|
|
158
|
-
# If the expression can't be parsed, eat the "elsif" block and
|
|
159
|
-
# continue to parse more "elsif" expression, if any.
|
|
160
|
-
try:
|
|
161
|
-
expr = BooleanExpression.parse(
|
|
162
|
-
self.env, stream.into_inner(tag=next(stream))
|
|
163
|
-
)
|
|
164
|
-
except LiquidSyntaxError as err:
|
|
165
|
-
self.env.error(err)
|
|
166
|
-
eat_block(stream, ENDELSIFBLOCK)
|
|
167
|
-
return IllegalNode(token)
|
|
168
|
-
|
|
169
|
-
alt_tok = stream.current
|
|
170
|
-
alt_block = parse_block(stream, ENDELSIFBLOCK)
|
|
171
|
-
|
|
172
|
-
alternatives.append(
|
|
173
|
-
ConditionalBlockNode(alt_tok, expression=expr, block=alt_block)
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
default: Optional[BlockNode] = None
|
|
177
|
-
|
|
178
|
-
if stream.current.is_tag(TAG_ELSE):
|
|
179
|
-
next(stream)
|
|
180
|
-
if stream.current.kind == TOKEN_EXPRESSION:
|
|
181
|
-
if self.mode == Mode.LAX:
|
|
182
|
-
# Superfluous expressions inside an `else` tag are ignored.
|
|
183
|
-
next(stream)
|
|
184
|
-
else:
|
|
185
|
-
raise LiquidSyntaxError(
|
|
186
|
-
"found an 'else' tag expression, did you mean 'elsif'?",
|
|
187
|
-
token=stream.current,
|
|
188
|
-
)
|
|
189
|
-
default = parse_block(stream, ENDIFELSEBLOCK)
|
|
190
|
-
|
|
191
|
-
# Extraneous `else` and `elsif` blocks are ignored.
|
|
192
|
-
if not stream.current.is_tag(TAG_ENDIF) and self.mode == Mode.LAX:
|
|
193
|
-
while stream.current.kind != TOKEN_EOF:
|
|
194
|
-
if (
|
|
195
|
-
stream.current.kind == TOKEN_TAG
|
|
196
|
-
and stream.current.value == TAG_ENDIF
|
|
197
|
-
):
|
|
198
|
-
break
|
|
199
|
-
next(stream)
|
|
200
|
-
|
|
201
|
-
stream.expect(TOKEN_TAG, value=TAG_ENDIF)
|
|
202
|
-
|
|
203
|
-
return self.node_class(
|
|
204
|
-
token=token,
|
|
205
|
-
condition=condition,
|
|
206
|
-
consequence=consequence,
|
|
207
|
-
alternatives=alternatives,
|
|
208
|
-
default=default,
|
|
209
|
-
)
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
"""The built-in _ifchanged_ tag."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import sys
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
7
|
-
from typing import Iterable
|
|
8
|
-
from typing import TextIO
|
|
9
|
-
|
|
10
|
-
from liquid.ast import BlockNode
|
|
11
|
-
from liquid.ast import Node
|
|
12
|
-
from liquid.parser import get_parser
|
|
13
|
-
from liquid.tag import Tag
|
|
14
|
-
from liquid.token import TOKEN_TAG
|
|
15
|
-
from liquid.token import Token
|
|
16
|
-
|
|
17
|
-
if TYPE_CHECKING:
|
|
18
|
-
from liquid import Environment
|
|
19
|
-
from liquid.context import RenderContext
|
|
20
|
-
from liquid.stream import TokenStream
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
TAG_IFCHANGED = sys.intern("ifchanged")
|
|
24
|
-
TAG_ENDIFCHANGED = sys.intern("endifchanged")
|
|
25
|
-
|
|
26
|
-
ENDIFCHANGEDBLOCK = frozenset((TAG_ENDIFCHANGED,))
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class IfChangedNode(Node):
|
|
30
|
-
"""The built-in _ifchanged_ tag."""
|
|
31
|
-
|
|
32
|
-
__slots__ = ("block",)
|
|
33
|
-
|
|
34
|
-
def __init__(self, token: Token, block: BlockNode):
|
|
35
|
-
super().__init__(token)
|
|
36
|
-
self.block = block
|
|
37
|
-
self.blank = block.blank
|
|
38
|
-
|
|
39
|
-
def __str__(self) -> str:
|
|
40
|
-
return f"{{% ifchanged %}}{{ {self.block} }}{{% endifchanged %}}"
|
|
41
|
-
|
|
42
|
-
def render_to_output(self, context: RenderContext, buffer: TextIO) -> int:
|
|
43
|
-
"""Render the node to the output buffer."""
|
|
44
|
-
# Render to an intermediate buffer.
|
|
45
|
-
buf = context.get_buffer(buffer)
|
|
46
|
-
self.block.render(context, buf)
|
|
47
|
-
val = buf.getvalue()
|
|
48
|
-
|
|
49
|
-
# The context will update its namespace if needed.
|
|
50
|
-
if context.ifchanged(val):
|
|
51
|
-
return buffer.write(val)
|
|
52
|
-
return 0
|
|
53
|
-
|
|
54
|
-
async def render_to_output_async(
|
|
55
|
-
self, context: RenderContext, buffer: TextIO
|
|
56
|
-
) -> int:
|
|
57
|
-
"""Render the node to the output buffer."""
|
|
58
|
-
# Render to an intermediate buffer.
|
|
59
|
-
buf = context.get_buffer(buffer)
|
|
60
|
-
await self.block.render_async(context, buf)
|
|
61
|
-
val = buf.getvalue()
|
|
62
|
-
|
|
63
|
-
# The context will update its namespace if needed.
|
|
64
|
-
if context.ifchanged(val):
|
|
65
|
-
return buffer.write(val)
|
|
66
|
-
return 0
|
|
67
|
-
|
|
68
|
-
def children(
|
|
69
|
-
self,
|
|
70
|
-
static_context: RenderContext, # noqa: ARG002
|
|
71
|
-
*,
|
|
72
|
-
include_partials: bool = True, # noqa: ARG002
|
|
73
|
-
) -> Iterable[Node]:
|
|
74
|
-
"""Return this node's children."""
|
|
75
|
-
yield self.block
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class IfChangedTag(Tag):
|
|
79
|
-
"""The built-in _ifchanged_ tag."""
|
|
80
|
-
|
|
81
|
-
name = TAG_IFCHANGED
|
|
82
|
-
end = TAG_ENDIFCHANGED
|
|
83
|
-
node_class = IfChangedNode
|
|
84
|
-
|
|
85
|
-
def __init__(self, env: Environment):
|
|
86
|
-
super().__init__(env)
|
|
87
|
-
self.parser = get_parser(self.env)
|
|
88
|
-
|
|
89
|
-
def parse(self, stream: TokenStream) -> Node:
|
|
90
|
-
"""Parse tokens from _stream_ into an AST node."""
|
|
91
|
-
token = stream.eat(TOKEN_TAG)
|
|
92
|
-
block = self.parser.parse_block(stream, ENDIFCHANGEDBLOCK)
|
|
93
|
-
stream.expect(TOKEN_TAG, value=TAG_ENDIFCHANGED)
|
|
94
|
-
return self.node_class(token, block)
|