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,275 +0,0 @@
|
|
|
1
|
-
"""Built-in loop expressions."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from itertools import islice
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
7
|
-
from typing import Any
|
|
8
|
-
from typing import Iterator
|
|
9
|
-
from typing import Mapping
|
|
10
|
-
from typing import Sequence
|
|
11
|
-
from typing import Union
|
|
12
|
-
|
|
13
|
-
from liquid import Mode
|
|
14
|
-
from liquid.exceptions import LiquidSyntaxError
|
|
15
|
-
from liquid.exceptions import LiquidTypeError
|
|
16
|
-
from liquid.exceptions import LiquidValueError
|
|
17
|
-
from liquid.expression import Expression
|
|
18
|
-
from liquid.limits import to_int
|
|
19
|
-
from liquid.token import TOKEN_ASSIGN
|
|
20
|
-
from liquid.token import TOKEN_COLON
|
|
21
|
-
from liquid.token import TOKEN_COLS
|
|
22
|
-
from liquid.token import TOKEN_COMMA
|
|
23
|
-
from liquid.token import TOKEN_CONTINUE
|
|
24
|
-
from liquid.token import TOKEN_EOF
|
|
25
|
-
from liquid.token import TOKEN_IN
|
|
26
|
-
from liquid.token import TOKEN_LIMIT
|
|
27
|
-
from liquid.token import TOKEN_OFFSET
|
|
28
|
-
from liquid.token import TOKEN_REVERSED
|
|
29
|
-
|
|
30
|
-
from .primitive import StringLiteral
|
|
31
|
-
from .primitive import parse_identifier
|
|
32
|
-
from .primitive import parse_primitive
|
|
33
|
-
|
|
34
|
-
if TYPE_CHECKING:
|
|
35
|
-
from liquid import Environment
|
|
36
|
-
from liquid import RenderContext
|
|
37
|
-
from liquid import TokenStream
|
|
38
|
-
from liquid.token import Token
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class LoopExpression(Expression):
|
|
42
|
-
"""An expression for the `for` and `tablerow` tag."""
|
|
43
|
-
|
|
44
|
-
__slots__ = ("identifier", "iterable", "limit", "offset", "reversed", "cols")
|
|
45
|
-
|
|
46
|
-
def __init__(
|
|
47
|
-
self,
|
|
48
|
-
token: Token,
|
|
49
|
-
identifier: str,
|
|
50
|
-
iterable: Expression,
|
|
51
|
-
*,
|
|
52
|
-
limit: Expression | None,
|
|
53
|
-
offset: Expression | None,
|
|
54
|
-
reversed_: bool,
|
|
55
|
-
cols: Expression | None,
|
|
56
|
-
) -> None:
|
|
57
|
-
super().__init__(token)
|
|
58
|
-
self.identifier = identifier
|
|
59
|
-
self.iterable = iterable
|
|
60
|
-
self.limit = limit
|
|
61
|
-
self.offset = offset
|
|
62
|
-
self.reversed = reversed_
|
|
63
|
-
self.cols = cols
|
|
64
|
-
|
|
65
|
-
def __eq__(self, other: object) -> bool:
|
|
66
|
-
return (
|
|
67
|
-
isinstance(other, LoopExpression)
|
|
68
|
-
and self.identifier == other.identifier
|
|
69
|
-
and self.iterable == other.iterable
|
|
70
|
-
and self.limit == other.limit
|
|
71
|
-
and self.offset == other.offset
|
|
72
|
-
and self.cols == other.cols
|
|
73
|
-
and self.reversed == other.reversed
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
def __str__(self) -> str:
|
|
77
|
-
buf = [f"{self.identifier} in", str(self.iterable)]
|
|
78
|
-
|
|
79
|
-
if self.limit is not None:
|
|
80
|
-
buf.append(f"limit:{self.limit}")
|
|
81
|
-
|
|
82
|
-
if self.offset is not None:
|
|
83
|
-
buf.append(f"offset:{self.offset}")
|
|
84
|
-
|
|
85
|
-
if self.cols is not None:
|
|
86
|
-
buf.append(f"cols:{self.cols}")
|
|
87
|
-
|
|
88
|
-
if self.reversed:
|
|
89
|
-
buf.append("reversed")
|
|
90
|
-
|
|
91
|
-
return " ".join(buf)
|
|
92
|
-
|
|
93
|
-
def _to_iter(
|
|
94
|
-
self, obj: object, context: RenderContext
|
|
95
|
-
) -> tuple[Iterator[Any], int]:
|
|
96
|
-
if isinstance(obj, Mapping):
|
|
97
|
-
return iter(obj.items()), len(obj)
|
|
98
|
-
if isinstance(obj, range):
|
|
99
|
-
try:
|
|
100
|
-
return iter(obj), len(obj)
|
|
101
|
-
except OverflowError as err:
|
|
102
|
-
raise LiquidValueError(
|
|
103
|
-
"range literal length limit reached", token=None
|
|
104
|
-
) from err
|
|
105
|
-
if isinstance(obj, str) and not context.env.string_sequences:
|
|
106
|
-
return (iter([]), 0) if not obj else (iter([obj]), 1)
|
|
107
|
-
if isinstance(obj, Sequence):
|
|
108
|
-
return iter(obj), len(obj)
|
|
109
|
-
|
|
110
|
-
return iter([]), 0
|
|
111
|
-
|
|
112
|
-
def _to_int(self, obj: object, *, token: Token) -> int:
|
|
113
|
-
try:
|
|
114
|
-
return to_int(obj)
|
|
115
|
-
except (ValueError, TypeError) as err:
|
|
116
|
-
raise LiquidTypeError(
|
|
117
|
-
f"expected an integer, found {obj.__class__.__name__}",
|
|
118
|
-
token=token,
|
|
119
|
-
) from err
|
|
120
|
-
|
|
121
|
-
def _slice(
|
|
122
|
-
self,
|
|
123
|
-
it: Iterator[object],
|
|
124
|
-
length: int,
|
|
125
|
-
context: RenderContext,
|
|
126
|
-
*,
|
|
127
|
-
limit: int | None,
|
|
128
|
-
offset: int | str | None,
|
|
129
|
-
) -> tuple[Iterator[object], int]:
|
|
130
|
-
offset_key = f"{self.identifier}-{self.iterable}"
|
|
131
|
-
|
|
132
|
-
if offset == "continue":
|
|
133
|
-
start = context.stopindex(offset_key)
|
|
134
|
-
else:
|
|
135
|
-
start = int(offset or 0)
|
|
136
|
-
|
|
137
|
-
stop = None if limit is None else limit + start
|
|
138
|
-
|
|
139
|
-
start_ = min(max(start, 0), length)
|
|
140
|
-
stop_ = min(stop or length, length)
|
|
141
|
-
length_ = max(stop_ - start_, 0)
|
|
142
|
-
|
|
143
|
-
context.stopindex(key=offset_key, index=stop_)
|
|
144
|
-
it = islice(it, start_, stop_)
|
|
145
|
-
|
|
146
|
-
if self.reversed:
|
|
147
|
-
return reversed(list(it)), length_
|
|
148
|
-
return it, length_
|
|
149
|
-
|
|
150
|
-
def evaluate(self, context: RenderContext) -> tuple[Iterator[object], int]:
|
|
151
|
-
it, length = self._to_iter(self.iterable.evaluate(context), context)
|
|
152
|
-
limit = (
|
|
153
|
-
self._to_int(self.limit.evaluate(context), token=self.limit.token)
|
|
154
|
-
if self.limit
|
|
155
|
-
else None
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
if isinstance(self.offset, StringLiteral):
|
|
159
|
-
offset: Union[str, int, None] = self.offset.value
|
|
160
|
-
if offset != "continue":
|
|
161
|
-
offset = self._to_int(offset, token=self.offset.token)
|
|
162
|
-
elif self.offset is None:
|
|
163
|
-
offset = None
|
|
164
|
-
else:
|
|
165
|
-
offset = self._to_int(
|
|
166
|
-
self.offset.evaluate(context), token=self.offset.token
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
return self._slice(it, length, context, limit=limit, offset=offset)
|
|
170
|
-
|
|
171
|
-
async def evaluate_async(
|
|
172
|
-
self, context: RenderContext
|
|
173
|
-
) -> tuple[Iterator[object], int]:
|
|
174
|
-
it, length = self._to_iter(await self.iterable.evaluate_async(context), context)
|
|
175
|
-
limit = (
|
|
176
|
-
self._to_int(
|
|
177
|
-
await self.limit.evaluate_async(context), token=self.limit.token
|
|
178
|
-
)
|
|
179
|
-
if self.limit
|
|
180
|
-
else None
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
if self.offset is None:
|
|
184
|
-
offset: str | int | None = None
|
|
185
|
-
elif isinstance(self.offset, StringLiteral):
|
|
186
|
-
offset = self.offset.evaluate(context)
|
|
187
|
-
if offset != "continue":
|
|
188
|
-
offset = self._to_int(offset, token=self.offset.token)
|
|
189
|
-
else:
|
|
190
|
-
offset = self._to_int(
|
|
191
|
-
await self.offset.evaluate_async(context), token=self.offset.token
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
return self._slice(it, length, context, limit=limit, offset=offset)
|
|
195
|
-
|
|
196
|
-
def children(self) -> list[Expression]:
|
|
197
|
-
children = [self.iterable]
|
|
198
|
-
|
|
199
|
-
if self.limit is not None:
|
|
200
|
-
children.append(self.limit)
|
|
201
|
-
|
|
202
|
-
if self.offset is not None:
|
|
203
|
-
children.append(self.offset)
|
|
204
|
-
|
|
205
|
-
if self.cols is not None:
|
|
206
|
-
children.append(self.cols)
|
|
207
|
-
|
|
208
|
-
return children
|
|
209
|
-
|
|
210
|
-
@staticmethod
|
|
211
|
-
def parse(env: Environment, tokens: TokenStream) -> LoopExpression:
|
|
212
|
-
"""Parse a loop expression from _tokens_."""
|
|
213
|
-
token = tokens.current
|
|
214
|
-
identifier = parse_identifier(env, tokens)
|
|
215
|
-
tokens.eat(TOKEN_IN)
|
|
216
|
-
iterable = parse_primitive(env, tokens)
|
|
217
|
-
|
|
218
|
-
reversed_ = False
|
|
219
|
-
offset: Expression | None = None
|
|
220
|
-
limit: Expression | None = None
|
|
221
|
-
cols: Expression | None = None
|
|
222
|
-
|
|
223
|
-
argument_separators = (
|
|
224
|
-
(TOKEN_COLON, TOKEN_ASSIGN) if env.keyword_assignment else (TOKEN_COLON,)
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
# Leading commas are OK
|
|
228
|
-
if tokens.current.kind == TOKEN_COMMA:
|
|
229
|
-
next(tokens)
|
|
230
|
-
|
|
231
|
-
while True:
|
|
232
|
-
arg_token = next(tokens)
|
|
233
|
-
kind = arg_token.kind
|
|
234
|
-
|
|
235
|
-
if kind == TOKEN_LIMIT:
|
|
236
|
-
tokens.eat_one_of(*argument_separators)
|
|
237
|
-
limit = parse_primitive(env, tokens)
|
|
238
|
-
elif kind == TOKEN_REVERSED:
|
|
239
|
-
reversed_ = True
|
|
240
|
-
elif kind == TOKEN_OFFSET:
|
|
241
|
-
tokens.eat_one_of(*argument_separators)
|
|
242
|
-
offset_token = tokens.current
|
|
243
|
-
if offset_token.kind == TOKEN_CONTINUE:
|
|
244
|
-
next(tokens)
|
|
245
|
-
offset = StringLiteral(token=offset_token, value="continue")
|
|
246
|
-
else:
|
|
247
|
-
offset = parse_primitive(env, tokens)
|
|
248
|
-
elif kind == TOKEN_COLS:
|
|
249
|
-
tokens.eat_one_of(*argument_separators)
|
|
250
|
-
cols = parse_primitive(env, tokens)
|
|
251
|
-
elif kind == TOKEN_COMMA:
|
|
252
|
-
if env.mode == Mode.STRICT and tokens.peek.kind == TOKEN_COMMA:
|
|
253
|
-
raise LiquidSyntaxError(
|
|
254
|
-
f"expected 'reversed', 'offset' or 'limit', found {kind}",
|
|
255
|
-
token=tokens.peek,
|
|
256
|
-
)
|
|
257
|
-
continue
|
|
258
|
-
elif kind == TOKEN_EOF:
|
|
259
|
-
break
|
|
260
|
-
else:
|
|
261
|
-
raise LiquidSyntaxError(
|
|
262
|
-
"expected 'reversed', 'offset' or 'limit'",
|
|
263
|
-
token=arg_token,
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
tokens.eat(TOKEN_EOF)
|
|
267
|
-
return LoopExpression(
|
|
268
|
-
token,
|
|
269
|
-
identifier,
|
|
270
|
-
iterable,
|
|
271
|
-
limit=limit,
|
|
272
|
-
offset=offset,
|
|
273
|
-
reversed_=reversed_,
|
|
274
|
-
cols=cols,
|
|
275
|
-
)
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
"""A built-in expression for paths to a value/variable.
|
|
2
|
-
|
|
3
|
-
Note that a path might contain just one segment, in which case we might just
|
|
4
|
-
call it a "variable".
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import re
|
|
10
|
-
import sys
|
|
11
|
-
from typing import TYPE_CHECKING
|
|
12
|
-
from typing import Union
|
|
13
|
-
|
|
14
|
-
from liquid import Mode
|
|
15
|
-
from liquid.exceptions import LiquidSyntaxError
|
|
16
|
-
from liquid.expression import Expression
|
|
17
|
-
from liquid.limits import to_int
|
|
18
|
-
from liquid.token import TOKEN_DOT
|
|
19
|
-
from liquid.token import TOKEN_FLOAT
|
|
20
|
-
from liquid.token import TOKEN_IDENTINDEX
|
|
21
|
-
from liquid.token import TOKEN_IDENTSTRING
|
|
22
|
-
from liquid.token import TOKEN_INTEGER
|
|
23
|
-
from liquid.token import TOKEN_LBRACKET
|
|
24
|
-
from liquid.token import TOKEN_RBRACKET
|
|
25
|
-
from liquid.token import TOKEN_WORD
|
|
26
|
-
|
|
27
|
-
if TYPE_CHECKING:
|
|
28
|
-
from liquid import Environment
|
|
29
|
-
from liquid import RenderContext
|
|
30
|
-
from liquid import Token
|
|
31
|
-
from liquid import TokenStream
|
|
32
|
-
|
|
33
|
-
Segments = list[Union[str, int, "Path"]]
|
|
34
|
-
Location = tuple[Union[str, int, "Location"], ...]
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# This is use for pretty printing paths with shorthand notation where possible.
|
|
38
|
-
RE_PROPERTY = re.compile(r"[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*")
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class Path(Expression):
|
|
42
|
-
__slots__ = ("path",)
|
|
43
|
-
|
|
44
|
-
def __init__(self, token: Token, path: Segments):
|
|
45
|
-
super().__init__(token)
|
|
46
|
-
self.path = path
|
|
47
|
-
|
|
48
|
-
def __eq__(self, other: object) -> bool:
|
|
49
|
-
return isinstance(other, Path) and self.path == other.path
|
|
50
|
-
|
|
51
|
-
def __str__(self) -> str:
|
|
52
|
-
it = iter(self.path)
|
|
53
|
-
buf = [str(next(it))]
|
|
54
|
-
for segment in it:
|
|
55
|
-
if isinstance(segment, Path):
|
|
56
|
-
buf.append(f"[{segment}]")
|
|
57
|
-
elif isinstance(segment, str):
|
|
58
|
-
if RE_PROPERTY.fullmatch(segment):
|
|
59
|
-
buf.append(f".{segment}")
|
|
60
|
-
else:
|
|
61
|
-
buf.append(f"[{segment!r}]")
|
|
62
|
-
else:
|
|
63
|
-
buf.append(f"[{segment}]")
|
|
64
|
-
return "".join(buf)
|
|
65
|
-
|
|
66
|
-
def __hash__(self) -> int:
|
|
67
|
-
return hash(self.path)
|
|
68
|
-
|
|
69
|
-
def __sizeof__(self) -> int:
|
|
70
|
-
return super().__sizeof__() + sys.getsizeof(self.path)
|
|
71
|
-
|
|
72
|
-
def location(self) -> Location:
|
|
73
|
-
"""Return this path's segments as a tuple of strings and ints.
|
|
74
|
-
|
|
75
|
-
Segments can also be nested tuples of strings, ints and tuples if the path
|
|
76
|
-
contains nested paths.
|
|
77
|
-
"""
|
|
78
|
-
return tuple([s.location() if isinstance(s, Path) else s for s in self.path])
|
|
79
|
-
|
|
80
|
-
def head(self) -> int | str | Path:
|
|
81
|
-
return self.path[0]
|
|
82
|
-
|
|
83
|
-
def evaluate(self, context: RenderContext) -> object:
|
|
84
|
-
return context.get(
|
|
85
|
-
[p.evaluate(context) if isinstance(p, Path) else p for p in self.path],
|
|
86
|
-
token=self.token,
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
async def evaluate_async(self, context: RenderContext) -> object:
|
|
90
|
-
return await context.get_async(
|
|
91
|
-
[
|
|
92
|
-
await p.evaluate_async(context) if isinstance(p, Path) else p
|
|
93
|
-
for p in self.path
|
|
94
|
-
],
|
|
95
|
-
token=self.token,
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
def children(self) -> list[Expression]:
|
|
99
|
-
return [p for p in self.path if isinstance(p, Expression)]
|
|
100
|
-
|
|
101
|
-
@staticmethod
|
|
102
|
-
def parse(env: Environment, tokens: TokenStream) -> Path: # noqa: PLR0912
|
|
103
|
-
token = tokens.current
|
|
104
|
-
segments: Segments = []
|
|
105
|
-
|
|
106
|
-
while True:
|
|
107
|
-
kind, value, _index, _source = tokens.current
|
|
108
|
-
|
|
109
|
-
if kind == TOKEN_WORD:
|
|
110
|
-
segments.append(value)
|
|
111
|
-
if tokens.peek.kind == TOKEN_WORD:
|
|
112
|
-
# Two consecutive words indicate end of path.
|
|
113
|
-
next(tokens)
|
|
114
|
-
break
|
|
115
|
-
elif kind == TOKEN_IDENTSTRING:
|
|
116
|
-
segments.append(value)
|
|
117
|
-
if env.mode == Mode.STRICT and tokens.peek.kind == TOKEN_WORD:
|
|
118
|
-
raise LiquidSyntaxError(
|
|
119
|
-
f"expected a dot or bracket notation, found {tokens.peek.kind}",
|
|
120
|
-
token=tokens.peek,
|
|
121
|
-
)
|
|
122
|
-
elif kind == TOKEN_IDENTINDEX:
|
|
123
|
-
segments.append(to_int(value))
|
|
124
|
-
if env.mode == Mode.STRICT and tokens.peek.kind == TOKEN_WORD:
|
|
125
|
-
raise LiquidSyntaxError(
|
|
126
|
-
f"expected a dot or bracket notation, found {tokens.peek.kind}",
|
|
127
|
-
token=tokens.peek,
|
|
128
|
-
)
|
|
129
|
-
elif kind == TOKEN_LBRACKET:
|
|
130
|
-
next(tokens)
|
|
131
|
-
segments.append(Path.parse(env, tokens))
|
|
132
|
-
tokens.expect(TOKEN_RBRACKET)
|
|
133
|
-
if env.mode == Mode.STRICT and tokens.peek.kind == TOKEN_WORD:
|
|
134
|
-
raise LiquidSyntaxError(
|
|
135
|
-
f"expected a dot or bracket notation, found {tokens.peek.kind}",
|
|
136
|
-
token=tokens.peek,
|
|
137
|
-
)
|
|
138
|
-
elif kind == TOKEN_DOT:
|
|
139
|
-
if (
|
|
140
|
-
not env.shorthand_indexes
|
|
141
|
-
and env.mode == Mode.STRICT
|
|
142
|
-
and tokens.peek.kind != TOKEN_WORD
|
|
143
|
-
):
|
|
144
|
-
raise LiquidSyntaxError(
|
|
145
|
-
f"expected an identifier, found {tokens.peek.kind}",
|
|
146
|
-
token=tokens.peek,
|
|
147
|
-
)
|
|
148
|
-
elif kind == TOKEN_FLOAT and env.shorthand_indexes:
|
|
149
|
-
segments.extend(to_int(i) for i in value.rstrip(".").split("."))
|
|
150
|
-
elif kind == TOKEN_INTEGER and env.shorthand_indexes:
|
|
151
|
-
segments.append(to_int(value))
|
|
152
|
-
else:
|
|
153
|
-
break
|
|
154
|
-
|
|
155
|
-
next(tokens)
|
|
156
|
-
|
|
157
|
-
if not segments:
|
|
158
|
-
raise LiquidSyntaxError(
|
|
159
|
-
"missing or unexpected path segment",
|
|
160
|
-
token=tokens.current,
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
return Path(token, segments)
|