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/_filter.py ADDED
@@ -0,0 +1,174 @@
1
+ from __future__ import annotations
2
+
3
+ import decimal
4
+ import re
5
+ from collections.abc import Callable, Generator, Iterable, Mapping
6
+ from dataclasses import dataclass, field
7
+ from decimal import Decimal
8
+ from typing import TYPE_CHECKING, Concatenate
9
+
10
+ from ._drop import liquid_object, liquid_primitive
11
+ from ._missing import MISSING
12
+ from ._nothing import NOTHING
13
+ from .exceptions import LiquidFilterError, LiquidFilterInterrupt
14
+
15
+ if TYPE_CHECKING:
16
+ import datetime as dt
17
+
18
+ from ._context import RenderContext
19
+ from .environment import Environment
20
+ from .tokens import Token
21
+
22
+
23
+ type Filter = Callable[Concatenate[FilterContext, object, ...], object]
24
+
25
+ RE_DIGITS = re.compile(r"^[0-9]+")
26
+
27
+
28
+ @dataclass(slots=True)
29
+ class FilterContext:
30
+ """Liquid config, state and utilities for filter implementations."""
31
+
32
+ context: RenderContext
33
+ span: Token
34
+
35
+ missing: object = field(init=False)
36
+ env: Environment = field(init=False)
37
+
38
+ def __post_init__(self) -> None:
39
+ self.missing = MISSING
40
+ self.env = self.context.env
41
+
42
+ def get_item(self, obj: object, key: object, default: object = MISSING) -> object:
43
+ """Get `key` from `obj` with Shopify/liquid semantics."""
44
+ if isinstance(obj, str) and isinstance(key, str):
45
+ return key if key in obj else None
46
+
47
+ if isinstance(obj, (int, float, Decimal)) and isinstance(
48
+ key, (int, float, Decimal)
49
+ ):
50
+ return obj == key
51
+
52
+ if self.is_nil(obj):
53
+ raise LiquidFilterInterrupt(
54
+ f"can't read property {obj}[{key}]",
55
+ token=self.span,
56
+ source=self.context.template.source,
57
+ name=self.context.template.name,
58
+ )
59
+
60
+ if not (isinstance(obj, Mapping) or liquid_object(obj)):
61
+ raise LiquidFilterError(
62
+ f"can't read property {obj}[{key}]",
63
+ token=self.span,
64
+ source=self.context.template.source,
65
+ name=self.context.template.name,
66
+ )
67
+
68
+ result, _ = self.context.resolve_path(obj, [key]) # pyright: ignore[reportUnknownArgumentType]
69
+ return default if result == NOTHING else result
70
+
71
+ def resolve(self, name: object) -> object:
72
+ return self.context.resolve(name)
73
+
74
+ def input_array(self, obj: object) -> list[object]:
75
+ """Coerce `obj` to a list suitable for filters that expect an array input."""
76
+ if liquid_primitive(obj):
77
+ obj = obj.__liquid__("array", self.context)
78
+
79
+ if isinstance(obj, (list, tuple)):
80
+ return _flatten(obj) # pyright: ignore[reportUnknownArgumentType]
81
+
82
+ return [obj]
83
+
84
+ def is_nil(self, obj: object) -> bool:
85
+ """Return true if `obj` is Liquid nullish."""
86
+ return self.context.env.is_nil(obj)
87
+
88
+ def truthy(self, obj: object) -> bool:
89
+ """Return true if `obj` is Liquid truthy."""
90
+ return self.context.env.truthy(obj, self.context)
91
+
92
+ def to_int(self, obj: object) -> int:
93
+ """Coerce `obj` to an integer."""
94
+ if self.is_nil(obj):
95
+ raise LiquidFilterError(
96
+ "invalid integer",
97
+ token=self.span,
98
+ source=self.context.template.source,
99
+ name=self.context.template.name,
100
+ )
101
+ return self.context.env.to_int(obj, self.context, self.span)
102
+
103
+ def to_numeric(self, obj: object, default: int | Decimal) -> float | int | Decimal:
104
+ """Coerce `obj` to an integer, float or Decimal."""
105
+ if isinstance(obj, str):
106
+ try:
107
+ return int(obj)
108
+ except ValueError:
109
+ pass
110
+
111
+ try:
112
+ return float(obj)
113
+ except ValueError:
114
+ pass
115
+
116
+ # This is different than env.to_numeric
117
+ if match := RE_DIGITS.match(obj):
118
+ return int(match.group())
119
+
120
+ return default
121
+
122
+ return self.context.env.to_numeric(obj, self.context, default)
123
+
124
+ def to_decimal(self, obj: object, default: Decimal | int) -> Decimal | int:
125
+ if self.is_nil(obj):
126
+ return default
127
+
128
+ if obj is True:
129
+ return default
130
+
131
+ if isinstance(obj, int):
132
+ return obj
133
+
134
+ try:
135
+ return Decimal(str(obj))
136
+ except (ValueError, TypeError, decimal.InvalidOperation):
137
+ if isinstance(obj, str):
138
+ match = RE_DIGITS.match(obj)
139
+ if match:
140
+ return Decimal(match.group())
141
+ return default
142
+
143
+ def to_str[T](self, obj: object, default: T) -> str | T:
144
+ """Coerce `obj` to a string."""
145
+ return (
146
+ default
147
+ if obj is None
148
+ else self.context.env.to_str(obj, self.context, self.span)
149
+ )
150
+
151
+ def to_date(self, obj: object) -> dt.datetime | None:
152
+ """Coerce `obj` to a datatime, or ``None`` if `obj` can not be interpreted as a datetime."""
153
+ return self.env.to_date(obj, self.context, self.span)
154
+
155
+ def error(self, message: str) -> LiquidFilterError:
156
+ """Return a Liquid exception with suitable error context data."""
157
+ return LiquidFilterError(
158
+ message,
159
+ token=self.span,
160
+ source=self.context.template.source,
161
+ name=self.context.template.name,
162
+ )
163
+
164
+
165
+ def _flatten(it: Iterable[object], level: int = 5) -> list[object]:
166
+
167
+ def flatten_(it: Iterable[object], level: int = 5) -> Generator[object]:
168
+ for obj in it:
169
+ if level < 1 or not isinstance(obj, (list, tuple)):
170
+ yield obj
171
+ else:
172
+ yield from flatten_(obj, level=level - 1) # pyright: ignore[reportUnknownArgumentType]
173
+
174
+ return list(flatten_(it, level))
liquid/_io.py ADDED
@@ -0,0 +1,18 @@
1
+ class StringBuffer:
2
+ """An output text buffer that keeps track of its own length."""
3
+
4
+ def __init__(self) -> None:
5
+ self._parts: list[str] = []
6
+ self._length = 0
7
+
8
+ def write(self, s: str) -> None:
9
+ """Write `s` to the buffer."""
10
+ self._parts.append(s)
11
+ self._length += len(s)
12
+
13
+ def __len__(self) -> int:
14
+ return self._length
15
+
16
+ def getvalue(self) -> str:
17
+ """Return the contents of the buffer."""
18
+ return "".join(self._parts)
@@ -0,0 +1,100 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC
4
+ from typing import TYPE_CHECKING, Protocol
5
+
6
+ from . import expressions, tokens
7
+ from ._parser import PRECEDENCE_LOWEST
8
+ from .exceptions import LiquidSyntaxError
9
+ from .tags import ErrorNode
10
+ from .tokens import Token, TokenKind, span
11
+
12
+ if TYPE_CHECKING:
13
+ from ._markup import Expression, Markup
14
+ from ._parser import Precedence
15
+
16
+
17
+ class _LegacyParserProtocol(Protocol):
18
+ def parse_output(self) -> Markup: ...
19
+ def parse_tag(self) -> Markup: ...
20
+ def parse_expression(
21
+ self,
22
+ precedence: Precedence = PRECEDENCE_LOWEST,
23
+ *,
24
+ infix: bool = False,
25
+ expect_end_of_expression: bool = False,
26
+ ) -> Expression: ...
27
+ def current(self) -> Token: ...
28
+ def next(self) -> Token: ...
29
+ def kind(self) -> TokenKind: ...
30
+ def carry_whitespace_control(self) -> None: ...
31
+
32
+
33
+ class LaxParserMixin(ABC, _LegacyParserProtocol):
34
+ """A legacy Liquid parser that captures and suppresses syntax errors.
35
+
36
+ Other parse-time errors such as ``ResourceLimitError`` will still be raised.
37
+ """
38
+
39
+ __slots__ = ()
40
+
41
+ def parse_output(self) -> Markup:
42
+ try:
43
+ return super().parse_output() # type: ignore
44
+ except LiquidSyntaxError as err:
45
+ return self.error(err)
46
+
47
+ def parse_tag(self) -> Markup:
48
+ try:
49
+ return super().parse_tag() # type: ignore
50
+ except LiquidSyntaxError as err:
51
+ return self.error(err)
52
+
53
+ def parse_expression(
54
+ self,
55
+ precedence: Precedence = PRECEDENCE_LOWEST,
56
+ *,
57
+ infix: bool = False,
58
+ expect_end_of_expression: bool = False,
59
+ ) -> Expression:
60
+ start_token = self.current()
61
+
62
+ try:
63
+ return super().parse_expression( # type: ignore
64
+ precedence,
65
+ infix=infix,
66
+ expect_end_of_expression=expect_end_of_expression,
67
+ )
68
+ except LiquidSyntaxError as err:
69
+ while self.kind() not in (
70
+ tokens.TOKEN_WC,
71
+ tokens.TOKEN_TAG_END,
72
+ tokens.TOKEN_OUT_END,
73
+ ):
74
+ self.pos += 1 # type: ignore
75
+
76
+ return expressions.ErrorExpr(span(start_token, self.current()), err)
77
+
78
+ def error(self, err: LiquidSyntaxError) -> ErrorNode:
79
+ # Skip tokens up to the next delimiter.
80
+ start_token = self.current()
81
+
82
+ while 1:
83
+ match self.kind():
84
+ case (
85
+ tokens.TOKEN_EOI
86
+ | tokens.TOKEN_COMMENT_END
87
+ | tokens.TOKEN_COMMENT_START
88
+ | tokens.TOKEN_OUT_END
89
+ | tokens.TOKEN_OUT_START
90
+ | tokens.TOKEN_TAG_END
91
+ | tokens.TOKEN_TAG_START
92
+ ):
93
+ break
94
+ case tokens.TOKEN_WC:
95
+ self.carry_whitespace_control()
96
+ case _:
97
+ self.pos += 1 # type: ignore
98
+
99
+ end_token = self.next()
100
+ return ErrorNode(span(start_token, end_token), err)
liquid/_lexer.py ADDED
@@ -0,0 +1,93 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from typing import TYPE_CHECKING
5
+
6
+ from .exceptions import ResourceLimitError
7
+ from .tokens import TOKEN_WC
8
+
9
+ if TYPE_CHECKING:
10
+ import re
11
+
12
+ from .environment import Environment
13
+ from .tokens import Token, TokenKind
14
+
15
+
16
+ type State = Callable[[], State | None]
17
+
18
+
19
+ class Lexer:
20
+ """Template source tokenizer."""
21
+
22
+ __slots__ = (
23
+ "length",
24
+ "pos",
25
+ "source",
26
+ "start",
27
+ "tokens",
28
+ )
29
+
30
+ def __init__(self, env: Environment, source: str) -> None:
31
+ self.source = source
32
+ self.length = len(source)
33
+ self.start = 0
34
+ self.pos = 0
35
+ self.tokens: list[Token] = []
36
+
37
+ @classmethod
38
+ def tokenize(cls, env: Environment, source: str) -> list[Token]:
39
+ lexer = cls(env, source)
40
+ try:
41
+ lexer.run()
42
+ except RecursionError:
43
+ raise ResourceLimitError("maximum recursion depth exceeded")
44
+ return lexer.tokens
45
+
46
+ def run(self) -> None:
47
+ state: State | None = self.scan_markup
48
+ while state:
49
+ state = state()
50
+
51
+ def emit(self, kind: TokenKind) -> None:
52
+ self.tokens.append((kind, self.start, self.pos))
53
+ self.start = self.pos
54
+
55
+ def index(self, pattern: re.Pattern[str]) -> int | None:
56
+ if match := pattern.search(self.source, self.pos):
57
+ return match.start()
58
+ return None
59
+
60
+ def scan(self, pattern: re.Pattern[str]) -> str | None:
61
+ if match := pattern.match(self.source, self.pos):
62
+ self.pos = match.end()
63
+ return match[0]
64
+ return None
65
+
66
+ def scan_until(self, pattern: re.Pattern[str]) -> str | None:
67
+ if match := pattern.search(self.source, self.pos):
68
+ self.pos = match.start()
69
+ return self.source[self.start : match.start()]
70
+ return None
71
+
72
+ def skip(self, pattern: re.Pattern[str]) -> bool:
73
+ if match := pattern.match(self.source, self.pos):
74
+ self.pos = match.end()
75
+ self.start = self.pos
76
+ return True
77
+ return False
78
+
79
+ def skip_until(self, pattern: re.Pattern[str]) -> re.Match[str] | None:
80
+ if match := pattern.search(self.source, self.pos):
81
+ self.pos = match.start()
82
+ return match
83
+ return None
84
+
85
+ def accept_whitespace_control(self) -> bool:
86
+ if self.pos < self.length and self.source[self.pos] == "-":
87
+ self.pos += 1
88
+ self.emit(TOKEN_WC)
89
+ return True
90
+ return False
91
+
92
+ def scan_markup(self) -> State | None:
93
+ raise NotImplementedError