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.
Files changed (212) hide show
  1. liquid/__about__.py +1 -0
  2. liquid/__init__.py +161 -187
  3. liquid/_base_tag.py +66 -0
  4. liquid/{utils/chain_map.py → _chain_map.py} +18 -19
  5. liquid/_context.py +465 -0
  6. liquid/_drop.py +108 -0
  7. liquid/_filter.py +174 -0
  8. liquid/_io.py +18 -0
  9. liquid/_lax_parser_mixin.py +100 -0
  10. liquid/_lexer.py +93 -0
  11. liquid/_lexer_extended.py +623 -0
  12. liquid/_lexer_legacy.py +536 -0
  13. liquid/_loader.py +84 -0
  14. liquid/{utils/lru_cache.py → _lru_cache.py} +28 -36
  15. liquid/_markup.py +253 -0
  16. liquid/_messages.py +292 -0
  17. liquid/_missing.py +3 -0
  18. liquid/_nothing.py +28 -0
  19. liquid/_parser.py +460 -0
  20. liquid/_parser_extended.py +293 -0
  21. liquid/_parser_legacy.py +819 -0
  22. liquid/_resource_limits.py +50 -0
  23. liquid/_slice.py +41 -0
  24. liquid/_static_analysis.py +503 -0
  25. liquid/_strftime.py +186 -0
  26. liquid/_template.py +536 -0
  27. liquid/drops/__init__.py +20 -0
  28. liquid/drops/forloop.py +78 -0
  29. liquid/drops/iterable.py +25 -0
  30. liquid/drops/range.py +76 -0
  31. liquid/drops/self_.py +45 -0
  32. liquid/drops/tablerowloop.py +142 -0
  33. liquid/drops/undefined.py +228 -0
  34. liquid/environment.py +717 -551
  35. liquid/exceptions.py +67 -214
  36. liquid/expressions.py +1184 -0
  37. liquid/filters/__init__.py +134 -0
  38. liquid/filters/abs.py +7 -0
  39. liquid/filters/append.py +5 -0
  40. liquid/filters/at_least.py +9 -0
  41. liquid/filters/at_most.py +9 -0
  42. liquid/filters/babel.py +560 -0
  43. liquid/filters/base64.py +26 -0
  44. liquid/filters/capitalize.py +5 -0
  45. liquid/filters/ceil.py +8 -0
  46. liquid/filters/compact.py +25 -0
  47. liquid/filters/concat.py +11 -0
  48. liquid/filters/date.py +27 -0
  49. liquid/filters/default.py +25 -0
  50. liquid/filters/divided_by.py +21 -0
  51. liquid/filters/downcase.py +5 -0
  52. liquid/filters/escape.py +56 -0
  53. liquid/filters/find.py +56 -0
  54. liquid/filters/first.py +19 -0
  55. liquid/filters/floor.py +8 -0
  56. liquid/filters/html.py +23 -0
  57. liquid/filters/index.py +12 -0
  58. liquid/filters/join.py +13 -0
  59. liquid/filters/json_.py +30 -0
  60. liquid/filters/last.py +17 -0
  61. liquid/filters/map.py +5 -0
  62. liquid/filters/minus.py +13 -0
  63. liquid/filters/modulo.py +18 -0
  64. liquid/filters/newline_to_br.py +16 -0
  65. liquid/filters/plus.py +13 -0
  66. liquid/filters/prepend.py +5 -0
  67. liquid/filters/reject.py +19 -0
  68. liquid/filters/remove.py +36 -0
  69. liquid/filters/replace.py +32 -0
  70. liquid/filters/reverse.py +5 -0
  71. liquid/filters/round.py +35 -0
  72. liquid/filters/size.py +25 -0
  73. liquid/filters/slice.py +39 -0
  74. liquid/filters/sort.py +148 -0
  75. liquid/filters/split.py +34 -0
  76. liquid/filters/squish.py +9 -0
  77. liquid/filters/strip.py +13 -0
  78. liquid/filters/strip_html.py +19 -0
  79. liquid/filters/strip_newlines.py +17 -0
  80. liquid/filters/sum.py +21 -0
  81. liquid/filters/times.py +13 -0
  82. liquid/filters/translate.py +380 -0
  83. liquid/filters/truncate.py +39 -0
  84. liquid/filters/uniq.py +11 -0
  85. liquid/filters/upcase.py +5 -0
  86. liquid/filters/url.py +18 -0
  87. liquid/filters/where.py +18 -0
  88. liquid/loaders/__init__.py +17 -0
  89. liquid/loaders/caching_file_system_loader.py +64 -0
  90. liquid/loaders/choice_loader.py +93 -0
  91. liquid/loaders/dict_loader.py +56 -0
  92. liquid/{builtin/loaders → loaders}/file_system_loader.py +51 -55
  93. liquid/{builtin/loaders → loaders}/mixins.py +39 -51
  94. liquid/loaders/package_loader.py +110 -0
  95. liquid/tags/__init__.py +61 -0
  96. liquid/tags/assign.py +68 -0
  97. liquid/tags/capture.py +83 -0
  98. liquid/tags/case.py +199 -0
  99. liquid/tags/comment.py +56 -0
  100. liquid/tags/cycle.py +131 -0
  101. liquid/tags/decrement.py +55 -0
  102. liquid/tags/doc.py +56 -0
  103. liquid/tags/echo.py +65 -0
  104. liquid/tags/else_.py +53 -0
  105. liquid/tags/error.py +51 -0
  106. liquid/tags/extends.py +448 -0
  107. liquid/tags/for_.py +421 -0
  108. liquid/tags/if_.py +156 -0
  109. liquid/tags/ifchanged.py +69 -0
  110. liquid/tags/include.py +223 -0
  111. liquid/tags/increment.py +55 -0
  112. liquid/tags/inline_comment.py +70 -0
  113. liquid/tags/liquid.py +56 -0
  114. liquid/tags/macro.py +281 -0
  115. liquid/tags/output.py +58 -0
  116. liquid/tags/raw.py +60 -0
  117. liquid/tags/render.py +245 -0
  118. liquid/tags/tablerow.py +254 -0
  119. liquid/tags/template_comment.py +56 -0
  120. liquid/tags/translate.py +316 -0
  121. liquid/tags/unless.py +121 -0
  122. liquid/tags/with_.py +83 -0
  123. liquid/tokens.py +370 -0
  124. {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/METADATA +32 -39
  125. python_liquid-3.0.0.dist-info/RECORD +128 -0
  126. {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/WHEEL +1 -1
  127. {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/licenses/LICENSE +1 -1
  128. liquid/analyze_tags.py +0 -214
  129. liquid/ast.py +0 -252
  130. liquid/builtin/__init__.py +0 -203
  131. liquid/builtin/content.py +0 -41
  132. liquid/builtin/drops/__init__.py +0 -3
  133. liquid/builtin/drops/drops.py +0 -13
  134. liquid/builtin/expressions/__init__.py +0 -47
  135. liquid/builtin/expressions/_tokenize.py +0 -186
  136. liquid/builtin/expressions/arguments.py +0 -207
  137. liquid/builtin/expressions/filtered.py +0 -387
  138. liquid/builtin/expressions/logical.py +0 -672
  139. liquid/builtin/expressions/loop.py +0 -275
  140. liquid/builtin/expressions/path.py +0 -163
  141. liquid/builtin/expressions/primitive.py +0 -417
  142. liquid/builtin/filters/__init__.py +0 -0
  143. liquid/builtin/filters/array.py +0 -329
  144. liquid/builtin/filters/extra.py +0 -83
  145. liquid/builtin/filters/math.py +0 -129
  146. liquid/builtin/filters/misc.py +0 -137
  147. liquid/builtin/filters/string.py +0 -416
  148. liquid/builtin/illegal.py +0 -28
  149. liquid/builtin/loaders/__init__.py +0 -0
  150. liquid/builtin/loaders/caching_file_system_loader.py +0 -61
  151. liquid/builtin/loaders/choice_loader.py +0 -94
  152. liquid/builtin/loaders/dict_loader.py +0 -64
  153. liquid/builtin/loaders/package_loader.py +0 -132
  154. liquid/builtin/output.py +0 -74
  155. liquid/builtin/tags/__init__.py +0 -0
  156. liquid/builtin/tags/assign_tag.py +0 -75
  157. liquid/builtin/tags/capture_tag.py +0 -98
  158. liquid/builtin/tags/case_tag.py +0 -287
  159. liquid/builtin/tags/comment_tag.py +0 -77
  160. liquid/builtin/tags/cycle_tag.py +0 -132
  161. liquid/builtin/tags/decrement_tag.py +0 -59
  162. liquid/builtin/tags/doc_tag.py +0 -68
  163. liquid/builtin/tags/echo_tag.py +0 -45
  164. liquid/builtin/tags/for_tag.py +0 -337
  165. liquid/builtin/tags/if_tag.py +0 -209
  166. liquid/builtin/tags/ifchanged_tag.py +0 -94
  167. liquid/builtin/tags/include_tag.py +0 -251
  168. liquid/builtin/tags/increment_tag.py +0 -60
  169. liquid/builtin/tags/inline_comment_tag.py +0 -47
  170. liquid/builtin/tags/liquid_tag.py +0 -183
  171. liquid/builtin/tags/render_tag.py +0 -371
  172. liquid/builtin/tags/tablerow_tag.py +0 -320
  173. liquid/builtin/tags/unless_tag.py +0 -218
  174. liquid/context.py +0 -615
  175. liquid/expression.py +0 -41
  176. liquid/extra/__init__.py +0 -98
  177. liquid/extra/filters/__init__.py +0 -33
  178. liquid/extra/filters/_json.py +0 -33
  179. liquid/extra/filters/array.py +0 -60
  180. liquid/extra/filters/babel.py +0 -552
  181. liquid/extra/filters/html.py +0 -32
  182. liquid/extra/filters/translate.py +0 -448
  183. liquid/extra/tags/__init__.py +0 -17
  184. liquid/extra/tags/_with.py +0 -80
  185. liquid/extra/tags/extends_tag.py +0 -576
  186. liquid/extra/tags/macro_tag.py +0 -270
  187. liquid/extra/tags/snippet_tag.py +0 -107
  188. liquid/extra/tags/translate_tag.py +0 -388
  189. liquid/filter.py +0 -230
  190. liquid/future/__init__.py +0 -3
  191. liquid/future/environment.py +0 -30
  192. liquid/future/filters/__init__.py +0 -1
  193. liquid/future/tags/__init__.py +0 -1
  194. liquid/lex.py +0 -260
  195. liquid/limits.py +0 -66
  196. liquid/loader.py +0 -141
  197. liquid/messages.py +0 -357
  198. liquid/mode.py +0 -12
  199. liquid/output.py +0 -35
  200. liquid/parser.py +0 -119
  201. liquid/span.py +0 -35
  202. liquid/static_analysis.py +0 -443
  203. liquid/stream.py +0 -148
  204. liquid/stringify.py +0 -31
  205. liquid/tag.py +0 -47
  206. liquid/template.py +0 -658
  207. liquid/token.py +0 -145
  208. liquid/undefined.py +0 -218
  209. liquid/utils/__init__.py +0 -15
  210. liquid/utils/html.py +0 -57
  211. liquid/utils/text.py +0 -28
  212. python_liquid-2.3.6.dist-info/RECORD +0 -96
@@ -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
- - `&` -> `&amp;`
65
- - `<` -> `&lt;`
66
- - `>` -> `&gt;`
67
- - `'` -> `&#39;`
68
- - `"` -> `&#34;`
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., `&amp;` stays `&amp;`).
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)