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
@@ -0,0 +1,316 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from gettext import NullTranslations
6
+ from typing import TYPE_CHECKING, cast
7
+
8
+ import markupsafe
9
+
10
+ from .. import expressions
11
+ from .._messages import MESSAGES, MessageText, Translations
12
+ from ..exceptions import TranslationSyntaxError
13
+ from ..expressions import Name
14
+ from ..tokens import TOKEN_COMMA, TOKEN_TAG_END
15
+ from .output import OutputStatement
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Iterable
19
+
20
+ from .._context import RenderContext
21
+ from .._io import StringBuffer
22
+ from .._markup import Block, Expression, Markup, Partial
23
+ from .._parser import Parser
24
+ from .._template import Template
25
+ from ..expressions import KeywordArgument
26
+ from ..tokens import Token
27
+
28
+ RE_VARS = re.compile(r"(?<!%)%\((\w+)\)s")
29
+ RE_WHITESPACE = re.compile(r"\s*\n\s*")
30
+
31
+
32
+ class TranslateTag:
33
+ blank = False
34
+ tag = "translate"
35
+
36
+ default_translations = NullTranslations()
37
+ translations_var = "translations"
38
+ message_count_var = "count"
39
+ message_context_var = "context"
40
+
41
+ __slots__ = ("args", "plural_block", "singular_block", "token")
42
+
43
+ def __init__(
44
+ self,
45
+ token: Token,
46
+ args: list[KeywordArgument],
47
+ singular_block: MessageBlock,
48
+ plural_block: MessageBlock | None,
49
+ ) -> None:
50
+ self.token = token
51
+ self.args = args
52
+ self.singular_block = singular_block
53
+ self.plural_block = plural_block
54
+
55
+ @classmethod
56
+ def parse(cls, token: Token, parser: Parser) -> Markup:
57
+ if parser.kind() == TOKEN_COMMA:
58
+ # Leading commas are OK.
59
+ parser.next()
60
+
61
+ args = parser.parse_keyword_arguments(require_commas=True)
62
+
63
+ if parser.kind() == TOKEN_COMMA:
64
+ # Trailing commas are OK.
65
+ parser.next()
66
+
67
+ parser.carry_whitespace_control()
68
+ parser.eat(TOKEN_TAG_END)
69
+
70
+ singular_block = cls.message_block(
71
+ parser, parser.parse_block(("endtranslate", "plural"))
72
+ )
73
+ plural_block: MessageBlock | None = None
74
+
75
+ if parser.tag("plural"):
76
+ parser.eat_empty_tag("plural")
77
+ plural_block = cls.message_block(
78
+ parser, parser.parse_block(("endtranslate",))
79
+ )
80
+
81
+ parser.eat_empty_tag("endtranslate")
82
+ return TranslateTag(token, args, singular_block, plural_block)
83
+
84
+ @classmethod
85
+ def message_block(cls, parser: Parser, block: Block) -> MessageBlock:
86
+ message_text: list[str] = []
87
+ message_vars: list[str] = []
88
+
89
+ for node in block:
90
+ if isinstance(node, str):
91
+ message_text.append(node.replace("%", "%%"))
92
+
93
+ elif isinstance(node, OutputStatement) and isinstance(
94
+ node.expression, expressions.Variable
95
+ ):
96
+ expr = node.expression
97
+
98
+ if not isinstance(expr.root, Name) or expr.selectors:
99
+ raise TranslationSyntaxError(
100
+ "expected a translation variable, found a path",
101
+ token=expr.span,
102
+ source=parser.source,
103
+ name=parser.template_name,
104
+ )
105
+
106
+ message_text.append(f"%({expr.root.value})s")
107
+ message_vars.append(expr.root.value)
108
+
109
+ else:
110
+ raise TranslationSyntaxError(
111
+ "unexpected markup in translation text",
112
+ token=node.token,
113
+ source=parser.source,
114
+ name=parser.template_name,
115
+ )
116
+
117
+ msg = "".join(message_text)
118
+ return MessageBlock(block, RE_WHITESPACE.sub(" ", msg.strip()), message_vars)
119
+
120
+ def render(self, context: RenderContext, buffer: StringBuffer) -> None:
121
+ translations = self.resolve_translations(context)
122
+ scope: dict[str, object] = {}
123
+
124
+ with context.extend(scope):
125
+ for arg in self.args:
126
+ scope[arg.name.value] = arg.expr.evaluate(context)
127
+
128
+ count = self.resolve_count(context, scope)
129
+ message_context = self.resolve_message_context(context, scope)
130
+
131
+ message_text = self.gettext(
132
+ translations,
133
+ count=count,
134
+ message_context=message_context,
135
+ )
136
+
137
+ return buffer.write(self._format_message(context, message_text))
138
+
139
+ async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
140
+ translations = self.resolve_translations(context)
141
+ scope: dict[str, object] = {}
142
+
143
+ with context.extend(scope):
144
+ for arg in self.args:
145
+ scope[arg.name.value] = await arg.expr.evaluate_async(context)
146
+
147
+ count = self.resolve_count(context, scope)
148
+ message_context = self.resolve_message_context(context, scope)
149
+
150
+ message_text = self.gettext(
151
+ translations,
152
+ count=count,
153
+ message_context=message_context,
154
+ )
155
+
156
+ return buffer.write(self._format_message(context, message_text))
157
+
158
+ def resolve_translations(
159
+ self, context: RenderContext
160
+ ) -> Translations | NullTranslations:
161
+ """Return a translations object from the current render context."""
162
+ t = context.resolve(self.translations_var)
163
+
164
+ if context.env.is_nil(t):
165
+ return self.default_translations
166
+
167
+ return cast("Translations", t)
168
+
169
+ def resolve_count(
170
+ self,
171
+ context: RenderContext,
172
+ block_scope: dict[str, object],
173
+ ) -> int | None:
174
+ """Return a message count."""
175
+ return int(
176
+ context.env.to_numeric(
177
+ block_scope.get(self.message_count_var, 1), context, 1
178
+ )
179
+ )
180
+
181
+ def resolve_message_context(
182
+ self,
183
+ context: RenderContext,
184
+ block_scope: dict[str, object],
185
+ ) -> str | None:
186
+ """Return the message context string."""
187
+ message_context = block_scope.pop(self.message_context_var, None)
188
+
189
+ if message_context:
190
+ return (
191
+ str(message_context)
192
+ if not isinstance(message_context, str)
193
+ else message_context
194
+ ) # Just in case we get a Markupsafe object.
195
+
196
+ return None
197
+
198
+ def gettext(
199
+ self,
200
+ translations: Translations | NullTranslations,
201
+ count: int | None,
202
+ message_context: str | None,
203
+ ) -> str:
204
+ """Get translated text from the given translations object."""
205
+ if self.plural_block and count:
206
+ if message_context:
207
+ return translations.npgettext(
208
+ message_context,
209
+ self.singular_block.text,
210
+ self.plural_block.text,
211
+ count,
212
+ )
213
+
214
+ return translations.ngettext(
215
+ self.singular_block.text,
216
+ self.plural_block.text,
217
+ count,
218
+ )
219
+
220
+ if message_context:
221
+ return translations.pgettext(message_context, self.singular_block.text)
222
+
223
+ return translations.gettext(self.singular_block.text)
224
+
225
+ def children(self) -> list[Markup]:
226
+ result = [
227
+ node for node in self.singular_block.block if not isinstance(node, str)
228
+ ]
229
+
230
+ if self.plural_block:
231
+ result.extend(
232
+ node for node in self.plural_block.block if not isinstance(node, str)
233
+ )
234
+
235
+ return result
236
+
237
+ def expressions(self) -> list[Expression]:
238
+ return [arg.expr for arg in self.args]
239
+
240
+ def block_scope(self) -> list[Name]:
241
+ return [arg.name for arg in self.args]
242
+
243
+ def template_scope(self) -> list[Name]:
244
+ return []
245
+
246
+ def partials(self, context: RenderContext) -> list[Partial]:
247
+ return []
248
+
249
+ async def partials_async(self, context: RenderContext) -> list[Partial]:
250
+ return []
251
+
252
+ def messages(self, template: Template) -> Iterable[MessageText]:
253
+ if not self.singular_block.block:
254
+ return ()
255
+
256
+ message_context: Expression | None = None
257
+ for arg in self.args:
258
+ if arg.name.value == self.message_context_var:
259
+ message_context = arg.expr
260
+
261
+ if self.plural_block:
262
+ if message_context and isinstance(
263
+ message_context, expressions.StringLiteral
264
+ ):
265
+ funcname = "npgettext"
266
+ message: MESSAGES = (
267
+ (message_context.value, "c"),
268
+ self.singular_block.text,
269
+ self.plural_block.text,
270
+ )
271
+ else:
272
+ funcname = "ngettext"
273
+ message = (
274
+ self.singular_block.text,
275
+ self.plural_block.text,
276
+ )
277
+ elif message_context and isinstance(message_context, expressions.StringLiteral):
278
+ funcname = "pgettext"
279
+ message = (
280
+ (message_context.value, "c"),
281
+ self.singular_block.text,
282
+ )
283
+ else:
284
+ funcname = "gettext"
285
+ message = (self.singular_block.text,)
286
+
287
+ return (
288
+ MessageText(
289
+ lineno=template.line_col(self.token)[0],
290
+ funcname=funcname,
291
+ message=message,
292
+ ),
293
+ )
294
+
295
+ def _format_message(
296
+ self,
297
+ context: RenderContext,
298
+ message_text: str,
299
+ ) -> str:
300
+ """Return the message string formatted with the given message variables."""
301
+ if context.env.autoescape:
302
+ message_text = markupsafe.Markup(message_text)
303
+
304
+ _vars = {
305
+ k: context.env.to_str(context.resolve(k), context, self.token)
306
+ for k in RE_VARS.findall(message_text)
307
+ }
308
+
309
+ return message_text % _vars
310
+
311
+
312
+ @dataclass(slots=True)
313
+ class MessageBlock:
314
+ block: Block
315
+ text: str
316
+ args: list[str]
liquid/tags/unless.py ADDED
@@ -0,0 +1,121 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from ..tokens import (
6
+ TOKEN_TAG_END,
7
+ TOKEN_TAG_NAME,
8
+ TOKEN_TAG_START,
9
+ )
10
+ from .else_ import ElseBlock
11
+ from .if_ import ElsIfBlock, IfBlock
12
+
13
+ if TYPE_CHECKING:
14
+ from .._context import RenderContext
15
+ from .._io import StringBuffer
16
+ from .._markup import Expression, Markup, Partial
17
+ from .._parser import Parser
18
+ from ..expressions import Name
19
+ from ..tokens import Token
20
+
21
+
22
+ class UnlessTag:
23
+ tag = ""
24
+
25
+ __slots__ = ("blank", "blocks", "token")
26
+
27
+ def __init__(
28
+ self, token: Token, blocks: list[UnlessBlock | 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[UnlessBlock | 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", "endunless"))
48
+ blocks.append(UnlessBlock(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", "endunless"))
60
+ )
61
+ )
62
+ else:
63
+ break
64
+
65
+ parser.eat_empty_tag("endunless")
66
+ return UnlessTag(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)
75
+ parser.carry_whitespace_control()
76
+ parser.eat(TOKEN_TAG_END)
77
+ return ElsIfBlock(
78
+ token, expression, parser.parse_block(("else", "elsif", "endunless"))
79
+ )
80
+
81
+ def render(self, context: RenderContext, buffer: StringBuffer) -> None:
82
+ for block in self.blocks:
83
+ if isinstance(block, UnlessBlock):
84
+ if not context.env.truthy(block.expression.evaluate(context), context):
85
+ return block.render(context, buffer)
86
+ elif context.env.truthy(block.expression.evaluate(context), context):
87
+ return block.render(context, buffer)
88
+
89
+ async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
90
+ for block in self.blocks:
91
+ if isinstance(block, UnlessBlock):
92
+ if not context.env.truthy(
93
+ await block.expression.evaluate_async(context), context
94
+ ):
95
+ return await block.render_async(context, buffer)
96
+ elif context.env.truthy(
97
+ await block.expression.evaluate_async(context), context
98
+ ):
99
+ return await block.render_async(context, buffer)
100
+
101
+ def children(self) -> list[Markup]:
102
+ return list(self.blocks)
103
+
104
+ def expressions(self) -> list[Expression]:
105
+ return []
106
+
107
+ def block_scope(self) -> list[Name]:
108
+ return []
109
+
110
+ def template_scope(self) -> list[Name]:
111
+ return []
112
+
113
+ def partials(self, context: RenderContext) -> list[Partial]:
114
+ return []
115
+
116
+ async def partials_async(self, context: RenderContext) -> list[Partial]:
117
+ return []
118
+
119
+
120
+ class UnlessBlock(IfBlock):
121
+ tag = "unless"
liquid/tags/with_.py ADDED
@@ -0,0 +1,83 @@
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 TOKEN_COMMA, 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 KeywordArgument, Name
14
+ from ..tokens import Token
15
+
16
+
17
+ class WithTag:
18
+ tag = "with"
19
+
20
+ __slots__ = ("args", "blank", "block", "token")
21
+
22
+ def __init__(self, token: Token, args: list[KeywordArgument], block: Block) -> None:
23
+ self.token = token
24
+ self.args = args
25
+ self.block = block
26
+ self.blank = is_blank_block(block)
27
+
28
+ @staticmethod
29
+ def parse(token: Token, parser: Parser) -> Markup:
30
+ if parser.kind() == TOKEN_COMMA:
31
+ # Leading commas are OK.
32
+ parser.next()
33
+
34
+ args = parser.parse_keyword_arguments(require_commas=True)
35
+
36
+ if parser.kind() == TOKEN_COMMA:
37
+ # Trailing commas are OK.
38
+ parser.next()
39
+
40
+ parser.carry_whitespace_control()
41
+ parser.eat(TOKEN_TAG_END)
42
+
43
+ block = parser.parse_block(("endwith",))
44
+ parser.eat_empty_tag("endwith")
45
+ return WithTag(token, args, block)
46
+
47
+ def render(self, context: RenderContext, buffer: StringBuffer) -> None:
48
+ scope: dict[str, object] = {}
49
+
50
+ with context.extend(scope):
51
+ # Later args have access to earlier args.
52
+ for arg in self.args:
53
+ scope[arg.name.value] = arg.expr.evaluate(context)
54
+
55
+ render_block(self.block, context, buffer)
56
+
57
+ async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
58
+ scope: dict[str, object] = {}
59
+
60
+ with context.extend(scope):
61
+ # Later args have access to earlier args.
62
+ for arg in self.args:
63
+ scope[arg.name.value] = await arg.expr.evaluate_async(context)
64
+
65
+ await render_block_async(self.block, context, buffer)
66
+
67
+ def children(self) -> list[Markup]:
68
+ return [node for node in self.block if not isinstance(node, str)]
69
+
70
+ def expressions(self) -> list[Expression]:
71
+ return [arg.expr for arg in self.args]
72
+
73
+ def block_scope(self) -> list[Name]:
74
+ return [arg.name for arg in self.args]
75
+
76
+ def template_scope(self) -> list[Name]:
77
+ return []
78
+
79
+ def partials(self, context: RenderContext) -> list[Partial]:
80
+ return []
81
+
82
+ async def partials_async(self, context: RenderContext) -> list[Partial]:
83
+ return []