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
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import importlib.resources
|
|
5
|
+
from pathlib import Path, PurePath
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from .._loader import Loader, TemplateSource
|
|
9
|
+
from ..exceptions import TemplateNotFoundError
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Iterable
|
|
13
|
+
from types import ModuleType
|
|
14
|
+
|
|
15
|
+
from .._context import RenderContext
|
|
16
|
+
from ..environment import Environment
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PackageLoader(Loader):
|
|
20
|
+
"""A template loader that reads templates from Python packages.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
package
|
|
25
|
+
Import name of a package containing Liquid templates.
|
|
26
|
+
package_path
|
|
27
|
+
One or more directories in the package containing Liquid templates.
|
|
28
|
+
encoding
|
|
29
|
+
Encoding of template files.
|
|
30
|
+
ext
|
|
31
|
+
A default file extension to use if one is not provided. Should include
|
|
32
|
+
a leading period.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
package: str | ModuleType,
|
|
38
|
+
*,
|
|
39
|
+
package_path: str | Iterable[str] = "templates",
|
|
40
|
+
encoding: str = "utf-8",
|
|
41
|
+
ext: str = ".liquid",
|
|
42
|
+
) -> None:
|
|
43
|
+
if isinstance(package_path, str):
|
|
44
|
+
self.paths = [importlib.resources.files(package).joinpath(package_path)]
|
|
45
|
+
else:
|
|
46
|
+
_package = importlib.resources.files(package)
|
|
47
|
+
self.paths = [_package.joinpath(path) for path in package_path]
|
|
48
|
+
|
|
49
|
+
self.encoding = encoding
|
|
50
|
+
self.ext = ext
|
|
51
|
+
|
|
52
|
+
def _resolve_path(self, name: str) -> Path:
|
|
53
|
+
template_path = PurePath(name)
|
|
54
|
+
if template_path.anchor:
|
|
55
|
+
template_path = PurePath(*template_path.parts[1:])
|
|
56
|
+
|
|
57
|
+
# Add suffix self.ext if template name does not have a suffix.
|
|
58
|
+
if not template_path.suffix:
|
|
59
|
+
template_path = template_path.with_suffix(self.ext)
|
|
60
|
+
|
|
61
|
+
for path in self.paths:
|
|
62
|
+
with importlib.resources.as_file(path) as base:
|
|
63
|
+
base_resolved = base.resolve()
|
|
64
|
+
source_path: Path = (base_resolved / template_path).resolve()
|
|
65
|
+
|
|
66
|
+
if not source_path.is_relative_to(base_resolved):
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
if not source_path.is_file():
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
# We don't want to expose a fully qualified path in error messages.
|
|
73
|
+
return base / template_path
|
|
74
|
+
|
|
75
|
+
raise TemplateNotFoundError(name)
|
|
76
|
+
|
|
77
|
+
def get_source(
|
|
78
|
+
self,
|
|
79
|
+
env: Environment,
|
|
80
|
+
name: str,
|
|
81
|
+
context: RenderContext | None = None,
|
|
82
|
+
**kwargs: object,
|
|
83
|
+
) -> TemplateSource:
|
|
84
|
+
source_path = self._resolve_path(name)
|
|
85
|
+
return TemplateSource(
|
|
86
|
+
text=source_path.read_text(self.encoding),
|
|
87
|
+
name=source_path.name,
|
|
88
|
+
path=str(source_path),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
async def get_source_async(
|
|
92
|
+
self,
|
|
93
|
+
env: Environment,
|
|
94
|
+
name: str,
|
|
95
|
+
context: RenderContext | None = None,
|
|
96
|
+
**kwargs: object,
|
|
97
|
+
) -> TemplateSource:
|
|
98
|
+
loop = asyncio.get_running_loop()
|
|
99
|
+
|
|
100
|
+
source_path = await loop.run_in_executor(None, self._resolve_path, name)
|
|
101
|
+
|
|
102
|
+
source_text = await loop.run_in_executor(
|
|
103
|
+
None, source_path.read_text, self.encoding
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return TemplateSource(
|
|
107
|
+
text=source_text,
|
|
108
|
+
name=source_path.name,
|
|
109
|
+
path=str(source_path),
|
|
110
|
+
)
|
liquid/tags/__init__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from .assign import AssignTag
|
|
2
|
+
from .capture import CaptureTag
|
|
3
|
+
from .case import CaseTag
|
|
4
|
+
from .comment import CommentTag
|
|
5
|
+
from .cycle import CycleTag
|
|
6
|
+
from .decrement import DecrementTag
|
|
7
|
+
from .doc import DocTag
|
|
8
|
+
from .echo import EchoTag
|
|
9
|
+
from .else_ import ElseBlock
|
|
10
|
+
from .error import ErrorNode
|
|
11
|
+
from .extends import BlockTag, ExtendsTag
|
|
12
|
+
from .for_ import BreakTag, ContinueTag, ForTag
|
|
13
|
+
from .if_ import IfTag
|
|
14
|
+
from .ifchanged import IfChangedTag
|
|
15
|
+
from .include import IncludeTag
|
|
16
|
+
from .increment import IncrementTag
|
|
17
|
+
from .inline_comment import InlineCommentTag
|
|
18
|
+
from .liquid import LiquidTag
|
|
19
|
+
from .macro import CallTag, MacroTag
|
|
20
|
+
from .output import OutputStatement
|
|
21
|
+
from .raw import RawTag
|
|
22
|
+
from .render import RenderTag
|
|
23
|
+
from .tablerow import TableRowTag
|
|
24
|
+
from .template_comment import TemplateComment
|
|
25
|
+
from .translate import TranslateTag
|
|
26
|
+
from .unless import UnlessTag
|
|
27
|
+
from .with_ import WithTag
|
|
28
|
+
|
|
29
|
+
__all__ = (
|
|
30
|
+
"AssignTag",
|
|
31
|
+
"BlockTag",
|
|
32
|
+
"BreakTag",
|
|
33
|
+
"CallTag",
|
|
34
|
+
"CaptureTag",
|
|
35
|
+
"CaseTag",
|
|
36
|
+
"CommentTag",
|
|
37
|
+
"ContinueTag",
|
|
38
|
+
"CycleTag",
|
|
39
|
+
"DecrementTag",
|
|
40
|
+
"DocTag",
|
|
41
|
+
"EchoTag",
|
|
42
|
+
"ElseBlock",
|
|
43
|
+
"ErrorNode",
|
|
44
|
+
"ExtendsTag",
|
|
45
|
+
"ForTag",
|
|
46
|
+
"IfChangedTag",
|
|
47
|
+
"IfTag",
|
|
48
|
+
"IncludeTag",
|
|
49
|
+
"IncrementTag",
|
|
50
|
+
"InlineCommentTag",
|
|
51
|
+
"LiquidTag",
|
|
52
|
+
"MacroTag",
|
|
53
|
+
"OutputStatement",
|
|
54
|
+
"RawTag",
|
|
55
|
+
"RenderTag",
|
|
56
|
+
"TableRowTag",
|
|
57
|
+
"TemplateComment",
|
|
58
|
+
"TranslateTag",
|
|
59
|
+
"UnlessTag",
|
|
60
|
+
"WithTag",
|
|
61
|
+
)
|
liquid/tags/assign.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from ..exceptions import LiquidSyntaxError
|
|
6
|
+
from ..tokens import TOKEN_ASSIGN, TOKEN_TAG_END
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .._context import RenderContext
|
|
10
|
+
from .._io import StringBuffer
|
|
11
|
+
from .._markup import Expression, Markup, Partial
|
|
12
|
+
from .._parser import Parser
|
|
13
|
+
from ..expressions import Name
|
|
14
|
+
from ..tokens import Token
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AssignTag:
|
|
18
|
+
blank = True
|
|
19
|
+
tag = "assign"
|
|
20
|
+
|
|
21
|
+
__slots__ = ("expression", "name", "token")
|
|
22
|
+
|
|
23
|
+
def __init__(self, token: Token, name: Name, expression: Expression) -> None:
|
|
24
|
+
self.token = token
|
|
25
|
+
self.name = name
|
|
26
|
+
self.expression = expression
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def parse(token: Token, parser: Parser) -> Markup:
|
|
30
|
+
name = parser.parse_identifier()
|
|
31
|
+
|
|
32
|
+
if name.value.endswith("?"):
|
|
33
|
+
raise LiquidSyntaxError(
|
|
34
|
+
"invalid variable name",
|
|
35
|
+
token=name.span,
|
|
36
|
+
source=parser.source,
|
|
37
|
+
name=parser.template_name,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
parser.eat(TOKEN_ASSIGN, "bad identifier or missing '='")
|
|
41
|
+
expression = parser.parse_filtered_expression()
|
|
42
|
+
parser.carry_whitespace_control()
|
|
43
|
+
parser.eat(TOKEN_TAG_END)
|
|
44
|
+
return AssignTag(token, name, expression)
|
|
45
|
+
|
|
46
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
47
|
+
context.assign(self.name.value, self.expression.evaluate(context))
|
|
48
|
+
|
|
49
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
50
|
+
context.assign(self.name.value, await self.expression.evaluate_async(context))
|
|
51
|
+
|
|
52
|
+
def children(self) -> list[Markup]:
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
def expressions(self) -> list[Expression]:
|
|
56
|
+
return [self.expression]
|
|
57
|
+
|
|
58
|
+
def block_scope(self) -> list[Name]:
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
def template_scope(self) -> list[Name]:
|
|
62
|
+
return [self.name]
|
|
63
|
+
|
|
64
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
65
|
+
return []
|
|
66
|
+
|
|
67
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
68
|
+
return []
|
liquid/tags/capture.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import markupsafe
|
|
6
|
+
|
|
7
|
+
from .._markup import render_block, render_block_async
|
|
8
|
+
from ..exceptions import LiquidSyntaxError
|
|
9
|
+
from ..tokens import TOKEN_TAG_END
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .._context import RenderContext
|
|
13
|
+
from .._io import StringBuffer
|
|
14
|
+
from .._markup import Block, Expression, Markup, Partial
|
|
15
|
+
from .._parser import Parser
|
|
16
|
+
from ..expressions import Name
|
|
17
|
+
from ..tokens import Token
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CaptureTag:
|
|
21
|
+
blank = True
|
|
22
|
+
tag = "capture"
|
|
23
|
+
|
|
24
|
+
__slots__ = ("block", "name", "token")
|
|
25
|
+
|
|
26
|
+
def __init__(self, token: Token, name: Name, block: Block) -> None:
|
|
27
|
+
self.token = token
|
|
28
|
+
self.name = name
|
|
29
|
+
self.block = block
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def parse(token: Token, parser: Parser) -> Markup:
|
|
33
|
+
name = parser.parse_identifier()
|
|
34
|
+
|
|
35
|
+
if name.value.endswith("?"):
|
|
36
|
+
raise LiquidSyntaxError(
|
|
37
|
+
"invalid variable name",
|
|
38
|
+
token=name.span,
|
|
39
|
+
source=parser.source,
|
|
40
|
+
name=parser.template_name,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
parser.carry_whitespace_control()
|
|
44
|
+
parser.eat(TOKEN_TAG_END)
|
|
45
|
+
|
|
46
|
+
block = parser.parse_block(end=("endcapture",))
|
|
47
|
+
parser.eat_empty_tag("endcapture")
|
|
48
|
+
return CaptureTag(token, name, block)
|
|
49
|
+
|
|
50
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
51
|
+
buf = context.env.buffer_factory()
|
|
52
|
+
render_block(self.block, context, buf)
|
|
53
|
+
|
|
54
|
+
if context.env.autoescape:
|
|
55
|
+
context.assign(self.name.value, markupsafe.Markup(buf.getvalue()))
|
|
56
|
+
else:
|
|
57
|
+
context.assign(self.name.value, buf.getvalue())
|
|
58
|
+
|
|
59
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
60
|
+
buf = context.env.buffer_factory()
|
|
61
|
+
await render_block_async(self.block, context, buf)
|
|
62
|
+
if context.env.autoescape:
|
|
63
|
+
context.assign(self.name.value, markupsafe.Markup(buf.getvalue()))
|
|
64
|
+
else:
|
|
65
|
+
context.assign(self.name.value, buf.getvalue())
|
|
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 []
|
|
72
|
+
|
|
73
|
+
def block_scope(self) -> list[Name]:
|
|
74
|
+
return []
|
|
75
|
+
|
|
76
|
+
def template_scope(self) -> list[Name]:
|
|
77
|
+
return [self.name]
|
|
78
|
+
|
|
79
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
80
|
+
return []
|
|
81
|
+
|
|
82
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
83
|
+
return []
|
liquid/tags/case.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
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 (
|
|
7
|
+
TOKEN_COMMA,
|
|
8
|
+
TOKEN_OR,
|
|
9
|
+
TOKEN_TAG_END,
|
|
10
|
+
TOKEN_TAG_NAME,
|
|
11
|
+
TOKEN_TAG_START,
|
|
12
|
+
TOKEN_TEXT,
|
|
13
|
+
)
|
|
14
|
+
from .else_ import ElseBlock
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Sequence
|
|
18
|
+
|
|
19
|
+
from .._context import RenderContext
|
|
20
|
+
from .._io import StringBuffer
|
|
21
|
+
from .._markup import Block, Expression, Markup, Partial
|
|
22
|
+
from .._parser import Parser
|
|
23
|
+
from ..expressions import Name
|
|
24
|
+
from ..tokens import Token
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CaseTag:
|
|
28
|
+
tag = "case"
|
|
29
|
+
|
|
30
|
+
__slots__ = ("blank", "blocks", "expression", "token")
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
token: Token,
|
|
35
|
+
expression: Expression,
|
|
36
|
+
blocks: Sequence[WhenBlock | ElseBlock],
|
|
37
|
+
) -> None:
|
|
38
|
+
self.token = token
|
|
39
|
+
self.expression = expression
|
|
40
|
+
self.blocks = blocks
|
|
41
|
+
|
|
42
|
+
self.blank = all(block.blank for block in blocks)
|
|
43
|
+
|
|
44
|
+
if self.blank:
|
|
45
|
+
# Discard blank strings from all blocks.
|
|
46
|
+
for block in blocks:
|
|
47
|
+
block.filter_strings()
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def parse(token: Token, parser: Parser) -> Markup:
|
|
51
|
+
parser.expect_expression()
|
|
52
|
+
expression = parser.parse_expression(expect_end_of_expression=True)
|
|
53
|
+
parser.carry_whitespace_control()
|
|
54
|
+
parser.eat(TOKEN_TAG_END)
|
|
55
|
+
|
|
56
|
+
if parser.kind() == TOKEN_TEXT:
|
|
57
|
+
# Junk between `{% case %}` and first `{% when %}`.
|
|
58
|
+
parser.eat(TOKEN_TEXT)
|
|
59
|
+
|
|
60
|
+
blocks: list[WhenBlock | ElseBlock] = []
|
|
61
|
+
|
|
62
|
+
while 1:
|
|
63
|
+
tag_name = parser.tags(("when", "else"))
|
|
64
|
+
|
|
65
|
+
if tag_name == "when":
|
|
66
|
+
blocks.append(CaseTag.parse_when(parser))
|
|
67
|
+
elif tag_name == "else":
|
|
68
|
+
name_token = parser.eat_tag("else")
|
|
69
|
+
blocks.append(
|
|
70
|
+
ElseBlock(
|
|
71
|
+
name_token, parser.parse_block(("endcase", "when", "else"))
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
parser.eat_empty_tag("endcase")
|
|
78
|
+
return CaseTag(token, expression, blocks)
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def parse_when(parser: Parser) -> WhenBlock:
|
|
82
|
+
parser.eat(TOKEN_TAG_START)
|
|
83
|
+
parser.skip_whitespace_control()
|
|
84
|
+
token = parser.eat(TOKEN_TAG_NAME)
|
|
85
|
+
|
|
86
|
+
if parser.kind() == TOKEN_COMMA:
|
|
87
|
+
parser.next()
|
|
88
|
+
|
|
89
|
+
right: list[Expression] = []
|
|
90
|
+
|
|
91
|
+
while 1:
|
|
92
|
+
right.append(parser.parse_expression())
|
|
93
|
+
|
|
94
|
+
if parser.kind() not in (TOKEN_COMMA, TOKEN_OR):
|
|
95
|
+
break
|
|
96
|
+
|
|
97
|
+
parser.next()
|
|
98
|
+
|
|
99
|
+
parser.carry_whitespace_control()
|
|
100
|
+
parser.eat(TOKEN_TAG_END)
|
|
101
|
+
block = parser.parse_block(("endcase", "when", "else"))
|
|
102
|
+
return WhenBlock(token, right, block)
|
|
103
|
+
|
|
104
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
105
|
+
left = self.expression.evaluate(context)
|
|
106
|
+
alt = True
|
|
107
|
+
|
|
108
|
+
# NOTE: This does not stop at the first truthy expression, nor does it stop
|
|
109
|
+
# at the first `else` block.
|
|
110
|
+
|
|
111
|
+
for block in self.blocks:
|
|
112
|
+
if isinstance(block, ElseBlock):
|
|
113
|
+
if alt:
|
|
114
|
+
block.render(context, buffer)
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
for expr in block.right:
|
|
118
|
+
if context.env.eq(left, expr.evaluate(context), context, expr.span):
|
|
119
|
+
alt = False
|
|
120
|
+
block.render(context, buffer)
|
|
121
|
+
|
|
122
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
123
|
+
left = await self.expression.evaluate_async(context)
|
|
124
|
+
alt = True
|
|
125
|
+
|
|
126
|
+
# NOTE: This does not stop at the first truthy expression, nor does it stop
|
|
127
|
+
# at the first `else` block.
|
|
128
|
+
|
|
129
|
+
for block in self.blocks:
|
|
130
|
+
if isinstance(block, ElseBlock):
|
|
131
|
+
if alt:
|
|
132
|
+
await block.render_async(context, buffer)
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
for expr in block.right:
|
|
136
|
+
if context.env.eq(
|
|
137
|
+
left, await expr.evaluate_async(context), context, expr.span
|
|
138
|
+
):
|
|
139
|
+
alt = False
|
|
140
|
+
await block.render_async(context, buffer)
|
|
141
|
+
|
|
142
|
+
def children(self) -> list[Markup]:
|
|
143
|
+
return list(self.blocks)
|
|
144
|
+
|
|
145
|
+
def expressions(self) -> list[Expression]:
|
|
146
|
+
return [self.expression]
|
|
147
|
+
|
|
148
|
+
def block_scope(self) -> list[Name]:
|
|
149
|
+
return []
|
|
150
|
+
|
|
151
|
+
def template_scope(self) -> list[Name]:
|
|
152
|
+
return []
|
|
153
|
+
|
|
154
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
155
|
+
return []
|
|
156
|
+
|
|
157
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
158
|
+
return []
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class WhenBlock:
|
|
162
|
+
tag = "when"
|
|
163
|
+
|
|
164
|
+
def __init__(self, token: Token, right: list[Expression], block: Block) -> None:
|
|
165
|
+
self.token = token
|
|
166
|
+
self.right = right
|
|
167
|
+
self.block = block
|
|
168
|
+
self.blank = is_blank_block(block)
|
|
169
|
+
|
|
170
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
171
|
+
render_block(self.block, context, buffer)
|
|
172
|
+
|
|
173
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
174
|
+
await render_block_async(self.block, context, buffer)
|
|
175
|
+
|
|
176
|
+
def children(self) -> list[Markup]:
|
|
177
|
+
return [node for node in self.block if not isinstance(node, str)]
|
|
178
|
+
|
|
179
|
+
async def children_async(self) -> list[Markup]:
|
|
180
|
+
return self.children()
|
|
181
|
+
|
|
182
|
+
def expressions(self) -> list[Expression]:
|
|
183
|
+
return self.right
|
|
184
|
+
|
|
185
|
+
def block_scope(self) -> list[Name]:
|
|
186
|
+
return []
|
|
187
|
+
|
|
188
|
+
def template_scope(self) -> list[Name]:
|
|
189
|
+
return []
|
|
190
|
+
|
|
191
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
192
|
+
return []
|
|
193
|
+
|
|
194
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
195
|
+
return []
|
|
196
|
+
|
|
197
|
+
def filter_strings(self) -> None:
|
|
198
|
+
markup: Block = [node for node in self.block if not isinstance(node, str)]
|
|
199
|
+
self.block = markup
|
liquid/tags/comment.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from ..tokens import TOKEN_COMMENT, TOKEN_TAG_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 CommentTag:
|
|
17
|
+
blank = True
|
|
18
|
+
tag = "comment"
|
|
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
|
+
comment = parser.eat(TOKEN_COMMENT)
|
|
31
|
+
parser.eat_empty_tag("endcomment")
|
|
32
|
+
return CommentTag(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 []
|
liquid/tags/cycle.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from ..expressions import Variable
|
|
7
|
+
from ..tokens import TOKEN_COLON, TOKEN_COMMA, TOKEN_TAG_END
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .._context import RenderContext
|
|
11
|
+
from .._io import StringBuffer
|
|
12
|
+
from .._markup import Expression, Markup, Partial
|
|
13
|
+
from .._parser import Parser
|
|
14
|
+
from ..expressions import Name
|
|
15
|
+
from ..tokens import Token
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CycleTag:
|
|
19
|
+
blank = False
|
|
20
|
+
tag = "cycle"
|
|
21
|
+
|
|
22
|
+
__slots__ = ("group", "items", "static_key", "token")
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self, token: Token, group: Expression | None, items: list[Expression]
|
|
26
|
+
) -> None:
|
|
27
|
+
self.token = token
|
|
28
|
+
self.group = group
|
|
29
|
+
self.items = items
|
|
30
|
+
self.static_key = ""
|
|
31
|
+
|
|
32
|
+
if group is None:
|
|
33
|
+
# This mimics Shopify/liquid by giving every Variable a unique ID.
|
|
34
|
+
self.static_key = str(
|
|
35
|
+
[f"{i}:{id(i)}" if isinstance(i, Variable) else str(i) for i in items]
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def parse(token: Token, parser: Parser) -> Markup:
|
|
40
|
+
group: Expression | None = None
|
|
41
|
+
items: list[Expression] = []
|
|
42
|
+
|
|
43
|
+
first = parser.parse_expression()
|
|
44
|
+
|
|
45
|
+
if parser.kind() == TOKEN_COLON:
|
|
46
|
+
group = first
|
|
47
|
+
parser.next()
|
|
48
|
+
else:
|
|
49
|
+
items.append(first)
|
|
50
|
+
|
|
51
|
+
if parser.kind() == TOKEN_COMMA:
|
|
52
|
+
parser.next()
|
|
53
|
+
|
|
54
|
+
items.extend(parser.parse_positional_arguments())
|
|
55
|
+
parser.carry_whitespace_control()
|
|
56
|
+
parser.eat(TOKEN_TAG_END)
|
|
57
|
+
|
|
58
|
+
return CycleTag(token, group, items)
|
|
59
|
+
|
|
60
|
+
def render(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
61
|
+
cycles = context.get_register("cycle", default=register_factory)
|
|
62
|
+
key = self.static_key
|
|
63
|
+
|
|
64
|
+
if self.group:
|
|
65
|
+
key = context.env.to_str(
|
|
66
|
+
self.group.evaluate(context),
|
|
67
|
+
context,
|
|
68
|
+
self.group.span,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
index = cycles[key]
|
|
72
|
+
|
|
73
|
+
if index < len(self.items):
|
|
74
|
+
expr = self.items[index]
|
|
75
|
+
buffer.write(context.env.to_str(expr.evaluate(context), context, expr.span))
|
|
76
|
+
|
|
77
|
+
index += 1
|
|
78
|
+
if index >= len(self.items):
|
|
79
|
+
index = 0
|
|
80
|
+
|
|
81
|
+
cycles[key] = index
|
|
82
|
+
|
|
83
|
+
async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
|
|
84
|
+
cycles = context.get_register("cycle", default=register_factory)
|
|
85
|
+
key = self.static_key
|
|
86
|
+
|
|
87
|
+
if self.group:
|
|
88
|
+
key = context.env.to_str(
|
|
89
|
+
await self.group.evaluate_async(context),
|
|
90
|
+
context,
|
|
91
|
+
self.group.span,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
index = cycles[key]
|
|
95
|
+
|
|
96
|
+
if index < len(self.items):
|
|
97
|
+
expr = self.items[index]
|
|
98
|
+
|
|
99
|
+
buffer.write(
|
|
100
|
+
context.env.to_str(
|
|
101
|
+
await expr.evaluate_async(context), context, expr.span
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
index += 1
|
|
106
|
+
if index >= len(self.items):
|
|
107
|
+
index = 0
|
|
108
|
+
|
|
109
|
+
cycles[key] = index
|
|
110
|
+
|
|
111
|
+
def children(self) -> list[Markup]:
|
|
112
|
+
return []
|
|
113
|
+
|
|
114
|
+
def expressions(self) -> list[Expression]:
|
|
115
|
+
return [self.group, *self.items] if self.group else self.items
|
|
116
|
+
|
|
117
|
+
def block_scope(self) -> list[Name]:
|
|
118
|
+
return []
|
|
119
|
+
|
|
120
|
+
def template_scope(self) -> list[Name]:
|
|
121
|
+
return []
|
|
122
|
+
|
|
123
|
+
def partials(self, context: RenderContext) -> list[Partial]:
|
|
124
|
+
return []
|
|
125
|
+
|
|
126
|
+
async def partials_async(self, context: RenderContext) -> list[Partial]:
|
|
127
|
+
return []
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def register_factory() -> defaultdict[str, int]:
|
|
131
|
+
return defaultdict(int)
|