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,251 +0,0 @@
1
- """The built-in _include_ tag."""
2
-
3
- from __future__ import annotations
4
-
5
- import sys
6
- from typing import TYPE_CHECKING
7
- from typing import Iterable
8
- from typing import Optional
9
- from typing import TextIO
10
-
11
- from liquid.ast import Node
12
- from liquid.ast import Partial
13
- from liquid.ast import PartialScope
14
- from liquid.builtin.drops import IterableDrop
15
- from liquid.builtin.expressions import Identifier
16
- from liquid.builtin.expressions import KeywordArgument
17
- from liquid.builtin.expressions import Literal
18
- from liquid.builtin.expressions import Path
19
- from liquid.builtin.expressions import parse_identifier
20
- from liquid.builtin.expressions import parse_string_or_path
21
- from liquid.exceptions import TemplateNotFoundError
22
- from liquid.tag import Tag
23
- from liquid.token import TOKEN_AS
24
- from liquid.token import TOKEN_FOR
25
- from liquid.token import TOKEN_TAG
26
- from liquid.token import TOKEN_WITH
27
- from liquid.token import TOKEN_WORD
28
- from liquid.token import Token
29
-
30
- if TYPE_CHECKING:
31
- from liquid.context import RenderContext
32
- from liquid.expression import Expression
33
- from liquid.stream import TokenStream
34
-
35
- TAG_INCLUDE = sys.intern("include")
36
-
37
-
38
- class IncludeNode(Node):
39
- """The built-in _include_ tag."""
40
-
41
- __slots__ = ("name", "var", "alias", "args")
42
- tag = TAG_INCLUDE
43
-
44
- def __init__(
45
- self,
46
- token: Token,
47
- name: Expression,
48
- var: Optional[Expression] = None,
49
- alias: Optional[Identifier] = None,
50
- args: Optional[list[KeywordArgument]] = None,
51
- ):
52
- super().__init__(token)
53
- self.name = name
54
- self.var = var
55
- self.alias = alias
56
- self.args = args or []
57
- self.blank = False
58
-
59
- def __str__(self) -> str:
60
- var = f" with {self.var}" if self.var else ""
61
- if self.alias:
62
- var += f" as {self.alias}"
63
- if self.args:
64
- var += ","
65
- args = " " + ", ".join(str(arg) for arg in self.args) if self.args else ""
66
- return f"{{% include {self.name}{var}{args} %}}"
67
-
68
- def render_to_output(self, context: RenderContext, buffer: TextIO) -> int:
69
- """Render the node to the output buffer."""
70
- name = self.name.evaluate(context)
71
-
72
- try:
73
- template = context.env.get_template(
74
- str(name), context=context, tag=self.tag
75
- )
76
- except TemplateNotFoundError as err:
77
- err.token = self.name.token
78
- err.template_name = context.template.full_name()
79
- raise
80
-
81
- namespace: dict[str, object] = {
82
- arg.name: arg.value.evaluate(context) for arg in self.args
83
- }
84
-
85
- with context.extend(namespace, template=template):
86
- # Bind a variable to the included template.
87
- if self.var is not None:
88
- val = self.var.evaluate(context)
89
- key = self.alias or template.name.split(".")[0]
90
-
91
- # If the variable is array-like, render the template once for each
92
- # item in the array.
93
- #
94
- # The reference implementation does not seem to distinguish between
95
- # "for" and "with". Just checks for array-like-ness.
96
- if isinstance(val, (tuple, list, IterableDrop)):
97
- # NOTE: What if an included template with a bound array updates
98
- # a keyword argument value? Do we need to update namespace
99
- # arguments after each loop?
100
- #
101
- # The reference implementation seems to evaluate arguments once,
102
- # before the loop.
103
- context.raise_for_loop_limit(len(val))
104
- for itm in val:
105
- namespace[key] = itm
106
- template.render_with_context(context, buffer, partial=True)
107
- else:
108
- namespace[key] = val
109
- template.render_with_context(context, buffer, partial=True)
110
- else:
111
- template.render_with_context(context, buffer, partial=True)
112
-
113
- return True
114
-
115
- async def render_to_output_async(
116
- self, context: RenderContext, buffer: TextIO
117
- ) -> int:
118
- """Render the node to the output buffer."""
119
- name = await self.name.evaluate_async(context)
120
-
121
- try:
122
- template = await context.env.get_template_async(
123
- str(name), context=context, tag=self.tag
124
- )
125
- except TemplateNotFoundError as err:
126
- err.token = self.name.token
127
- err.template_name = context.template.full_name()
128
- raise
129
-
130
- namespace: dict[str, object] = {
131
- arg.name: await arg.value.evaluate_async(context) for arg in self.args
132
- }
133
-
134
- with context.extend(namespace, template=template):
135
- if self.var is not None:
136
- val = await self.var.evaluate_async(context)
137
- key = self.alias or template.name.split(".")[0]
138
-
139
- if isinstance(val, (tuple, list, IterableDrop)):
140
- context.raise_for_loop_limit(len(val))
141
- for itm in val:
142
- namespace[key] = itm
143
- await template.render_with_context_async(
144
- context, buffer, partial=True
145
- )
146
- else:
147
- namespace[key] = val
148
- await template.render_with_context_async(
149
- context, buffer, partial=True
150
- )
151
- else:
152
- await template.render_with_context_async(context, buffer, partial=True)
153
-
154
- return True
155
-
156
- def children(
157
- self, static_context: RenderContext, *, include_partials: bool = True
158
- ) -> Iterable[Node]:
159
- """Return this node's children."""
160
- if include_partials:
161
- name = self.name.evaluate(static_context)
162
- try:
163
- template = static_context.env.get_template(
164
- str(name), context=static_context, tag=self.tag
165
- )
166
- yield from template.nodes
167
- except TemplateNotFoundError as err:
168
- err.token = self.name.token
169
- err.template_name = static_context.template.full_name()
170
- raise
171
-
172
- async def children_async(
173
- self, static_context: RenderContext, *, include_partials: bool = True
174
- ) -> Iterable[Node]:
175
- """Return this node's children."""
176
- if include_partials:
177
- name = await self.name.evaluate_async(static_context)
178
- try:
179
- template = await static_context.env.get_template_async(
180
- str(name), context=static_context, tag=self.tag
181
- )
182
- return template.nodes
183
- except TemplateNotFoundError as err:
184
- err.token = self.name.token
185
- err.template_name = static_context.template.full_name()
186
- raise
187
- return []
188
-
189
- def expressions(self) -> Iterable[Expression]:
190
- """Return this node's expressions."""
191
- yield self.name
192
- if self.var:
193
- yield self.var
194
- yield from (arg.value for arg in self.args)
195
-
196
- def partial_scope(self) -> Partial | None:
197
- """Return information about a partial template loaded by this node."""
198
- scope: list[Identifier] = [
199
- Identifier(arg.name, token=arg.token) for arg in self.args
200
- ]
201
-
202
- if self.var:
203
- if self.alias:
204
- scope.append(self.alias)
205
- elif isinstance(self.name, Literal):
206
- scope.append(
207
- Identifier(
208
- str(self.name.value).split(".", 1)[0], token=self.name.token
209
- )
210
- )
211
-
212
- return Partial(name=self.name, scope=PartialScope.SHARED, in_scope=scope)
213
-
214
-
215
- BIND_TOKENS = frozenset((TOKEN_WITH, TOKEN_FOR))
216
-
217
-
218
- class IncludeTag(Tag):
219
- """The built-in _include_ tag."""
220
-
221
- name = TAG_INCLUDE
222
- block = False
223
- node_class = IncludeNode
224
-
225
- def parse(self, stream: TokenStream) -> Node:
226
- """Read an IncludeNode from the given stream of tokens."""
227
- token = stream.eat(TOKEN_TAG)
228
- tokens = stream.into_inner(tag=token, eat=False)
229
-
230
- # Need a string or identifier that resolves to a string. This is the name
231
- # of the template to be included.
232
- name = parse_string_or_path(self.env, tokens)
233
- var: Optional[Path] = None
234
- alias: Optional[Identifier] = None
235
-
236
- # Optionally bind a variable to the included template context
237
- if tokens.current.kind in BIND_TOKENS:
238
- next(tokens) # Eat 'with' or 'for'
239
- tokens.expect(TOKEN_WORD)
240
- var = Path.parse(self.env, tokens)
241
-
242
- # The bound variable will take the name of the template by default,
243
- # or an alias if an identifier follows the "as" keyword.
244
- if tokens.current.kind == TOKEN_AS:
245
- next(tokens) # Eat 'as'
246
- tokens.expect(TOKEN_WORD)
247
- alias = parse_identifier(self.env, tokens)
248
-
249
- # Zero or more keyword arguments
250
- args = KeywordArgument.parse(self.env, tokens)
251
- return self.node_class(token, name, var, alias, args)
@@ -1,60 +0,0 @@
1
- """The built-in _increment_ tag."""
2
-
3
- from __future__ import annotations
4
-
5
- import sys
6
- from typing import TYPE_CHECKING
7
- from typing import Iterable
8
- from typing import TextIO
9
-
10
- from liquid.ast import Node
11
- from liquid.builtin.expressions import parse_identifier
12
- from liquid.tag import Tag
13
- from liquid.token import TOKEN_TAG
14
- from liquid.token import Token
15
-
16
- if TYPE_CHECKING:
17
- from liquid.builtin.expressions import Identifier
18
- from liquid.context import RenderContext
19
- from liquid.stream import TokenStream
20
-
21
-
22
- TAG_INCREMENT = sys.intern("increment")
23
-
24
-
25
- class IncrementNode(Node):
26
- """The built-in _increment_ tag."""
27
-
28
- __slots__ = ("name",)
29
-
30
- def __init__(self, token: Token, name: Identifier):
31
- super().__init__(token)
32
- self.name = name
33
- self.blank = False
34
-
35
- def __str__(self) -> str:
36
- return f"{{% increment {self.name} %}}"
37
-
38
- def render_to_output(self, context: RenderContext, buffer: TextIO) -> int:
39
- """Render the node to the output buffer."""
40
- return buffer.write(str(context.increment(self.name)))
41
-
42
- def template_scope(self) -> Iterable[Identifier]:
43
- """Return variables this node adds to the template local scope."""
44
- yield self.name
45
-
46
-
47
- class IncrementTag(Tag):
48
- """The built-in _increment_ tag."""
49
-
50
- name = TAG_INCREMENT
51
- block = False
52
- node_class = IncrementNode
53
-
54
- def parse(self, stream: TokenStream) -> IncrementNode:
55
- """Parse tokens from _stream_ into an AST node."""
56
- token = stream.eat(TOKEN_TAG)
57
- return self.node_class(
58
- token,
59
- name=parse_identifier(self.env, stream.into_inner(tag=token, eat=False)),
60
- )
@@ -1,47 +0,0 @@
1
- """The built-in inline comment tag."""
2
-
3
- from __future__ import annotations
4
-
5
- import re
6
- import sys
7
- from typing import TYPE_CHECKING
8
-
9
- from liquid.builtin.tags.comment_tag import CommentNode
10
- from liquid.exceptions import LiquidSyntaxError
11
- from liquid.tag import Tag
12
- from liquid.token import TOKEN_EXPRESSION
13
- from liquid.token import TOKEN_TAG
14
-
15
- if TYPE_CHECKING:
16
- from liquid.stream import TokenStream
17
-
18
-
19
- RE_INVALID_INLINE_COMMENT = re.compile(r"\n\s*[^#\s]")
20
-
21
-
22
- class InlineCommentNode(CommentNode):
23
- """The built-in inline comment tag."""
24
-
25
- def __str__(self) -> str:
26
- return f"{{% # {self.text} %}}"
27
-
28
-
29
- class InlineCommentTag(Tag):
30
- """The built-in inline comment tag."""
31
-
32
- block = False
33
- name = sys.intern("#")
34
- node_class = InlineCommentNode
35
-
36
- def parse(self, stream: TokenStream) -> InlineCommentNode:
37
- """Parse tokens from _stream_ into an AST node."""
38
- token = stream.expect(TOKEN_TAG)
39
- # Empty comment tag?
40
- if stream.peek.kind == TOKEN_EXPRESSION:
41
- next(stream)
42
- if RE_INVALID_INLINE_COMMENT.search(stream.current.value):
43
- raise LiquidSyntaxError(
44
- "every line of an inline comment must start with a '#' character",
45
- token=stream.current,
46
- )
47
- return self.node_class(token, text=stream.current.value)
@@ -1,183 +0,0 @@
1
- """The built-in _liquid_ tag."""
2
-
3
- from __future__ import annotations
4
-
5
- import re
6
- import sys
7
- from functools import partial
8
- from typing import TYPE_CHECKING
9
- from typing import Iterable
10
- from typing import Iterator
11
- from typing import Optional
12
- from typing import Pattern
13
- from typing import TextIO
14
-
15
- from liquid.ast import BlockNode
16
- from liquid.ast import Node
17
- from liquid.exceptions import LiquidSyntaxError
18
- from liquid.parser import get_parser
19
- from liquid.stream import TokenStream
20
- from liquid.tag import Tag
21
- from liquid.token import TOKEN_EOF
22
- from liquid.token import TOKEN_EXPRESSION
23
- from liquid.token import TOKEN_ILLEGAL
24
- from liquid.token import TOKEN_TAG
25
- from liquid.token import Token
26
-
27
- if TYPE_CHECKING:
28
- from liquid import Environment
29
- from liquid import RenderContext
30
-
31
- TAG_LIQUID = sys.intern("liquid")
32
-
33
-
34
- class LiquidNode(Node):
35
- """The built-in _liquid_ tag."""
36
-
37
- __slots__ = (
38
- "liquid_token",
39
- "block",
40
- )
41
-
42
- def __init__(self, token: Token, liquid_token: Optional[Token], block: BlockNode):
43
- super().__init__(token)
44
- self.liquid_token = liquid_token
45
- self.block = block
46
- self.blank = block.blank
47
-
48
- def __str__(self) -> str:
49
- # NOTE: We're using a string representation of the token, not the node.
50
- # Which might cause issues later.
51
- expr = self.liquid_token.value if self.liquid_token else ""
52
- return f"{{% liquid {expr} %}}"
53
-
54
- def render_to_output(self, context: RenderContext, buffer: TextIO) -> int:
55
- """Render the node to the output buffer."""
56
- return self.block.render(context, buffer)
57
-
58
- async def render_to_output_async(
59
- self, context: RenderContext, buffer: TextIO
60
- ) -> int:
61
- """Render the node to the output buffer."""
62
- return await self.block.render_async(context, buffer)
63
-
64
- def children(
65
- self,
66
- static_context: RenderContext, # noqa: ARG002
67
- *,
68
- include_partials: bool = True, # noqa: ARG002
69
- ) -> Iterable[Node]:
70
- """Return this node's children."""
71
- yield self.block
72
-
73
-
74
- class LiquidTag(Tag):
75
- """The built-in "liquid" tag."""
76
-
77
- name = TAG_LIQUID
78
- block = False
79
- node_class = LiquidNode
80
-
81
- def __init__(self, env: Environment):
82
- super().__init__(env)
83
-
84
- comment_start_string = env.comment_start_string.replace("{", "")
85
-
86
- if comment_start_string:
87
- seq = re.escape(comment_start_string)
88
- rules = (
89
- (
90
- "LIQUID_EXPR",
91
- rf"[ \t]*(?P<name>(\w+|{seq}))[ \t]*(?P<expr>.*?)[ \t\r]*?(\n+|$)",
92
- ),
93
- ("SKIP", r"[\r\n]+"),
94
- (TOKEN_ILLEGAL, r"."),
95
- )
96
- else:
97
- rules = (
98
- (
99
- "LIQUID_EXPR",
100
- r"[ \t]*(?P<name>#|\w+)[ \t]*(?P<expr>.*?)[ \t\r]*?(\n+|$)",
101
- ),
102
- ("SKIP", r"[\r\n]+"),
103
- (TOKEN_ILLEGAL, r"."),
104
- )
105
-
106
- self._tokenize = partial(
107
- _tokenize_liquid_expression,
108
- rules=_compile_rules(rules),
109
- comment_start_string=comment_start_string,
110
- )
111
-
112
- def parse(self, stream: TokenStream) -> Node:
113
- """Parse tokens from _stream_ into an AST node."""
114
- token = stream.eat(TOKEN_TAG)
115
- token_: Optional[Token] = None
116
-
117
- if stream.current.kind == TOKEN_EOF:
118
- # Empty liquid tag. Empty block.
119
- block = BlockNode(token, [])
120
- elif stream.current.kind == TOKEN_TAG:
121
- parser = get_parser(self.env)
122
- block = parser.parse_block(stream, end=())
123
- else:
124
- token_ = stream.expect(TOKEN_EXPRESSION)
125
- block = get_parser(self.env).parse_block(
126
- TokenStream(
127
- self._tokenize(
128
- token_.value,
129
- token=token_,
130
- ),
131
- block_depth_carry=stream.block_depth,
132
- ),
133
- end=(),
134
- )
135
-
136
- return self.node_class(token, token_, block=block)
137
-
138
-
139
- def _tokenize_liquid_expression(
140
- source: str,
141
- rules: Pattern[str],
142
- token: Token,
143
- comment_start_string: str,
144
- ) -> Iterator[Token]:
145
- """Tokenize a "{% liquid %}" tag."""
146
- for match in rules.finditer(source):
147
- kind = match.lastgroup
148
- assert kind is not None
149
-
150
- value = match.group()
151
-
152
- if kind == "LIQUID_EXPR":
153
- name = match.group("name")
154
- if name == comment_start_string:
155
- continue
156
-
157
- yield Token(
158
- TOKEN_TAG,
159
- value=name,
160
- start_index=token.start_index + match.start("name"),
161
- source=token.source,
162
- )
163
-
164
- if match.group("expr"):
165
- yield Token(
166
- TOKEN_EXPRESSION,
167
- value=match.group("expr"),
168
- start_index=token.start_index + match.start("expr"),
169
- source=token.source,
170
- )
171
- elif kind == "SKIP":
172
- continue
173
- else:
174
- raise LiquidSyntaxError(
175
- f"expected newline delimited tag expressions, found {value!r}",
176
- token=token,
177
- )
178
-
179
-
180
- def _compile_rules(rules: Iterable[tuple[str, str]]) -> Pattern[str]:
181
- """Compile the given rules into a single regular expression."""
182
- pattern = "|".join(f"(?P<{name}>{pattern})" for name, pattern in rules)
183
- return re.compile(pattern, re.DOTALL)