python-liquid 2.3.6__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- liquid/__about__.py +1 -0
- liquid/__init__.py +161 -187
- liquid/_base_tag.py +66 -0
- liquid/{utils/chain_map.py → _chain_map.py} +18 -19
- liquid/_context.py +465 -0
- liquid/_drop.py +108 -0
- liquid/_filter.py +174 -0
- liquid/_io.py +18 -0
- liquid/_lax_parser_mixin.py +100 -0
- liquid/_lexer.py +93 -0
- liquid/_lexer_extended.py +623 -0
- liquid/_lexer_legacy.py +536 -0
- liquid/_loader.py +84 -0
- liquid/{utils/lru_cache.py → _lru_cache.py} +28 -36
- liquid/_markup.py +253 -0
- liquid/_messages.py +292 -0
- liquid/_missing.py +3 -0
- liquid/_nothing.py +28 -0
- liquid/_parser.py +460 -0
- liquid/_parser_extended.py +293 -0
- liquid/_parser_legacy.py +819 -0
- liquid/_resource_limits.py +50 -0
- liquid/_slice.py +41 -0
- liquid/_static_analysis.py +503 -0
- liquid/_strftime.py +186 -0
- liquid/_template.py +536 -0
- liquid/drops/__init__.py +20 -0
- liquid/drops/forloop.py +78 -0
- liquid/drops/iterable.py +25 -0
- liquid/drops/range.py +76 -0
- liquid/drops/self_.py +45 -0
- liquid/drops/tablerowloop.py +142 -0
- liquid/drops/undefined.py +228 -0
- liquid/environment.py +717 -551
- liquid/exceptions.py +67 -214
- liquid/expressions.py +1184 -0
- liquid/filters/__init__.py +134 -0
- liquid/filters/abs.py +7 -0
- liquid/filters/append.py +5 -0
- liquid/filters/at_least.py +9 -0
- liquid/filters/at_most.py +9 -0
- liquid/filters/babel.py +560 -0
- liquid/filters/base64.py +26 -0
- liquid/filters/capitalize.py +5 -0
- liquid/filters/ceil.py +8 -0
- liquid/filters/compact.py +25 -0
- liquid/filters/concat.py +11 -0
- liquid/filters/date.py +27 -0
- liquid/filters/default.py +25 -0
- liquid/filters/divided_by.py +21 -0
- liquid/filters/downcase.py +5 -0
- liquid/filters/escape.py +56 -0
- liquid/filters/find.py +56 -0
- liquid/filters/first.py +19 -0
- liquid/filters/floor.py +8 -0
- liquid/filters/html.py +23 -0
- liquid/filters/index.py +12 -0
- liquid/filters/join.py +13 -0
- liquid/filters/json_.py +30 -0
- liquid/filters/last.py +17 -0
- liquid/filters/map.py +5 -0
- liquid/filters/minus.py +13 -0
- liquid/filters/modulo.py +18 -0
- liquid/filters/newline_to_br.py +16 -0
- liquid/filters/plus.py +13 -0
- liquid/filters/prepend.py +5 -0
- liquid/filters/reject.py +19 -0
- liquid/filters/remove.py +36 -0
- liquid/filters/replace.py +32 -0
- liquid/filters/reverse.py +5 -0
- liquid/filters/round.py +35 -0
- liquid/filters/size.py +25 -0
- liquid/filters/slice.py +39 -0
- liquid/filters/sort.py +148 -0
- liquid/filters/split.py +34 -0
- liquid/filters/squish.py +9 -0
- liquid/filters/strip.py +13 -0
- liquid/filters/strip_html.py +19 -0
- liquid/filters/strip_newlines.py +17 -0
- liquid/filters/sum.py +21 -0
- liquid/filters/times.py +13 -0
- liquid/filters/translate.py +380 -0
- liquid/filters/truncate.py +39 -0
- liquid/filters/uniq.py +11 -0
- liquid/filters/upcase.py +5 -0
- liquid/filters/url.py +18 -0
- liquid/filters/where.py +18 -0
- liquid/loaders/__init__.py +17 -0
- liquid/loaders/caching_file_system_loader.py +64 -0
- liquid/loaders/choice_loader.py +93 -0
- liquid/loaders/dict_loader.py +56 -0
- liquid/{builtin/loaders → loaders}/file_system_loader.py +51 -55
- liquid/{builtin/loaders → loaders}/mixins.py +39 -51
- liquid/loaders/package_loader.py +110 -0
- liquid/tags/__init__.py +61 -0
- liquid/tags/assign.py +68 -0
- liquid/tags/capture.py +83 -0
- liquid/tags/case.py +199 -0
- liquid/tags/comment.py +56 -0
- liquid/tags/cycle.py +131 -0
- liquid/tags/decrement.py +55 -0
- liquid/tags/doc.py +56 -0
- liquid/tags/echo.py +65 -0
- liquid/tags/else_.py +53 -0
- liquid/tags/error.py +51 -0
- liquid/tags/extends.py +448 -0
- liquid/tags/for_.py +421 -0
- liquid/tags/if_.py +156 -0
- liquid/tags/ifchanged.py +69 -0
- liquid/tags/include.py +223 -0
- liquid/tags/increment.py +55 -0
- liquid/tags/inline_comment.py +70 -0
- liquid/tags/liquid.py +56 -0
- liquid/tags/macro.py +281 -0
- liquid/tags/output.py +58 -0
- liquid/tags/raw.py +60 -0
- liquid/tags/render.py +245 -0
- liquid/tags/tablerow.py +254 -0
- liquid/tags/template_comment.py +56 -0
- liquid/tags/translate.py +316 -0
- liquid/tags/unless.py +121 -0
- liquid/tags/with_.py +83 -0
- liquid/tokens.py +370 -0
- {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/METADATA +32 -39
- python_liquid-3.0.0.dist-info/RECORD +128 -0
- {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/WHEEL +1 -1
- {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/licenses/LICENSE +1 -1
- liquid/analyze_tags.py +0 -214
- liquid/ast.py +0 -252
- liquid/builtin/__init__.py +0 -203
- liquid/builtin/content.py +0 -41
- liquid/builtin/drops/__init__.py +0 -3
- liquid/builtin/drops/drops.py +0 -13
- liquid/builtin/expressions/__init__.py +0 -47
- liquid/builtin/expressions/_tokenize.py +0 -186
- liquid/builtin/expressions/arguments.py +0 -207
- liquid/builtin/expressions/filtered.py +0 -387
- liquid/builtin/expressions/logical.py +0 -672
- liquid/builtin/expressions/loop.py +0 -275
- liquid/builtin/expressions/path.py +0 -163
- liquid/builtin/expressions/primitive.py +0 -417
- liquid/builtin/filters/__init__.py +0 -0
- liquid/builtin/filters/array.py +0 -329
- liquid/builtin/filters/extra.py +0 -83
- liquid/builtin/filters/math.py +0 -129
- liquid/builtin/filters/misc.py +0 -137
- liquid/builtin/filters/string.py +0 -416
- liquid/builtin/illegal.py +0 -28
- liquid/builtin/loaders/__init__.py +0 -0
- liquid/builtin/loaders/caching_file_system_loader.py +0 -61
- liquid/builtin/loaders/choice_loader.py +0 -94
- liquid/builtin/loaders/dict_loader.py +0 -64
- liquid/builtin/loaders/package_loader.py +0 -132
- liquid/builtin/output.py +0 -74
- liquid/builtin/tags/__init__.py +0 -0
- liquid/builtin/tags/assign_tag.py +0 -75
- liquid/builtin/tags/capture_tag.py +0 -98
- liquid/builtin/tags/case_tag.py +0 -287
- liquid/builtin/tags/comment_tag.py +0 -77
- liquid/builtin/tags/cycle_tag.py +0 -132
- liquid/builtin/tags/decrement_tag.py +0 -59
- liquid/builtin/tags/doc_tag.py +0 -68
- liquid/builtin/tags/echo_tag.py +0 -45
- liquid/builtin/tags/for_tag.py +0 -337
- liquid/builtin/tags/if_tag.py +0 -209
- liquid/builtin/tags/ifchanged_tag.py +0 -94
- liquid/builtin/tags/include_tag.py +0 -251
- liquid/builtin/tags/increment_tag.py +0 -60
- liquid/builtin/tags/inline_comment_tag.py +0 -47
- liquid/builtin/tags/liquid_tag.py +0 -183
- liquid/builtin/tags/render_tag.py +0 -371
- liquid/builtin/tags/tablerow_tag.py +0 -320
- liquid/builtin/tags/unless_tag.py +0 -218
- liquid/context.py +0 -615
- liquid/expression.py +0 -41
- liquid/extra/__init__.py +0 -98
- liquid/extra/filters/__init__.py +0 -33
- liquid/extra/filters/_json.py +0 -33
- liquid/extra/filters/array.py +0 -60
- liquid/extra/filters/babel.py +0 -552
- liquid/extra/filters/html.py +0 -32
- liquid/extra/filters/translate.py +0 -448
- liquid/extra/tags/__init__.py +0 -17
- liquid/extra/tags/_with.py +0 -80
- liquid/extra/tags/extends_tag.py +0 -576
- liquid/extra/tags/macro_tag.py +0 -270
- liquid/extra/tags/snippet_tag.py +0 -107
- liquid/extra/tags/translate_tag.py +0 -388
- liquid/filter.py +0 -230
- liquid/future/__init__.py +0 -3
- liquid/future/environment.py +0 -30
- liquid/future/filters/__init__.py +0 -1
- liquid/future/tags/__init__.py +0 -1
- liquid/lex.py +0 -260
- liquid/limits.py +0 -66
- liquid/loader.py +0 -141
- liquid/messages.py +0 -357
- liquid/mode.py +0 -12
- liquid/output.py +0 -35
- liquid/parser.py +0 -119
- liquid/span.py +0 -35
- liquid/static_analysis.py +0 -443
- liquid/stream.py +0 -148
- liquid/stringify.py +0 -31
- liquid/tag.py +0 -47
- liquid/template.py +0 -658
- liquid/token.py +0 -145
- liquid/undefined.py +0 -218
- liquid/utils/__init__.py +0 -15
- liquid/utils/html.py +0 -57
- liquid/utils/text.py +0 -28
- python_liquid-2.3.6.dist-info/RECORD +0 -96
|
@@ -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
|
-
)
|