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,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
@@ -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)