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
liquid/filters/babel.py
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime, time
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
6
|
+
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
7
|
+
|
|
8
|
+
from babel import Locale, UnknownLocaleError, dates, numbers, units
|
|
9
|
+
from dateutil import parser
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .._filter import FilterContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _resolve_locale(
|
|
16
|
+
context: FilterContext,
|
|
17
|
+
locale_var: str,
|
|
18
|
+
default: Locale,
|
|
19
|
+
) -> Locale:
|
|
20
|
+
locale_ = context.resolve(locale_var)
|
|
21
|
+
|
|
22
|
+
if context.is_nil(locale_):
|
|
23
|
+
locale = default
|
|
24
|
+
|
|
25
|
+
elif isinstance(locale_, str):
|
|
26
|
+
try:
|
|
27
|
+
locale = Locale.parse(locale_)
|
|
28
|
+
except UnknownLocaleError:
|
|
29
|
+
locale = default
|
|
30
|
+
|
|
31
|
+
else:
|
|
32
|
+
raise context.error(
|
|
33
|
+
f"expected a string argument, found {locale_.__class__.__name__}"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return locale
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Currency:
|
|
40
|
+
"""A Liquid filter for formatting currency values.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
currency_code_var
|
|
45
|
+
The name of a render context variable that resolves to the current currency code.
|
|
46
|
+
Defaults to ``"currency_code"``.
|
|
47
|
+
default_currency_code
|
|
48
|
+
A fallback currency code if `currency_code_var` can not be resolved. Defaults to
|
|
49
|
+
``"USD"``.
|
|
50
|
+
locale_var
|
|
51
|
+
The name of a render context variable that resolves to the current locale.
|
|
52
|
+
Defaults to ``"locale"``.
|
|
53
|
+
default_locale
|
|
54
|
+
A fallback locale to use if `locale_var` can not be resolved. Defaults to
|
|
55
|
+
``"en_US"``.
|
|
56
|
+
format_var
|
|
57
|
+
The name of a render context variable that resolves to the current currency
|
|
58
|
+
format string. Defaults to ``"currency_format"``.
|
|
59
|
+
default_format
|
|
60
|
+
A fallback currency format that is used if `format_var` can not be resolved.
|
|
61
|
+
Defaults to `None`, which means the standard format for the current locale will
|
|
62
|
+
be used.
|
|
63
|
+
currency_digits
|
|
64
|
+
Indicates if the format should override locale specific trailing digit behavior.
|
|
65
|
+
Defaults to ``False``.
|
|
66
|
+
input_locale_var
|
|
67
|
+
The name of a render context variable that resolves to a locale suitable for
|
|
68
|
+
parsing input strings to decimals. Defaults to ``"input_locale"``.
|
|
69
|
+
default_input_locale
|
|
70
|
+
A fallback locale to use if `input_locale_var` can not be resolved. Defaults to
|
|
71
|
+
``"en_US"``.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
*,
|
|
77
|
+
currency_code_var: str = "currency_code",
|
|
78
|
+
default_currency_code: str = "USD",
|
|
79
|
+
locale_var: str = "locale",
|
|
80
|
+
default_locale: str = "en_US",
|
|
81
|
+
format_var: str = "currency_format",
|
|
82
|
+
default_format: str | None = None,
|
|
83
|
+
currency_digits: bool = True,
|
|
84
|
+
input_locale_var: str = "input_locale",
|
|
85
|
+
default_input_locale: str = "en_US",
|
|
86
|
+
) -> None:
|
|
87
|
+
self.currency_code_var = currency_code_var
|
|
88
|
+
self.default_currency_code = default_currency_code
|
|
89
|
+
self.locale_var = locale_var
|
|
90
|
+
self.default_locale = Locale.parse(default_locale)
|
|
91
|
+
self.format_var = format_var
|
|
92
|
+
self.default_format = default_format
|
|
93
|
+
self.currency_digits = currency_digits
|
|
94
|
+
self.input_locale_var = input_locale_var
|
|
95
|
+
self.default_input_locale = Locale.parse(default_input_locale)
|
|
96
|
+
|
|
97
|
+
def __call__(
|
|
98
|
+
self,
|
|
99
|
+
context: FilterContext,
|
|
100
|
+
left: object,
|
|
101
|
+
*,
|
|
102
|
+
group_separator: bool = True,
|
|
103
|
+
) -> str:
|
|
104
|
+
"""Apply the filter and return the result."""
|
|
105
|
+
locale = _resolve_locale(
|
|
106
|
+
context,
|
|
107
|
+
self.locale_var,
|
|
108
|
+
default=self.default_locale,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
input_locale = _resolve_locale(
|
|
112
|
+
context,
|
|
113
|
+
self.input_locale_var,
|
|
114
|
+
default=self.default_input_locale,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
format_ = context.resolve(self.format_var)
|
|
118
|
+
if context.is_nil(format_):
|
|
119
|
+
format_ = self.default_format
|
|
120
|
+
|
|
121
|
+
if format_ is not None and not isinstance(format_, str):
|
|
122
|
+
raise context.error(f"expected a string argument, found {format_}")
|
|
123
|
+
|
|
124
|
+
currency_code = context.resolve(self.currency_code_var)
|
|
125
|
+
if context.is_nil(currency_code):
|
|
126
|
+
currency_code = self.default_currency_code
|
|
127
|
+
|
|
128
|
+
if not isinstance(currency_code, str):
|
|
129
|
+
raise context.error(f"expected a string argument, found {currency_code}")
|
|
130
|
+
|
|
131
|
+
return numbers.format_currency(
|
|
132
|
+
_parse_decimal(context, left, input_locale),
|
|
133
|
+
currency_code,
|
|
134
|
+
format=format_,
|
|
135
|
+
locale=locale,
|
|
136
|
+
group_separator=group_separator,
|
|
137
|
+
currency_digits=self.currency_digits,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _parse_decimal(
|
|
142
|
+
context: FilterContext, val: object, locale: str | Locale
|
|
143
|
+
) -> Decimal:
|
|
144
|
+
if isinstance(val, str):
|
|
145
|
+
try:
|
|
146
|
+
return numbers.parse_decimal(val, locale)
|
|
147
|
+
except numbers.NumberFormatError:
|
|
148
|
+
return Decimal(0)
|
|
149
|
+
|
|
150
|
+
if isinstance(val, (Decimal, float, int)):
|
|
151
|
+
return Decimal(val)
|
|
152
|
+
|
|
153
|
+
# Give objects that implement __int__ etc. a chance.
|
|
154
|
+
return Decimal(context.to_decimal(val, 0))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class DateTime:
|
|
158
|
+
"""A Liquid filter for formatting datetime objects.
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
timezone_var
|
|
163
|
+
The name of a render context variable that resolves to a timezone. Defaults to
|
|
164
|
+
``"timezone"``.
|
|
165
|
+
default_timezone
|
|
166
|
+
A fallback timezone to use if `timezone_var` can not be resolved. Defaults to
|
|
167
|
+
``"UTC"``.
|
|
168
|
+
locale_var
|
|
169
|
+
The name of a render context variable that resolves to the current locale.
|
|
170
|
+
Defaults to ``"locale"``.
|
|
171
|
+
default_locale
|
|
172
|
+
A fallback locale to use if `locale_var` can not be resolved. Defaults to
|
|
173
|
+
``"en_US"``.
|
|
174
|
+
format_var
|
|
175
|
+
The name of a render context variable that resolves to the current datetime
|
|
176
|
+
format string. Defaults to ``"datetime_format"``.
|
|
177
|
+
default_format
|
|
178
|
+
A fallback datetime format that is used if `format_var` can not be resolved.
|
|
179
|
+
Defaults to ``"medium"``.
|
|
180
|
+
input_timezone_var
|
|
181
|
+
The name of a render context variable that resolves to a timezone for parsing
|
|
182
|
+
datetimes entered as strings. Defaults to ``"input_timezone"``.
|
|
183
|
+
default_input_timezone
|
|
184
|
+
A fallback timezone to use if `input_timezone_var` can not be resolved. Defaults
|
|
185
|
+
to ``"UTC"``.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
formats: ClassVar[dict[str, str]] = {
|
|
189
|
+
"short": "short",
|
|
190
|
+
"medium": "medium",
|
|
191
|
+
"long": "long",
|
|
192
|
+
"full": "full",
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
def __init__(
|
|
196
|
+
self,
|
|
197
|
+
*,
|
|
198
|
+
timezone_var: str = "timezone",
|
|
199
|
+
default_timezone: str = "UTC",
|
|
200
|
+
locale_var: str = "locale",
|
|
201
|
+
default_locale: str = "en_US",
|
|
202
|
+
format_var: str = "datetime_format",
|
|
203
|
+
default_format: str = "medium",
|
|
204
|
+
input_timezone_var: str = "input_timezone",
|
|
205
|
+
default_input_timezone: str = "UTC",
|
|
206
|
+
) -> None:
|
|
207
|
+
self.timezone_var = timezone_var
|
|
208
|
+
self.default_timezone = ZoneInfo(default_timezone)
|
|
209
|
+
self.locale_var = locale_var
|
|
210
|
+
self.default_locale = Locale.parse(default_locale)
|
|
211
|
+
self.format_var = format_var
|
|
212
|
+
self.default_format = self.formats.get(default_format, default_format)
|
|
213
|
+
self.input_timezone_var = input_timezone_var
|
|
214
|
+
self.default_input_timezone = ZoneInfo(default_input_timezone)
|
|
215
|
+
|
|
216
|
+
def __call__(
|
|
217
|
+
self,
|
|
218
|
+
context: FilterContext,
|
|
219
|
+
left: object,
|
|
220
|
+
*,
|
|
221
|
+
format: str | None = None,
|
|
222
|
+
) -> str:
|
|
223
|
+
"""Apply the filter and return the result."""
|
|
224
|
+
locale = _resolve_locale(
|
|
225
|
+
context,
|
|
226
|
+
self.locale_var,
|
|
227
|
+
default=self.default_locale,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
if format:
|
|
231
|
+
_format = self.formats.get(format, format)
|
|
232
|
+
else:
|
|
233
|
+
format_string = context.resolve(self.format_var)
|
|
234
|
+
|
|
235
|
+
if context.is_nil(format_string):
|
|
236
|
+
_format = self.default_format
|
|
237
|
+
elif not isinstance(format_string, str):
|
|
238
|
+
raise context.error(
|
|
239
|
+
f"expected a string argument, found {format_string}"
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
_format = self.formats.get(format_string, format_string)
|
|
243
|
+
|
|
244
|
+
tzinfo = self._resolve_timezone(
|
|
245
|
+
context,
|
|
246
|
+
self.timezone_var,
|
|
247
|
+
default=self.default_timezone,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
input_tzinfo = self._resolve_timezone(
|
|
251
|
+
context,
|
|
252
|
+
self.input_timezone_var,
|
|
253
|
+
default=self.default_input_timezone,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
return dates.format_datetime(
|
|
257
|
+
_parse_datetime(context, left, input_tzinfo),
|
|
258
|
+
format=_format,
|
|
259
|
+
locale=locale,
|
|
260
|
+
tzinfo=tzinfo,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
def _resolve_timezone(
|
|
264
|
+
self,
|
|
265
|
+
context: FilterContext,
|
|
266
|
+
timezone_var: str,
|
|
267
|
+
default: ZoneInfo,
|
|
268
|
+
) -> ZoneInfo:
|
|
269
|
+
_tz = context.resolve(timezone_var)
|
|
270
|
+
|
|
271
|
+
if context.is_nil(_tz):
|
|
272
|
+
timezone = default
|
|
273
|
+
elif isinstance(_tz, str):
|
|
274
|
+
try:
|
|
275
|
+
timezone = ZoneInfo(_tz)
|
|
276
|
+
except ZoneInfoNotFoundError:
|
|
277
|
+
timezone = default
|
|
278
|
+
else:
|
|
279
|
+
raise context.error(f"expected a string argument, found {_tz}")
|
|
280
|
+
|
|
281
|
+
return timezone
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _parse_number(val: str) -> int | float:
|
|
285
|
+
try:
|
|
286
|
+
return int(val)
|
|
287
|
+
except ValueError:
|
|
288
|
+
# Let the ValueError raise
|
|
289
|
+
return float(val)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _parse_datetime(
|
|
293
|
+
context: FilterContext,
|
|
294
|
+
val: object,
|
|
295
|
+
default_timezone: ZoneInfo,
|
|
296
|
+
) -> date | time | datetime | int | float | None:
|
|
297
|
+
if isinstance(val, str):
|
|
298
|
+
if val.lower() in ("now", "today"):
|
|
299
|
+
# `date.format_datetime` will use the current timestamp if
|
|
300
|
+
# given `None`.
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
# String representations of ints and floats need to be cast
|
|
304
|
+
# to an int or float and not passed to the fuzzy parser.
|
|
305
|
+
try:
|
|
306
|
+
return _parse_number(val)
|
|
307
|
+
except ValueError:
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
date_ = parser.parse(val)
|
|
312
|
+
if date_.tzinfo is None:
|
|
313
|
+
return date_.replace(tzinfo=default_timezone)
|
|
314
|
+
return date_
|
|
315
|
+
except parser.ParserError as err:
|
|
316
|
+
raise context.error(str(err)) from err
|
|
317
|
+
|
|
318
|
+
if not isinstance(val, (date, time, datetime, int, float)):
|
|
319
|
+
raise context.error(f"date expected date or time, found {type(val).__name__}")
|
|
320
|
+
|
|
321
|
+
return val
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class Number:
|
|
325
|
+
"""A Liquid filter for formatting decimal values.
|
|
326
|
+
|
|
327
|
+
Parameters
|
|
328
|
+
----------
|
|
329
|
+
decimal_quantization_var
|
|
330
|
+
The name of a render context variable that resolves to the decimal quantization
|
|
331
|
+
to be used. Defaults to ``"decimal_quantization"``.
|
|
332
|
+
default_decimal_quantization:
|
|
333
|
+
A fallback decimal quantization if `decimal_quantization_var` can not be
|
|
334
|
+
resolved. Defaults to ``False``.
|
|
335
|
+
locale_var
|
|
336
|
+
The name of a render context variable that resolves to the current locale.
|
|
337
|
+
Defaults to ``"locale"``.
|
|
338
|
+
default_locale
|
|
339
|
+
A fallback locale to use if `locale_var` can not be resolved. Defaults to
|
|
340
|
+
``"en_US"``.
|
|
341
|
+
format_var
|
|
342
|
+
The name of a render context variable that resolves to the current decimal format
|
|
343
|
+
string. Defaults to ``"decimal_format"``.
|
|
344
|
+
default_format
|
|
345
|
+
A fallback decimal format that is used if `format_var` can not be resolved.
|
|
346
|
+
Defaults to ``None``, which means the standard format for the current locale will
|
|
347
|
+
be used.
|
|
348
|
+
input_locale_var
|
|
349
|
+
The name of a render context variable that resolves to a locale suitable for
|
|
350
|
+
parsing input strings to decimals. Defaults to ``"input_locale"``.
|
|
351
|
+
default_input_locale
|
|
352
|
+
A fallback locale to use if `input_locale_var` can not be resolved. Defaults to
|
|
353
|
+
``"en_US"``.
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
def __init__(
|
|
357
|
+
self,
|
|
358
|
+
*,
|
|
359
|
+
decimal_quantization_var: str = "decimal_quantization",
|
|
360
|
+
default_decimal_quantization: bool = False,
|
|
361
|
+
locale_var: str = "locale",
|
|
362
|
+
default_locale: str = "en_US",
|
|
363
|
+
format_var: str = "decimal_format",
|
|
364
|
+
default_format: str | None = None,
|
|
365
|
+
input_locale_var: str = "input_locale",
|
|
366
|
+
default_input_locale: str = "en_US",
|
|
367
|
+
) -> None:
|
|
368
|
+
self.decimal_quantization_var = decimal_quantization_var
|
|
369
|
+
self.default_decimal_quantization = default_decimal_quantization
|
|
370
|
+
self.locale_var = locale_var
|
|
371
|
+
self.default_locale = Locale.parse(default_locale)
|
|
372
|
+
self.format_var = format_var
|
|
373
|
+
self.default_format = default_format
|
|
374
|
+
self.input_locale_var = input_locale_var
|
|
375
|
+
self.default_input_locale = Locale.parse(default_input_locale)
|
|
376
|
+
|
|
377
|
+
def __call__(
|
|
378
|
+
self,
|
|
379
|
+
context: FilterContext,
|
|
380
|
+
left: object,
|
|
381
|
+
*,
|
|
382
|
+
group_separator: bool = True,
|
|
383
|
+
) -> str:
|
|
384
|
+
"""Apply the filter and return the result."""
|
|
385
|
+
decimal_quantization = context.resolve(self.decimal_quantization_var)
|
|
386
|
+
if context.is_nil(decimal_quantization):
|
|
387
|
+
decimal_quantization = self.default_decimal_quantization
|
|
388
|
+
|
|
389
|
+
if not isinstance(decimal_quantization, bool):
|
|
390
|
+
raise context.error(
|
|
391
|
+
f"expected a Boolean argument, found {decimal_quantization!r}"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
locale = _resolve_locale(
|
|
395
|
+
context,
|
|
396
|
+
self.locale_var,
|
|
397
|
+
default=self.default_locale,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
input_locale = _resolve_locale(
|
|
401
|
+
context,
|
|
402
|
+
self.input_locale_var,
|
|
403
|
+
default=self.default_input_locale,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
format_ = context.resolve(self.format_var)
|
|
407
|
+
if context.is_nil(format_):
|
|
408
|
+
format_ = self.default_format
|
|
409
|
+
|
|
410
|
+
if format_ is not None and not isinstance(format_, str):
|
|
411
|
+
raise context.error(f"expected a string argument, found {format_}")
|
|
412
|
+
|
|
413
|
+
return numbers.format_decimal( # type: ignore
|
|
414
|
+
_parse_decimal(context, left, input_locale),
|
|
415
|
+
format=format_,
|
|
416
|
+
locale=locale,
|
|
417
|
+
group_separator=group_separator,
|
|
418
|
+
decimal_quantization=decimal_quantization,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
class Unit:
|
|
423
|
+
"""A Liquid filter for formatting units of measurement.
|
|
424
|
+
|
|
425
|
+
Parameters
|
|
426
|
+
----------
|
|
427
|
+
locale_var
|
|
428
|
+
The name of a render context variable that resolves to the current locale.
|
|
429
|
+
Defaults to ``"locale"``.
|
|
430
|
+
default_locale
|
|
431
|
+
A fallback locale to use if `locale_var` can not be resolved. Defaults to
|
|
432
|
+
``"en_US"``.
|
|
433
|
+
length_var
|
|
434
|
+
The name of a render context variable that resolves to a unit format length.
|
|
435
|
+
Should be one of ``"short"``, ``"long"`` or ``"narrow"``. Defaults to ``"long"``.
|
|
436
|
+
default_length
|
|
437
|
+
A fallback format length to use if `length_var` can not be resolved.
|
|
438
|
+
format_var
|
|
439
|
+
The name of a render context variable that resolves to a decimal format string.
|
|
440
|
+
Defaults to ``"unit_format"``.
|
|
441
|
+
default_format
|
|
442
|
+
A fallback decimal format to use if `format_var` can not be resolved. Defaults to
|
|
443
|
+
``None``, meaning the locale's standard decimal format is used.
|
|
444
|
+
input_locale_var
|
|
445
|
+
The name of a render context variable that resolves to a locale suitable for
|
|
446
|
+
parsing input strings to decimals. Defaults to ``"input_locale"``.
|
|
447
|
+
default_input_locale
|
|
448
|
+
A fallback locale to use if `input_locale_var` can not be resolved. Defaults to
|
|
449
|
+
``"en_US"``.
|
|
450
|
+
"""
|
|
451
|
+
|
|
452
|
+
def __init__(
|
|
453
|
+
self,
|
|
454
|
+
*,
|
|
455
|
+
locale_var: str = "locale",
|
|
456
|
+
default_locale: str = "en_US",
|
|
457
|
+
length_var: str = "unit_length",
|
|
458
|
+
default_length: str = "long",
|
|
459
|
+
format_var: str = "unit_format",
|
|
460
|
+
default_format: str | None = None,
|
|
461
|
+
input_locale_var: str = "input_locale",
|
|
462
|
+
default_input_locale: str = "en_US",
|
|
463
|
+
) -> None:
|
|
464
|
+
self.locale_var = locale_var
|
|
465
|
+
self.default_locale = Locale.parse(default_locale)
|
|
466
|
+
self.length_var = length_var
|
|
467
|
+
self.default_length = default_length
|
|
468
|
+
self.format_var = format_var
|
|
469
|
+
self.default_format = default_format
|
|
470
|
+
self.input_locale_var = input_locale_var
|
|
471
|
+
self.default_input_locale = Locale.parse(default_input_locale)
|
|
472
|
+
|
|
473
|
+
def __call__(
|
|
474
|
+
self,
|
|
475
|
+
context: FilterContext,
|
|
476
|
+
left: object,
|
|
477
|
+
measurement_unit: str,
|
|
478
|
+
*,
|
|
479
|
+
denominator: object = None,
|
|
480
|
+
denominator_unit: str | None = None,
|
|
481
|
+
length: str | None = None,
|
|
482
|
+
format: str | None = None,
|
|
483
|
+
) -> str:
|
|
484
|
+
try:
|
|
485
|
+
return self._inner(
|
|
486
|
+
context,
|
|
487
|
+
left,
|
|
488
|
+
measurement_unit,
|
|
489
|
+
denominator=denominator,
|
|
490
|
+
denominator_unit=denominator_unit,
|
|
491
|
+
length=length,
|
|
492
|
+
format=format,
|
|
493
|
+
)
|
|
494
|
+
except TypeError as err:
|
|
495
|
+
raise context.error(str(err)) from err
|
|
496
|
+
except units.UnknownUnitError as err:
|
|
497
|
+
raise context.error(str(err)) from err
|
|
498
|
+
|
|
499
|
+
def _inner(
|
|
500
|
+
self,
|
|
501
|
+
context: FilterContext,
|
|
502
|
+
left: object,
|
|
503
|
+
measurement_unit: str,
|
|
504
|
+
*,
|
|
505
|
+
denominator: object = None,
|
|
506
|
+
denominator_unit: str | None = None,
|
|
507
|
+
length: str | None = None,
|
|
508
|
+
format: str | None = None,
|
|
509
|
+
) -> str:
|
|
510
|
+
locale = _resolve_locale(
|
|
511
|
+
context,
|
|
512
|
+
self.locale_var,
|
|
513
|
+
default=self.default_locale,
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
input_locale = _resolve_locale(
|
|
517
|
+
context,
|
|
518
|
+
self.input_locale_var,
|
|
519
|
+
default=self.default_input_locale,
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
length_ = length if length else context.resolve(self.length_var)
|
|
523
|
+
|
|
524
|
+
if length_ not in ("short", "long", "narrow"):
|
|
525
|
+
length_ = self.default_length
|
|
526
|
+
|
|
527
|
+
assert isinstance(length_, str)
|
|
528
|
+
|
|
529
|
+
if format:
|
|
530
|
+
format_: str | None = format
|
|
531
|
+
else:
|
|
532
|
+
format_string = context.resolve(self.format_var)
|
|
533
|
+
if isinstance(format_string, str):
|
|
534
|
+
format_ = format_string
|
|
535
|
+
else:
|
|
536
|
+
format_ = self.default_format
|
|
537
|
+
|
|
538
|
+
if denominator is not None or denominator_unit is not None:
|
|
539
|
+
denominator_ = (
|
|
540
|
+
_parse_decimal(context, denominator, input_locale)
|
|
541
|
+
if denominator is not None
|
|
542
|
+
else 1
|
|
543
|
+
)
|
|
544
|
+
return units.format_compound_unit(
|
|
545
|
+
_parse_decimal(context, left, input_locale),
|
|
546
|
+
numerator_unit=measurement_unit,
|
|
547
|
+
denominator_value=denominator_, # type: ignore
|
|
548
|
+
denominator_unit=denominator_unit,
|
|
549
|
+
length=length_, # type: ignore
|
|
550
|
+
format=format_,
|
|
551
|
+
locale=locale,
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
return units.format_unit(
|
|
555
|
+
_parse_decimal(context, left, input_locale),
|
|
556
|
+
measurement_unit=measurement_unit,
|
|
557
|
+
length=length_, # type: ignore
|
|
558
|
+
format=format_,
|
|
559
|
+
locale=locale,
|
|
560
|
+
)
|
liquid/filters/base64.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import binascii
|
|
3
|
+
|
|
4
|
+
from .._filter import FilterContext
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def base64_encode(context: FilterContext, obj: object) -> str:
|
|
8
|
+
return base64.b64encode(context.to_str(obj, "").encode()).decode()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def base64_decode(context: FilterContext, obj: object) -> str:
|
|
12
|
+
try:
|
|
13
|
+
return base64.b64decode(context.to_str(obj, "")).decode()
|
|
14
|
+
except binascii.Error:
|
|
15
|
+
raise context.error("invalid base64-encoded string")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def base64_url_safe_encode(context: FilterContext, obj: object) -> str:
|
|
19
|
+
return base64.urlsafe_b64encode(context.to_str(obj, "").encode()).decode()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def base64_url_safe_decode(context: FilterContext, obj: object) -> str:
|
|
23
|
+
try:
|
|
24
|
+
return base64.urlsafe_b64decode(context.to_str(obj, "")).decode()
|
|
25
|
+
except binascii.Error:
|
|
26
|
+
raise context.error("invalid base64-encoded string")
|
liquid/filters/ceil.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from .._filter import FilterContext
|
|
2
|
+
from ..exceptions import LiquidFilterInterrupt
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def compact(
|
|
6
|
+
context: FilterContext, left: object, prop: object = None
|
|
7
|
+
) -> list[object] | None:
|
|
8
|
+
if prop is None:
|
|
9
|
+
return [item for item in context.input_array(left) if not context.is_nil(item)]
|
|
10
|
+
|
|
11
|
+
result: list[object] = []
|
|
12
|
+
|
|
13
|
+
for obj in context.input_array(left):
|
|
14
|
+
try:
|
|
15
|
+
val = context.get_item(obj, prop)
|
|
16
|
+
except LiquidFilterInterrupt:
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
if val == context.missing:
|
|
20
|
+
raise context.error(f"can't read property {prop!r}")
|
|
21
|
+
|
|
22
|
+
if not context.is_nil(val):
|
|
23
|
+
result.append(obj)
|
|
24
|
+
|
|
25
|
+
return result
|
liquid/filters/concat.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .._filter import FilterContext
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def concat(context: FilterContext, left: object, right: object) -> list[object]:
|
|
5
|
+
if not isinstance(right, (list, tuple)):
|
|
6
|
+
raise context.error("concat requires an array argument")
|
|
7
|
+
|
|
8
|
+
if context.is_nil(left):
|
|
9
|
+
return right # type: ignore
|
|
10
|
+
|
|
11
|
+
return context.input_array(left) + right # type: ignore
|
liquid/filters/date.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import markupsafe
|
|
2
|
+
|
|
3
|
+
from .._filter import FilterContext
|
|
4
|
+
from .._strftime import ruby_strftime
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def date(context: FilterContext, obj: object, fmt: str) -> object:
|
|
8
|
+
if context.is_nil(obj):
|
|
9
|
+
return ""
|
|
10
|
+
|
|
11
|
+
if context.is_nil(fmt) or fmt == "":
|
|
12
|
+
return context.to_str(obj, "")
|
|
13
|
+
|
|
14
|
+
date_ = context.to_date(obj)
|
|
15
|
+
|
|
16
|
+
if date_ is None:
|
|
17
|
+
return obj
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
result = ruby_strftime(date_, fmt)
|
|
21
|
+
except (ValueError, TypeError) as err:
|
|
22
|
+
raise context.error(str(err))
|
|
23
|
+
|
|
24
|
+
if context.env.autoescape and isinstance(fmt, markupsafe.Markup):
|
|
25
|
+
return markupsafe.Markup(result)
|
|
26
|
+
|
|
27
|
+
return result
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
from .._filter import FilterContext
|
|
4
|
+
from ..drops import EMPTY, FalsyStrictUndefined
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def default(
|
|
8
|
+
context: FilterContext,
|
|
9
|
+
left: object,
|
|
10
|
+
right: object = None,
|
|
11
|
+
allow_false: bool = False,
|
|
12
|
+
) -> object:
|
|
13
|
+
if isinstance(left, FalsyStrictUndefined):
|
|
14
|
+
return right
|
|
15
|
+
|
|
16
|
+
if not isinstance(left, bool) and isinstance(left, (int, float, Decimal)):
|
|
17
|
+
return left
|
|
18
|
+
|
|
19
|
+
if allow_false and left is False:
|
|
20
|
+
return left
|
|
21
|
+
|
|
22
|
+
if not context.truthy(left) or left == EMPTY:
|
|
23
|
+
return right
|
|
24
|
+
|
|
25
|
+
return left
|