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/tags/translate.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from gettext import NullTranslations
|
|
6
|
+
from typing import TYPE_CHECKING, cast
|
|
7
|
+
|
|
8
|
+
import markupsafe
|
|
9
|
+
|
|
10
|
+
from .. import expressions
|
|
11
|
+
from .._messages import MESSAGES, MessageText, Translations
|
|
12
|
+
from ..exceptions import TranslationSyntaxError
|
|
13
|
+
from ..expressions import Name
|
|
14
|
+
from ..tokens import TOKEN_COMMA, TOKEN_TAG_END
|
|
15
|
+
from .output import OutputStatement
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Iterable
|
|
19
|
+
|
|
20
|
+
from .._context import RenderContext
|
|
21
|
+
from .._io import StringBuffer
|
|
22
|
+
from .._markup import Block, Expression, Markup, Partial
|
|
23
|
+
from .._parser import Parser
|
|
24
|
+
from .._template import Template
|
|
25
|
+
from ..expressions import KeywordArgument
|
|
26
|
+
from ..tokens import Token
|
|
27
|
+
|
|
28
|
+
RE_VARS = re.compile(r"(?<!%)%\((\w+)\)s")
|
|
29
|
+
RE_WHITESPACE = re.compile(r"\s*\n\s*")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TranslateTag:
|
|
33
|
+
blank = False
|
|
34
|
+
tag = "translate"
|
|
35
|
+
|
|
36
|
+
default_translations = NullTranslations()
|
|
37
|
+
translations_var = "translations"
|
|
38
|
+
message_count_var = "count"
|
|
39
|
+
message_context_var = "context"
|
|
40
|
+
|
|
41
|
+
__slots__ = ("args", "plural_block", "singular_block", "token")
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
token: Token,
|
|
46
|
+
args: list[KeywordArgument],
|
|
47
|
+
singular_block: MessageBlock,
|
|
48
|
+
plural_block: MessageBlock | None,
|
|
49
|
+
) -> None:
|
|
50
|
+
self.token = token
|
|
51
|
+
self.args = args
|
|
52
|
+
self.singular_block = singular_block
|
|
53
|
+
self.plural_block = plural_block
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def parse(cls, token: Token, parser: Parser) -> Markup:
|
|
57
|
+
if parser.kind() == TOKEN_COMMA:
|
|
58
|
+
# Leading commas are OK.
|
|
59
|
+
parser.next()
|
|
60
|
+
|
|
61
|
+
args = parser.parse_keyword_arguments(require_commas=True)
|
|
62
|
+
|
|
63
|
+
if parser.kind() == TOKEN_COMMA:
|
|
64
|
+
# Trailing commas are OK.
|
|
65
|
+
parser.next()
|
|
66
|
+
|
|
67
|
+
parser.carry_whitespace_control()
|
|
68
|
+
parser.eat(TOKEN_TAG_END)
|
|
69
|
+
|
|
70
|
+
singular_block = cls.message_block(
|
|
71
|
+
parser, parser.parse_block(("endtranslate", "plural"))
|
|
72
|
+
)
|
|
73
|
+
plural_block: MessageBlock | None = None
|
|
74
|
+
|
|
75
|
+
if parser.tag("plural"):
|
|
76
|
+
parser.eat_empty_tag("plural")
|
|
77
|
+
plural_block = cls.message_block(
|
|
78
|
+
parser, parser.parse_block(("endtranslate",))
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
parser.eat_empty_tag("endtranslate")
|
|
82
|
+
return TranslateTag(token, args, singular_block, plural_block)
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def message_block(cls, parser: Parser, block: Block) -> MessageBlock:
|
|
86
|
+
message_text: list[str] = []
|
|
87
|
+
message_vars: list[str] = []
|
|
88
|
+
|
|
89
|
+
for node in block:
|
|
90
|
+
if isinstance(node, str):
|
|
91
|
+
message_text.append(node.replace("%", "%%"))
|
|
92
|
+
|
|
93
|
+
elif isinstance(node, OutputStatement) and isinstance(
|
|
94
|
+
node.expression, expressions.Variable
|
|
95
|
+
):
|
|
96
|
+
expr = node.expression
|
|
97
|
+
|
|
98
|
+
if not isinstance(expr.root, Name) or expr.selectors:
|
|
99
|
+
raise TranslationSyntaxError(
|
|
100
|
+
"expected a translation variable, found a path",
|
|
101
|
+
token=expr.span,
|
|
102
|
+
source=parser.source,
|
|
103
|
+
name=parser.template_name,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
message_text.append(f"%({expr.root.value})s")
|
|
107
|
+
message_vars.append(expr.root.value)
|
|
108
|
+
|
|
109
|
+
else:
|
|
110
|
+
raise TranslationSyntaxError(
|
|
111
|
+
"unexpected markup in translation text",
|
|
112
|
+
token=node.token,
|
|
113
|
+
source=parser.source,
|
|
114
|
+
name=parser.template_name,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
msg = "".join(message_text)
|
|
118
|
+
return MessageBlock(block, RE_WHITESPACE.sub(" ", msg.strip()), message_vars)
|
|
119
|
+
|
|
120
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
121
|
+
translations = self.resolve_translations(context)
|
|
122
|
+
scope: dict[str, object] = {}
|
|
123
|
+
|
|
124
|
+
with context.extend(scope):
|
|
125
|
+
for arg in self.args:
|
|
126
|
+
scope[arg.name.value] = arg.expr.evaluate(context)
|
|
127
|
+
|
|
128
|
+
count = self.resolve_count(context, scope)
|
|
129
|
+
message_context = self.resolve_message_context(context, scope)
|
|
130
|
+
|
|
131
|
+
message_text = self.gettext(
|
|
132
|
+
translations,
|
|
133
|
+
count=count,
|
|
134
|
+
message_context=message_context,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return buffer.write(self._format_message(context, message_text))
|
|
138
|
+
|
|
139
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
140
|
+
translations = self.resolve_translations(context)
|
|
141
|
+
scope: dict[str, object] = {}
|
|
142
|
+
|
|
143
|
+
with context.extend(scope):
|
|
144
|
+
for arg in self.args:
|
|
145
|
+
scope[arg.name.value] = await arg.expr.evaluate_async(context)
|
|
146
|
+
|
|
147
|
+
count = self.resolve_count(context, scope)
|
|
148
|
+
message_context = self.resolve_message_context(context, scope)
|
|
149
|
+
|
|
150
|
+
message_text = self.gettext(
|
|
151
|
+
translations,
|
|
152
|
+
count=count,
|
|
153
|
+
message_context=message_context,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return buffer.write(self._format_message(context, message_text))
|
|
157
|
+
|
|
158
|
+
def resolve_translations(
|
|
159
|
+
self, context: RenderContext
|
|
160
|
+
) -> Translations | NullTranslations:
|
|
161
|
+
"""Return a translations object from the current render context."""
|
|
162
|
+
t = context.resolve(self.translations_var)
|
|
163
|
+
|
|
164
|
+
if context.env.is_nil(t):
|
|
165
|
+
return self.default_translations
|
|
166
|
+
|
|
167
|
+
return cast("Translations", t)
|
|
168
|
+
|
|
169
|
+
def resolve_count(
|
|
170
|
+
self,
|
|
171
|
+
context: RenderContext,
|
|
172
|
+
block_scope: dict[str, object],
|
|
173
|
+
) -> int | None:
|
|
174
|
+
"""Return a message count."""
|
|
175
|
+
return int(
|
|
176
|
+
context.env.to_numeric(
|
|
177
|
+
block_scope.get(self.message_count_var, 1), context, 1
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def resolve_message_context(
|
|
182
|
+
self,
|
|
183
|
+
context: RenderContext,
|
|
184
|
+
block_scope: dict[str, object],
|
|
185
|
+
) -> str | None:
|
|
186
|
+
"""Return the message context string."""
|
|
187
|
+
message_context = block_scope.pop(self.message_context_var, None)
|
|
188
|
+
|
|
189
|
+
if message_context:
|
|
190
|
+
return (
|
|
191
|
+
str(message_context)
|
|
192
|
+
if not isinstance(message_context, str)
|
|
193
|
+
else message_context
|
|
194
|
+
) # Just in case we get a Markupsafe object.
|
|
195
|
+
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
def gettext(
|
|
199
|
+
self,
|
|
200
|
+
translations: Translations | NullTranslations,
|
|
201
|
+
count: int | None,
|
|
202
|
+
message_context: str | None,
|
|
203
|
+
) -> str:
|
|
204
|
+
"""Get translated text from the given translations object."""
|
|
205
|
+
if self.plural_block and count:
|
|
206
|
+
if message_context:
|
|
207
|
+
return translations.npgettext(
|
|
208
|
+
message_context,
|
|
209
|
+
self.singular_block.text,
|
|
210
|
+
self.plural_block.text,
|
|
211
|
+
count,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return translations.ngettext(
|
|
215
|
+
self.singular_block.text,
|
|
216
|
+
self.plural_block.text,
|
|
217
|
+
count,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if message_context:
|
|
221
|
+
return translations.pgettext(message_context, self.singular_block.text)
|
|
222
|
+
|
|
223
|
+
return translations.gettext(self.singular_block.text)
|
|
224
|
+
|
|
225
|
+
def children(self) -> list[Markup]:
|
|
226
|
+
result = [
|
|
227
|
+
node for node in self.singular_block.block if not isinstance(node, str)
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
if self.plural_block:
|
|
231
|
+
result.extend(
|
|
232
|
+
node for node in self.plural_block.block if not isinstance(node, str)
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
return result
|
|
236
|
+
|
|
237
|
+
def expressions(self) -> list[Expression]:
|
|
238
|
+
return [arg.expr for arg in self.args]
|
|
239
|
+
|
|
240
|
+
def block_scope(self) -> list[Name]:
|
|
241
|
+
return [arg.name for arg in self.args]
|
|
242
|
+
|
|
243
|
+
def template_scope(self) -> list[Name]:
|
|
244
|
+
return []
|
|
245
|
+
|
|
246
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
247
|
+
return []
|
|
248
|
+
|
|
249
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
250
|
+
return []
|
|
251
|
+
|
|
252
|
+
def messages(self, template: Template) -> Iterable[MessageText]:
|
|
253
|
+
if not self.singular_block.block:
|
|
254
|
+
return ()
|
|
255
|
+
|
|
256
|
+
message_context: Expression | None = None
|
|
257
|
+
for arg in self.args:
|
|
258
|
+
if arg.name.value == self.message_context_var:
|
|
259
|
+
message_context = arg.expr
|
|
260
|
+
|
|
261
|
+
if self.plural_block:
|
|
262
|
+
if message_context and isinstance(
|
|
263
|
+
message_context, expressions.StringLiteral
|
|
264
|
+
):
|
|
265
|
+
funcname = "npgettext"
|
|
266
|
+
message: MESSAGES = (
|
|
267
|
+
(message_context.value, "c"),
|
|
268
|
+
self.singular_block.text,
|
|
269
|
+
self.plural_block.text,
|
|
270
|
+
)
|
|
271
|
+
else:
|
|
272
|
+
funcname = "ngettext"
|
|
273
|
+
message = (
|
|
274
|
+
self.singular_block.text,
|
|
275
|
+
self.plural_block.text,
|
|
276
|
+
)
|
|
277
|
+
elif message_context and isinstance(message_context, expressions.StringLiteral):
|
|
278
|
+
funcname = "pgettext"
|
|
279
|
+
message = (
|
|
280
|
+
(message_context.value, "c"),
|
|
281
|
+
self.singular_block.text,
|
|
282
|
+
)
|
|
283
|
+
else:
|
|
284
|
+
funcname = "gettext"
|
|
285
|
+
message = (self.singular_block.text,)
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
MessageText(
|
|
289
|
+
lineno=template.line_col(self.token)[0],
|
|
290
|
+
funcname=funcname,
|
|
291
|
+
message=message,
|
|
292
|
+
),
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def _format_message(
|
|
296
|
+
self,
|
|
297
|
+
context: RenderContext,
|
|
298
|
+
message_text: str,
|
|
299
|
+
) -> str:
|
|
300
|
+
"""Return the message string formatted with the given message variables."""
|
|
301
|
+
if context.env.autoescape:
|
|
302
|
+
message_text = markupsafe.Markup(message_text)
|
|
303
|
+
|
|
304
|
+
_vars = {
|
|
305
|
+
k: context.env.to_str(context.resolve(k), context, self.token)
|
|
306
|
+
for k in RE_VARS.findall(message_text)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return message_text % _vars
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@dataclass(slots=True)
|
|
313
|
+
class MessageBlock:
|
|
314
|
+
block: Block
|
|
315
|
+
text: str
|
|
316
|
+
args: list[str]
|
liquid/tags/unless.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from ..tokens import (
|
|
6
|
+
TOKEN_TAG_END,
|
|
7
|
+
TOKEN_TAG_NAME,
|
|
8
|
+
TOKEN_TAG_START,
|
|
9
|
+
)
|
|
10
|
+
from .else_ import ElseBlock
|
|
11
|
+
from .if_ import ElsIfBlock, IfBlock
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .._context import RenderContext
|
|
15
|
+
from .._io import StringBuffer
|
|
16
|
+
from .._markup import Expression, Markup, Partial
|
|
17
|
+
from .._parser import Parser
|
|
18
|
+
from ..expressions import Name
|
|
19
|
+
from ..tokens import Token
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class UnlessTag:
|
|
23
|
+
tag = ""
|
|
24
|
+
|
|
25
|
+
__slots__ = ("blank", "blocks", "token")
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self, token: Token, blocks: list[UnlessBlock | ElsIfBlock | ElseBlock]
|
|
29
|
+
) -> None:
|
|
30
|
+
self.token = token
|
|
31
|
+
self.blocks = blocks
|
|
32
|
+
self.blank = all(block.blank for block in self.blocks)
|
|
33
|
+
|
|
34
|
+
if self.blank:
|
|
35
|
+
for block in self.blocks:
|
|
36
|
+
block.filter_strings()
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def parse(cls, token: Token, parser: Parser) -> Markup:
|
|
40
|
+
blocks: list[UnlessBlock | ElsIfBlock | ElseBlock] = []
|
|
41
|
+
|
|
42
|
+
parser.expect_expression()
|
|
43
|
+
expression = parser.parse_expression(infix=True, expect_end_of_expression=True)
|
|
44
|
+
parser.carry_whitespace_control()
|
|
45
|
+
parser.eat(TOKEN_TAG_END)
|
|
46
|
+
|
|
47
|
+
block = parser.parse_block(("else", "elsif", "endunless"))
|
|
48
|
+
blocks.append(UnlessBlock(token, expression, block))
|
|
49
|
+
|
|
50
|
+
while 1:
|
|
51
|
+
tag_name = parser.tags(("else", "elsif"))
|
|
52
|
+
|
|
53
|
+
if tag_name == "elsif":
|
|
54
|
+
blocks.append(cls.parse_else_if(parser))
|
|
55
|
+
elif tag_name == "else":
|
|
56
|
+
name_token = parser.eat_tag("else")
|
|
57
|
+
blocks.append(
|
|
58
|
+
ElseBlock(
|
|
59
|
+
name_token, parser.parse_block(("else", "elsif", "endunless"))
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
parser.eat_empty_tag("endunless")
|
|
66
|
+
return UnlessTag(token, blocks)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def parse_else_if(cls, parser: Parser) -> ElsIfBlock:
|
|
70
|
+
parser.eat(TOKEN_TAG_START)
|
|
71
|
+
parser.skip_whitespace_control()
|
|
72
|
+
token = parser.eat(TOKEN_TAG_NAME)
|
|
73
|
+
parser.expect_expression()
|
|
74
|
+
expression = parser.parse_expression(infix=True)
|
|
75
|
+
parser.carry_whitespace_control()
|
|
76
|
+
parser.eat(TOKEN_TAG_END)
|
|
77
|
+
return ElsIfBlock(
|
|
78
|
+
token, expression, parser.parse_block(("else", "elsif", "endunless"))
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
82
|
+
for block in self.blocks:
|
|
83
|
+
if isinstance(block, UnlessBlock):
|
|
84
|
+
if not context.env.truthy(block.expression.evaluate(context), context):
|
|
85
|
+
return block.render(context, buffer)
|
|
86
|
+
elif context.env.truthy(block.expression.evaluate(context), context):
|
|
87
|
+
return block.render(context, buffer)
|
|
88
|
+
|
|
89
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
90
|
+
for block in self.blocks:
|
|
91
|
+
if isinstance(block, UnlessBlock):
|
|
92
|
+
if not context.env.truthy(
|
|
93
|
+
await block.expression.evaluate_async(context), context
|
|
94
|
+
):
|
|
95
|
+
return await block.render_async(context, buffer)
|
|
96
|
+
elif context.env.truthy(
|
|
97
|
+
await block.expression.evaluate_async(context), context
|
|
98
|
+
):
|
|
99
|
+
return await block.render_async(context, buffer)
|
|
100
|
+
|
|
101
|
+
def children(self) -> list[Markup]:
|
|
102
|
+
return list(self.blocks)
|
|
103
|
+
|
|
104
|
+
def expressions(self) -> list[Expression]:
|
|
105
|
+
return []
|
|
106
|
+
|
|
107
|
+
def block_scope(self) -> list[Name]:
|
|
108
|
+
return []
|
|
109
|
+
|
|
110
|
+
def template_scope(self) -> list[Name]:
|
|
111
|
+
return []
|
|
112
|
+
|
|
113
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
114
|
+
return []
|
|
115
|
+
|
|
116
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
117
|
+
return []
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class UnlessBlock(IfBlock):
|
|
121
|
+
tag = "unless"
|
liquid/tags/with_.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from .._markup import is_blank_block, render_block, render_block_async
|
|
6
|
+
from ..tokens import TOKEN_COMMA, TOKEN_TAG_END
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .._context import RenderContext
|
|
10
|
+
from .._io import StringBuffer
|
|
11
|
+
from .._markup import Block, Expression, Markup, Partial
|
|
12
|
+
from .._parser import Parser
|
|
13
|
+
from ..expressions import KeywordArgument, Name
|
|
14
|
+
from ..tokens import Token
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WithTag:
|
|
18
|
+
tag = "with"
|
|
19
|
+
|
|
20
|
+
__slots__ = ("args", "blank", "block", "token")
|
|
21
|
+
|
|
22
|
+
def __init__(self, token: Token, args: list[KeywordArgument], block: Block) -> None:
|
|
23
|
+
self.token = token
|
|
24
|
+
self.args = args
|
|
25
|
+
self.block = block
|
|
26
|
+
self.blank = is_blank_block(block)
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def parse(token: Token, parser: Parser) -> Markup:
|
|
30
|
+
if parser.kind() == TOKEN_COMMA:
|
|
31
|
+
# Leading commas are OK.
|
|
32
|
+
parser.next()
|
|
33
|
+
|
|
34
|
+
args = parser.parse_keyword_arguments(require_commas=True)
|
|
35
|
+
|
|
36
|
+
if parser.kind() == TOKEN_COMMA:
|
|
37
|
+
# Trailing commas are OK.
|
|
38
|
+
parser.next()
|
|
39
|
+
|
|
40
|
+
parser.carry_whitespace_control()
|
|
41
|
+
parser.eat(TOKEN_TAG_END)
|
|
42
|
+
|
|
43
|
+
block = parser.parse_block(("endwith",))
|
|
44
|
+
parser.eat_empty_tag("endwith")
|
|
45
|
+
return WithTag(token, args, block)
|
|
46
|
+
|
|
47
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
48
|
+
scope: dict[str, object] = {}
|
|
49
|
+
|
|
50
|
+
with context.extend(scope):
|
|
51
|
+
# Later args have access to earlier args.
|
|
52
|
+
for arg in self.args:
|
|
53
|
+
scope[arg.name.value] = arg.expr.evaluate(context)
|
|
54
|
+
|
|
55
|
+
render_block(self.block, context, buffer)
|
|
56
|
+
|
|
57
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
58
|
+
scope: dict[str, object] = {}
|
|
59
|
+
|
|
60
|
+
with context.extend(scope):
|
|
61
|
+
# Later args have access to earlier args.
|
|
62
|
+
for arg in self.args:
|
|
63
|
+
scope[arg.name.value] = await arg.expr.evaluate_async(context)
|
|
64
|
+
|
|
65
|
+
await render_block_async(self.block, context, buffer)
|
|
66
|
+
|
|
67
|
+
def children(self) -> list[Markup]:
|
|
68
|
+
return [node for node in self.block if not isinstance(node, str)]
|
|
69
|
+
|
|
70
|
+
def expressions(self) -> list[Expression]:
|
|
71
|
+
return [arg.expr for arg in self.args]
|
|
72
|
+
|
|
73
|
+
def block_scope(self) -> list[Name]:
|
|
74
|
+
return [arg.name for arg in self.args]
|
|
75
|
+
|
|
76
|
+
def template_scope(self) -> list[Name]:
|
|
77
|
+
return []
|
|
78
|
+
|
|
79
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
80
|
+
return []
|
|
81
|
+
|
|
82
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
83
|
+
return []
|