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.
Files changed (212) hide show
  1. liquid/__about__.py +1 -0
  2. liquid/__init__.py +161 -187
  3. liquid/_base_tag.py +66 -0
  4. liquid/{utils/chain_map.py → _chain_map.py} +18 -19
  5. liquid/_context.py +465 -0
  6. liquid/_drop.py +108 -0
  7. liquid/_filter.py +174 -0
  8. liquid/_io.py +18 -0
  9. liquid/_lax_parser_mixin.py +100 -0
  10. liquid/_lexer.py +93 -0
  11. liquid/_lexer_extended.py +623 -0
  12. liquid/_lexer_legacy.py +536 -0
  13. liquid/_loader.py +84 -0
  14. liquid/{utils/lru_cache.py → _lru_cache.py} +28 -36
  15. liquid/_markup.py +253 -0
  16. liquid/_messages.py +292 -0
  17. liquid/_missing.py +3 -0
  18. liquid/_nothing.py +28 -0
  19. liquid/_parser.py +460 -0
  20. liquid/_parser_extended.py +293 -0
  21. liquid/_parser_legacy.py +819 -0
  22. liquid/_resource_limits.py +50 -0
  23. liquid/_slice.py +41 -0
  24. liquid/_static_analysis.py +503 -0
  25. liquid/_strftime.py +186 -0
  26. liquid/_template.py +536 -0
  27. liquid/drops/__init__.py +20 -0
  28. liquid/drops/forloop.py +78 -0
  29. liquid/drops/iterable.py +25 -0
  30. liquid/drops/range.py +76 -0
  31. liquid/drops/self_.py +45 -0
  32. liquid/drops/tablerowloop.py +142 -0
  33. liquid/drops/undefined.py +228 -0
  34. liquid/environment.py +717 -551
  35. liquid/exceptions.py +67 -214
  36. liquid/expressions.py +1184 -0
  37. liquid/filters/__init__.py +134 -0
  38. liquid/filters/abs.py +7 -0
  39. liquid/filters/append.py +5 -0
  40. liquid/filters/at_least.py +9 -0
  41. liquid/filters/at_most.py +9 -0
  42. liquid/filters/babel.py +560 -0
  43. liquid/filters/base64.py +26 -0
  44. liquid/filters/capitalize.py +5 -0
  45. liquid/filters/ceil.py +8 -0
  46. liquid/filters/compact.py +25 -0
  47. liquid/filters/concat.py +11 -0
  48. liquid/filters/date.py +27 -0
  49. liquid/filters/default.py +25 -0
  50. liquid/filters/divided_by.py +21 -0
  51. liquid/filters/downcase.py +5 -0
  52. liquid/filters/escape.py +56 -0
  53. liquid/filters/find.py +56 -0
  54. liquid/filters/first.py +19 -0
  55. liquid/filters/floor.py +8 -0
  56. liquid/filters/html.py +23 -0
  57. liquid/filters/index.py +12 -0
  58. liquid/filters/join.py +13 -0
  59. liquid/filters/json_.py +30 -0
  60. liquid/filters/last.py +17 -0
  61. liquid/filters/map.py +5 -0
  62. liquid/filters/minus.py +13 -0
  63. liquid/filters/modulo.py +18 -0
  64. liquid/filters/newline_to_br.py +16 -0
  65. liquid/filters/plus.py +13 -0
  66. liquid/filters/prepend.py +5 -0
  67. liquid/filters/reject.py +19 -0
  68. liquid/filters/remove.py +36 -0
  69. liquid/filters/replace.py +32 -0
  70. liquid/filters/reverse.py +5 -0
  71. liquid/filters/round.py +35 -0
  72. liquid/filters/size.py +25 -0
  73. liquid/filters/slice.py +39 -0
  74. liquid/filters/sort.py +148 -0
  75. liquid/filters/split.py +34 -0
  76. liquid/filters/squish.py +9 -0
  77. liquid/filters/strip.py +13 -0
  78. liquid/filters/strip_html.py +19 -0
  79. liquid/filters/strip_newlines.py +17 -0
  80. liquid/filters/sum.py +21 -0
  81. liquid/filters/times.py +13 -0
  82. liquid/filters/translate.py +380 -0
  83. liquid/filters/truncate.py +39 -0
  84. liquid/filters/uniq.py +11 -0
  85. liquid/filters/upcase.py +5 -0
  86. liquid/filters/url.py +18 -0
  87. liquid/filters/where.py +18 -0
  88. liquid/loaders/__init__.py +17 -0
  89. liquid/loaders/caching_file_system_loader.py +64 -0
  90. liquid/loaders/choice_loader.py +93 -0
  91. liquid/loaders/dict_loader.py +56 -0
  92. liquid/{builtin/loaders → loaders}/file_system_loader.py +51 -55
  93. liquid/{builtin/loaders → loaders}/mixins.py +39 -51
  94. liquid/loaders/package_loader.py +110 -0
  95. liquid/tags/__init__.py +61 -0
  96. liquid/tags/assign.py +68 -0
  97. liquid/tags/capture.py +83 -0
  98. liquid/tags/case.py +199 -0
  99. liquid/tags/comment.py +56 -0
  100. liquid/tags/cycle.py +131 -0
  101. liquid/tags/decrement.py +55 -0
  102. liquid/tags/doc.py +56 -0
  103. liquid/tags/echo.py +65 -0
  104. liquid/tags/else_.py +53 -0
  105. liquid/tags/error.py +51 -0
  106. liquid/tags/extends.py +448 -0
  107. liquid/tags/for_.py +421 -0
  108. liquid/tags/if_.py +156 -0
  109. liquid/tags/ifchanged.py +69 -0
  110. liquid/tags/include.py +223 -0
  111. liquid/tags/increment.py +55 -0
  112. liquid/tags/inline_comment.py +70 -0
  113. liquid/tags/liquid.py +56 -0
  114. liquid/tags/macro.py +281 -0
  115. liquid/tags/output.py +58 -0
  116. liquid/tags/raw.py +60 -0
  117. liquid/tags/render.py +245 -0
  118. liquid/tags/tablerow.py +254 -0
  119. liquid/tags/template_comment.py +56 -0
  120. liquid/tags/translate.py +316 -0
  121. liquid/tags/unless.py +121 -0
  122. liquid/tags/with_.py +83 -0
  123. liquid/tokens.py +370 -0
  124. {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/METADATA +32 -39
  125. python_liquid-3.0.0.dist-info/RECORD +128 -0
  126. {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/WHEEL +1 -1
  127. {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/licenses/LICENSE +1 -1
  128. liquid/analyze_tags.py +0 -214
  129. liquid/ast.py +0 -252
  130. liquid/builtin/__init__.py +0 -203
  131. liquid/builtin/content.py +0 -41
  132. liquid/builtin/drops/__init__.py +0 -3
  133. liquid/builtin/drops/drops.py +0 -13
  134. liquid/builtin/expressions/__init__.py +0 -47
  135. liquid/builtin/expressions/_tokenize.py +0 -186
  136. liquid/builtin/expressions/arguments.py +0 -207
  137. liquid/builtin/expressions/filtered.py +0 -387
  138. liquid/builtin/expressions/logical.py +0 -672
  139. liquid/builtin/expressions/loop.py +0 -275
  140. liquid/builtin/expressions/path.py +0 -163
  141. liquid/builtin/expressions/primitive.py +0 -417
  142. liquid/builtin/filters/__init__.py +0 -0
  143. liquid/builtin/filters/array.py +0 -329
  144. liquid/builtin/filters/extra.py +0 -83
  145. liquid/builtin/filters/math.py +0 -129
  146. liquid/builtin/filters/misc.py +0 -137
  147. liquid/builtin/filters/string.py +0 -416
  148. liquid/builtin/illegal.py +0 -28
  149. liquid/builtin/loaders/__init__.py +0 -0
  150. liquid/builtin/loaders/caching_file_system_loader.py +0 -61
  151. liquid/builtin/loaders/choice_loader.py +0 -94
  152. liquid/builtin/loaders/dict_loader.py +0 -64
  153. liquid/builtin/loaders/package_loader.py +0 -132
  154. liquid/builtin/output.py +0 -74
  155. liquid/builtin/tags/__init__.py +0 -0
  156. liquid/builtin/tags/assign_tag.py +0 -75
  157. liquid/builtin/tags/capture_tag.py +0 -98
  158. liquid/builtin/tags/case_tag.py +0 -287
  159. liquid/builtin/tags/comment_tag.py +0 -77
  160. liquid/builtin/tags/cycle_tag.py +0 -132
  161. liquid/builtin/tags/decrement_tag.py +0 -59
  162. liquid/builtin/tags/doc_tag.py +0 -68
  163. liquid/builtin/tags/echo_tag.py +0 -45
  164. liquid/builtin/tags/for_tag.py +0 -337
  165. liquid/builtin/tags/if_tag.py +0 -209
  166. liquid/builtin/tags/ifchanged_tag.py +0 -94
  167. liquid/builtin/tags/include_tag.py +0 -251
  168. liquid/builtin/tags/increment_tag.py +0 -60
  169. liquid/builtin/tags/inline_comment_tag.py +0 -47
  170. liquid/builtin/tags/liquid_tag.py +0 -183
  171. liquid/builtin/tags/render_tag.py +0 -371
  172. liquid/builtin/tags/tablerow_tag.py +0 -320
  173. liquid/builtin/tags/unless_tag.py +0 -218
  174. liquid/context.py +0 -615
  175. liquid/expression.py +0 -41
  176. liquid/extra/__init__.py +0 -98
  177. liquid/extra/filters/__init__.py +0 -33
  178. liquid/extra/filters/_json.py +0 -33
  179. liquid/extra/filters/array.py +0 -60
  180. liquid/extra/filters/babel.py +0 -552
  181. liquid/extra/filters/html.py +0 -32
  182. liquid/extra/filters/translate.py +0 -448
  183. liquid/extra/tags/__init__.py +0 -17
  184. liquid/extra/tags/_with.py +0 -80
  185. liquid/extra/tags/extends_tag.py +0 -576
  186. liquid/extra/tags/macro_tag.py +0 -270
  187. liquid/extra/tags/snippet_tag.py +0 -107
  188. liquid/extra/tags/translate_tag.py +0 -388
  189. liquid/filter.py +0 -230
  190. liquid/future/__init__.py +0 -3
  191. liquid/future/environment.py +0 -30
  192. liquid/future/filters/__init__.py +0 -1
  193. liquid/future/tags/__init__.py +0 -1
  194. liquid/lex.py +0 -260
  195. liquid/limits.py +0 -66
  196. liquid/loader.py +0 -141
  197. liquid/messages.py +0 -357
  198. liquid/mode.py +0 -12
  199. liquid/output.py +0 -35
  200. liquid/parser.py +0 -119
  201. liquid/span.py +0 -35
  202. liquid/static_analysis.py +0 -443
  203. liquid/stream.py +0 -148
  204. liquid/stringify.py +0 -31
  205. liquid/tag.py +0 -47
  206. liquid/template.py +0 -658
  207. liquid/token.py +0 -145
  208. liquid/undefined.py +0 -218
  209. liquid/utils/__init__.py +0 -15
  210. liquid/utils/html.py +0 -57
  211. liquid/utils/text.py +0 -28
  212. 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
@@ -1,12 +0,0 @@
1
- """Template error tolerance modes."""
2
-
3
- from enum import IntEnum
4
- from enum import auto
5
-
6
-
7
- class Mode(IntEnum):
8
- """Template error tolerance mode."""
9
-
10
- LAX = auto()
11
- WARN = auto()
12
- STRICT = auto()
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