python-liquid 2.3.5__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 (213) 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/loaders/file_system_loader.py +143 -0
  93. liquid/{builtin/loaders → loaders}/mixins.py +40 -52
  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.5.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.5.dist-info → python_liquid-3.0.0.dist-info}/WHEEL +1 -1
  127. {python_liquid-2.3.5.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/file_system_loader.py +0 -146
  154. liquid/builtin/loaders/package_loader.py +0 -132
  155. liquid/builtin/output.py +0 -74
  156. liquid/builtin/tags/__init__.py +0 -0
  157. liquid/builtin/tags/assign_tag.py +0 -75
  158. liquid/builtin/tags/capture_tag.py +0 -98
  159. liquid/builtin/tags/case_tag.py +0 -287
  160. liquid/builtin/tags/comment_tag.py +0 -77
  161. liquid/builtin/tags/cycle_tag.py +0 -132
  162. liquid/builtin/tags/decrement_tag.py +0 -59
  163. liquid/builtin/tags/doc_tag.py +0 -68
  164. liquid/builtin/tags/echo_tag.py +0 -45
  165. liquid/builtin/tags/for_tag.py +0 -337
  166. liquid/builtin/tags/if_tag.py +0 -209
  167. liquid/builtin/tags/ifchanged_tag.py +0 -94
  168. liquid/builtin/tags/include_tag.py +0 -251
  169. liquid/builtin/tags/increment_tag.py +0 -60
  170. liquid/builtin/tags/inline_comment_tag.py +0 -47
  171. liquid/builtin/tags/liquid_tag.py +0 -183
  172. liquid/builtin/tags/render_tag.py +0 -371
  173. liquid/builtin/tags/tablerow_tag.py +0 -320
  174. liquid/builtin/tags/unless_tag.py +0 -218
  175. liquid/context.py +0 -615
  176. liquid/expression.py +0 -41
  177. liquid/extra/__init__.py +0 -98
  178. liquid/extra/filters/__init__.py +0 -33
  179. liquid/extra/filters/_json.py +0 -33
  180. liquid/extra/filters/array.py +0 -60
  181. liquid/extra/filters/babel.py +0 -552
  182. liquid/extra/filters/html.py +0 -32
  183. liquid/extra/filters/translate.py +0 -448
  184. liquid/extra/tags/__init__.py +0 -17
  185. liquid/extra/tags/_with.py +0 -80
  186. liquid/extra/tags/extends_tag.py +0 -576
  187. liquid/extra/tags/macro_tag.py +0 -270
  188. liquid/extra/tags/snippet_tag.py +0 -107
  189. liquid/extra/tags/translate_tag.py +0 -388
  190. liquid/filter.py +0 -230
  191. liquid/future/__init__.py +0 -3
  192. liquid/future/environment.py +0 -30
  193. liquid/future/filters/__init__.py +0 -1
  194. liquid/future/tags/__init__.py +0 -1
  195. liquid/lex.py +0 -260
  196. liquid/limits.py +0 -66
  197. liquid/loader.py +0 -141
  198. liquid/messages.py +0 -357
  199. liquid/mode.py +0 -12
  200. liquid/output.py +0 -35
  201. liquid/parser.py +0 -119
  202. liquid/span.py +0 -35
  203. liquid/static_analysis.py +0 -443
  204. liquid/stream.py +0 -148
  205. liquid/stringify.py +0 -31
  206. liquid/tag.py +0 -47
  207. liquid/template.py +0 -658
  208. liquid/token.py +0 -145
  209. liquid/undefined.py +0 -218
  210. liquid/utils/__init__.py +0 -15
  211. liquid/utils/html.py +0 -57
  212. liquid/utils/text.py +0 -28
  213. python_liquid-2.3.5.dist-info/RECORD +0 -96
@@ -1,672 +0,0 @@
1
- """Built-in logical expressions."""
2
-
3
- from __future__ import annotations
4
-
5
- from decimal import Decimal
6
- from typing import TYPE_CHECKING
7
- from typing import Collection
8
-
9
- from liquid.exceptions import LiquidSyntaxError
10
- from liquid.exceptions import LiquidTypeError
11
- from liquid.expression import Expression
12
- from liquid.limits import to_int
13
- from liquid.token import TOKEN_AND
14
- from liquid.token import TOKEN_BLANK
15
- from liquid.token import TOKEN_CONTAINS
16
- from liquid.token import TOKEN_EMPTY
17
- from liquid.token import TOKEN_EOF
18
- from liquid.token import TOKEN_EQ
19
- from liquid.token import TOKEN_FALSE
20
- from liquid.token import TOKEN_FLOAT
21
- from liquid.token import TOKEN_GE
22
- from liquid.token import TOKEN_GT
23
- from liquid.token import TOKEN_IDENTSTRING
24
- from liquid.token import TOKEN_INTEGER
25
- from liquid.token import TOKEN_LBRACKET
26
- from liquid.token import TOKEN_LE
27
- from liquid.token import TOKEN_LG
28
- from liquid.token import TOKEN_LPAREN
29
- from liquid.token import TOKEN_LT
30
- from liquid.token import TOKEN_NE
31
- from liquid.token import TOKEN_NIL
32
- from liquid.token import TOKEN_NOT
33
- from liquid.token import TOKEN_NULL
34
- from liquid.token import TOKEN_OR
35
- from liquid.token import TOKEN_RANGE_LITERAL
36
- from liquid.token import TOKEN_RPAREN
37
- from liquid.token import TOKEN_STRING
38
- from liquid.token import TOKEN_TRUE
39
- from liquid.token import TOKEN_WORD
40
- from liquid.token import Token
41
- from liquid.undefined import is_undefined
42
-
43
- from .path import Path
44
- from .primitive import Blank
45
- from .primitive import Empty
46
- from .primitive import FalseLiteral
47
- from .primitive import FloatLiteral
48
- from .primitive import IntegerLiteral
49
- from .primitive import Nil
50
- from .primitive import RangeLiteral
51
- from .primitive import StringLiteral
52
- from .primitive import TrueLiteral
53
-
54
- if TYPE_CHECKING:
55
- from liquid import Environment
56
- from liquid import RenderContext
57
- from liquid import TokenStream
58
-
59
- PRECEDENCE_LOWEST = 1
60
- PRECEDENCE_LOGICAL_RIGHT = 2
61
- PRECEDENCE_LOGICAL_OR = 3
62
- PRECEDENCE_LOGICAL_AND = 4
63
- PRECEDENCE_RELATIONAL = 5
64
- PRECEDENCE_MEMBERSHIP = 6
65
- PRECEDENCE_PREFIX = 7
66
-
67
- PRECEDENCES = {
68
- TOKEN_EQ: PRECEDENCE_RELATIONAL,
69
- TOKEN_LT: PRECEDENCE_RELATIONAL,
70
- TOKEN_GT: PRECEDENCE_RELATIONAL,
71
- TOKEN_NE: PRECEDENCE_RELATIONAL,
72
- TOKEN_LG: PRECEDENCE_RELATIONAL,
73
- TOKEN_LE: PRECEDENCE_RELATIONAL,
74
- TOKEN_GE: PRECEDENCE_RELATIONAL,
75
- TOKEN_CONTAINS: PRECEDENCE_MEMBERSHIP,
76
- # TOKEN_IN: PRECEDENCE_MEMBERSHIP,
77
- TOKEN_AND: PRECEDENCE_LOGICAL_RIGHT,
78
- TOKEN_OR: PRECEDENCE_LOGICAL_RIGHT,
79
- TOKEN_NOT: PRECEDENCE_PREFIX,
80
- TOKEN_RPAREN: PRECEDENCE_LOWEST,
81
- }
82
-
83
- BINARY_OPERATORS = frozenset(
84
- [
85
- TOKEN_EQ,
86
- TOKEN_LT,
87
- TOKEN_GT,
88
- TOKEN_LG,
89
- TOKEN_NE,
90
- TOKEN_LE,
91
- TOKEN_GE,
92
- TOKEN_CONTAINS,
93
- # TOKEN_IN,
94
- TOKEN_AND,
95
- TOKEN_OR,
96
- ]
97
- )
98
-
99
-
100
- class BooleanExpression(Expression):
101
- """An expression that evaluates to true or false."""
102
-
103
- __slots__ = ("expression",)
104
-
105
- def __init__(self, token: Token, expression: Expression):
106
- super().__init__(token)
107
- self.expression = expression
108
-
109
- def __eq__(self, other: object) -> bool:
110
- return (
111
- isinstance(other, BooleanExpression) and self.expression == other.expression
112
- )
113
-
114
- def __str__(self) -> str:
115
- def _str(expression: Expression, parent_precedence: int) -> str:
116
- if isinstance(expression, LogicalAndExpression):
117
- precedence = PRECEDENCE_LOGICAL_AND
118
- op = "and"
119
- left = _str(expression.left, precedence)
120
- right = _str(expression.right, precedence)
121
- elif isinstance(expression, LogicalOrExpression):
122
- precedence = PRECEDENCE_LOGICAL_OR
123
- op = "or"
124
- left = _str(expression.left, precedence)
125
- right = _str(expression.right, precedence)
126
- elif isinstance(expression, LogicalNotExpression):
127
- operand_str = _str(expression.right, PRECEDENCE_PREFIX)
128
- expr = f"not {operand_str}"
129
- if parent_precedence > PRECEDENCE_PREFIX:
130
- return f"({expr})"
131
- return expr
132
- else:
133
- return str(expression)
134
-
135
- expr = f"{left} {op} {right}"
136
- if precedence < parent_precedence:
137
- return f"({expr})"
138
- return expr
139
-
140
- return _str(self.expression, 0)
141
-
142
- def evaluate(self, context: RenderContext) -> bool:
143
- return is_truthy(self.expression.evaluate(context))
144
-
145
- async def evaluate_async(self, context: RenderContext) -> bool:
146
- return is_truthy(await self.expression.evaluate_async(context))
147
-
148
- def children(self) -> list[Expression]:
149
- return [self.expression]
150
-
151
- @staticmethod
152
- def parse(
153
- env: Environment, tokens: TokenStream, *, inline: bool = False
154
- ) -> BooleanExpression:
155
- """Return a new BooleanExpression parsed from tokens in _tokens_.
156
-
157
- If _inline_ is `False`, we expect the stream to be empty after parsing
158
- a Boolean expression and will raise a syntax error if it's not.
159
- """
160
- expr = parse_boolean_primitive(env, tokens)
161
- if not inline:
162
- tokens.eat(TOKEN_EOF)
163
- return BooleanExpression(expr.token, expr)
164
-
165
-
166
- class LogicalNotExpression(Expression):
167
- __slots__ = ("right",)
168
-
169
- def __init__(self, token: Token, right: Expression):
170
- super().__init__(token=token)
171
- self.right = right
172
-
173
- def __eq__(self, other: object) -> bool:
174
- return isinstance(other, LogicalNotExpression) and self.right == other.right
175
-
176
- def __str__(self) -> str:
177
- return f"not {self.right}"
178
-
179
- def evaluate(self, context: RenderContext) -> object:
180
- return not is_truthy(self.right.evaluate(context))
181
-
182
- async def evaluate_async(self, context: RenderContext) -> object:
183
- return not is_truthy(await self.right.evaluate_async(context))
184
-
185
- def children(self) -> list[Expression]:
186
- return [self.right]
187
-
188
- @staticmethod
189
- def parse(env: Environment, tokens: TokenStream) -> LogicalNotExpression:
190
- """Parse a not expression from _tokens_."""
191
- if not env.logical_not_operator:
192
- raise LiquidSyntaxError(
193
- "disallowed not operator in logical expression", token=tokens.current
194
- )
195
-
196
- tokens.eat(TOKEN_NOT)
197
- expr = parse_boolean_primitive(env, tokens)
198
- return LogicalNotExpression(expr.token, expr)
199
-
200
-
201
- class LogicalAndExpression(Expression):
202
- __slots__ = ("left", "right")
203
-
204
- def __init__(self, token: Token, left: Expression, right: Expression) -> None:
205
- super().__init__(token=token)
206
- self.left = left
207
- self.right = right
208
-
209
- def __str__(self) -> str:
210
- return f"{self.left} and {self.right}"
211
-
212
- def evaluate(self, context: RenderContext) -> object:
213
- return is_truthy(self.left.evaluate(context)) and is_truthy(
214
- self.right.evaluate(context)
215
- )
216
-
217
- async def evaluate_async(self, context: RenderContext) -> object:
218
- return is_truthy(await self.left.evaluate_async(context)) and is_truthy(
219
- await self.right.evaluate_async(context)
220
- )
221
-
222
- def children(self) -> list[Expression]:
223
- return [self.left, self.right]
224
-
225
-
226
- class LogicalOrExpression(Expression):
227
- __slots__ = ("left", "right")
228
-
229
- def __init__(self, token: Token, left: Expression, right: Expression) -> None:
230
- super().__init__(token=token)
231
- self.left = left
232
- self.right = right
233
-
234
- def __str__(self) -> str:
235
- return f"{self.left} or {self.right}"
236
-
237
- def evaluate(self, context: RenderContext) -> object:
238
- return is_truthy(self.left.evaluate(context)) or is_truthy(
239
- self.right.evaluate(context)
240
- )
241
-
242
- async def evaluate_async(self, context: RenderContext) -> object:
243
- return is_truthy(await self.left.evaluate_async(context)) or is_truthy(
244
- await self.right.evaluate_async(context)
245
- )
246
-
247
- def children(self) -> list[Expression]:
248
- return [self.left, self.right]
249
-
250
-
251
- class EqExpression(Expression):
252
- __slots__ = ("left", "right")
253
-
254
- def __init__(self, token: Token, left: Expression, right: Expression) -> None:
255
- super().__init__(token=token)
256
- self.left = left
257
- self.right = right
258
-
259
- def __str__(self) -> str:
260
- return f"{self.left} == {self.right}"
261
-
262
- def evaluate(self, context: RenderContext) -> object:
263
- return _eq(self.left.evaluate(context), self.right.evaluate(context))
264
-
265
- async def evaluate_async(self, context: RenderContext) -> object:
266
- return _eq(
267
- await self.left.evaluate_async(context),
268
- await self.right.evaluate_async(context),
269
- )
270
-
271
- def children(self) -> list[Expression]:
272
- return [self.left, self.right]
273
-
274
-
275
- class NeExpression(Expression):
276
- __slots__ = ("left", "right")
277
-
278
- def __init__(self, token: Token, left: Expression, right: Expression) -> None:
279
- super().__init__(token=token)
280
- self.left = left
281
- self.right = right
282
-
283
- def __str__(self) -> str:
284
- return f"{self.left} != {self.right}"
285
-
286
- def evaluate(self, context: RenderContext) -> object:
287
- return not _eq(self.left.evaluate(context), self.right.evaluate(context))
288
-
289
- async def evaluate_async(self, context: RenderContext) -> object:
290
- return not _eq(
291
- await self.left.evaluate_async(context),
292
- await self.right.evaluate_async(context),
293
- )
294
-
295
- def children(self) -> list[Expression]:
296
- return [self.left, self.right]
297
-
298
-
299
- class LeExpression(Expression):
300
- __slots__ = ("left", "right")
301
-
302
- def __init__(self, token: Token, left: Expression, right: Expression) -> None:
303
- super().__init__(token=token)
304
- self.left = left
305
- self.right = right
306
-
307
- def __str__(self) -> str:
308
- return f"{self.left} <= {self.right}"
309
-
310
- def evaluate(self, context: RenderContext) -> object:
311
- left = self.left.evaluate(context)
312
- right = self.right.evaluate(context)
313
- return _le(left, right)
314
-
315
- async def evaluate_async(self, context: RenderContext) -> object:
316
- left = await self.left.evaluate_async(context)
317
- right = await self.right.evaluate_async(context)
318
- return _le(left, right)
319
-
320
- def children(self) -> list[Expression]:
321
- return [self.left, self.right]
322
-
323
-
324
- class GeExpression(Expression):
325
- __slots__ = ("left", "right")
326
-
327
- def __init__(self, token: Token, left: Expression, right: Expression) -> None:
328
- super().__init__(token=token)
329
- self.left = left
330
- self.right = right
331
-
332
- def __str__(self) -> str:
333
- return f"{self.left} >= {self.right}"
334
-
335
- def evaluate(self, context: RenderContext) -> object:
336
- left = self.left.evaluate(context)
337
- right = self.right.evaluate(context)
338
- return _le(right, left)
339
-
340
- async def evaluate_async(self, context: RenderContext) -> object:
341
- left = await self.left.evaluate_async(context)
342
- right = await self.right.evaluate_async(context)
343
- return _le(right, left)
344
-
345
- def children(self) -> list[Expression]:
346
- return [self.left, self.right]
347
-
348
-
349
- class LtExpression(Expression):
350
- __slots__ = ("left", "right")
351
-
352
- def __init__(self, token: Token, left: Expression, right: Expression) -> None:
353
- super().__init__(token=token)
354
- self.left = left
355
- self.right = right
356
-
357
- def __str__(self) -> str:
358
- return f"{self.left} < {self.right}"
359
-
360
- def evaluate(self, context: RenderContext) -> object:
361
- return _lt(
362
- self.token, self.left.evaluate(context), self.right.evaluate(context)
363
- )
364
-
365
- async def evaluate_async(self, context: RenderContext) -> object:
366
- return _lt(
367
- self.token,
368
- await self.left.evaluate_async(context),
369
- await self.right.evaluate_async(context),
370
- )
371
-
372
- def children(self) -> list[Expression]:
373
- return [self.left, self.right]
374
-
375
-
376
- class GtExpression(Expression):
377
- __slots__ = ("left", "right")
378
-
379
- def __init__(self, token: Token, left: Expression, right: Expression) -> None:
380
- super().__init__(token=token)
381
- self.left = left
382
- self.right = right
383
-
384
- def __str__(self) -> str:
385
- return f"{self.left} > {self.right}"
386
-
387
- def evaluate(self, context: RenderContext) -> object:
388
- return _lt(
389
- self.token, self.right.evaluate(context), self.left.evaluate(context)
390
- )
391
-
392
- async def evaluate_async(self, context: RenderContext) -> object:
393
- return _lt(
394
- self.token,
395
- await self.right.evaluate_async(context),
396
- await self.left.evaluate_async(context),
397
- )
398
-
399
- def children(self) -> list[Expression]:
400
- return [self.left, self.right]
401
-
402
-
403
- class ContainsExpression(Expression):
404
- __slots__ = ("left", "right")
405
-
406
- def __init__(self, token: Token, left: Expression, right: Expression) -> None:
407
- super().__init__(token=token)
408
- self.left = left
409
- self.right = right
410
-
411
- def __str__(self) -> str:
412
- return f"{self.left} contains {self.right}"
413
-
414
- def evaluate(self, context: RenderContext) -> object:
415
- return _contains(
416
- self.token, self.left.evaluate(context), self.right.evaluate(context)
417
- )
418
-
419
- async def evaluate_async(self, context: RenderContext) -> object:
420
- return _contains(
421
- self.token,
422
- await self.left.evaluate_async(context),
423
- await self.right.evaluate_async(context),
424
- )
425
-
426
- def children(self) -> list[Expression]:
427
- return [self.left, self.right]
428
-
429
-
430
- def parse_boolean_primitive( # noqa: PLR0912
431
- env: Environment, tokens: TokenStream, precedence: int = PRECEDENCE_LOWEST
432
- ) -> Expression:
433
- """Parse a Boolean expression from tokens in _stream_."""
434
- left: Expression
435
- token = tokens.current
436
- kind = token.kind
437
-
438
- if kind == TOKEN_TRUE:
439
- left = TrueLiteral(token)
440
- next(tokens)
441
- elif kind == TOKEN_FALSE:
442
- left = FalseLiteral(token)
443
- next(tokens)
444
- elif kind in (TOKEN_NIL, TOKEN_NULL):
445
- left = Nil(token)
446
- next(tokens)
447
- elif kind == TOKEN_INTEGER:
448
- left = IntegerLiteral(token, to_int(token.value))
449
- next(tokens)
450
- elif kind == TOKEN_FLOAT:
451
- left = FloatLiteral(token, float(token.value))
452
- next(tokens)
453
- elif kind == TOKEN_STRING:
454
- left = StringLiteral(token, token.value)
455
- next(tokens)
456
- elif kind == TOKEN_RANGE_LITERAL:
457
- left = RangeLiteral.parse(env, tokens)
458
- elif kind == TOKEN_BLANK:
459
- left = Blank(token)
460
- next(tokens)
461
- elif kind == TOKEN_EMPTY:
462
- left = Empty(token)
463
- next(tokens)
464
- elif kind in (TOKEN_WORD, TOKEN_IDENTSTRING, TOKEN_LBRACKET):
465
- left = Path.parse(env, tokens)
466
- elif kind == TOKEN_LPAREN:
467
- left = parse_grouped_expression(env, tokens)
468
- elif kind == TOKEN_NOT:
469
- left = LogicalNotExpression.parse(env, tokens)
470
- else:
471
- raise LiquidSyntaxError(
472
- f"expected a primitive expression, found {token.kind}",
473
- token=tokens.current,
474
- )
475
-
476
- while True:
477
- token = tokens.current
478
- if (
479
- token == tokens.eof
480
- or PRECEDENCES.get(token.kind, PRECEDENCE_LOWEST) < precedence
481
- ):
482
- break
483
-
484
- if token.kind not in BINARY_OPERATORS:
485
- return left
486
-
487
- left = parse_infix_expression(env, tokens, left)
488
-
489
- return left
490
-
491
-
492
- def parse_infix_expression( # noqa: PLR0911
493
- env: Environment, stream: TokenStream, left: Expression
494
- ) -> Expression: # noqa: PLR0911
495
- """Return a logical, comparison, or membership expression parsed from _stream_."""
496
- token = next(stream)
497
- assert token is not None
498
- precedence = PRECEDENCES.get(token.kind, PRECEDENCE_LOWEST)
499
-
500
- if token.kind == TOKEN_EQ:
501
- return EqExpression(
502
- token, left, parse_boolean_primitive(env, stream, precedence)
503
- )
504
- if token.kind == TOKEN_LT:
505
- return LtExpression(
506
- token, left, parse_boolean_primitive(env, stream, precedence)
507
- )
508
- if token.kind == TOKEN_GT:
509
- return GtExpression(
510
- token, left, parse_boolean_primitive(env, stream, precedence)
511
- )
512
- if token.kind in (TOKEN_NE, TOKEN_LG):
513
- return NeExpression(
514
- token, left, parse_boolean_primitive(env, stream, precedence)
515
- )
516
- if token.kind == TOKEN_LE:
517
- return LeExpression(
518
- token, left, parse_boolean_primitive(env, stream, precedence)
519
- )
520
- if token.kind == TOKEN_GE:
521
- return GeExpression(
522
- token, left, parse_boolean_primitive(env, stream, precedence)
523
- )
524
- if token.kind == TOKEN_CONTAINS:
525
- return ContainsExpression(
526
- token, left, parse_boolean_primitive(env, stream, precedence)
527
- )
528
- # if token.kind == TOKEN_IN:
529
- # return InExpression(
530
- # token, left, parse_boolean_primitive(env, stream, precedence)
531
- # )
532
- if token.kind == TOKEN_AND:
533
- return LogicalAndExpression(
534
- token, left, parse_boolean_primitive(env, stream, precedence)
535
- )
536
- if token.kind == TOKEN_OR:
537
- return LogicalOrExpression(
538
- token, left, parse_boolean_primitive(env, stream, precedence)
539
- )
540
-
541
- raise LiquidSyntaxError(
542
- f"expected an infix expression, found {token.kind}",
543
- token=token,
544
- )
545
-
546
-
547
- def parse_grouped_expression(env: Environment, tokens: TokenStream) -> Expression:
548
- """Parse an expression from tokens in _tokens_ until the next right parenthesis."""
549
- if not env.logical_parentheses:
550
- raise LiquidSyntaxError(
551
- "disallowed parentheses in logical expression", token=tokens.current
552
- )
553
-
554
- tokens.eat(TOKEN_LPAREN)
555
- expr = parse_boolean_primitive(env, tokens)
556
- token = next(tokens)
557
-
558
- while token.kind != TOKEN_RPAREN:
559
- if token is None:
560
- raise LiquidSyntaxError("unbalanced parentheses", token=token)
561
-
562
- if token.kind not in BINARY_OPERATORS:
563
- raise LiquidSyntaxError(
564
- f"expected an infix expression, found {tokens.current.kind}",
565
- token=token,
566
- )
567
-
568
- expr = parse_infix_expression(env, tokens, expr)
569
-
570
- if token.kind != TOKEN_RPAREN:
571
- raise LiquidSyntaxError("unbalanced parentheses", token=token)
572
-
573
- return expr
574
-
575
-
576
- def is_truthy(obj: object) -> bool:
577
- """Return _True_ if _obj_ is considered Liquid truthy."""
578
- if hasattr(obj, "__liquid__"):
579
- obj = obj.__liquid__()
580
- if is_undefined(obj):
581
- return False
582
- return not (obj is False or obj is None)
583
-
584
-
585
- def _eq(left: object, right: object) -> bool:
586
- if is_undefined(left):
587
- return right is None or is_undefined(right)
588
-
589
- if is_undefined(right):
590
- return left is None or is_undefined(left)
591
-
592
- if hasattr(left, "__liquid__"):
593
- left = left.__liquid__()
594
-
595
- if hasattr(right, "__liquid__"):
596
- right = right.__liquid__()
597
-
598
- if isinstance(right, (Empty, Blank)):
599
- left, right = right, left
600
-
601
- # Remember 1 == True and 0 == False in Python
602
- if isinstance(right, bool):
603
- left, right = right, left
604
-
605
- if isinstance(left, bool):
606
- return isinstance(right, bool) and left == right
607
-
608
- return left == right
609
-
610
-
611
- def _lt(token: Token, left: object, right: object) -> bool:
612
- if hasattr(left, "__liquid__"):
613
- left = left.__liquid__()
614
-
615
- if hasattr(right, "__liquid__"):
616
- right = right.__liquid__()
617
-
618
- if isinstance(left, str) and isinstance(right, str):
619
- return left < right
620
-
621
- if isinstance(left, (int, float, Decimal)) and isinstance(
622
- right, (int, float, Decimal)
623
- ):
624
- return left < right
625
-
626
- if left is None or right is None:
627
- return False
628
-
629
- raise LiquidTypeError(
630
- f"'<' and '>' are not supported between '{left.__class__.__name__}' "
631
- f"and '{right.__class__.__name__}'",
632
- token=token,
633
- )
634
-
635
-
636
- def _le(left: object, right: object) -> bool:
637
- if hasattr(left, "__liquid__"):
638
- left = left.__liquid__()
639
-
640
- if hasattr(right, "__liquid__"):
641
- right = right.__liquid__()
642
-
643
- if isinstance(left, str) and isinstance(right, str):
644
- return left <= right
645
-
646
- if isinstance(left, bool) or isinstance(right, bool):
647
- return False
648
-
649
- if isinstance(left, (int, float, Decimal)) and isinstance(
650
- right, (int, float, Decimal)
651
- ):
652
- return left <= right
653
-
654
- if left is None or right is None:
655
- return False
656
-
657
- return _eq(left, right)
658
-
659
-
660
- def _contains(token: Token, left: object, right: object) -> bool:
661
- if not is_truthy(left) or not is_truthy(right):
662
- return False
663
- if isinstance(left, str):
664
- return str(right) in left
665
- if isinstance(left, Collection):
666
- return right in left
667
-
668
- raise LiquidTypeError(
669
- f"'in' and 'contains' are not supported between '{left.__class__.__name__}' "
670
- f"and '{right.__class__.__name__}'",
671
- token=token,
672
- )