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/output.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from .._context import RenderContext
|
|
7
|
+
from .._io import StringBuffer
|
|
8
|
+
from .._markup import Expression, Markup, Partial
|
|
9
|
+
from ..expressions import Name
|
|
10
|
+
from ..tokens import Token
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OutputStatement:
|
|
14
|
+
blank = False
|
|
15
|
+
tag = ""
|
|
16
|
+
|
|
17
|
+
__slots__ = ("expression", "token")
|
|
18
|
+
|
|
19
|
+
def __init__(self, token: Token, expression: Expression) -> None:
|
|
20
|
+
self.token = token
|
|
21
|
+
self.expression = expression
|
|
22
|
+
|
|
23
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
24
|
+
buffer.write(
|
|
25
|
+
context.env.serialize(
|
|
26
|
+
self.expression.evaluate(context), context, self.expression.span
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
31
|
+
buffer.write(
|
|
32
|
+
context.env.serialize(
|
|
33
|
+
await self.expression.evaluate_async(context),
|
|
34
|
+
context,
|
|
35
|
+
self.expression.span,
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def children(self) -> list[Markup]:
|
|
40
|
+
return []
|
|
41
|
+
|
|
42
|
+
async def children_async(self) -> list[Markup]:
|
|
43
|
+
return []
|
|
44
|
+
|
|
45
|
+
def expressions(self) -> list[Expression]:
|
|
46
|
+
return [self.expression]
|
|
47
|
+
|
|
48
|
+
def block_scope(self) -> list[Name]:
|
|
49
|
+
return []
|
|
50
|
+
|
|
51
|
+
def template_scope(self) -> list[Name]:
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
58
|
+
return []
|
liquid/tags/raw.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from ..tokens import TOKEN_TAG_END, TOKEN_TEXT, token_value
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .._context import RenderContext
|
|
9
|
+
from .._io import StringBuffer
|
|
10
|
+
from .._markup import Expression, Markup, Partial
|
|
11
|
+
from .._parser import Parser
|
|
12
|
+
from ..expressions import Name
|
|
13
|
+
from ..tokens import Token
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RawTag:
|
|
17
|
+
blank = False
|
|
18
|
+
tag = "raw"
|
|
19
|
+
|
|
20
|
+
__slots__ = ("text", "token")
|
|
21
|
+
|
|
22
|
+
def __init__(self, token: Token, text: str) -> None:
|
|
23
|
+
self.token = token
|
|
24
|
+
self.text = text
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def parse(token: Token, parser: Parser) -> Markup:
|
|
28
|
+
parser.carry_whitespace_control()
|
|
29
|
+
parser.eat(TOKEN_TAG_END)
|
|
30
|
+
text = (
|
|
31
|
+
token_value(parser.eat(TOKEN_TEXT), parser.source)
|
|
32
|
+
if parser.kind() == TOKEN_TEXT
|
|
33
|
+
else ""
|
|
34
|
+
)
|
|
35
|
+
parser.eat_empty_tag("endraw")
|
|
36
|
+
return RawTag(token, text)
|
|
37
|
+
|
|
38
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
39
|
+
buffer.write(self.text)
|
|
40
|
+
|
|
41
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
42
|
+
buffer.write(self.text)
|
|
43
|
+
|
|
44
|
+
def children(self) -> list[Markup]:
|
|
45
|
+
return []
|
|
46
|
+
|
|
47
|
+
def expressions(self) -> list[Expression]:
|
|
48
|
+
return []
|
|
49
|
+
|
|
50
|
+
def block_scope(self) -> list[Name]:
|
|
51
|
+
return []
|
|
52
|
+
|
|
53
|
+
def template_scope(self) -> list[Name]:
|
|
54
|
+
return []
|
|
55
|
+
|
|
56
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
57
|
+
return []
|
|
58
|
+
|
|
59
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
60
|
+
return []
|
liquid/tags/render.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from .._markup import SCOPE_ISOLATED, Partial
|
|
6
|
+
from .._nothing import NOTHING
|
|
7
|
+
from ..drops import ForLoop
|
|
8
|
+
from ..exceptions import NoSuchTemplateError, TemplateNotFoundError
|
|
9
|
+
from ..expressions import Name, StringLiteral
|
|
10
|
+
from ..tokens import TOKEN_COMMA, TOKEN_TAG_END
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .._context import RenderContext
|
|
14
|
+
from .._io import StringBuffer
|
|
15
|
+
from .._markup import Expression, Markup
|
|
16
|
+
from .._parser import Parser
|
|
17
|
+
from ..expressions import KeywordArgument
|
|
18
|
+
from ..tokens import Token
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RenderTag:
|
|
22
|
+
blank = False
|
|
23
|
+
tag = "render"
|
|
24
|
+
|
|
25
|
+
__slots__ = ("alias", "args", "loop", "name", "token", "variable")
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
token: Token,
|
|
30
|
+
*,
|
|
31
|
+
name: StringLiteral,
|
|
32
|
+
loop: bool,
|
|
33
|
+
variable: Expression | None,
|
|
34
|
+
alias: Name | None,
|
|
35
|
+
args: list[KeywordArgument],
|
|
36
|
+
) -> None:
|
|
37
|
+
self.token = token
|
|
38
|
+
self.name = name
|
|
39
|
+
self.loop = loop
|
|
40
|
+
self.variable = variable
|
|
41
|
+
self.alias = alias
|
|
42
|
+
self.args = args
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def parse(token: Token, parser: Parser) -> Markup:
|
|
46
|
+
name_expr = parser.parse_string_literal()
|
|
47
|
+
|
|
48
|
+
loop = False
|
|
49
|
+
variable: Expression | None = None
|
|
50
|
+
alias: Name | None = None
|
|
51
|
+
|
|
52
|
+
# `for`, `with` and `as` don't have their own tokens kinds.
|
|
53
|
+
ident = parser.value()
|
|
54
|
+
if ident in ("for", "with"):
|
|
55
|
+
loop = ident == "for"
|
|
56
|
+
parser.next()
|
|
57
|
+
variable = parser.parse_expression()
|
|
58
|
+
|
|
59
|
+
if parser.value() == "as":
|
|
60
|
+
parser.next()
|
|
61
|
+
alias = parser.parse_identifier()
|
|
62
|
+
|
|
63
|
+
if parser.kind() == TOKEN_COMMA:
|
|
64
|
+
parser.next()
|
|
65
|
+
|
|
66
|
+
args = parser.parse_keyword_arguments(require_commas=False)
|
|
67
|
+
|
|
68
|
+
parser.carry_whitespace_control()
|
|
69
|
+
parser.eat(TOKEN_TAG_END)
|
|
70
|
+
|
|
71
|
+
return RenderTag(
|
|
72
|
+
token,
|
|
73
|
+
name=name_expr,
|
|
74
|
+
loop=loop,
|
|
75
|
+
variable=variable,
|
|
76
|
+
alias=alias,
|
|
77
|
+
args=args,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
81
|
+
template_name = self.name.value
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
template = context.env.get_template(
|
|
85
|
+
template_name, context=context, tag="render"
|
|
86
|
+
)
|
|
87
|
+
except TemplateNotFoundError as err:
|
|
88
|
+
# Promote TemplateNotFoundError to NoSuchTemplateError
|
|
89
|
+
raise NoSuchTemplateError(
|
|
90
|
+
str(err),
|
|
91
|
+
token=self.name.span,
|
|
92
|
+
source=context.template.source,
|
|
93
|
+
name=context.template.name,
|
|
94
|
+
) from None
|
|
95
|
+
|
|
96
|
+
bind_key = self.alias.value if self.alias else template.name.split(".", 1)[0]
|
|
97
|
+
bind_value: object
|
|
98
|
+
|
|
99
|
+
if self.variable:
|
|
100
|
+
bind_value = self.variable.evaluate(context)
|
|
101
|
+
else:
|
|
102
|
+
bind_value = context.resolve(template_name)
|
|
103
|
+
if bind_value == NOTHING:
|
|
104
|
+
bind_value = None
|
|
105
|
+
|
|
106
|
+
scope = {arg.name.value: arg.expr.evaluate(context) for arg in self.args}
|
|
107
|
+
|
|
108
|
+
ctx = context.copy(
|
|
109
|
+
scope,
|
|
110
|
+
disabled_tags=("include",),
|
|
111
|
+
block_scope=False,
|
|
112
|
+
carry_loop_iterations=True,
|
|
113
|
+
template=template,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if self.loop and isinstance(bind_value, list):
|
|
117
|
+
ctx.raise_for_loop_limit(len(bind_value)) # pyright: ignore[reportUnknownArgumentType]
|
|
118
|
+
|
|
119
|
+
forloop = ForLoop(bind_key, len(bind_value), None) # pyright: ignore[reportUnknownArgumentType]
|
|
120
|
+
scope["forloop"] = forloop
|
|
121
|
+
|
|
122
|
+
for item in bind_value: # pyright: ignore[reportUnknownVariableType]
|
|
123
|
+
scope[bind_key] = item
|
|
124
|
+
forloop.step()
|
|
125
|
+
template.render_with_context(ctx, buffer)
|
|
126
|
+
else:
|
|
127
|
+
scope[bind_key] = bind_value
|
|
128
|
+
template.render_with_context(ctx, buffer)
|
|
129
|
+
|
|
130
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
131
|
+
template_name = self.name.value
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
template = await context.env.get_template_async(
|
|
135
|
+
template_name, context=context, tag="render"
|
|
136
|
+
)
|
|
137
|
+
except TemplateNotFoundError as err:
|
|
138
|
+
# Promote TemplateNotFoundError to NoSuchTemplateError
|
|
139
|
+
raise NoSuchTemplateError(
|
|
140
|
+
str(err),
|
|
141
|
+
token=self.name.span,
|
|
142
|
+
source=context.template.source,
|
|
143
|
+
name=context.template.name,
|
|
144
|
+
) from None
|
|
145
|
+
|
|
146
|
+
bind_key = self.alias.value if self.alias else template.name.split(".", 1)[0]
|
|
147
|
+
bind_value: object
|
|
148
|
+
|
|
149
|
+
if self.variable:
|
|
150
|
+
bind_value = await self.variable.evaluate_async(context)
|
|
151
|
+
else:
|
|
152
|
+
bind_value = context.resolve(template_name)
|
|
153
|
+
if bind_value == NOTHING:
|
|
154
|
+
bind_value = None
|
|
155
|
+
|
|
156
|
+
scope = {
|
|
157
|
+
arg.name.value: await arg.expr.evaluate_async(context) for arg in self.args
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
ctx = context.copy(
|
|
161
|
+
scope,
|
|
162
|
+
disabled_tags=("include",),
|
|
163
|
+
block_scope=False,
|
|
164
|
+
carry_loop_iterations=True,
|
|
165
|
+
template=template,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if self.loop and isinstance(bind_value, list):
|
|
169
|
+
ctx.raise_for_loop_limit(len(bind_value)) # pyright: ignore[reportUnknownArgumentType]
|
|
170
|
+
|
|
171
|
+
forloop = ForLoop(bind_key, len(bind_value), None) # pyright: ignore[reportUnknownArgumentType]
|
|
172
|
+
scope["forloop"] = forloop
|
|
173
|
+
for item in bind_value: # pyright: ignore[reportUnknownVariableType]
|
|
174
|
+
scope[bind_key] = item
|
|
175
|
+
forloop.step()
|
|
176
|
+
await template.render_with_context_async(ctx, buffer)
|
|
177
|
+
else:
|
|
178
|
+
scope[bind_key] = bind_value
|
|
179
|
+
await template.render_with_context_async(ctx, buffer)
|
|
180
|
+
|
|
181
|
+
def children(self) -> list[Markup]:
|
|
182
|
+
return []
|
|
183
|
+
|
|
184
|
+
def expressions(self) -> list[Expression]:
|
|
185
|
+
result: list[Expression] = []
|
|
186
|
+
|
|
187
|
+
if self.variable:
|
|
188
|
+
result.append(self.variable)
|
|
189
|
+
|
|
190
|
+
result.extend(arg.expr for arg in self.args)
|
|
191
|
+
return result
|
|
192
|
+
|
|
193
|
+
def block_scope(self) -> list[Name]:
|
|
194
|
+
return []
|
|
195
|
+
|
|
196
|
+
def template_scope(self) -> list[Name]:
|
|
197
|
+
return []
|
|
198
|
+
|
|
199
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
200
|
+
template = context.env.get_template(
|
|
201
|
+
self.name.value,
|
|
202
|
+
context=context,
|
|
203
|
+
tag="render",
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
scope: list[Name] = [arg.name for arg in self.args]
|
|
207
|
+
|
|
208
|
+
if self.variable:
|
|
209
|
+
if self.alias:
|
|
210
|
+
scope.append(self.alias)
|
|
211
|
+
else:
|
|
212
|
+
scope.append(Name(self.name.token, self.name.value))
|
|
213
|
+
|
|
214
|
+
return [
|
|
215
|
+
Partial(
|
|
216
|
+
template=template,
|
|
217
|
+
scope=SCOPE_ISOLATED,
|
|
218
|
+
in_scope=scope,
|
|
219
|
+
key=hash((self.name.value, ":".join(n.value for n in scope))),
|
|
220
|
+
)
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
224
|
+
template = await context.env.get_template_async(
|
|
225
|
+
self.name.value,
|
|
226
|
+
context=context,
|
|
227
|
+
tag="render",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
scope: list[Name] = [arg.name for arg in self.args]
|
|
231
|
+
|
|
232
|
+
if self.variable:
|
|
233
|
+
if self.alias:
|
|
234
|
+
scope.append(self.alias)
|
|
235
|
+
else:
|
|
236
|
+
scope.append(Name(self.name.token, self.name.value))
|
|
237
|
+
|
|
238
|
+
return [
|
|
239
|
+
Partial(
|
|
240
|
+
template=template,
|
|
241
|
+
scope=SCOPE_ISOLATED,
|
|
242
|
+
in_scope=scope,
|
|
243
|
+
key=hash((self.name.value, ":".join(n.value for n in scope))),
|
|
244
|
+
)
|
|
245
|
+
]
|
liquid/tags/tablerow.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from .._drop import liquid_iterable
|
|
6
|
+
from .._markup import render_block, render_block_async
|
|
7
|
+
from .._slice import slice_sequence
|
|
8
|
+
from ..drops import TableRowLoop
|
|
9
|
+
from ..exceptions import LiquidSyntaxError
|
|
10
|
+
from ..tokens import TOKEN_COMMA, TOKEN_IN, TOKEN_TAG_END
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Iterable
|
|
14
|
+
|
|
15
|
+
from .._context import RenderContext
|
|
16
|
+
from .._io import StringBuffer
|
|
17
|
+
from .._markup import Block, Expression, Markup, Partial
|
|
18
|
+
from .._parser import Parser
|
|
19
|
+
from ..expressions import Name
|
|
20
|
+
from ..tokens import Token
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TableRowTag:
|
|
24
|
+
blank = False
|
|
25
|
+
tag = "tablerow"
|
|
26
|
+
|
|
27
|
+
__slots__ = (
|
|
28
|
+
"block",
|
|
29
|
+
"cols",
|
|
30
|
+
"expression",
|
|
31
|
+
"limit",
|
|
32
|
+
"name",
|
|
33
|
+
"offset",
|
|
34
|
+
"token",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
token: Token,
|
|
40
|
+
name: Name,
|
|
41
|
+
expression: Expression,
|
|
42
|
+
block: Block,
|
|
43
|
+
*,
|
|
44
|
+
cols: Expression | None,
|
|
45
|
+
offset: Expression | None,
|
|
46
|
+
limit: Expression | None,
|
|
47
|
+
) -> None:
|
|
48
|
+
self.token = token
|
|
49
|
+
self.name = name
|
|
50
|
+
self.expression = expression
|
|
51
|
+
self.block: Block = block
|
|
52
|
+
self.offset = offset
|
|
53
|
+
self.limit = limit
|
|
54
|
+
self.cols = cols
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def parse(token: Token, parser: Parser) -> Markup:
|
|
58
|
+
ident = parser.parse_identifier()
|
|
59
|
+
parser.eat(TOKEN_IN, message="missing 'in'")
|
|
60
|
+
parser.expect_expression()
|
|
61
|
+
expr = parser.parse_expression()
|
|
62
|
+
|
|
63
|
+
if parser.kind() == TOKEN_COMMA:
|
|
64
|
+
parser.next()
|
|
65
|
+
|
|
66
|
+
args = parser.parse_keyword_arguments(require_commas=False)
|
|
67
|
+
|
|
68
|
+
cols: Expression | None = None
|
|
69
|
+
offset: Expression | None = None
|
|
70
|
+
limit: Expression | None = None
|
|
71
|
+
|
|
72
|
+
for arg in args:
|
|
73
|
+
if arg.name.value == "offset":
|
|
74
|
+
offset = arg.expr
|
|
75
|
+
elif arg.name.value == "limit":
|
|
76
|
+
limit = arg.expr
|
|
77
|
+
elif arg.name.value == "cols":
|
|
78
|
+
cols = arg.expr
|
|
79
|
+
elif arg.name.value == "range":
|
|
80
|
+
pass
|
|
81
|
+
else:
|
|
82
|
+
raise LiquidSyntaxError(
|
|
83
|
+
f"unknown argument {arg.name.value!r}",
|
|
84
|
+
token=arg.name.token,
|
|
85
|
+
source=parser.source,
|
|
86
|
+
name=parser.template_name,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
parser.carry_whitespace_control()
|
|
90
|
+
parser.eat(TOKEN_TAG_END)
|
|
91
|
+
|
|
92
|
+
block = parser.parse_block(("endtablerow",))
|
|
93
|
+
parser.eat_empty_tag("endtablerow")
|
|
94
|
+
|
|
95
|
+
return TableRowTag(
|
|
96
|
+
token,
|
|
97
|
+
ident,
|
|
98
|
+
expr,
|
|
99
|
+
block,
|
|
100
|
+
cols=cols,
|
|
101
|
+
offset=offset,
|
|
102
|
+
limit=limit,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
106
|
+
offset = _to_int(self.offset, context, 0, 0)
|
|
107
|
+
limit = _to_int(self.limit, context, None, 0)
|
|
108
|
+
|
|
109
|
+
target = self.expression.evaluate(context)
|
|
110
|
+
it: Iterable[object]
|
|
111
|
+
|
|
112
|
+
if context.env.is_nil(target):
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
if liquid_iterable(target):
|
|
116
|
+
it = target.__liquid_iter__(offset, limit, False)
|
|
117
|
+
length = len(it)
|
|
118
|
+
else:
|
|
119
|
+
a = context.env.to_array(target, context, self.expression.span)
|
|
120
|
+
it, length = slice_sequence(a, offset, limit, False)
|
|
121
|
+
|
|
122
|
+
cols = _to_int(self.cols, context, length, 0)
|
|
123
|
+
tablerowloop = TableRowLoop(length, cols)
|
|
124
|
+
|
|
125
|
+
name = self.name.value
|
|
126
|
+
namespace: dict[str, object] = {"tablerowloop": tablerowloop}
|
|
127
|
+
|
|
128
|
+
with context.loop(namespace, tablerowloop):
|
|
129
|
+
buffer.write('<tr class="row1">\n')
|
|
130
|
+
|
|
131
|
+
for item in it:
|
|
132
|
+
namespace[name] = item
|
|
133
|
+
buffer.write(f'<td class="col{tablerowloop["col"]}">')
|
|
134
|
+
render_block(self.block, context, buffer)
|
|
135
|
+
buffer.write("</td>")
|
|
136
|
+
|
|
137
|
+
if context.interrupts and context.interrupts.pop() == "break":
|
|
138
|
+
break
|
|
139
|
+
|
|
140
|
+
if tablerowloop["col_last"] and not tablerowloop["last"]:
|
|
141
|
+
buffer.write(f'</tr>\n<tr class="row{tablerowloop["row"] + 1}">')
|
|
142
|
+
|
|
143
|
+
tablerowloop.step()
|
|
144
|
+
|
|
145
|
+
buffer.write("</tr>\n")
|
|
146
|
+
|
|
147
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
148
|
+
offset = await _to_int_async(self.offset, context, 0, 0)
|
|
149
|
+
limit = await _to_int_async(self.limit, context, None, 0)
|
|
150
|
+
|
|
151
|
+
target = await self.expression.evaluate_async(context)
|
|
152
|
+
it: Iterable[object]
|
|
153
|
+
|
|
154
|
+
if context.env.is_nil(target):
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
if liquid_iterable(target):
|
|
158
|
+
it = target.__liquid_iter__(offset, limit, False)
|
|
159
|
+
length = len(it)
|
|
160
|
+
else:
|
|
161
|
+
a = context.env.to_array(target, context, self.expression.span)
|
|
162
|
+
it, length = slice_sequence(a, offset, limit, False)
|
|
163
|
+
|
|
164
|
+
cols = await _to_int_async(self.cols, context, length, 0)
|
|
165
|
+
tablerowloop = TableRowLoop(length, cols)
|
|
166
|
+
|
|
167
|
+
name = self.name.value
|
|
168
|
+
namespace: dict[str, object] = {"tablerowloop": tablerowloop}
|
|
169
|
+
|
|
170
|
+
with context.loop(namespace, tablerowloop):
|
|
171
|
+
buffer.write('<tr class="row1">\n')
|
|
172
|
+
|
|
173
|
+
for item in it:
|
|
174
|
+
namespace[name] = item
|
|
175
|
+
buffer.write(f'<td class="col{tablerowloop["col"]}">')
|
|
176
|
+
await render_block_async(self.block, context, buffer)
|
|
177
|
+
buffer.write("</td>")
|
|
178
|
+
|
|
179
|
+
if context.interrupts and context.interrupts.pop() == "break":
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
if tablerowloop["col_last"] and not tablerowloop["last"]:
|
|
183
|
+
buffer.write(f'</tr>\n<tr class="row{tablerowloop["row"] + 1}">')
|
|
184
|
+
|
|
185
|
+
tablerowloop.step()
|
|
186
|
+
|
|
187
|
+
buffer.write("</tr>\n")
|
|
188
|
+
|
|
189
|
+
def children(self) -> list[Markup]:
|
|
190
|
+
result: list[Markup] = [
|
|
191
|
+
node for node in self.block if not isinstance(node, str)
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
def expressions(self) -> list[Expression]:
|
|
197
|
+
result = [self.expression]
|
|
198
|
+
|
|
199
|
+
if self.cols:
|
|
200
|
+
result.append(self.cols)
|
|
201
|
+
|
|
202
|
+
if self.offset:
|
|
203
|
+
result.append(self.offset)
|
|
204
|
+
|
|
205
|
+
if self.limit:
|
|
206
|
+
result.append(self.limit)
|
|
207
|
+
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
def block_scope(self) -> list[Name]:
|
|
211
|
+
return [self.name]
|
|
212
|
+
|
|
213
|
+
def template_scope(self) -> list[Name]:
|
|
214
|
+
return []
|
|
215
|
+
|
|
216
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
217
|
+
return []
|
|
218
|
+
|
|
219
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
220
|
+
return []
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _to_int[T](
|
|
224
|
+
expression: Expression | None,
|
|
225
|
+
context: RenderContext,
|
|
226
|
+
default: T,
|
|
227
|
+
null: T,
|
|
228
|
+
) -> int | T:
|
|
229
|
+
if expression is None:
|
|
230
|
+
return default
|
|
231
|
+
|
|
232
|
+
value = expression.evaluate(context)
|
|
233
|
+
|
|
234
|
+
if context.env.is_nil(value):
|
|
235
|
+
return null
|
|
236
|
+
|
|
237
|
+
return context.env.to_int(value, context, expression.span)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
async def _to_int_async[T](
|
|
241
|
+
expression: Expression | None,
|
|
242
|
+
context: RenderContext,
|
|
243
|
+
default: T,
|
|
244
|
+
null: T,
|
|
245
|
+
) -> int | T:
|
|
246
|
+
if expression is None:
|
|
247
|
+
return default
|
|
248
|
+
|
|
249
|
+
value = await expression.evaluate_async(context)
|
|
250
|
+
|
|
251
|
+
if context.env.is_nil(value):
|
|
252
|
+
return null
|
|
253
|
+
|
|
254
|
+
return context.env.to_int(value, context, expression.span)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from ..tokens import TOKEN_COMMENT, TOKEN_COMMENT_END, token_value
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .._context import RenderContext
|
|
9
|
+
from .._io import StringBuffer
|
|
10
|
+
from .._markup import Expression, Markup, Partial
|
|
11
|
+
from .._parser import Parser
|
|
12
|
+
from ..expressions import Name
|
|
13
|
+
from ..tokens import Token
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TemplateComment:
|
|
17
|
+
blank = True
|
|
18
|
+
tag = ""
|
|
19
|
+
|
|
20
|
+
__slots__ = ("text", "token")
|
|
21
|
+
|
|
22
|
+
def __init__(self, token: Token, text: str) -> None:
|
|
23
|
+
self.token = token
|
|
24
|
+
self.text = text
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def parse(token: Token, parser: Parser) -> Markup:
|
|
28
|
+
parser.carry_whitespace_control()
|
|
29
|
+
comment = parser.eat(TOKEN_COMMENT)
|
|
30
|
+
parser.carry_whitespace_control()
|
|
31
|
+
parser.eat(TOKEN_COMMENT_END)
|
|
32
|
+
return TemplateComment(token, token_value(comment, parser.source))
|
|
33
|
+
|
|
34
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
def children(self) -> list[Markup]:
|
|
41
|
+
return []
|
|
42
|
+
|
|
43
|
+
def expressions(self) -> list[Expression]:
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
def block_scope(self) -> list[Name]:
|
|
47
|
+
return []
|
|
48
|
+
|
|
49
|
+
def template_scope(self) -> list[Name]:
|
|
50
|
+
return []
|
|
51
|
+
|
|
52
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
56
|
+
return []
|