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/messages.py
DELETED
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
"""Translation related objects and message extraction."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
from abc import ABC
|
|
7
|
-
from abc import abstractmethod
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import TYPE_CHECKING
|
|
10
|
-
from typing import Any
|
|
11
|
-
from typing import Callable
|
|
12
|
-
from typing import Iterable
|
|
13
|
-
from typing import Iterator
|
|
14
|
-
from typing import NamedTuple
|
|
15
|
-
from typing import Protocol
|
|
16
|
-
from typing import Union
|
|
17
|
-
|
|
18
|
-
from babel.messages import Catalog
|
|
19
|
-
|
|
20
|
-
from .builtin.expressions import Filter
|
|
21
|
-
from .builtin.expressions import FilteredExpression
|
|
22
|
-
from .builtin.expressions import TernaryFilteredExpression
|
|
23
|
-
from .builtin.tags.comment_tag import CommentNode
|
|
24
|
-
from .context import RenderContext
|
|
25
|
-
from .token import TOKEN_TAG
|
|
26
|
-
from .token import Token
|
|
27
|
-
|
|
28
|
-
if TYPE_CHECKING:
|
|
29
|
-
from .ast import Node
|
|
30
|
-
from .environment import Environment
|
|
31
|
-
from .expression import Expression
|
|
32
|
-
from .template import BoundTemplate
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
DEFAULT_KEYWORDS = {
|
|
36
|
-
"t": None,
|
|
37
|
-
"trans": None,
|
|
38
|
-
"translate": None,
|
|
39
|
-
"gettext": None,
|
|
40
|
-
"ngettext": (1, 2),
|
|
41
|
-
"pgettext": ((1, "c"), 2),
|
|
42
|
-
"npgettext": ((1, "c"), 2, 3),
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
DEFAULT_COMMENT_TAGS = [
|
|
46
|
-
"Translators:",
|
|
47
|
-
]
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class Translations(Protocol):
|
|
51
|
-
"""Message catalog interface.
|
|
52
|
-
|
|
53
|
-
An object implementing this protocol is expected to be available in
|
|
54
|
-
a render context.
|
|
55
|
-
|
|
56
|
-
Could be a `GNUTranslations` instance from the `gettext` module,
|
|
57
|
-
a Babel `Translations` object, or any object implementing `gettext`,
|
|
58
|
-
`ngettext`, `pgettext` and `npgettext` methods.
|
|
59
|
-
"""
|
|
60
|
-
|
|
61
|
-
def gettext(self, message: str) -> str:
|
|
62
|
-
"""Lookup the message in the catalog."""
|
|
63
|
-
|
|
64
|
-
def ngettext(self, singular: str, plural: str, n: int) -> str:
|
|
65
|
-
"""Do plural-forms message lookup."""
|
|
66
|
-
|
|
67
|
-
def pgettext(self, context: str, message: str) -> str:
|
|
68
|
-
"""Lookup the context and message in the catalog."""
|
|
69
|
-
|
|
70
|
-
def npgettext(self, context: str, singular: str, plural: str, n: int) -> str:
|
|
71
|
-
"""Do plural-forms context and message lookup."""
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
MESSAGES = Union[
|
|
75
|
-
str,
|
|
76
|
-
tuple[str, ...],
|
|
77
|
-
tuple[tuple[str, str], str],
|
|
78
|
-
tuple[tuple[str, str], str, str],
|
|
79
|
-
]
|
|
80
|
-
|
|
81
|
-
SPEC = Union[
|
|
82
|
-
tuple[int, ...],
|
|
83
|
-
tuple[tuple[int, str], int],
|
|
84
|
-
tuple[tuple[int, str], int, int],
|
|
85
|
-
]
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class MessageTuple(NamedTuple):
|
|
89
|
-
"""The tuple expected to be returned from babel extraction methods."""
|
|
90
|
-
|
|
91
|
-
lineno: int
|
|
92
|
-
funcname: str
|
|
93
|
-
message: MESSAGES
|
|
94
|
-
comments: list[str]
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
class MessageText(NamedTuple):
|
|
98
|
-
"""Partial message tuple returned from translatable tags."""
|
|
99
|
-
|
|
100
|
-
lineno: int
|
|
101
|
-
funcname: str
|
|
102
|
-
message: MESSAGES
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
class TranslatableTag(ABC):
|
|
106
|
-
"""Base class for translatable tags."""
|
|
107
|
-
|
|
108
|
-
@abstractmethod
|
|
109
|
-
def messages(self) -> Iterable[MessageText]:
|
|
110
|
-
"""Generate a sequence of translation messages."""
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
class TranslatableFilter(ABC):
|
|
114
|
-
"""Base class for translatable filters."""
|
|
115
|
-
|
|
116
|
-
@abstractmethod
|
|
117
|
-
def message(
|
|
118
|
-
self,
|
|
119
|
-
left: Expression,
|
|
120
|
-
_filter: Filter,
|
|
121
|
-
lineno: int,
|
|
122
|
-
) -> MessageText | None:
|
|
123
|
-
"""Return a translation message."""
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def extract_from_templates(
|
|
127
|
-
*templates: BoundTemplate,
|
|
128
|
-
keywords: dict[str, Any] | None = None,
|
|
129
|
-
comment_tags: list[str] | None = None,
|
|
130
|
-
strip_comment_tags: bool = False,
|
|
131
|
-
) -> Catalog:
|
|
132
|
-
"""Extract messages from one or more templates.
|
|
133
|
-
|
|
134
|
-
This function returns a single `babel.messages.Catalog` containing
|
|
135
|
-
messages from all the given templates.
|
|
136
|
-
|
|
137
|
-
Args:
|
|
138
|
-
templates: templates to extract messages from.
|
|
139
|
-
keywords: a Babel compatible mapping of translatable "function"
|
|
140
|
-
names to argument specs. The included translation filters and tag
|
|
141
|
-
transform their messages into typical *gettext format, regardless
|
|
142
|
-
of their names.
|
|
143
|
-
comment_tags: a list of translator tags to search for and
|
|
144
|
-
include in extracted messages.
|
|
145
|
-
strip_comment_tags: if `True`, remove comment tags from collected
|
|
146
|
-
message comments.
|
|
147
|
-
"""
|
|
148
|
-
keywords = keywords or DEFAULT_KEYWORDS
|
|
149
|
-
comment_tags = comment_tags or DEFAULT_COMMENT_TAGS
|
|
150
|
-
catalog = Catalog()
|
|
151
|
-
for template in templates:
|
|
152
|
-
for lineno, funcname, messages, comments in extract_from_template(
|
|
153
|
-
template, keywords, comment_tags
|
|
154
|
-
):
|
|
155
|
-
# A partial reimplementation of Babel's messages.extract function.
|
|
156
|
-
# See https://github.com/python-babel/babel/blob/master/babel/messages/extract.py#L262
|
|
157
|
-
spec: SPEC = keywords[funcname] or (1,)
|
|
158
|
-
if not isinstance(messages, (list, tuple)):
|
|
159
|
-
messages = (messages,) # noqa: PLW2901
|
|
160
|
-
if not messages:
|
|
161
|
-
continue
|
|
162
|
-
if len(spec) != len(messages):
|
|
163
|
-
continue
|
|
164
|
-
|
|
165
|
-
# Assumes context is the first item in the spec
|
|
166
|
-
if isinstance(spec[0], tuple):
|
|
167
|
-
# context aware message
|
|
168
|
-
context = messages[spec[0][0] - 1][0]
|
|
169
|
-
message = [messages[i - 1] for i in spec[1:]]
|
|
170
|
-
else:
|
|
171
|
-
context = None
|
|
172
|
-
message = [messages[i - 1] for i in spec if isinstance(i, int)]
|
|
173
|
-
|
|
174
|
-
if not message[0]:
|
|
175
|
-
# Empty message
|
|
176
|
-
continue
|
|
177
|
-
|
|
178
|
-
if strip_comment_tags:
|
|
179
|
-
comments = _strip_comment_tags(comments, comment_tags) # noqa: PLW2901
|
|
180
|
-
|
|
181
|
-
# Use the template's path if it has one
|
|
182
|
-
template_name = template.name
|
|
183
|
-
if isinstance(template.path, Path):
|
|
184
|
-
template_name = str(template.path.joinpath(template_name))
|
|
185
|
-
elif isinstance(template.path, str):
|
|
186
|
-
template_name = os.path.join(template.path, template_name)
|
|
187
|
-
|
|
188
|
-
catalog.add(
|
|
189
|
-
message[0] if len(message) == 1 else message, # type: ignore
|
|
190
|
-
"",
|
|
191
|
-
[(template_name, lineno)],
|
|
192
|
-
auto_comments=comments,
|
|
193
|
-
context=context,
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
return catalog
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def extract_from_template(
|
|
200
|
-
template: BoundTemplate,
|
|
201
|
-
keywords: Union[list[str], dict[str, Any], None] = None,
|
|
202
|
-
comment_tags: list[str] | None = None,
|
|
203
|
-
) -> Iterator[MessageTuple]:
|
|
204
|
-
"""Extract translation messages from a Liquid template."""
|
|
205
|
-
_comment_tags = comment_tags or DEFAULT_COMMENT_TAGS
|
|
206
|
-
_comments: list[tuple[int, str]] = []
|
|
207
|
-
_keywords = keywords or DEFAULT_KEYWORDS
|
|
208
|
-
ctx = RenderContext(template)
|
|
209
|
-
|
|
210
|
-
_line_number = line_number_factory(template.nodes[0].token.source)
|
|
211
|
-
|
|
212
|
-
def visit_expression(expr: Expression, lineno: int) -> Iterator[MessageTuple]:
|
|
213
|
-
if isinstance(expr, (FilteredExpression, TernaryFilteredExpression)):
|
|
214
|
-
for _lineno, funcname, message in _extract_from_filters(
|
|
215
|
-
template.env, expr, lineno, _keywords
|
|
216
|
-
):
|
|
217
|
-
if _comments and _comments[-1][0] < lineno - 1:
|
|
218
|
-
_comments.clear()
|
|
219
|
-
|
|
220
|
-
yield MessageTuple(
|
|
221
|
-
lineno=_lineno,
|
|
222
|
-
funcname=funcname,
|
|
223
|
-
message=message,
|
|
224
|
-
comments=[text for _, text in _comments],
|
|
225
|
-
)
|
|
226
|
-
_comments.clear()
|
|
227
|
-
|
|
228
|
-
for expression in expr.children():
|
|
229
|
-
yield from visit_expression(expression, lineno)
|
|
230
|
-
|
|
231
|
-
def visit(node: Node) -> Iterator[MessageTuple]:
|
|
232
|
-
token = node.token
|
|
233
|
-
lineno = _line_number(token)
|
|
234
|
-
if isinstance(node, CommentNode) and node.text is not None:
|
|
235
|
-
comment_text = node.text.strip()
|
|
236
|
-
for comment_tag in _comment_tags:
|
|
237
|
-
if comment_text.startswith(comment_tag):
|
|
238
|
-
# Our multi-line comments are wrapped in a tag, so we're
|
|
239
|
-
# only ever going to have one comment text object to deal
|
|
240
|
-
# with.
|
|
241
|
-
_comments.clear()
|
|
242
|
-
_comments.append((lineno, comment_text))
|
|
243
|
-
break
|
|
244
|
-
elif (
|
|
245
|
-
token.kind == TOKEN_TAG
|
|
246
|
-
and token.value in _keywords
|
|
247
|
-
and isinstance(node, TranslatableTag)
|
|
248
|
-
):
|
|
249
|
-
for lineno, funcname, message in node.messages():
|
|
250
|
-
if _comments and _comments[-1][0] < lineno - 1:
|
|
251
|
-
_comments.clear()
|
|
252
|
-
|
|
253
|
-
yield MessageTuple(
|
|
254
|
-
lineno=lineno,
|
|
255
|
-
funcname=funcname,
|
|
256
|
-
message=message,
|
|
257
|
-
comments=[text for _, text in _comments],
|
|
258
|
-
)
|
|
259
|
-
_comments.clear()
|
|
260
|
-
|
|
261
|
-
for child in node.children(ctx, include_partials=False):
|
|
262
|
-
for expr in child.expressions():
|
|
263
|
-
yield from visit_expression(expr, _line_number(expr.token))
|
|
264
|
-
yield from visit(child)
|
|
265
|
-
|
|
266
|
-
for node in template.nodes:
|
|
267
|
-
for expr in node.expressions():
|
|
268
|
-
yield from visit_expression(expr, _line_number(expr.token))
|
|
269
|
-
yield from visit(node)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
def _extract_from_filters(
|
|
273
|
-
environment: Environment,
|
|
274
|
-
expression: FilteredExpression | TernaryFilteredExpression,
|
|
275
|
-
lineno: int,
|
|
276
|
-
keywords: Union[list[str], dict[str, Any]],
|
|
277
|
-
) -> Iterable[MessageText]:
|
|
278
|
-
if isinstance(expression, FilteredExpression) and expression.filters:
|
|
279
|
-
first_filter = expression.filters[0]
|
|
280
|
-
if first_filter.name in keywords:
|
|
281
|
-
filter_callable = environment.filters.get(first_filter.name)
|
|
282
|
-
if isinstance(filter_callable, TranslatableFilter): # noqa: SIM102
|
|
283
|
-
if message := filter_callable.message( # type: ignore
|
|
284
|
-
expression.left,
|
|
285
|
-
first_filter,
|
|
286
|
-
lineno,
|
|
287
|
-
):
|
|
288
|
-
yield message
|
|
289
|
-
|
|
290
|
-
if isinstance(expression, TernaryFilteredExpression):
|
|
291
|
-
yield from _extract_from_filters(environment, expression.left, lineno, keywords)
|
|
292
|
-
|
|
293
|
-
if expression.filters and expression.alternative:
|
|
294
|
-
first_filter = expression.filters[0]
|
|
295
|
-
if first_filter.name in keywords:
|
|
296
|
-
filter_callable = environment.filters.get(first_filter.name)
|
|
297
|
-
if isinstance(filter_callable, TranslatableFilter): # noqa: SIM102
|
|
298
|
-
if message := filter_callable.message( # type: ignore
|
|
299
|
-
expression.alternative,
|
|
300
|
-
first_filter,
|
|
301
|
-
lineno,
|
|
302
|
-
):
|
|
303
|
-
yield message
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def _strip_comment_tags(comments: list[str], tags: list[str]) -> list[str]:
|
|
307
|
-
"""Similar to Babel's messages.extract._strip_comment_tags."""
|
|
308
|
-
|
|
309
|
-
def _strip(line: str) -> str:
|
|
310
|
-
for tag in tags:
|
|
311
|
-
if line.startswith(tag):
|
|
312
|
-
return line[len(tag) :].strip()
|
|
313
|
-
return line
|
|
314
|
-
|
|
315
|
-
return [_strip(comment) for comment in comments]
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
def line_number(token: Token) -> int:
|
|
319
|
-
"""Return _token_'s line number."""
|
|
320
|
-
lines = token.source.splitlines(keepends=True)
|
|
321
|
-
cumulative_length = 0
|
|
322
|
-
target_line_index = -1
|
|
323
|
-
|
|
324
|
-
for i, line in enumerate(lines):
|
|
325
|
-
cumulative_length += len(line)
|
|
326
|
-
if token.start_index < cumulative_length:
|
|
327
|
-
target_line_index = i
|
|
328
|
-
break
|
|
329
|
-
|
|
330
|
-
if target_line_index == -1:
|
|
331
|
-
raise ValueError("index is out of bounds for the given string")
|
|
332
|
-
|
|
333
|
-
# Line number (1-based)
|
|
334
|
-
return target_line_index + 1
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
def line_number_factory(source: str) -> Callable[[Token], int]:
|
|
338
|
-
"""Return a function for looking up token line numbers in _source_."""
|
|
339
|
-
lines = source.splitlines(keepends=True)
|
|
340
|
-
|
|
341
|
-
def _line_number(token: Token) -> int:
|
|
342
|
-
cumulative_length = 0
|
|
343
|
-
target_line_index = -1
|
|
344
|
-
|
|
345
|
-
for i, line in enumerate(lines):
|
|
346
|
-
cumulative_length += len(line)
|
|
347
|
-
if token.start_index < cumulative_length:
|
|
348
|
-
target_line_index = i
|
|
349
|
-
break
|
|
350
|
-
|
|
351
|
-
if target_line_index == -1:
|
|
352
|
-
raise ValueError("index is out of bounds for the given string")
|
|
353
|
-
|
|
354
|
-
# Line number (1-based)
|
|
355
|
-
return target_line_index + 1
|
|
356
|
-
|
|
357
|
-
return _line_number
|
liquid/mode.py
DELETED
liquid/output.py
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"""Template output streams."""
|
|
2
|
-
|
|
3
|
-
from io import StringIO
|
|
4
|
-
from typing import Optional
|
|
5
|
-
|
|
6
|
-
from liquid.exceptions import OutputStreamLimitError
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class LimitedStringIO(StringIO):
|
|
10
|
-
"""A StringIO subclass that limits the number of bytes that can be written."""
|
|
11
|
-
|
|
12
|
-
def __init__(
|
|
13
|
-
self,
|
|
14
|
-
limit: int,
|
|
15
|
-
initial_value: Optional[str] = None,
|
|
16
|
-
newline: Optional[str] = None,
|
|
17
|
-
) -> None:
|
|
18
|
-
super().__init__(initial_value, newline)
|
|
19
|
-
self.limit = limit
|
|
20
|
-
self.size = 0
|
|
21
|
-
|
|
22
|
-
def write(self, __s: str) -> int: # noqa: D102
|
|
23
|
-
if __s:
|
|
24
|
-
self.size += len(__s.encode("utf-8"))
|
|
25
|
-
if self.size > self.limit:
|
|
26
|
-
raise OutputStreamLimitError("output stream limit reached", token=None)
|
|
27
|
-
return super().write(__s)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class NullIO(StringIO):
|
|
31
|
-
"""A StringIO subclass that is a null op. It doesn't write anything."""
|
|
32
|
-
|
|
33
|
-
def write(self, _s: str) -> int:
|
|
34
|
-
"""Pretend to write the string to the stream."""
|
|
35
|
-
return 0
|
liquid/parser.py
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
"""Liquid template parser."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from functools import lru_cache
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
7
|
-
from typing import Container
|
|
8
|
-
from typing import Iterator
|
|
9
|
-
|
|
10
|
-
from .ast import BlockNode
|
|
11
|
-
from .ast import Node
|
|
12
|
-
from .exceptions import BlockNestingError
|
|
13
|
-
from .exceptions import LiquidError
|
|
14
|
-
from .token import TOKEN_COMMENT
|
|
15
|
-
from .token import TOKEN_CONTENT
|
|
16
|
-
from .token import TOKEN_DOC
|
|
17
|
-
from .token import TOKEN_EOF
|
|
18
|
-
from .token import TOKEN_ILLEGAL
|
|
19
|
-
from .token import TOKEN_OUTPUT
|
|
20
|
-
from .token import TOKEN_TAG
|
|
21
|
-
|
|
22
|
-
if TYPE_CHECKING:
|
|
23
|
-
from .environment import Environment
|
|
24
|
-
from .stream import TokenStream
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class Parser:
|
|
28
|
-
"""A Liquid template parser. Create a parse tree from a stream of tokens."""
|
|
29
|
-
|
|
30
|
-
__slots__ = ("env",)
|
|
31
|
-
|
|
32
|
-
def __init__(self, env: Environment):
|
|
33
|
-
self.env = env
|
|
34
|
-
|
|
35
|
-
def parse(self, stream: TokenStream) -> list[Node]:
|
|
36
|
-
"""Parse tokens from _stream_ into a list of nodes."""
|
|
37
|
-
return list(self._parse(stream))
|
|
38
|
-
|
|
39
|
-
def _parse(self, stream: TokenStream) -> Iterator[Node]:
|
|
40
|
-
tags = self.env.tags
|
|
41
|
-
illegal = tags[TOKEN_ILLEGAL]
|
|
42
|
-
content = tags[TOKEN_CONTENT]
|
|
43
|
-
output = tags[TOKEN_OUTPUT]
|
|
44
|
-
|
|
45
|
-
while stream.current.kind != TOKEN_EOF:
|
|
46
|
-
token = stream.current
|
|
47
|
-
kind = token.kind
|
|
48
|
-
|
|
49
|
-
try:
|
|
50
|
-
if kind == TOKEN_OUTPUT:
|
|
51
|
-
yield output.get_node(stream)
|
|
52
|
-
elif kind == TOKEN_DOC:
|
|
53
|
-
yield tags.get(TOKEN_DOC, illegal).get_node(stream)
|
|
54
|
-
elif stream.current.kind == TOKEN_TAG:
|
|
55
|
-
yield tags.get(token.value, illegal).get_node(stream)
|
|
56
|
-
elif kind == "COMMENT":
|
|
57
|
-
yield tags.get(TOKEN_COMMENT, illegal).get_node(stream)
|
|
58
|
-
else:
|
|
59
|
-
yield content.get_node(stream)
|
|
60
|
-
except LiquidError as err:
|
|
61
|
-
self.env.error(err, token=stream.current)
|
|
62
|
-
|
|
63
|
-
next(stream)
|
|
64
|
-
|
|
65
|
-
def parse_block(self, stream: TokenStream, end: Container[str]) -> BlockNode:
|
|
66
|
-
"""Parse tokens from _stream_ until we reach a token in _end_."""
|
|
67
|
-
tags = self.env.tags
|
|
68
|
-
illegal = tags[TOKEN_ILLEGAL]
|
|
69
|
-
content = tags[TOKEN_CONTENT]
|
|
70
|
-
output = tags[TOKEN_OUTPUT]
|
|
71
|
-
|
|
72
|
-
nodes: list[Node] = []
|
|
73
|
-
stream.block_depth += 1
|
|
74
|
-
|
|
75
|
-
if stream.block_depth > self.env.block_nesting_limit:
|
|
76
|
-
raise BlockNestingError("block nesting limit reached", token=None)
|
|
77
|
-
|
|
78
|
-
while stream.current.kind != TOKEN_EOF:
|
|
79
|
-
if stream.current.kind == TOKEN_TAG and stream.current.value in end:
|
|
80
|
-
break
|
|
81
|
-
token = stream.current
|
|
82
|
-
kind = token.kind
|
|
83
|
-
|
|
84
|
-
try:
|
|
85
|
-
if kind == TOKEN_OUTPUT:
|
|
86
|
-
nodes.append(output.get_node(stream))
|
|
87
|
-
elif kind == TOKEN_DOC:
|
|
88
|
-
nodes.append(tags.get(TOKEN_DOC, illegal).get_node(stream))
|
|
89
|
-
elif stream.current.kind == TOKEN_TAG:
|
|
90
|
-
nodes.append(tags.get(token.value, illegal).get_node(stream))
|
|
91
|
-
elif kind == "COMMENT":
|
|
92
|
-
nodes.append(tags.get(TOKEN_COMMENT, illegal).get_node(stream))
|
|
93
|
-
else:
|
|
94
|
-
nodes.append(content.get_node(stream))
|
|
95
|
-
except LiquidError as err:
|
|
96
|
-
self.env.error(err, token=stream.current)
|
|
97
|
-
|
|
98
|
-
next(stream)
|
|
99
|
-
|
|
100
|
-
stream.block_depth -= 1
|
|
101
|
-
return BlockNode(stream.current, nodes)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def eat_block(stream: TokenStream, end: Container[str]) -> None:
|
|
105
|
-
"""Advance the stream until we reach a token in _end_.
|
|
106
|
-
|
|
107
|
-
This is used to discard blocks after a syntax error is found, in the hope
|
|
108
|
-
that we can continue to parse more of the stream after the offending block.
|
|
109
|
-
"""
|
|
110
|
-
while stream.current.kind != TOKEN_EOF:
|
|
111
|
-
if stream.current.kind == TOKEN_TAG and stream.current.value in end:
|
|
112
|
-
break
|
|
113
|
-
next(stream)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
@lru_cache(maxsize=128)
|
|
117
|
-
def get_parser(env: Environment) -> Parser:
|
|
118
|
-
"""Return a template parser for the given environment."""
|
|
119
|
-
return Parser(env)
|
liquid/span.py
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"""The location of a variable, tag or filter in a template."""
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@dataclass
|
|
7
|
-
class Span:
|
|
8
|
-
"""The location of a variable, tag or filter in a template."""
|
|
9
|
-
|
|
10
|
-
template_name: str
|
|
11
|
-
"""The template name."""
|
|
12
|
-
|
|
13
|
-
index: int
|
|
14
|
-
"""A start index into the template source text."""
|
|
15
|
-
|
|
16
|
-
def line_col(self, source: str) -> tuple[int, int]:
|
|
17
|
-
"""Return a tuple of (line number, column number) for this span in _source_."""
|
|
18
|
-
lines = source.splitlines(keepends=True)
|
|
19
|
-
cumulative_length = 0
|
|
20
|
-
target_line_index = -1
|
|
21
|
-
|
|
22
|
-
for i, line in enumerate(lines):
|
|
23
|
-
cumulative_length += len(line)
|
|
24
|
-
if self.index < cumulative_length:
|
|
25
|
-
target_line_index = i
|
|
26
|
-
break
|
|
27
|
-
|
|
28
|
-
if target_line_index == -1:
|
|
29
|
-
raise ValueError("index is out of bounds for the given string")
|
|
30
|
-
|
|
31
|
-
# Line number (1-based)
|
|
32
|
-
line_number = target_line_index + 1
|
|
33
|
-
# Column number within the line
|
|
34
|
-
column_number = self.index - (cumulative_length - len(lines[target_line_index]))
|
|
35
|
-
return line_number, column_number
|