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
liquid/tags/for_.py ADDED
@@ -0,0 +1,421 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import defaultdict
4
+ from typing import TYPE_CHECKING
5
+
6
+ from .._drop import liquid_async_iterable, liquid_iterable
7
+ from .._markup import is_blank_block, render_block, render_block_async
8
+ from .._slice import slice_sequence
9
+ from ..drops import AsyncIterableDrop, ForLoop, Undefined
10
+ from ..exceptions import LiquidSyntaxError
11
+ from ..expressions import KeywordArgument, Name, Variable
12
+ from ..tokens import TOKEN_COMMA, TOKEN_IN, TOKEN_TAG_END
13
+
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Iterable
16
+
17
+ from .._context import RenderContext
18
+ from .._io import StringBuffer
19
+ from .._markup import Block, Expression, Markup, Partial
20
+ from .._parser import Parser
21
+ from ..tokens import Token
22
+
23
+
24
+ class ForTag:
25
+ tag = "for"
26
+
27
+ __slots__ = (
28
+ "blank",
29
+ "block",
30
+ "default",
31
+ "expression",
32
+ "limit",
33
+ "name",
34
+ "offset",
35
+ "offset_key",
36
+ "reversed",
37
+ "token",
38
+ )
39
+
40
+ def __init__(
41
+ self,
42
+ token: Token,
43
+ name: Name,
44
+ expression: Expression,
45
+ block: Block,
46
+ *,
47
+ offset: Expression | None,
48
+ limit: Expression | None,
49
+ reversed: bool,
50
+ default: Block | None,
51
+ ) -> None:
52
+ self.token = token
53
+ self.name = name
54
+ self.expression = expression
55
+ self.block: Block = block
56
+ self.offset = offset
57
+ self.limit = limit
58
+ self.reversed = reversed
59
+ self.default: Block | None = default
60
+
61
+ self.blank = is_blank_block(self.block) and (
62
+ not default or is_blank_block(default)
63
+ )
64
+
65
+ if self.blank:
66
+ self.block = [node for node in self.block if not isinstance(node, str)]
67
+ if self.default:
68
+ self.default = [
69
+ node for node in self.default if not isinstance(node, str)
70
+ ]
71
+
72
+ self.offset_key = f"{self.name.value}-{self.expression}"
73
+
74
+ @staticmethod
75
+ def parse(token: Token, parser: Parser) -> Markup:
76
+ ident = parser.parse_identifier()
77
+ parser.eat(TOKEN_IN, message="missing 'in'")
78
+ parser.expect_expression()
79
+ expr = parser.parse_expression()
80
+
81
+ if parser.kind() == TOKEN_COMMA:
82
+ parser.next()
83
+
84
+ reversed_, offset, limit = ForTag.unpack_args(
85
+ parser, parser.parse_arguments(require_commas=False)
86
+ )
87
+
88
+ parser.carry_whitespace_control()
89
+ parser.eat(TOKEN_TAG_END)
90
+
91
+ block = parser.parse_block(("else", "endfor"))
92
+ default: Block | None = None
93
+
94
+ if parser.tag("else"):
95
+ parser.eat_empty_tag("else")
96
+ default = parser.parse_block(("else", "endfor"))
97
+
98
+ parser.eat_empty_tag("endfor")
99
+
100
+ return ForTag(
101
+ token,
102
+ ident,
103
+ expr,
104
+ block,
105
+ offset=offset,
106
+ limit=limit,
107
+ reversed=reversed_,
108
+ default=default,
109
+ )
110
+
111
+ @staticmethod
112
+ def unpack_args(
113
+ parser: Parser, args: list[Expression | KeywordArgument]
114
+ ) -> tuple[bool, Expression | None, Expression | None]:
115
+ reversed_: bool = False
116
+ offset: Expression | None = None
117
+ limit: Expression | None = None
118
+
119
+ for arg in args:
120
+ if isinstance(arg, KeywordArgument):
121
+ if arg.name.value == "offset":
122
+ offset = arg.expr
123
+ elif arg.name.value == "limit":
124
+ limit = arg.expr
125
+ else:
126
+ raise LiquidSyntaxError(
127
+ f"unknown argument {arg.name.value!r}",
128
+ token=arg.name.token,
129
+ source=parser.source,
130
+ name=parser.template_name,
131
+ )
132
+
133
+ elif (
134
+ isinstance(arg, Variable)
135
+ and isinstance(arg.root, Name)
136
+ and arg.root.value == "reversed"
137
+ and not arg.selectors
138
+ ):
139
+ reversed_ = True
140
+
141
+ else:
142
+ raise LiquidSyntaxError(
143
+ "unexpected argument",
144
+ token=arg.token,
145
+ source=parser.source,
146
+ name=parser.template_name,
147
+ )
148
+
149
+ return reversed_, offset, limit
150
+
151
+ def render(self, context: RenderContext, buffer: StringBuffer) -> None:
152
+ target = self.expression.evaluate(context)
153
+
154
+ offsets = context.get_register("loop_offset", default=register_factory)
155
+
156
+ if self.offset:
157
+ offset_ = self.offset.evaluate(context)
158
+
159
+ if isinstance(offset_, Undefined) and offset_.path == "continue":
160
+ offset = offsets[self.offset_key]
161
+ else:
162
+ offset = context.env.to_int(offset_, context, self.offset.span)
163
+
164
+ else:
165
+ offset = 0
166
+
167
+ if self.limit:
168
+ limit = context.env.to_int(
169
+ self.limit.evaluate(context), context, self.limit.span
170
+ )
171
+ else:
172
+ limit = None
173
+
174
+ it: Iterable[object]
175
+
176
+ if liquid_iterable(target):
177
+ it = target.__liquid_iter__(offset, limit, self.reversed)
178
+ length = len(it)
179
+
180
+ else:
181
+ a = context.env.to_array(target, context, self.expression.span)
182
+ if not offset and not limit:
183
+ it = a if not self.reversed else reversed(a)
184
+ length = len(a)
185
+
186
+ else:
187
+ it, length = slice_sequence(a, offset, limit, self.reversed)
188
+
189
+ offsets[self.offset_key] = offset + length
190
+
191
+ if not length:
192
+ if self.default:
193
+ render_block(self.default, context, buffer)
194
+ return
195
+
196
+ name = self.name.value
197
+ parent_loop = context.parent_loop()
198
+
199
+ forloop = ForLoop(f"{name}-{self.expression}", length, parent_loop)
200
+ namespace: dict[str, object] = {"forloop": forloop}
201
+
202
+ with context.loop(namespace, forloop):
203
+ for obj in it:
204
+ namespace[name] = obj
205
+ forloop.step()
206
+ render_block(self.block, context, buffer)
207
+
208
+ if context.interrupts:
209
+ interrupt = context.interrupts.pop()
210
+ if interrupt == "break":
211
+ break
212
+
213
+ if interrupt == "continue":
214
+ continue
215
+
216
+ context.interrupts.append(interrupt)
217
+
218
+ async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
219
+ target = await self.expression.evaluate_async(context)
220
+
221
+ offsets = context.get_register("loop_offset", default=register_factory)
222
+
223
+ if self.offset:
224
+ offset_ = await self.offset.evaluate_async(context)
225
+
226
+ if isinstance(offset_, Undefined) and offset_.path == "continue":
227
+ offset = offsets[self.offset_key]
228
+ else:
229
+ offset = context.env.to_int(offset_, context, self.offset.span)
230
+ else:
231
+ offset = 0
232
+
233
+ if self.limit:
234
+ limit = context.env.to_int(
235
+ await self.limit.evaluate_async(context), context, self.limit.span
236
+ )
237
+ else:
238
+ limit = None
239
+
240
+ it: Iterable[object] | AsyncIterableDrop[object]
241
+
242
+ if liquid_async_iterable(target):
243
+ it = await target.__liquid_iter_async__(offset, limit, self.reversed)
244
+ length = len(it)
245
+
246
+ elif liquid_iterable(target):
247
+ it = target.__liquid_iter__(offset, limit, self.reversed)
248
+ length = len(it)
249
+
250
+ else:
251
+ a = context.env.to_array(target, context, self.expression.span)
252
+ if not offset and not limit:
253
+ it = a if not self.reversed else reversed(a)
254
+ length = len(a)
255
+ else:
256
+ it, length = slice_sequence(a, offset, limit, self.reversed)
257
+
258
+ offsets[self.offset_key] = offset + length
259
+
260
+ if not length:
261
+ if self.default:
262
+ await render_block_async(self.default, context, buffer)
263
+ return
264
+
265
+ name = self.name.value
266
+ parent_loop = context.parent_loop()
267
+
268
+ forloop = ForLoop(f"{name}-{self.expression}", length, parent_loop)
269
+ namespace: dict[str, object] = {"forloop": forloop}
270
+
271
+ with context.loop(namespace, forloop):
272
+ if isinstance(it, AsyncIterableDrop):
273
+ async for obj in it:
274
+ namespace[name] = obj
275
+ forloop.step()
276
+ await render_block_async(self.block, context, buffer)
277
+
278
+ if context.interrupts:
279
+ interrupt = context.interrupts.pop()
280
+ if interrupt == "break":
281
+ break
282
+
283
+ if interrupt == "continue":
284
+ continue
285
+
286
+ context.interrupts.append(interrupt)
287
+
288
+ else:
289
+ for obj in it:
290
+ namespace[name] = obj
291
+ forloop.step()
292
+ await render_block_async(self.block, context, buffer)
293
+
294
+ if context.interrupts:
295
+ interrupt = context.interrupts.pop()
296
+ if interrupt == "break":
297
+ break
298
+
299
+ if interrupt == "continue":
300
+ continue
301
+
302
+ context.interrupts.append(interrupt)
303
+
304
+ def children(self) -> list[Markup]:
305
+ result: list[Markup] = [
306
+ node for node in self.block if not isinstance(node, str)
307
+ ]
308
+
309
+ if self.default:
310
+ for node in self.default:
311
+ if not isinstance(node, str):
312
+ result.append(node)
313
+
314
+ return result
315
+
316
+ def expressions(self) -> list[Expression]:
317
+ result = [self.expression]
318
+
319
+ if self.offset:
320
+ result.append(self.offset)
321
+
322
+ if self.limit:
323
+ result.append(self.limit)
324
+
325
+ return result
326
+
327
+ def block_scope(self) -> list[Name]:
328
+ return [self.name]
329
+
330
+ def template_scope(self) -> list[Name]:
331
+ return []
332
+
333
+ def partials(self, context: RenderContext) -> list[Partial]:
334
+ return []
335
+
336
+ async def partials_async(self, context: RenderContext) -> list[Partial]:
337
+ return []
338
+
339
+
340
+ class BreakTag:
341
+ blank = True
342
+ tag = "break"
343
+
344
+ __slots__ = ("token",)
345
+
346
+ def __init__(self, token: Token) -> None:
347
+ self.token = token
348
+
349
+ @staticmethod
350
+ def parse(token: Token, parser: Parser) -> Markup:
351
+ parser.carry_whitespace_control()
352
+ parser.eat(TOKEN_TAG_END)
353
+ return BreakTag(token)
354
+
355
+ def render(self, context: RenderContext, buffer: StringBuffer) -> None:
356
+ context.interrupts.append("break")
357
+
358
+ async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
359
+ context.interrupts.append("break")
360
+
361
+ def children(self) -> list[Markup]:
362
+ return []
363
+
364
+ def expressions(self) -> list[Expression]:
365
+ return []
366
+
367
+ def block_scope(self) -> list[Name]:
368
+ return []
369
+
370
+ def template_scope(self) -> list[Name]:
371
+ return []
372
+
373
+ def partials(self, context: RenderContext) -> list[Partial]:
374
+ return []
375
+
376
+ async def partials_async(self, context: RenderContext) -> list[Partial]:
377
+ return []
378
+
379
+
380
+ class ContinueTag:
381
+ blank = True
382
+ tag = "continue"
383
+
384
+ __slots__ = ("token",)
385
+
386
+ def __init__(self, token: Token) -> None:
387
+ self.token = token
388
+
389
+ @staticmethod
390
+ def parse(token: Token, parser: Parser) -> Markup:
391
+ parser.carry_whitespace_control()
392
+ parser.eat(TOKEN_TAG_END)
393
+ return ContinueTag(token)
394
+
395
+ def render(self, context: RenderContext, buffer: StringBuffer) -> None:
396
+ context.interrupts.append("continue")
397
+
398
+ async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
399
+ context.interrupts.append("continue")
400
+
401
+ def children(self) -> list[Markup]:
402
+ return []
403
+
404
+ def expressions(self) -> list[Expression]:
405
+ return []
406
+
407
+ def block_scope(self) -> list[Name]:
408
+ return []
409
+
410
+ def template_scope(self) -> list[Name]:
411
+ return []
412
+
413
+ def partials(self, context: RenderContext) -> list[Partial]:
414
+ return []
415
+
416
+ async def partials_async(self, context: RenderContext) -> list[Partial]:
417
+ return []
418
+
419
+
420
+ def register_factory() -> defaultdict[str, int]:
421
+ return defaultdict(int)
liquid/tags/if_.py ADDED
@@ -0,0 +1,156 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from .._markup import is_blank_block, render_block, render_block_async
6
+ from ..tokens import (
7
+ TOKEN_TAG_END,
8
+ TOKEN_TAG_NAME,
9
+ TOKEN_TAG_START,
10
+ )
11
+ from .else_ import ElseBlock
12
+
13
+ if TYPE_CHECKING:
14
+ from .._context import RenderContext
15
+ from .._io import StringBuffer
16
+ from .._markup import Block, Expression, Markup, Partial
17
+ from .._parser import Parser
18
+ from ..expressions import Name
19
+ from ..tokens import Token
20
+
21
+
22
+ class IfTag:
23
+ tag = ""
24
+
25
+ __slots__ = ("blank", "blocks", "token")
26
+
27
+ def __init__(
28
+ self, token: Token, blocks: list[IfBlock | ElsIfBlock | ElseBlock]
29
+ ) -> None:
30
+ self.token = token
31
+ self.blocks = blocks
32
+ self.blank = all(block.blank for block in self.blocks)
33
+
34
+ if self.blank:
35
+ for block in self.blocks:
36
+ block.filter_strings()
37
+
38
+ @classmethod
39
+ def parse(cls, token: Token, parser: Parser) -> Markup:
40
+ blocks: list[IfBlock | ElsIfBlock | ElseBlock] = []
41
+
42
+ parser.expect_expression()
43
+ expression = parser.parse_expression(infix=True, expect_end_of_expression=True)
44
+ parser.carry_whitespace_control()
45
+ parser.eat(TOKEN_TAG_END)
46
+
47
+ block = parser.parse_block(("else", "elsif", "endif"))
48
+ blocks.append(IfBlock(token, expression, block))
49
+
50
+ while 1:
51
+ tag_name = parser.tags(("else", "elsif"))
52
+
53
+ if tag_name == "elsif":
54
+ blocks.append(cls.parse_else_if(parser))
55
+ elif tag_name == "else":
56
+ name_token = parser.eat_tag("else")
57
+ blocks.append(
58
+ ElseBlock(
59
+ name_token, parser.parse_block(("else", "elsif", "endif"))
60
+ )
61
+ )
62
+ else:
63
+ break
64
+
65
+ parser.eat_empty_tag("endif")
66
+ return IfTag(token, blocks)
67
+
68
+ @classmethod
69
+ def parse_else_if(cls, parser: Parser) -> ElsIfBlock:
70
+ parser.eat(TOKEN_TAG_START)
71
+ parser.skip_whitespace_control()
72
+ token = parser.eat(TOKEN_TAG_NAME)
73
+ parser.expect_expression()
74
+ expression = parser.parse_expression(infix=True, expect_end_of_expression=True)
75
+ parser.carry_whitespace_control()
76
+ parser.eat(TOKEN_TAG_END)
77
+ return ElsIfBlock(
78
+ token, expression, parser.parse_block(("else", "elsif", "endif"))
79
+ )
80
+
81
+ def render(self, context: RenderContext, buffer: StringBuffer) -> None:
82
+ for block in self.blocks:
83
+ if context.env.truthy(block.expression.evaluate(context), context):
84
+ return block.render(context, buffer)
85
+
86
+ async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
87
+ for block in self.blocks:
88
+ if context.env.truthy(
89
+ await block.expression.evaluate_async(context), context
90
+ ):
91
+ return await block.render_async(context, buffer)
92
+
93
+ def children(self) -> list[Markup]:
94
+ return list(self.blocks)
95
+
96
+ def expressions(self) -> list[Expression]:
97
+ return []
98
+
99
+ def block_scope(self) -> list[Name]:
100
+ return []
101
+
102
+ def template_scope(self) -> list[Name]:
103
+ return []
104
+
105
+ def partials(self, context: RenderContext) -> list[Partial]:
106
+ return []
107
+
108
+ async def partials_async(self, context: RenderContext) -> list[Partial]:
109
+ return []
110
+
111
+
112
+ class IfBlock:
113
+ tag = "if"
114
+
115
+ __slots__ = ("blank", "block", "expression", "token")
116
+
117
+ def __init__(self, token: Token, expression: Expression, block: Block) -> None:
118
+ self.token = token
119
+ self.expression = expression
120
+ self.block = block
121
+ self.blank = is_blank_block(block)
122
+
123
+ def render(self, context: RenderContext, buffer: StringBuffer) -> None:
124
+ render_block(self.block, context, buffer)
125
+
126
+ async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
127
+ await render_block_async(self.block, context, buffer)
128
+
129
+ def children(self) -> list[Markup]:
130
+ return [node for node in self.block if not isinstance(node, str)]
131
+
132
+ async def children_async(self) -> list[Markup]:
133
+ return self.children()
134
+
135
+ def expressions(self) -> list[Expression]:
136
+ return [self.expression]
137
+
138
+ def block_scope(self) -> list[Name]:
139
+ return []
140
+
141
+ def template_scope(self) -> list[Name]:
142
+ return []
143
+
144
+ def partials(self, context: RenderContext) -> list[Partial]:
145
+ return []
146
+
147
+ async def partials_async(self, context: RenderContext) -> list[Partial]:
148
+ return []
149
+
150
+ def filter_strings(self) -> None:
151
+ markup: Block = [node for node in self.block if not isinstance(node, str)]
152
+ self.block = markup
153
+
154
+
155
+ class ElsIfBlock(IfBlock):
156
+ tag = "elsif"
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from .._markup import render_block, render_block_async
6
+ from ..tokens import TOKEN_TAG_END
7
+
8
+ if TYPE_CHECKING:
9
+ from .._context import RenderContext
10
+ from .._io import StringBuffer
11
+ from .._markup import Block, Expression, Markup, Partial
12
+ from .._parser import Parser
13
+ from ..expressions import Name
14
+ from ..tokens import Token
15
+
16
+
17
+ class IfChangedTag:
18
+ blank = False
19
+ tag = "ifchanged"
20
+
21
+ __slots__ = ("block", "token")
22
+
23
+ def __init__(self, token: Token, block: Block) -> None:
24
+ self.token = token
25
+ self.block = block
26
+
27
+ @staticmethod
28
+ def parse(token: Token, parser: Parser) -> Markup:
29
+ parser.carry_whitespace_control()
30
+ parser.eat(TOKEN_TAG_END)
31
+ block = parser.parse_block(end=("endifchanged",))
32
+ parser.eat_empty_tag("endifchanged")
33
+ return IfChangedTag(token, block)
34
+
35
+ def render(self, context: RenderContext, buffer: StringBuffer) -> None:
36
+ buf = context.env.buffer_factory()
37
+ render_block(self.block, context, buf)
38
+ buffered = buf.getvalue()
39
+
40
+ if buffered != context.get_register("ifchanged", str):
41
+ buffer.write(buffered)
42
+ context.registers["ifchanged"] = buffered
43
+
44
+ async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
45
+ buf = context.env.buffer_factory()
46
+ await render_block_async(self.block, context, buf)
47
+ buffered = buf.getvalue()
48
+
49
+ if buffered != context.get_register("ifchanged", str):
50
+ buffer.write(buffered)
51
+ context.registers["ifchanged"] = buffered
52
+
53
+ def children(self) -> list[Markup]:
54
+ return [node for node in self.block if not isinstance(node, str)]
55
+
56
+ def expressions(self) -> list[Expression]:
57
+ return []
58
+
59
+ def block_scope(self) -> list[Name]:
60
+ return []
61
+
62
+ def template_scope(self) -> list[Name]:
63
+ return []
64
+
65
+ def partials(self, context: RenderContext) -> list[Partial]:
66
+ return []
67
+
68
+ async def partials_async(self, context: RenderContext) -> list[Partial]:
69
+ return []