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/builtin/filters/string.py
DELETED
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
"""Filter functions that operate on strings."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import base64
|
|
6
|
-
import binascii
|
|
7
|
-
import html
|
|
8
|
-
import re
|
|
9
|
-
import urllib.parse
|
|
10
|
-
from typing import TYPE_CHECKING
|
|
11
|
-
from typing import Any
|
|
12
|
-
from typing import Optional
|
|
13
|
-
from typing import Union
|
|
14
|
-
|
|
15
|
-
from liquid import Markup
|
|
16
|
-
from liquid import escape as markupsafe_escape
|
|
17
|
-
from liquid import soft_str
|
|
18
|
-
from liquid.exceptions import FilterArgumentError
|
|
19
|
-
from liquid.exceptions import FilterError
|
|
20
|
-
from liquid.filter import liquid_filter
|
|
21
|
-
from liquid.filter import string_filter
|
|
22
|
-
from liquid.filter import with_environment
|
|
23
|
-
from liquid.limits import to_int
|
|
24
|
-
from liquid.undefined import Undefined
|
|
25
|
-
from liquid.undefined import is_undefined
|
|
26
|
-
from liquid.utils.html import strip_tags
|
|
27
|
-
from liquid.utils.text import truncate_chars
|
|
28
|
-
|
|
29
|
-
if TYPE_CHECKING:
|
|
30
|
-
from liquid import Environment
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@string_filter
|
|
34
|
-
def append(val: str, arg: object) -> str:
|
|
35
|
-
"""Return a copy of _val_ concatenated with _arg_.
|
|
36
|
-
|
|
37
|
-
If _arg_ is not a string, it will be converted to one before concatenation.
|
|
38
|
-
"""
|
|
39
|
-
if not isinstance(arg, str):
|
|
40
|
-
arg = str(arg)
|
|
41
|
-
return val + arg
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@string_filter
|
|
45
|
-
def capitalize(val: str) -> str:
|
|
46
|
-
"""Return _val_ with the first character in uppercase and the rest lowercase."""
|
|
47
|
-
return val.capitalize()
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@string_filter
|
|
51
|
-
def downcase(val: str) -> str:
|
|
52
|
-
"""Return a copy of _val_ with all characters converted to lowercase."""
|
|
53
|
-
return val.lower()
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@with_environment
|
|
57
|
-
@string_filter
|
|
58
|
-
def escape(val: str, *, environment: Environment) -> str:
|
|
59
|
-
"""Escape special characters in a string for safe use in HTML.
|
|
60
|
-
|
|
61
|
-
This filter replaces the characters `&`, `<`, `>`, `'`, and `"` with their
|
|
62
|
-
corresponding HTML-safe sequences:
|
|
63
|
-
|
|
64
|
-
- `&` -> `&`
|
|
65
|
-
- `<` -> `<`
|
|
66
|
-
- `>` -> `>`
|
|
67
|
-
- `'` -> `'`
|
|
68
|
-
- `"` -> `"`
|
|
69
|
-
|
|
70
|
-
This helps prevent HTML injection (XSS) when rendering untrusted content in
|
|
71
|
-
HTML element bodies or attributes.
|
|
72
|
-
|
|
73
|
-
Important: This filter does **not** make strings safe for use in JavaScript,
|
|
74
|
-
including in `<script>` blocks, inline event handler attributes (e.g. `onerror`),
|
|
75
|
-
or other JavaScript contexts. For those cases, use the `escapejs` filter instead.
|
|
76
|
-
|
|
77
|
-
When `autoescape` is enabled in the environment, this filter uses the same
|
|
78
|
-
escaping logic as the environment (via `markupsafe.escape()`). Otherwise, it
|
|
79
|
-
falls back to Python's standard `html.escape()`.
|
|
80
|
-
|
|
81
|
-
Args:
|
|
82
|
-
val: The input string to escape.
|
|
83
|
-
environment: The current rendering environment.
|
|
84
|
-
|
|
85
|
-
Returns:
|
|
86
|
-
A string with HTML-special characters replaced by safe escape sequences.
|
|
87
|
-
"""
|
|
88
|
-
if environment.autoescape:
|
|
89
|
-
return markupsafe_escape(str(val))
|
|
90
|
-
return html.escape(val)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
@with_environment
|
|
94
|
-
@string_filter
|
|
95
|
-
def escape_once(val: str, *, environment: Environment) -> str:
|
|
96
|
-
"""Escape a string for HTML, but avoid double-escaping existing entities.
|
|
97
|
-
|
|
98
|
-
Converts characters like `&`, `<`, and `>` to their HTML-safe sequences,
|
|
99
|
-
but leaves existing HTML entities untouched (e.g., `&` stays `&`).
|
|
100
|
-
|
|
101
|
-
This is useful when escaping content that may already be partially escaped.
|
|
102
|
-
|
|
103
|
-
See the `escape` filter for details and limitations.
|
|
104
|
-
"""
|
|
105
|
-
if environment.autoescape:
|
|
106
|
-
return Markup(val).unescape()
|
|
107
|
-
return html.escape(html.unescape(val))
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
@string_filter
|
|
111
|
-
def lstrip(val: str) -> str:
|
|
112
|
-
"""Return a copy of _val_ with leading whitespace removed."""
|
|
113
|
-
return val.lstrip()
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
RE_LINETERM = re.compile(r"\r?\n")
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
@with_environment
|
|
120
|
-
@string_filter
|
|
121
|
-
def newline_to_br(val: str, *, environment: Environment) -> str:
|
|
122
|
-
"""Return a copy of _val_ with LF or CRLF converted to `<br />`, plus a newline."""
|
|
123
|
-
# The reference implementation was changed to replace "\r\n" as well as "\n",
|
|
124
|
-
# but they don't seem to care about "\r" (Mac OS).
|
|
125
|
-
if environment.autoescape:
|
|
126
|
-
val = markupsafe_escape(val)
|
|
127
|
-
return Markup(RE_LINETERM.sub("<br />\n", val))
|
|
128
|
-
return RE_LINETERM.sub("<br />\n", val)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
@string_filter
|
|
132
|
-
def prepend(val: str, arg: str) -> str:
|
|
133
|
-
"""Return a copy of _arg_ concatenated with _val_."""
|
|
134
|
-
return soft_str(arg) + val
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
@string_filter
|
|
138
|
-
def remove(val: str, arg: str) -> str:
|
|
139
|
-
"""Return a copy of _val_ with all occurrences of _arg_ removed."""
|
|
140
|
-
return val.replace(soft_str(arg), "")
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
@string_filter
|
|
144
|
-
def remove_first(val: str, arg: str) -> str:
|
|
145
|
-
"""Return a copy of _val_ with the first occurrence of _arg_ removed."""
|
|
146
|
-
return val.replace(soft_str(arg), "", 1)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
@string_filter
|
|
150
|
-
def remove_last(val: str, arg: str) -> str:
|
|
151
|
-
"""Return a copy of _val_ with last occurrence of _arg_ removed."""
|
|
152
|
-
try:
|
|
153
|
-
before, _, after = val.rpartition(soft_str(arg))
|
|
154
|
-
except ValueError:
|
|
155
|
-
# empty separator
|
|
156
|
-
return val
|
|
157
|
-
if before:
|
|
158
|
-
return before + after
|
|
159
|
-
return val
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
@string_filter
|
|
163
|
-
def replace(val: str, seq: str, sub: str = "") -> str:
|
|
164
|
-
"""Return a copy of _val_ with each occurrence of _seq_ replaced with _sub_."""
|
|
165
|
-
return val.replace(soft_str(seq), soft_str(sub))
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
@string_filter
|
|
169
|
-
def replace_first(val: str, seq: str, sub: str = "") -> str:
|
|
170
|
-
"""Return a copy of _val_ with the first occurrence of _seq_ replaced with _sub_."""
|
|
171
|
-
return val.replace(soft_str(seq), soft_str(sub), 1)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
@string_filter
|
|
175
|
-
def replace_last(val: str, seq: str, sub: str) -> str:
|
|
176
|
-
"""Return a copy of _val_ with the last occurrence of _seq_ replaced with _sub_."""
|
|
177
|
-
try:
|
|
178
|
-
before, _, after = val.rpartition(soft_str(seq))
|
|
179
|
-
except ValueError:
|
|
180
|
-
# empty separator
|
|
181
|
-
return val + soft_str(sub)
|
|
182
|
-
if before:
|
|
183
|
-
return before + soft_str(sub) + after
|
|
184
|
-
return val
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
@string_filter
|
|
188
|
-
def upcase(val: str) -> str:
|
|
189
|
-
"""Return a copy of _val_ with all characters converted to uppercase."""
|
|
190
|
-
return val.upper()
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
MAX_SLICE_ARG = (1 << 63) - 1
|
|
194
|
-
MIN_SLICE_ARG = -(1 << 63)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def _slice_arg(val: Any) -> int:
|
|
198
|
-
# The reference implementation does not cast floats to int.
|
|
199
|
-
if isinstance(val, float):
|
|
200
|
-
raise FilterArgumentError(
|
|
201
|
-
f"slice expected an integer start, found {type(val).__name__}", token=None
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
try:
|
|
205
|
-
rv = to_int(val)
|
|
206
|
-
except (ValueError, TypeError) as err:
|
|
207
|
-
raise FilterArgumentError(
|
|
208
|
-
f"slice expected an integer start, found {type(val).__name__}", token=None
|
|
209
|
-
) from err
|
|
210
|
-
|
|
211
|
-
rv = min(rv, MAX_SLICE_ARG)
|
|
212
|
-
return max(rv, MIN_SLICE_ARG)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
@liquid_filter
|
|
216
|
-
def slice_(val: Any, start: Any, length: Any = 1) -> Union[str, list[object]]:
|
|
217
|
-
"""Return the subsequence of _val_ starting at _start_ with up to _length_ chars.
|
|
218
|
-
|
|
219
|
-
Array-like objects return a list, strings return a substring, all other objects are
|
|
220
|
-
cast to a string before returning a substring.
|
|
221
|
-
"""
|
|
222
|
-
if not isinstance(val, (list, tuple, range, str)):
|
|
223
|
-
val = str(val)
|
|
224
|
-
|
|
225
|
-
if is_undefined(start):
|
|
226
|
-
raise FilterArgumentError(
|
|
227
|
-
"slice expected an integer, found Undefined", token=None
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
if is_undefined(length):
|
|
231
|
-
length = 1
|
|
232
|
-
|
|
233
|
-
_start = _slice_arg(start)
|
|
234
|
-
_length = _slice_arg(length)
|
|
235
|
-
end: Optional[int] = _start + _length
|
|
236
|
-
|
|
237
|
-
# A negative start index and a length that exceeds the theoretical length
|
|
238
|
-
# of the sequence.
|
|
239
|
-
if isinstance(end, int) and _start < 0 <= end:
|
|
240
|
-
end = None
|
|
241
|
-
|
|
242
|
-
if isinstance(val, str):
|
|
243
|
-
return val[_start:end]
|
|
244
|
-
return list(val[_start:end])
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
@string_filter
|
|
248
|
-
def split(val: str, sep: Any) -> list[str]:
|
|
249
|
-
"""Split string _val_ on delimiter _sep_.
|
|
250
|
-
|
|
251
|
-
If _sep_ is empty or _undefined_, _val_ is split into a list of single
|
|
252
|
-
characters. If _val_ is empty or equal to _sep_, an empty list is returned.
|
|
253
|
-
"""
|
|
254
|
-
if isinstance(sep, Undefined):
|
|
255
|
-
sep.poke()
|
|
256
|
-
return list(val)
|
|
257
|
-
|
|
258
|
-
if sep is None or sep == "":
|
|
259
|
-
return list(val)
|
|
260
|
-
|
|
261
|
-
sep = soft_str(sep)
|
|
262
|
-
|
|
263
|
-
if not val or val == sep:
|
|
264
|
-
return []
|
|
265
|
-
|
|
266
|
-
return val.split(None if sep == " " else sep)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
@string_filter
|
|
270
|
-
def strip(val: str) -> str:
|
|
271
|
-
"""Return a copy of _val_ with leading and trailing whitespace removed."""
|
|
272
|
-
return val.strip()
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
@string_filter
|
|
276
|
-
def rstrip(val: str) -> str:
|
|
277
|
-
"""Return a copy of _val_ with trailing whitespace removed."""
|
|
278
|
-
return val.rstrip()
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
@with_environment
|
|
282
|
-
@string_filter
|
|
283
|
-
def strip_html(val: str, *, environment: Environment) -> str:
|
|
284
|
-
"""Return a copy of _val_ with all HTML tags removed."""
|
|
285
|
-
stripped = strip_tags(val)
|
|
286
|
-
if environment.autoescape and isinstance(val, Markup):
|
|
287
|
-
return Markup(stripped)
|
|
288
|
-
return stripped
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
@with_environment
|
|
292
|
-
@string_filter
|
|
293
|
-
def strip_newlines(val: str, *, environment: Environment) -> str:
|
|
294
|
-
"""Return ta copy of _val_ with all newline characters removed."""
|
|
295
|
-
if environment.autoescape:
|
|
296
|
-
val = markupsafe_escape(val)
|
|
297
|
-
return Markup(RE_LINETERM.sub("", val))
|
|
298
|
-
return RE_LINETERM.sub("", val)
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
@string_filter
|
|
302
|
-
def truncate(val: str, num: Any = 50, end: str = "...") -> str:
|
|
303
|
-
"""Return a copy of _val_ truncated to _num_ characters."""
|
|
304
|
-
if is_undefined(num):
|
|
305
|
-
raise FilterArgumentError(
|
|
306
|
-
"truncate expected an integer, found Undefined", token=None
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
try:
|
|
310
|
-
num = to_int(num)
|
|
311
|
-
except ValueError as err:
|
|
312
|
-
raise FilterArgumentError(
|
|
313
|
-
f"truncate expected an integer, found {type(num).__name__}", token=None
|
|
314
|
-
) from err
|
|
315
|
-
|
|
316
|
-
end = str(end)
|
|
317
|
-
return truncate_chars(val, num, end)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
# Mimic reference implementation's limit to the number of words that can be truncated.
|
|
321
|
-
MAX_TRUNC_WORDS = (1 << 31) - 1
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
@string_filter
|
|
325
|
-
def truncatewords(val: str, num: Any = 15, end: str = "...") -> str:
|
|
326
|
-
"""Return a copy of _val_ truncated to at most _num_ words."""
|
|
327
|
-
if is_undefined(num):
|
|
328
|
-
raise FilterArgumentError(
|
|
329
|
-
"truncate expected an integer, found Undefined", token=None
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
try:
|
|
333
|
-
num = to_int(num)
|
|
334
|
-
except ValueError as err:
|
|
335
|
-
raise FilterArgumentError(
|
|
336
|
-
f"truncate expected an integer, found {type(num).__name__}", token=None
|
|
337
|
-
) from err
|
|
338
|
-
|
|
339
|
-
end = str(end)
|
|
340
|
-
|
|
341
|
-
# The reference implementation seems to force a minimum `num` of 1.
|
|
342
|
-
if num <= 0:
|
|
343
|
-
num = 1
|
|
344
|
-
|
|
345
|
-
# Replaces consecutive whitespace with a single newline.
|
|
346
|
-
words = val.split()
|
|
347
|
-
|
|
348
|
-
# This too mimics the reference implementation's big integer work around.
|
|
349
|
-
if num >= MAX_TRUNC_WORDS:
|
|
350
|
-
return val
|
|
351
|
-
|
|
352
|
-
if len(words) < num:
|
|
353
|
-
return " ".join(words)
|
|
354
|
-
|
|
355
|
-
return " ".join(words[:num]) + end
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
@with_environment
|
|
359
|
-
@string_filter
|
|
360
|
-
def url_encode(val: str, *, environment: Environment) -> str:
|
|
361
|
-
"""Return a percent-encoded copy of _val_ so it is useable in a URL."""
|
|
362
|
-
if environment.autoescape:
|
|
363
|
-
return Markup(urllib.parse.quote_plus(val))
|
|
364
|
-
return urllib.parse.quote_plus(val)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
@string_filter
|
|
368
|
-
def url_decode(val: str) -> str:
|
|
369
|
-
"""Return a copy of _val_ after decoding percent-encoded sequences."""
|
|
370
|
-
# Assuming URL decoded strings are all unsafe.
|
|
371
|
-
return urllib.parse.unquote_plus(val)
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
@string_filter
|
|
375
|
-
def base64_encode(val: str) -> str:
|
|
376
|
-
"""Return _val_ encoded in base64."""
|
|
377
|
-
return base64.b64encode(val.encode()).decode()
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
@string_filter
|
|
381
|
-
def base64_decode(val: str) -> str:
|
|
382
|
-
"""Return _val_ decoded as base64.
|
|
383
|
-
|
|
384
|
-
The decoded value is assumed to be UTF-8 and will be decoded as UTF-8.
|
|
385
|
-
"""
|
|
386
|
-
try:
|
|
387
|
-
return base64.b64decode(val).decode()
|
|
388
|
-
except binascii.Error as err:
|
|
389
|
-
raise FilterError("invalid base64-encoded string", token=None) from err
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
@string_filter
|
|
393
|
-
def base64_url_safe_encode(val: str) -> str:
|
|
394
|
-
"""Return _val_ encoded in URL-safe base64."""
|
|
395
|
-
return base64.urlsafe_b64encode(val.encode()).decode()
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
@string_filter
|
|
399
|
-
def base64_url_safe_decode(val: str) -> str:
|
|
400
|
-
"""Return _val_ decoded as URL-safe base64.
|
|
401
|
-
|
|
402
|
-
The decoded value is assumed to be UTF-8 and will be decoded as UTF-8.
|
|
403
|
-
"""
|
|
404
|
-
try:
|
|
405
|
-
return base64.urlsafe_b64decode(val).decode()
|
|
406
|
-
except binascii.Error as err:
|
|
407
|
-
raise FilterError("invalid base64-encoded string", token=None) from err
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
RE_WHITESPACE = re.compile(r"\s+")
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
@string_filter
|
|
414
|
-
def squish(val: str) -> str:
|
|
415
|
-
"""Strip whitespace and replace whitespace with a single space."""
|
|
416
|
-
return RE_WHITESPACE.sub(" ", val.strip())
|
liquid/builtin/illegal.py
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
"""Tag definition for illegal or unregistered tags."""
|
|
2
|
-
|
|
3
|
-
from liquid.ast import IllegalNode
|
|
4
|
-
from liquid.exceptions import LiquidSyntaxError
|
|
5
|
-
from liquid.stream import TokenStream
|
|
6
|
-
from liquid.tag import Tag
|
|
7
|
-
from liquid.token import TOKEN_EXPRESSION
|
|
8
|
-
from liquid.token import TOKEN_ILLEGAL
|
|
9
|
-
from liquid.token import TOKEN_TAG
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Illegal(Tag):
|
|
13
|
-
"""Tag definition for illegal or unregistered tags."""
|
|
14
|
-
|
|
15
|
-
name = TOKEN_ILLEGAL
|
|
16
|
-
block = False
|
|
17
|
-
|
|
18
|
-
def parse(self, stream: TokenStream) -> IllegalNode:
|
|
19
|
-
"""Parse tokens from _stream_ into an AST node."""
|
|
20
|
-
token = stream.expect(TOKEN_TAG)
|
|
21
|
-
|
|
22
|
-
if stream.peek.kind == TOKEN_EXPRESSION:
|
|
23
|
-
next(stream)
|
|
24
|
-
|
|
25
|
-
msg = (
|
|
26
|
-
"missing tag name" if not token.value else f"unexpected tag '{token.value}'"
|
|
27
|
-
)
|
|
28
|
-
raise LiquidSyntaxError(msg, token=token)
|
|
File without changes
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"""A file system loader that caches parsed templates in memory."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
|
-
from typing import Iterable
|
|
7
|
-
from typing import Optional
|
|
8
|
-
from typing import Union
|
|
9
|
-
|
|
10
|
-
from .file_system_loader import FileSystemLoader
|
|
11
|
-
from .mixins import CachingLoaderMixin
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class CachingFileSystemLoader(CachingLoaderMixin, FileSystemLoader):
|
|
18
|
-
"""A file system loader that caches parsed templates in memory.
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
search_path: One or more paths to search.
|
|
22
|
-
encoding: Open template files with the given encoding.
|
|
23
|
-
ext: A default file extension. Should include a leading period.
|
|
24
|
-
reject_symlinks: When `True`, reject paths to symlinks that resolve to files
|
|
25
|
-
outside the search path. Defaults to `False`.
|
|
26
|
-
auto_reload: If `True`, automatically reload a cached template if it has been
|
|
27
|
-
updated.
|
|
28
|
-
namespace_key: The name of a global render context variable or loader keyword
|
|
29
|
-
argument that resolves to the current loader "namespace" or "scope".
|
|
30
|
-
|
|
31
|
-
If you're developing a multi-user application, a good namespace might be
|
|
32
|
-
`uid`, where `uid` is a unique identifier for a user and templates are
|
|
33
|
-
arranged in folders named for each `uid` inside the search path.
|
|
34
|
-
capacity: The maximum number of templates to hold in the cache before removing
|
|
35
|
-
the least recently used template.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
def __init__(
|
|
39
|
-
self,
|
|
40
|
-
search_path: Union[str, Path, Iterable[Union[str, Path]]],
|
|
41
|
-
encoding: str = "utf-8",
|
|
42
|
-
ext: Optional[str] = None,
|
|
43
|
-
*,
|
|
44
|
-
reject_symlinks: bool = False,
|
|
45
|
-
auto_reload: bool = True,
|
|
46
|
-
namespace_key: str = "",
|
|
47
|
-
capacity: int = 300,
|
|
48
|
-
):
|
|
49
|
-
super().__init__(
|
|
50
|
-
auto_reload=auto_reload,
|
|
51
|
-
namespace_key=namespace_key,
|
|
52
|
-
capacity=capacity,
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
FileSystemLoader.__init__(
|
|
56
|
-
self,
|
|
57
|
-
search_path=search_path,
|
|
58
|
-
encoding=encoding,
|
|
59
|
-
ext=ext,
|
|
60
|
-
reject_symlinks=reject_symlinks,
|
|
61
|
-
)
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
"""A template loader that delegates to other template loaders."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
|
-
from typing import Optional
|
|
7
|
-
|
|
8
|
-
from liquid.exceptions import TemplateNotFoundError
|
|
9
|
-
from liquid.loader import BaseLoader
|
|
10
|
-
from liquid.loader import TemplateSource
|
|
11
|
-
|
|
12
|
-
from .mixins import CachingLoaderMixin
|
|
13
|
-
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from liquid import Environment
|
|
16
|
-
from liquid import RenderContext
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ChoiceLoader(BaseLoader):
|
|
20
|
-
"""A template loader that delegates to other template loaders.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
loaders: A list of loaders implementing `liquid.loaders.BaseLoader`.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
def __init__(self, loaders: list[BaseLoader]):
|
|
27
|
-
super().__init__()
|
|
28
|
-
self.loaders = loaders
|
|
29
|
-
|
|
30
|
-
def get_source(
|
|
31
|
-
self,
|
|
32
|
-
env: Environment,
|
|
33
|
-
template_name: str,
|
|
34
|
-
*,
|
|
35
|
-
context: Optional[RenderContext] = None,
|
|
36
|
-
**kwargs: object,
|
|
37
|
-
) -> TemplateSource:
|
|
38
|
-
"""Get source information for a template."""
|
|
39
|
-
for loader in self.loaders:
|
|
40
|
-
try:
|
|
41
|
-
return loader.get_source(env, template_name, context=context, **kwargs)
|
|
42
|
-
except TemplateNotFoundError:
|
|
43
|
-
pass
|
|
44
|
-
|
|
45
|
-
raise TemplateNotFoundError(template_name)
|
|
46
|
-
|
|
47
|
-
async def get_source_async(
|
|
48
|
-
self,
|
|
49
|
-
env: Environment,
|
|
50
|
-
template_name: str,
|
|
51
|
-
*,
|
|
52
|
-
context: Optional[RenderContext] = None,
|
|
53
|
-
**kwargs: object,
|
|
54
|
-
) -> TemplateSource:
|
|
55
|
-
"""Get source information for a template."""
|
|
56
|
-
for loader in self.loaders:
|
|
57
|
-
try:
|
|
58
|
-
return await loader.get_source_async(
|
|
59
|
-
env, template_name, context=context, **kwargs
|
|
60
|
-
)
|
|
61
|
-
except TemplateNotFoundError:
|
|
62
|
-
pass
|
|
63
|
-
|
|
64
|
-
raise TemplateNotFoundError(template_name)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class CachingChoiceLoader(CachingLoaderMixin, ChoiceLoader):
|
|
68
|
-
"""A `ChoiceLoader` that caches parsed templates in memory.
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
loaders: A list of loaders implementing `liquid.loaders.BaseLoader`.
|
|
72
|
-
auto_reload: If `True`, automatically reload a cached template if it has been
|
|
73
|
-
updated.
|
|
74
|
-
namespace_key: The name of a global render context variable or loader keyword
|
|
75
|
-
argument that resolves to the current loader "namespace" or "scope".
|
|
76
|
-
capacity: The maximum number of templates to hold in the cache before removing
|
|
77
|
-
the least recently used template.
|
|
78
|
-
"""
|
|
79
|
-
|
|
80
|
-
def __init__(
|
|
81
|
-
self,
|
|
82
|
-
loaders: list[BaseLoader],
|
|
83
|
-
*,
|
|
84
|
-
auto_reload: bool = True,
|
|
85
|
-
namespace_key: str = "",
|
|
86
|
-
capacity: int = 300,
|
|
87
|
-
):
|
|
88
|
-
super().__init__(
|
|
89
|
-
auto_reload=auto_reload,
|
|
90
|
-
namespace_key=namespace_key,
|
|
91
|
-
capacity=capacity,
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
ChoiceLoader.__init__(self, loaders)
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
"""A template loader that loads templates from a dictionary of strings."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
|
-
from typing import Optional
|
|
7
|
-
|
|
8
|
-
from liquid.exceptions import TemplateNotFoundError
|
|
9
|
-
from liquid.loader import BaseLoader
|
|
10
|
-
from liquid.loader import TemplateSource
|
|
11
|
-
|
|
12
|
-
from .mixins import CachingLoaderMixin
|
|
13
|
-
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from liquid import Environment
|
|
16
|
-
from liquid.context import RenderContext
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class DictLoader(BaseLoader):
|
|
20
|
-
"""A loader that loads templates from a dictionary.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
templates: A dictionary mapping template names to template source strings.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
def __init__(self, templates: dict[str, str]):
|
|
27
|
-
super().__init__()
|
|
28
|
-
self.templates = templates
|
|
29
|
-
|
|
30
|
-
def get_source(
|
|
31
|
-
self,
|
|
32
|
-
env: Environment, # noqa: ARG002
|
|
33
|
-
template_name: str,
|
|
34
|
-
*,
|
|
35
|
-
context: Optional[RenderContext] = None, # noqa: ARG002
|
|
36
|
-
**kwargs: object, # noqa: ARG002
|
|
37
|
-
) -> TemplateSource:
|
|
38
|
-
"""Get the source, filename and reload helper for a template."""
|
|
39
|
-
try:
|
|
40
|
-
source = self.templates[template_name]
|
|
41
|
-
except KeyError as err:
|
|
42
|
-
raise TemplateNotFoundError(template_name) from err
|
|
43
|
-
|
|
44
|
-
return TemplateSource(source, template_name, None)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class CachingDictLoader(CachingLoaderMixin, DictLoader):
|
|
48
|
-
"""A `DictLoader` that caches parsed templates in memory."""
|
|
49
|
-
|
|
50
|
-
def __init__(
|
|
51
|
-
self,
|
|
52
|
-
templates: dict[str, str],
|
|
53
|
-
*,
|
|
54
|
-
auto_reload: bool = True,
|
|
55
|
-
namespace_key: str = "",
|
|
56
|
-
capacity: int = 300,
|
|
57
|
-
):
|
|
58
|
-
super().__init__(
|
|
59
|
-
auto_reload=auto_reload,
|
|
60
|
-
namespace_key=namespace_key,
|
|
61
|
-
capacity=capacity,
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
DictLoader.__init__(self, templates)
|