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
liquid/static_analysis.py DELETED
@@ -1,443 +0,0 @@
1
- """Analyze variable, tag and filter usage by traversing a template's syntax tree."""
2
-
3
- from __future__ import annotations
4
-
5
- import re
6
- from collections import defaultdict
7
- from dataclasses import dataclass
8
- from dataclasses import field
9
- from typing import TYPE_CHECKING
10
- from typing import Iterable
11
- from typing import Optional
12
- from typing import Union
13
-
14
- from .ast import BlockNode
15
- from .ast import ConditionalBlockNode
16
- from .ast import PartialScope
17
- from .builtin.expressions import FilteredExpression
18
- from .builtin.expressions import Path
19
- from .builtin.expressions import TernaryFilteredExpression
20
- from .builtin.tags.case_tag import MultiExpressionBlockNode
21
- from .context import RenderContext
22
- from .span import Span
23
- from .token import TOKEN_TAG
24
-
25
- if TYPE_CHECKING:
26
- from .ast import Node
27
- from .expression import Expression
28
- from .template import BoundTemplate
29
-
30
-
31
- RE_PROPERTY = re.compile(r"[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*")
32
- Segments = list[Union[int, str, "Segments"]]
33
-
34
-
35
- @dataclass(frozen=True)
36
- class Variable:
37
- """A variable as sequence of segments that make up its path and its location.
38
-
39
- Variables with the same segments compare equal, regardless of span.
40
- """
41
-
42
- segments: Segments
43
- """The variable's segments."""
44
-
45
- span: Span = field(hash=False, compare=False)
46
- """The variable's location."""
47
-
48
- def __str__(self) -> str:
49
- return self._segments_str(self.segments)
50
-
51
- def _segments_str(self, segments: Segments) -> str:
52
- it = iter(segments)
53
- buf = [str(next(it))]
54
-
55
- for segment in it:
56
- if isinstance(segment, list):
57
- buf.append(f"[{self._segments_str(segment)}]")
58
- elif isinstance(segment, str):
59
- if RE_PROPERTY.fullmatch(segment):
60
- buf.append(f".{segment}")
61
- else:
62
- buf.append(f"[{segment!r}]")
63
- else:
64
- buf.append(f"[{segment}]")
65
- return "".join(buf)
66
-
67
- def __hash__(self) -> int:
68
- return hash(str(self))
69
-
70
-
71
- class _StaticScope:
72
- def __init__(self, globals: set[str]) -> None: # noqa: A002
73
- self.stack = [globals]
74
-
75
- def __contains__(self, key: str) -> bool:
76
- return any(key in scope for scope in self.stack)
77
-
78
- def push(self, scope: set[str]) -> _StaticScope:
79
- self.stack.append(scope)
80
- return self
81
-
82
- def pop(self) -> set[str]:
83
- return self.stack.pop()
84
-
85
- def add(self, name: str) -> None:
86
- """Add a name to the root/template scope."""
87
- self.stack[0].add(name)
88
-
89
-
90
- class _VariableMap:
91
- def __init__(self) -> None:
92
- self._data: dict[str, list[Variable]] = {}
93
-
94
- def __getitem__(self, var: Variable) -> list[Variable]:
95
- k = str(var.segments[0])
96
- if k not in self._data:
97
- self._data[k] = []
98
- return self._data[k]
99
-
100
- def add(self, var: Variable) -> None:
101
- self[var].append(var)
102
-
103
- def as_dict(self) -> dict[str, list[Variable]]:
104
- return self._data
105
-
106
-
107
- @dataclass(frozen=True)
108
- class TemplateAnalysis:
109
- """The result of analyzing a template using `Template.analyze()`.
110
-
111
- Args:
112
- variables: All referenced variables, whether they are in scope or not.
113
- Including references to names such as `forloop` from the `for` tag.
114
- locals: Template variables that are added to the template local
115
- scope, whether they are subsequently used or not.
116
- globals: Template variables that, on the given line number and
117
- "file", are out of scope or are assumed to be "global". That is, expected to
118
- be included by the application developer rather than a template author.
119
- filters: All filters found during static analysis.
120
- tags: All tags found during static analysis.
121
- """
122
-
123
- variables: dict[str, list[Variable]]
124
- globals: dict[str, list[Variable]]
125
- locals: dict[str, list[Variable]]
126
- filters: dict[str, list[Span]]
127
- tags: dict[str, list[Span]]
128
-
129
-
130
- def analyze(template: BoundTemplate, *, include_partials: bool) -> TemplateAnalysis:
131
- """Report variable, filters and tags used in the given template.
132
-
133
- This is used by `BoundTemplate.analyze`.
134
- """
135
- variables = _VariableMap()
136
- globals = _VariableMap() # noqa: A001
137
- locals = _VariableMap() # noqa: A001
138
-
139
- filters: defaultdict[str, list[Span]] = defaultdict(list)
140
- tags: defaultdict[str, list[Span]] = defaultdict(list)
141
-
142
- template_scope: set[str] = set()
143
- root_scope = _StaticScope(template_scope)
144
- static_context = RenderContext(template)
145
-
146
- # Names of partial templates that have already been analyzed.
147
- # Keys are hashes of partial template name and its arguments. If we've
148
- # visited a template before but ith different arguments, later visits
149
- # only record global variables so as not to double count locals, filters
150
- # and tags.
151
- seen: defaultdict[str, set[Optional[int]]] = defaultdict(set)
152
-
153
- def _visit(
154
- node: Node,
155
- template_name: str,
156
- scope: _StaticScope,
157
- *,
158
- just_globals: bool = False,
159
- ) -> None:
160
- if template_name and not just_globals:
161
- seen[template_name].add(None)
162
-
163
- # Update tags from node.token
164
- if (
165
- not just_globals
166
- and not isinstance(
167
- node, (BlockNode, ConditionalBlockNode, MultiExpressionBlockNode)
168
- )
169
- and node.token.kind == TOKEN_TAG
170
- ):
171
- tags[node.token.value].append(Span(template_name, node.token.start_index))
172
-
173
- # Update variables from node.expressions()
174
- for expr in node.expressions():
175
- _analyze_variables(
176
- expr,
177
- template_name,
178
- scope,
179
- globals,
180
- _VariableMap() if just_globals else variables,
181
- )
182
-
183
- if not just_globals:
184
- # Update filters from expr
185
- for name, span in _extract_filters(expr, template_name):
186
- filters[name].append(span)
187
-
188
- # Update the template scope from node.template_scope()
189
- for ident in node.template_scope():
190
- scope.add(ident)
191
- locals.add(
192
- Variable(
193
- segments=[ident],
194
- span=Span(template_name, ident.token.start_index),
195
- )
196
- )
197
-
198
- if partial := node.partial_scope():
199
- partial_name = (
200
- partial.name
201
- if isinstance(partial.name, str)
202
- else str(partial.name.evaluate(static_context))
203
- )
204
-
205
- # If we've seen this partial before but with different arguments,
206
- # we might want to visit it again but only capture globals.
207
- _just_globals = partial_name in seen
208
- if partial.key in seen[partial_name]:
209
- # We've visited this partial template before with the same
210
- # arguments.
211
- return
212
-
213
- seen[partial_name].add(partial.key)
214
- partial_name = partial_name or template_name
215
-
216
- partial_scope = (
217
- _StaticScope(set(partial.in_scope))
218
- if partial.scope == PartialScope.ISOLATED
219
- else root_scope.push(set(partial.in_scope))
220
- )
221
-
222
- for child in node.children(
223
- static_context, include_partials=include_partials
224
- ):
225
- _visit(
226
- child,
227
- partial_name,
228
- partial_scope,
229
- just_globals=just_globals or _just_globals,
230
- )
231
-
232
- partial_scope.pop()
233
- else:
234
- scope.push(set(node.block_scope()))
235
- for child in node.children(
236
- static_context, include_partials=include_partials
237
- ):
238
- _visit(child, template_name, scope, just_globals=just_globals)
239
- scope.pop()
240
-
241
- for node in template.nodes:
242
- _visit(node, template.name, root_scope)
243
-
244
- return TemplateAnalysis(
245
- variables=variables.as_dict(),
246
- globals=globals.as_dict(),
247
- locals=locals.as_dict(),
248
- filters=dict(filters),
249
- tags=dict(tags),
250
- )
251
-
252
-
253
- async def analyze_async(
254
- template: BoundTemplate, *, include_partials: bool
255
- ) -> TemplateAnalysis:
256
- """Report variable, filters and tags used in the given template.
257
-
258
- This is used by `BoundTemplate.analyze_async`.
259
- """
260
- variables = _VariableMap()
261
- globals = _VariableMap() # noqa: A001
262
- locals = _VariableMap() # noqa: A001
263
-
264
- filters: defaultdict[str, list[Span]] = defaultdict(list)
265
- tags: defaultdict[str, list[Span]] = defaultdict(list)
266
-
267
- template_scope: set[str] = set()
268
- root_scope = _StaticScope(template_scope)
269
- static_context = RenderContext(template)
270
-
271
- # Names of partial templates that have already been analyzed.
272
- seen: defaultdict[str, set[Optional[int]]] = defaultdict(set)
273
-
274
- async def _visit(
275
- node: Node,
276
- template_name: str,
277
- scope: _StaticScope,
278
- *,
279
- just_globals: bool = False,
280
- ) -> None:
281
- if template_name and not just_globals:
282
- seen[template_name].add(None)
283
-
284
- # Update tags from node.token
285
- if (
286
- not just_globals
287
- and not isinstance(
288
- node, (BlockNode, ConditionalBlockNode, MultiExpressionBlockNode)
289
- )
290
- and node.token.kind == TOKEN_TAG
291
- ):
292
- tags[node.token.value].append(Span(template_name, node.token.start_index))
293
-
294
- # Update variables from node.expressions()
295
- for expr in node.expressions():
296
- _analyze_variables(
297
- expr,
298
- template_name,
299
- scope,
300
- globals,
301
- _VariableMap() if just_globals else variables,
302
- )
303
-
304
- if not just_globals:
305
- # Update filters from expr
306
- for name, span in _extract_filters(expr, template_name):
307
- filters[name].append(span)
308
-
309
- # Update the template scope from node.template_scope()
310
- for ident in node.template_scope():
311
- scope.add(ident)
312
- locals.add(
313
- Variable(
314
- segments=[ident],
315
- span=Span(template_name, ident.token.start_index),
316
- )
317
- )
318
-
319
- if partial := node.partial_scope():
320
- partial_name = (
321
- partial.name
322
- if isinstance(partial.name, str)
323
- else str(partial.name.evaluate(static_context))
324
- )
325
-
326
- # If we've seen this partial before but with different arguments,
327
- # we might want to visit it again but only capture globals.
328
- _just_globals = partial_name in seen
329
- if partial.key in seen[partial_name]:
330
- # We've visited this partial template before with the same
331
- # arguments.
332
- return
333
-
334
- seen[partial_name].add(partial.key)
335
- partial_name = partial_name or template_name
336
-
337
- partial_scope = (
338
- _StaticScope(set(partial.in_scope))
339
- if partial.scope == PartialScope.ISOLATED
340
- else root_scope.push(set(partial.in_scope))
341
- )
342
-
343
- for child in await node.children_async(
344
- static_context, include_partials=include_partials
345
- ):
346
- await _visit(
347
- child,
348
- partial_name,
349
- partial_scope,
350
- just_globals=just_globals or _just_globals,
351
- )
352
-
353
- partial_scope.pop()
354
- else:
355
- scope.push(set(node.block_scope()))
356
- for child in await node.children_async(
357
- static_context, include_partials=include_partials
358
- ):
359
- await _visit(child, template_name, scope, just_globals=just_globals)
360
- scope.pop()
361
-
362
- for node in template.nodes:
363
- await _visit(node, template.name, root_scope)
364
-
365
- return TemplateAnalysis(
366
- variables=variables.as_dict(),
367
- globals=globals.as_dict(),
368
- locals=locals.as_dict(),
369
- filters=dict(filters),
370
- tags=dict(tags),
371
- )
372
-
373
-
374
- def _extract_filters(
375
- expression: Expression, template_name: str
376
- ) -> Iterable[tuple[str, Span]]:
377
- if isinstance(expression, TernaryFilteredExpression):
378
- yield from _extract_filters(expression.left, template_name)
379
-
380
- if expression.filters:
381
- yield from (
382
- (f.name, Span(template_name, f.token.start_index))
383
- for f in expression.filters
384
- )
385
-
386
- if expression.tail_filters:
387
- yield from (
388
- (f.name, Span(template_name, f.token.start_index))
389
- for f in expression.tail_filters
390
- )
391
-
392
- if isinstance(expression, FilteredExpression) and expression.filters:
393
- yield from (
394
- (f.name, Span(template_name, f.token.start_index))
395
- for f in expression.filters
396
- )
397
-
398
- for expr in expression.children():
399
- yield from _extract_filters(expr, template_name)
400
-
401
-
402
- def _analyze_variables(
403
- expression: Expression,
404
- template_name: str,
405
- scope: _StaticScope,
406
- globals: _VariableMap, # noqa: A002
407
- variables: _VariableMap,
408
- ) -> None:
409
- if isinstance(expression, Path):
410
- var = Variable(
411
- segments=_segments(expression, template_name),
412
- span=Span(template_name, expression.token.start_index),
413
- )
414
-
415
- # NOTE: We're updating globals and variables here so we can manage scope while
416
- # traversing the expression. This is for the benefit of lambda expressions that
417
- # add names to the scope of their children.
418
- variables.add(var)
419
-
420
- root = str(var.segments[0])
421
- if root not in scope:
422
- globals.add(var)
423
-
424
- if child_scope := expression.scope():
425
- scope.push(set(child_scope))
426
- for expr in expression.children():
427
- _analyze_variables(expr, template_name, scope, globals, variables)
428
- scope.pop()
429
- else:
430
- for expr in expression.children():
431
- _analyze_variables(expr, template_name, scope, globals, variables)
432
-
433
-
434
- def _segments(path: Path, template_name: str) -> Segments:
435
- segments: Segments = []
436
-
437
- for segment in path.path:
438
- if isinstance(segment, Path):
439
- segments.append(_segments(segment, template_name))
440
- else:
441
- segments.append(segment)
442
-
443
- return segments
liquid/stream.py DELETED
@@ -1,148 +0,0 @@
1
- """Step through a sequence of tokens."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import Iterator
6
- from typing import Optional
7
-
8
- from .builtin.expressions import tokenize
9
- from .exceptions import LiquidSyntaxError
10
- from .token import TOKEN_EOF
11
- from .token import TOKEN_EXPRESSION
12
- from .token import Token
13
- from .token import reverse_operators
14
-
15
-
16
- class TokenStream:
17
- """Step through a sequence of tokens."""
18
-
19
- eof = Token(TOKEN_EOF, TOKEN_EOF, -1, "")
20
-
21
- def __init__(self, tokens: Iterator[Token], block_depth_carry: int = 0):
22
- self.tokens = list(tokens)
23
- self.pos = 0
24
- self.block_depth = block_depth_carry
25
-
26
- def __next__(self) -> Token:
27
- return self.next_token()
28
-
29
- def next(self) -> Token:
30
- """Return the next token and advance the iterator."""
31
- try:
32
- token = self.tokens[self.pos]
33
- self.pos += 1
34
- return token
35
- except IndexError:
36
- return self.eof
37
-
38
- def next_token(self) -> Token:
39
- """Return the next token and advance the iterator."""
40
- try:
41
- token = self.tokens[self.pos]
42
- self.pos += 1
43
- return token
44
- except IndexError:
45
- return self.eof
46
-
47
- @property
48
- def current(self) -> Token:
49
- """Return the token at the head of the stream without advancing."""
50
- try:
51
- return self.tokens[self.pos]
52
- except IndexError:
53
- return self.eof
54
-
55
- @property
56
- def peek(self) -> Token:
57
- """Look at the next token."""
58
- try:
59
- return self.tokens[self.pos + 1]
60
- except IndexError:
61
- return self.eof
62
-
63
- def _expect(self, tok: Token, typ: str, value: Optional[str] = None) -> Token:
64
- if tok.kind != typ or (value is not None and tok.value != value):
65
- _typ = self._operator(tok.kind)
66
- _expected_typ = self._operator(typ)
67
- if value is not None:
68
- msg = f"expected {_expected_typ} {value}, found {_typ}"
69
- else:
70
- msg = f"expected {_expected_typ}, found {_typ}"
71
- raise LiquidSyntaxError(msg, token=tok)
72
- return tok
73
-
74
- def expect(self, typ: str, value: Optional[str] = None) -> Token:
75
- """Check the current token in the stream matches the given type and value.
76
-
77
- Returns the current token if its type matches _typ_.
78
- Raises a `LiquidSyntaxError` if it doesn't.
79
- """
80
- return self._expect(self.current, typ, value)
81
-
82
- def expect_peek(self, typ: str, value: Optional[str] = None) -> Token:
83
- """Check the next token in the stream matches the given type and value.
84
-
85
- Returns the next token if its type matches _typ_.
86
- Raises a `LiquidSyntaxError` it doesn't.
87
- """
88
- return self._expect(self.peek, typ, value)
89
-
90
- def eat(self, typ: str) -> Token:
91
- """Consume and return the next token.
92
-
93
- If the type of the next token is equal to _typ_, raise an exceptions.
94
-
95
- This is equivalent to `stream.expect(typ)` followed by `next(stream)`.
96
- """
97
- tok = self.current
98
- if tok.kind != typ:
99
- msg = f"expected {self._operator(typ)}, found {self._operator(tok.kind)}"
100
- raise LiquidSyntaxError(msg, token=tok)
101
- return next(self)
102
-
103
- def eat_one_of(self, *typ: str) -> Token:
104
- """Consume and return the next token.
105
-
106
- If the type of the next token is equal to _typ_, raise an exceptions.
107
-
108
- This is equivalent to `stream.expect(typ)` followed by `next(stream)`.
109
- """
110
- tok = self.current
111
- if tok.kind not in typ:
112
- msg = f"expected one of {typ!r}, found {self._operator(tok.kind)}"
113
- raise LiquidSyntaxError(msg, token=tok)
114
- return next(self)
115
-
116
- def into_inner(
117
- self, *, tag: Optional[Token] = None, eat: bool = True
118
- ) -> TokenStream:
119
- """Return a stream of tokens for the current expression token.
120
-
121
- If the current token is not an expression, a `LiquidSyntaxError` is
122
- raised.
123
-
124
- If _tag_ is given, it will be used to add context information to the
125
- syntax error, should one be raised.
126
-
127
- If _eat_ is true (the default), the current token is consumed.
128
- """
129
- token = self.current
130
- if token.kind != TOKEN_EXPRESSION:
131
- raise LiquidSyntaxError("missing expression", token=tag or token)
132
-
133
- if eat:
134
- next(self)
135
- return TokenStream(tokenize(token.value, parent_token=token))
136
-
137
- def expect_eos(self) -> None:
138
- """Raise a syntax error if we're not at the end of the stream."""
139
- if self.current.kind != TOKEN_EOF:
140
- raise LiquidSyntaxError(
141
- f"unexpected token {self.current.kind}", token=self.current
142
- )
143
-
144
- def _operator(self, kind: str) -> str:
145
- try:
146
- return repr(reverse_operators[kind])
147
- except KeyError:
148
- return kind
liquid/stringify.py DELETED
@@ -1,31 +0,0 @@
1
- """Stringify a Python object ready for output in a Liquid template."""
2
- from typing import Any
3
-
4
- from liquid import Markup
5
- from liquid import escape
6
- from liquid import soft_str
7
-
8
-
9
- def to_liquid_string(val: Any, autoescape: bool) -> str:
10
- """Stringify a Python object ready for output in a Liquid template."""
11
- if isinstance(val, str) or (autoescape and hasattr(val, "__html__")):
12
- pass
13
- elif isinstance(val, bool):
14
- val = str(val).lower()
15
- elif val is None:
16
- val = ""
17
- elif isinstance(val, list):
18
- if autoescape:
19
- val = Markup("").join(soft_str(itm) for itm in val)
20
- else:
21
- val = "".join(soft_str(itm) for itm in val)
22
- elif isinstance(val, range):
23
- val = f"{val.start}..{val.stop - 1}"
24
- else:
25
- val = str(val)
26
-
27
- if autoescape:
28
- val = escape(val)
29
-
30
- assert isinstance(val, str)
31
- return val
liquid/tag.py DELETED
@@ -1,47 +0,0 @@
1
- """Liquid tag base class."""
2
-
3
- from __future__ import annotations
4
-
5
- from abc import ABC
6
- from abc import abstractmethod
7
- from typing import TYPE_CHECKING
8
-
9
- from .ast import IllegalNode
10
- from .ast import Node
11
- from .exceptions import LiquidError
12
- from .parser import eat_block
13
-
14
- if TYPE_CHECKING:
15
- from .environment import Environment
16
- from .stream import TokenStream
17
-
18
-
19
- class Tag(ABC):
20
- """Base class for all built-in and custom template tags."""
21
-
22
- block = True
23
- name = ""
24
- end = ""
25
-
26
- def __init__(self, env: Environment):
27
- self.env = env
28
-
29
- def get_node(self, stream: TokenStream) -> Node:
30
- """Wraps `Tag.parse`, possibly returning an `IllegalNode`."""
31
- try:
32
- return self.parse(stream)
33
- except LiquidError as err:
34
- token = stream.current
35
- if not err.token:
36
- err.token = token
37
-
38
- self.env.error(err)
39
-
40
- if self.block and hasattr(self, "end"):
41
- eat_block(stream, (self.end,))
42
-
43
- return IllegalNode(token)
44
-
45
- @abstractmethod
46
- def parse(self, stream: TokenStream) -> Node:
47
- """Return a parse tree node by parsing tokens from the given stream."""