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
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
"""A template loader that reads templates from Python packages."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import asyncio
|
|
6
|
-
import os
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
9
|
-
from typing import Iterable
|
|
10
|
-
from typing import Optional
|
|
11
|
-
from typing import Union
|
|
12
|
-
|
|
13
|
-
import importlib_resources
|
|
14
|
-
|
|
15
|
-
from liquid.exceptions import TemplateNotFoundError
|
|
16
|
-
from liquid.loader import BaseLoader
|
|
17
|
-
from liquid.loader import TemplateSource
|
|
18
|
-
|
|
19
|
-
if TYPE_CHECKING:
|
|
20
|
-
from types import ModuleType
|
|
21
|
-
|
|
22
|
-
from liquid import Environment
|
|
23
|
-
from liquid import RenderContext
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class PackageLoader(BaseLoader):
|
|
27
|
-
"""A template loader that reads templates from Python packages.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
package: Import name of a package containing Liquid templates.
|
|
31
|
-
package_path: One or more directories in the package containing Liquid
|
|
32
|
-
templates.
|
|
33
|
-
encoding: Encoding of template files.
|
|
34
|
-
ext: A default file extension to use if one is not provided. Should
|
|
35
|
-
include a leading period.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
def __init__(
|
|
39
|
-
self,
|
|
40
|
-
package: Union[str, ModuleType],
|
|
41
|
-
*,
|
|
42
|
-
package_path: Union[str, Iterable[str]] = "templates",
|
|
43
|
-
encoding: str = "utf-8",
|
|
44
|
-
ext: str = ".liquid",
|
|
45
|
-
) -> None:
|
|
46
|
-
if isinstance(package_path, str):
|
|
47
|
-
self.paths = [importlib_resources.files(package).joinpath(package_path)]
|
|
48
|
-
else:
|
|
49
|
-
_package = importlib_resources.files(package)
|
|
50
|
-
self.paths = [_package.joinpath(path) for path in package_path]
|
|
51
|
-
|
|
52
|
-
self.encoding = encoding
|
|
53
|
-
self.ext = ext
|
|
54
|
-
|
|
55
|
-
def _resolve_path(self, template_name: str) -> Path:
|
|
56
|
-
template_path = Path(template_name)
|
|
57
|
-
|
|
58
|
-
if not template_path.name:
|
|
59
|
-
raise TemplateNotFoundError(template_name)
|
|
60
|
-
|
|
61
|
-
# Add suffix self.ext if template name does not have a suffix.
|
|
62
|
-
if not template_path.suffix:
|
|
63
|
-
template_path = template_path.with_suffix(self.ext)
|
|
64
|
-
|
|
65
|
-
if os.path.pardir in template_path.parts or template_path.is_absolute():
|
|
66
|
-
raise TemplateNotFoundError(template_name)
|
|
67
|
-
|
|
68
|
-
for path in self.paths:
|
|
69
|
-
with importlib_resources.as_file(path) as base:
|
|
70
|
-
source_path: Path = base.joinpath(template_path)
|
|
71
|
-
|
|
72
|
-
try:
|
|
73
|
-
if not source_path.exists() or not source_path.is_file():
|
|
74
|
-
continue
|
|
75
|
-
except OSError:
|
|
76
|
-
# "File name too long", for example
|
|
77
|
-
continue
|
|
78
|
-
|
|
79
|
-
resolved = source_path.resolve(strict=False)
|
|
80
|
-
base_resolved = base.resolve(strict=False)
|
|
81
|
-
|
|
82
|
-
if not resolved.is_relative_to(base_resolved):
|
|
83
|
-
continue
|
|
84
|
-
|
|
85
|
-
return source_path
|
|
86
|
-
|
|
87
|
-
raise TemplateNotFoundError(template_name)
|
|
88
|
-
|
|
89
|
-
def get_source(
|
|
90
|
-
self,
|
|
91
|
-
env: Environment, # noqa: ARG002
|
|
92
|
-
template_name: str,
|
|
93
|
-
*,
|
|
94
|
-
context: Optional[RenderContext] = None, # noqa: ARG002
|
|
95
|
-
**kwargs: object, # noqa: ARG002
|
|
96
|
-
) -> TemplateSource:
|
|
97
|
-
"""Get source information for a template."""
|
|
98
|
-
source_path = self._resolve_path(template_name)
|
|
99
|
-
return TemplateSource(
|
|
100
|
-
text=source_path.read_text(self.encoding),
|
|
101
|
-
name=str(source_path),
|
|
102
|
-
uptodate=None,
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
async def get_source_async(
|
|
106
|
-
self,
|
|
107
|
-
env: Environment, # noqa: ARG002
|
|
108
|
-
template_name: str,
|
|
109
|
-
*,
|
|
110
|
-
context: Optional[RenderContext] = None, # noqa: ARG002
|
|
111
|
-
**kwargs: object, # noqa: ARG002
|
|
112
|
-
) -> TemplateSource:
|
|
113
|
-
"""Get source information for a template."""
|
|
114
|
-
loop = asyncio.get_running_loop()
|
|
115
|
-
|
|
116
|
-
source_path = await loop.run_in_executor(
|
|
117
|
-
None,
|
|
118
|
-
self._resolve_path,
|
|
119
|
-
template_name,
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
source_text = await loop.run_in_executor(
|
|
123
|
-
None,
|
|
124
|
-
source_path.read_text,
|
|
125
|
-
self.encoding,
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
return TemplateSource(
|
|
129
|
-
text=source_text,
|
|
130
|
-
name=str(source_path),
|
|
131
|
-
uptodate=None,
|
|
132
|
-
)
|
liquid/builtin/output.py
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"""Parse tree node and pseudo "tag" for output statements."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
|
-
from typing import Iterable
|
|
7
|
-
from typing import TextIO
|
|
8
|
-
|
|
9
|
-
from liquid.ast import Node
|
|
10
|
-
from liquid.builtin.expressions import FilteredExpression
|
|
11
|
-
from liquid.builtin.expressions import tokenize
|
|
12
|
-
from liquid.stream import TokenStream
|
|
13
|
-
from liquid.stringify import to_liquid_string
|
|
14
|
-
from liquid.tag import Tag
|
|
15
|
-
from liquid.token import TOKEN_EXPRESSION
|
|
16
|
-
from liquid.token import TOKEN_OUTPUT
|
|
17
|
-
from liquid.token import Token
|
|
18
|
-
|
|
19
|
-
if TYPE_CHECKING:
|
|
20
|
-
from liquid.context import RenderContext
|
|
21
|
-
from liquid.expression import Expression
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class OutputNode(Node):
|
|
25
|
-
"""Parse tree node representing an output statement."""
|
|
26
|
-
|
|
27
|
-
__slots__ = ("expression",)
|
|
28
|
-
|
|
29
|
-
def __init__(self, token: Token, expression: Expression):
|
|
30
|
-
super().__init__(token)
|
|
31
|
-
self.expression = expression
|
|
32
|
-
self.blank = False
|
|
33
|
-
|
|
34
|
-
def __str__(self) -> str:
|
|
35
|
-
return f"{{{{ {self.expression} }}}}"
|
|
36
|
-
|
|
37
|
-
def render_to_output(self, context: RenderContext, buffer: TextIO) -> int:
|
|
38
|
-
"""Render the node to the output buffer."""
|
|
39
|
-
return buffer.write(
|
|
40
|
-
to_liquid_string(self.expression.evaluate(context), context.autoescape)
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
async def render_to_output_async(
|
|
44
|
-
self, context: RenderContext, buffer: TextIO
|
|
45
|
-
) -> int:
|
|
46
|
-
"""Render the node to the output buffer."""
|
|
47
|
-
return buffer.write(
|
|
48
|
-
to_liquid_string(
|
|
49
|
-
await self.expression.evaluate_async(context), context.autoescape
|
|
50
|
-
)
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
def expressions(self) -> Iterable[Expression]:
|
|
54
|
-
"""Return this node's children."""
|
|
55
|
-
yield self.expression
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class Output(Tag):
|
|
59
|
-
"""Pseudo "tag" to register output statements with the environment."""
|
|
60
|
-
|
|
61
|
-
name = TOKEN_OUTPUT
|
|
62
|
-
block = False
|
|
63
|
-
node_class = OutputNode
|
|
64
|
-
|
|
65
|
-
def parse(self, stream: TokenStream) -> OutputNode:
|
|
66
|
-
"""Parse tokens from _stream_ into an AST node."""
|
|
67
|
-
token = stream.eat(TOKEN_OUTPUT)
|
|
68
|
-
expr_token = stream.expect(TOKEN_EXPRESSION)
|
|
69
|
-
return self.node_class(
|
|
70
|
-
token,
|
|
71
|
-
FilteredExpression.parse(
|
|
72
|
-
self.env, TokenStream(tokenize(expr_token.value, expr_token))
|
|
73
|
-
),
|
|
74
|
-
)
|
liquid/builtin/tags/__init__.py
DELETED
|
File without changes
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
"""The built-in _assign_ 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 FilteredExpression
|
|
12
|
-
from liquid.builtin.expressions import Identifier
|
|
13
|
-
from liquid.builtin.expressions import parse_identifier
|
|
14
|
-
from liquid.tag import Tag
|
|
15
|
-
from liquid.token import TOKEN_ASSIGN
|
|
16
|
-
from liquid.token import TOKEN_TAG
|
|
17
|
-
from liquid.token import Token
|
|
18
|
-
|
|
19
|
-
if TYPE_CHECKING:
|
|
20
|
-
from liquid.context import RenderContext
|
|
21
|
-
from liquid.expression import Expression
|
|
22
|
-
from liquid.stream import TokenStream
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
TAG_ASSIGN = sys.intern("assign")
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class AssignNode(Node):
|
|
29
|
-
"""The built-in _assign_ tag."""
|
|
30
|
-
|
|
31
|
-
__slots__ = ("name", "expression")
|
|
32
|
-
|
|
33
|
-
def __init__(self, token: Token, *, name: Identifier, expression: Expression):
|
|
34
|
-
super().__init__(token)
|
|
35
|
-
self.name = name
|
|
36
|
-
self.expression = expression
|
|
37
|
-
|
|
38
|
-
def __str__(self) -> str:
|
|
39
|
-
return f"{{% assign {self.name} = {self.expression} %}}"
|
|
40
|
-
|
|
41
|
-
def render_to_output(self, context: RenderContext, _: TextIO) -> int:
|
|
42
|
-
"""Render the node to the output buffer."""
|
|
43
|
-
context.assign(self.name, self.expression.evaluate(context))
|
|
44
|
-
return 0
|
|
45
|
-
|
|
46
|
-
async def render_to_output_async(self, context: RenderContext, _: TextIO) -> int:
|
|
47
|
-
"""Render the node to the output buffer."""
|
|
48
|
-
context.assign(self.name, await self.expression.evaluate_async(context))
|
|
49
|
-
return 0
|
|
50
|
-
|
|
51
|
-
def expressions(self) -> Iterable[Expression]:
|
|
52
|
-
"""Return this node's expressions."""
|
|
53
|
-
yield self.expression
|
|
54
|
-
|
|
55
|
-
def template_scope(self) -> Iterable[Identifier]:
|
|
56
|
-
"""Return variables this node adds to the template local scope."""
|
|
57
|
-
yield self.name
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class AssignTag(Tag):
|
|
61
|
-
"""The built-in _assign_ tag."""
|
|
62
|
-
|
|
63
|
-
name = TAG_ASSIGN
|
|
64
|
-
block = False
|
|
65
|
-
node_class = AssignNode
|
|
66
|
-
|
|
67
|
-
def parse(self, stream: TokenStream) -> AssignNode:
|
|
68
|
-
"""Parse tokens from _stream_ into an AST node."""
|
|
69
|
-
token = stream.eat(TOKEN_TAG)
|
|
70
|
-
tokens = stream.into_inner(tag=token, eat=False)
|
|
71
|
-
name = parse_identifier(self.env, tokens, allow_trailing_question_mark=False)
|
|
72
|
-
tokens.eat(TOKEN_ASSIGN)
|
|
73
|
-
return self.node_class(
|
|
74
|
-
token, name=name, expression=FilteredExpression.parse(self.env, tokens)
|
|
75
|
-
)
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
"""Parse tree node and tag definition for the built-in "capture" 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 import Markup
|
|
11
|
-
from liquid.ast import BlockNode
|
|
12
|
-
from liquid.ast import Node
|
|
13
|
-
from liquid.builtin.expressions import parse_identifier
|
|
14
|
-
from liquid.parser import get_parser
|
|
15
|
-
from liquid.tag import Tag
|
|
16
|
-
from liquid.token import TOKEN_EOF
|
|
17
|
-
from liquid.token import TOKEN_TAG
|
|
18
|
-
from liquid.token import Token
|
|
19
|
-
|
|
20
|
-
if TYPE_CHECKING:
|
|
21
|
-
from io import StringIO
|
|
22
|
-
|
|
23
|
-
from liquid.builtin.expressions import Identifier
|
|
24
|
-
from liquid.context import RenderContext
|
|
25
|
-
from liquid.stream import TokenStream
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
TAG_CAPTURE = sys.intern("capture")
|
|
29
|
-
TAG_ENDCAPTURE = sys.intern("endcapture")
|
|
30
|
-
|
|
31
|
-
ENDCAPTUREBLOCK = frozenset((TAG_ENDCAPTURE, TOKEN_EOF))
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class CaptureNode(Node):
|
|
35
|
-
"""Parse tree node for the built-in "capture" tag."""
|
|
36
|
-
|
|
37
|
-
__slots__ = ("name", "block")
|
|
38
|
-
|
|
39
|
-
def __init__(self, token: Token, name: Identifier, block: BlockNode):
|
|
40
|
-
super().__init__(token)
|
|
41
|
-
self.name = name
|
|
42
|
-
self.block = block
|
|
43
|
-
|
|
44
|
-
def __str__(self) -> str:
|
|
45
|
-
return f"{{% capture {self.name} %}}{self.block}{{% endcapture %}}"
|
|
46
|
-
|
|
47
|
-
def _assign(self, context: RenderContext, buf: StringIO) -> None:
|
|
48
|
-
if context.autoescape:
|
|
49
|
-
context.assign(self.name, Markup(buf.getvalue()))
|
|
50
|
-
else:
|
|
51
|
-
context.assign(self.name, buf.getvalue())
|
|
52
|
-
|
|
53
|
-
def render_to_output(self, context: RenderContext, buffer: TextIO) -> int:
|
|
54
|
-
"""Render the node to the output buffer."""
|
|
55
|
-
buf = context.get_buffer(buffer)
|
|
56
|
-
self.block.render(context, buf)
|
|
57
|
-
self._assign(context, buf)
|
|
58
|
-
return False
|
|
59
|
-
|
|
60
|
-
async def render_to_output_async(
|
|
61
|
-
self, context: RenderContext, buffer: TextIO
|
|
62
|
-
) -> int:
|
|
63
|
-
"""Render the node to the output buffer."""
|
|
64
|
-
buf = context.get_buffer(buffer)
|
|
65
|
-
await self.block.render_async(context, buf)
|
|
66
|
-
self._assign(context, buf)
|
|
67
|
-
return False
|
|
68
|
-
|
|
69
|
-
def children(
|
|
70
|
-
self,
|
|
71
|
-
static_context: RenderContext, # noqa: ARG002
|
|
72
|
-
*,
|
|
73
|
-
include_partials: bool = True, # noqa: ARG002
|
|
74
|
-
) -> Iterable[Node]:
|
|
75
|
-
"""Return this node's children."""
|
|
76
|
-
yield self.block
|
|
77
|
-
|
|
78
|
-
def template_scope(self) -> Iterable[Identifier]:
|
|
79
|
-
"""Return variables this node adds to the template local scope."""
|
|
80
|
-
yield self.name
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
class CaptureTag(Tag):
|
|
84
|
-
"""The built-in capture tag."""
|
|
85
|
-
|
|
86
|
-
name = TAG_CAPTURE
|
|
87
|
-
end = TAG_ENDCAPTURE
|
|
88
|
-
node_class = CaptureNode
|
|
89
|
-
|
|
90
|
-
def parse(self, stream: TokenStream) -> CaptureNode:
|
|
91
|
-
"""Parse tokens from _stream_ into an AST node."""
|
|
92
|
-
token = stream.eat(TOKEN_TAG)
|
|
93
|
-
tokens = stream.into_inner(tag=token)
|
|
94
|
-
name = parse_identifier(self.env, tokens)
|
|
95
|
-
tokens.expect_eos()
|
|
96
|
-
block = get_parser(self.env).parse_block(stream, ENDCAPTUREBLOCK)
|
|
97
|
-
stream.expect(TOKEN_TAG, value=TAG_ENDCAPTURE)
|
|
98
|
-
return self.node_class(token, name, block)
|
liquid/builtin/tags/case_tag.py
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
"""The built-in _case_ 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
|
-
from typing import Union
|
|
10
|
-
|
|
11
|
-
from liquid.ast import BlockNode
|
|
12
|
-
from liquid.ast import Node
|
|
13
|
-
from liquid.builtin.expressions import parse_primitive
|
|
14
|
-
from liquid.builtin.expressions.logical import _eq # type: ignore
|
|
15
|
-
from liquid.exceptions import LiquidSyntaxError
|
|
16
|
-
from liquid.expression import Expression
|
|
17
|
-
from liquid.parser import get_parser
|
|
18
|
-
from liquid.tag import Tag
|
|
19
|
-
from liquid.token import TOKEN_COMMA
|
|
20
|
-
from liquid.token import TOKEN_EOF
|
|
21
|
-
from liquid.token import TOKEN_OR
|
|
22
|
-
from liquid.token import TOKEN_TAG
|
|
23
|
-
from liquid.token import Token
|
|
24
|
-
|
|
25
|
-
if TYPE_CHECKING:
|
|
26
|
-
from liquid import Environment
|
|
27
|
-
from liquid import TokenStream
|
|
28
|
-
from liquid.context import RenderContext
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
TAG_CASE = sys.intern("case")
|
|
32
|
-
TAG_ENDCASE = sys.intern("endcase")
|
|
33
|
-
TAG_WHEN = sys.intern("when")
|
|
34
|
-
TAG_ELSE = sys.intern("else")
|
|
35
|
-
|
|
36
|
-
ENDWHENBLOCK = frozenset((TAG_ENDCASE, TAG_WHEN, TAG_ELSE, TOKEN_EOF))
|
|
37
|
-
ENDCASEBLOCK = frozenset((TAG_ENDCASE, TAG_ELSE))
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class CaseNode(Node):
|
|
41
|
-
"""The built-in _case_ tag."""
|
|
42
|
-
|
|
43
|
-
__slots__ = ("blocks", "expression")
|
|
44
|
-
|
|
45
|
-
def __init__(
|
|
46
|
-
self,
|
|
47
|
-
token: Token,
|
|
48
|
-
expression: Expression,
|
|
49
|
-
blocks: list[Union[MultiExpressionBlockNode, BlockNode]],
|
|
50
|
-
):
|
|
51
|
-
super().__init__(token)
|
|
52
|
-
self.expression = expression
|
|
53
|
-
self.blocks = blocks
|
|
54
|
-
|
|
55
|
-
self.blank = all(node.blank for node in self.blocks)
|
|
56
|
-
|
|
57
|
-
for node in self.blocks:
|
|
58
|
-
node.blank = self.blank
|
|
59
|
-
if isinstance(node, MultiExpressionBlockNode):
|
|
60
|
-
node.block.blank = self.blank
|
|
61
|
-
|
|
62
|
-
def __str__(self) -> str:
|
|
63
|
-
blocks: list[str] = []
|
|
64
|
-
for block in self.blocks:
|
|
65
|
-
if isinstance(block, BlockNode):
|
|
66
|
-
blocks.append(f"{{% else %}}{block}")
|
|
67
|
-
else:
|
|
68
|
-
blocks.append(str(block))
|
|
69
|
-
|
|
70
|
-
blocks_ = "".join(blocks)
|
|
71
|
-
|
|
72
|
-
return f"{{% case {self.expression} %}}\n{blocks_}{{% endcase %}}"
|
|
73
|
-
|
|
74
|
-
def render_to_output(self, context: RenderContext, buffer: TextIO) -> int:
|
|
75
|
-
"""Render the node to the output buffer."""
|
|
76
|
-
count = 0
|
|
77
|
-
default = True
|
|
78
|
-
|
|
79
|
-
for block in self.blocks:
|
|
80
|
-
# Always render truthy `when` blocks, no matter the order.
|
|
81
|
-
if isinstance(block, MultiExpressionBlockNode):
|
|
82
|
-
count_ = block.render(context, buffer)
|
|
83
|
-
if count_ >= 0:
|
|
84
|
-
default = False
|
|
85
|
-
count += count_
|
|
86
|
-
# Only render `else` blocks if all preceding `when` blocks are falsy.
|
|
87
|
-
# Multiple `else` blocks are OK.
|
|
88
|
-
elif default:
|
|
89
|
-
count += block.render(context, buffer)
|
|
90
|
-
|
|
91
|
-
return count
|
|
92
|
-
|
|
93
|
-
async def render_to_output_async(
|
|
94
|
-
self, context: RenderContext, buffer: TextIO
|
|
95
|
-
) -> int:
|
|
96
|
-
"""Render the node to the output buffer."""
|
|
97
|
-
count = 0
|
|
98
|
-
default = True
|
|
99
|
-
|
|
100
|
-
for block in self.blocks:
|
|
101
|
-
# Always render truthy `when` blocks, no matter the order.
|
|
102
|
-
if isinstance(block, MultiExpressionBlockNode):
|
|
103
|
-
count_ = await block.render_async(context, buffer)
|
|
104
|
-
if count_ >= 0:
|
|
105
|
-
default = False
|
|
106
|
-
count += count_
|
|
107
|
-
# Only render `else` blocks if all preceding `when` blocks are falsy.
|
|
108
|
-
# Multiple `else` blocks are OK.
|
|
109
|
-
elif default:
|
|
110
|
-
count += await block.render_async(context, buffer)
|
|
111
|
-
|
|
112
|
-
return count
|
|
113
|
-
|
|
114
|
-
def children(
|
|
115
|
-
self,
|
|
116
|
-
static_context: RenderContext, # noqa: ARG002
|
|
117
|
-
*,
|
|
118
|
-
include_partials: bool = True, # noqa: ARG002
|
|
119
|
-
) -> Iterable[Node]:
|
|
120
|
-
"""Return this node's children."""
|
|
121
|
-
yield from self.blocks
|
|
122
|
-
|
|
123
|
-
def expressions(self) -> Iterable[Expression]:
|
|
124
|
-
"""Return this node's expressions."""
|
|
125
|
-
yield self.expression
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
class CaseTag(Tag):
|
|
129
|
-
"""The built-in _case_ tag."""
|
|
130
|
-
|
|
131
|
-
name = TAG_CASE
|
|
132
|
-
end = TAG_ENDCASE
|
|
133
|
-
node_class = CaseNode
|
|
134
|
-
|
|
135
|
-
def __init__(self, env: Environment):
|
|
136
|
-
super().__init__(env)
|
|
137
|
-
self.parser = get_parser(self.env)
|
|
138
|
-
|
|
139
|
-
def parse(self, stream: TokenStream) -> Node:
|
|
140
|
-
"""Parse tokens from _stream_ into an AST node."""
|
|
141
|
-
token = stream.eat(TOKEN_TAG)
|
|
142
|
-
tokens = stream.into_inner(tag=token)
|
|
143
|
-
left = parse_primitive(self.env, tokens)
|
|
144
|
-
tokens.expect_eos()
|
|
145
|
-
|
|
146
|
-
# Eat whitespace or junk between `case` and when/else/endcase
|
|
147
|
-
while (
|
|
148
|
-
stream.current.kind not in (TOKEN_TAG, TOKEN_EOF)
|
|
149
|
-
and stream.current.value not in ENDWHENBLOCK
|
|
150
|
-
):
|
|
151
|
-
next(stream)
|
|
152
|
-
|
|
153
|
-
parse_block = get_parser(self.env).parse_block
|
|
154
|
-
blocks: list[Union[MultiExpressionBlockNode, BlockNode]] = []
|
|
155
|
-
|
|
156
|
-
while not stream.current.is_tag(TAG_ENDCASE):
|
|
157
|
-
if stream.current.is_tag(TAG_ELSE):
|
|
158
|
-
next(stream)
|
|
159
|
-
blocks.append(parse_block(stream, ENDWHENBLOCK))
|
|
160
|
-
elif stream.current.is_tag(TAG_WHEN):
|
|
161
|
-
alternative_token = next(stream)
|
|
162
|
-
expressions = self._parse_when_expression(
|
|
163
|
-
stream.into_inner(tag=alternative_token)
|
|
164
|
-
)
|
|
165
|
-
alternative_block = parse_block(stream, ENDWHENBLOCK)
|
|
166
|
-
|
|
167
|
-
blocks.append(
|
|
168
|
-
MultiExpressionBlockNode(
|
|
169
|
-
alternative_token,
|
|
170
|
-
alternative_block,
|
|
171
|
-
_AnyExpression(alternative_token, left, expressions),
|
|
172
|
-
)
|
|
173
|
-
)
|
|
174
|
-
else:
|
|
175
|
-
raise LiquidSyntaxError(
|
|
176
|
-
f"unexpected tag {stream.current.value!r}", token=stream.current
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
stream.expect(TOKEN_TAG, TAG_ENDCASE)
|
|
180
|
-
|
|
181
|
-
return self.node_class(
|
|
182
|
-
token,
|
|
183
|
-
left,
|
|
184
|
-
blocks,
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
def _parse_when_expression(self, stream: TokenStream) -> list[Expression]:
|
|
188
|
-
expressions: list[Expression] = [parse_primitive(self.env, stream)]
|
|
189
|
-
while stream.current.kind in (TOKEN_COMMA, TOKEN_OR):
|
|
190
|
-
next(stream)
|
|
191
|
-
try:
|
|
192
|
-
expressions.append(parse_primitive(self.env, stream))
|
|
193
|
-
except LiquidSyntaxError:
|
|
194
|
-
# Use expressions we have so far an discard the rest.
|
|
195
|
-
return expressions
|
|
196
|
-
|
|
197
|
-
return expressions
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
class _AnyExpression(Expression):
|
|
201
|
-
__slots__ = (
|
|
202
|
-
"left",
|
|
203
|
-
"expressions",
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
def __init__(
|
|
207
|
-
self, token: Token, left: Expression, expressions: list[Expression]
|
|
208
|
-
) -> None:
|
|
209
|
-
super().__init__(token)
|
|
210
|
-
self.left = left
|
|
211
|
-
self.expressions = expressions
|
|
212
|
-
|
|
213
|
-
def __str__(self) -> str:
|
|
214
|
-
return ", ".join(str(expr) for expr in self.expressions)
|
|
215
|
-
|
|
216
|
-
def evaluate(self, context: RenderContext) -> list[bool]:
|
|
217
|
-
left = self.left.evaluate(context)
|
|
218
|
-
return [_eq(left, right.evaluate(context)) for right in self.expressions]
|
|
219
|
-
|
|
220
|
-
async def evaluate_async(self, context: RenderContext) -> list[bool]:
|
|
221
|
-
left = await self.left.evaluate_async(context)
|
|
222
|
-
return [
|
|
223
|
-
_eq(left, await right.evaluate_async(context)) for right in self.expressions
|
|
224
|
-
]
|
|
225
|
-
|
|
226
|
-
def children(self) -> list[Expression]:
|
|
227
|
-
return self.expressions
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
class MultiExpressionBlockNode(Node):
|
|
231
|
-
"""A node containing a sequence of nodes guarded by a choice of expressions."""
|
|
232
|
-
|
|
233
|
-
__slots__ = ("block", "expression")
|
|
234
|
-
|
|
235
|
-
def __init__(
|
|
236
|
-
self,
|
|
237
|
-
token: Token,
|
|
238
|
-
block: BlockNode,
|
|
239
|
-
expression: _AnyExpression,
|
|
240
|
-
) -> None:
|
|
241
|
-
super().__init__(token)
|
|
242
|
-
self.block = block
|
|
243
|
-
self.expression = expression
|
|
244
|
-
self.blank = self.block.blank
|
|
245
|
-
|
|
246
|
-
def __str__(self) -> str:
|
|
247
|
-
return f"{{% when {self.expression} %}}{self.block}"
|
|
248
|
-
|
|
249
|
-
def render_to_output(self, context: RenderContext, buffer: TextIO) -> int:
|
|
250
|
-
"""Render the node to the output buffer."""
|
|
251
|
-
matches = self.expression.evaluate(context)
|
|
252
|
-
|
|
253
|
-
if any(matches):
|
|
254
|
-
return sum(
|
|
255
|
-
[self.block.render(context, buffer) for _ in range(matches.count(True))]
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
return -1
|
|
259
|
-
|
|
260
|
-
async def render_to_output_async(
|
|
261
|
-
self, context: RenderContext, buffer: TextIO
|
|
262
|
-
) -> int:
|
|
263
|
-
"""Render the node to the output buffer."""
|
|
264
|
-
matches = await self.expression.evaluate_async(context)
|
|
265
|
-
|
|
266
|
-
if any(matches):
|
|
267
|
-
return sum(
|
|
268
|
-
[
|
|
269
|
-
await self.block.render_async(context, buffer)
|
|
270
|
-
for _ in range(matches.count(True))
|
|
271
|
-
]
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
return -1
|
|
275
|
-
|
|
276
|
-
def children(
|
|
277
|
-
self,
|
|
278
|
-
static_context: RenderContext, # noqa: ARG002
|
|
279
|
-
*,
|
|
280
|
-
include_partials: bool = True, # noqa: ARG002
|
|
281
|
-
) -> Iterable[Node]:
|
|
282
|
-
"""Return this node's children."""
|
|
283
|
-
yield self.block
|
|
284
|
-
|
|
285
|
-
def expressions(self) -> Iterable[Expression]:
|
|
286
|
-
"""Return this node's expressions."""
|
|
287
|
-
yield self.expression
|