textmate-grammar-python 0.2.0__py3-none-any.whl → 0.3.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.
- textmate_grammar/__init__.py +1 -0
- textmate_grammar/elements.py +271 -88
- textmate_grammar/language.py +68 -25
- textmate_grammar/parser.py +59 -10
- textmate_grammar/utils/__init__.py +0 -0
- textmate_grammar/utils/cache.py +160 -0
- textmate_grammar/utils/exceptions.py +69 -0
- textmate_grammar/{handler.py → utils/handler.py} +81 -16
- textmate_grammar/{logger.py → utils/logger.py} +31 -11
- {textmate_grammar_python-0.2.0.dist-info → textmate_grammar_python-0.3.0.dist-info}/METADATA +32 -18
- textmate_grammar_python-0.3.0.dist-info/RECORD +21 -0
- {textmate_grammar_python-0.2.0.dist-info → textmate_grammar_python-0.3.0.dist-info}/WHEEL +1 -1
- textmate_grammar/exceptions.py +0 -30
- textmate_grammar_python-0.2.0.dist-info/RECORD +0 -19
- {textmate_grammar_python-0.2.0.dist-info → textmate_grammar_python-0.3.0.dist-info}/LICENSE +0 -0
textmate_grammar/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|
textmate_grammar/elements.py
CHANGED
|
@@ -6,8 +6,8 @@ from itertools import groupby
|
|
|
6
6
|
from pprint import pprint
|
|
7
7
|
from typing import TYPE_CHECKING, Generator
|
|
8
8
|
|
|
9
|
-
from .handler import POS, ContentHandler, Match, Pattern
|
|
10
|
-
from .logger import LOGGER
|
|
9
|
+
from .utils.handler import POS, ContentHandler, Match, Pattern
|
|
10
|
+
from .utils.logger import LOGGER
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
from .parser import GrammarParser
|
|
@@ -33,7 +33,20 @@ class Capture:
|
|
|
33
33
|
boundary: tuple[int, int],
|
|
34
34
|
key: str = "",
|
|
35
35
|
**kwargs,
|
|
36
|
-
)
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Initialize a new instance of the Element class.
|
|
39
|
+
|
|
40
|
+
:param handler: The content handler for the element.
|
|
41
|
+
:param pattern: The pattern used for matching.
|
|
42
|
+
:param matching: The match object.
|
|
43
|
+
:param parsers: A dictionary of grammar parsers.
|
|
44
|
+
:param starting: The starting position of the element.
|
|
45
|
+
:param boundary: The boundary position of the element.
|
|
46
|
+
:param key: The key for the element. Defaults to "".
|
|
47
|
+
:param **kwargs: Additional keyword arguments.
|
|
48
|
+
:returns: None
|
|
49
|
+
"""
|
|
37
50
|
self.handler = handler
|
|
38
51
|
self.pattern = pattern
|
|
39
52
|
self.matching = matching
|
|
@@ -57,7 +70,13 @@ class Capture:
|
|
|
57
70
|
return f"@capture<{self.key}>"
|
|
58
71
|
|
|
59
72
|
def dispatch(self) -> list[Capture | ContentElement]:
|
|
60
|
-
"""Dispatches the remaining parse of the capture group.
|
|
73
|
+
"""Dispatches the remaining parse of the capture group.
|
|
74
|
+
|
|
75
|
+
This method iterates over the defined parsers for the capture group and dispatches the remaining parse
|
|
76
|
+
based on the captured elements. It returns a list of captured elements or captures.
|
|
77
|
+
|
|
78
|
+
:return: A list of Capture or ContentElement objects representing the parsed elements.
|
|
79
|
+
"""
|
|
61
80
|
elements = []
|
|
62
81
|
for group_id, parser in self.parsers.items():
|
|
63
82
|
if group_id > self.pattern.number_of_captures():
|
|
@@ -100,22 +119,29 @@ class Capture:
|
|
|
100
119
|
return elements
|
|
101
120
|
|
|
102
121
|
|
|
103
|
-
def
|
|
122
|
+
def _dispatch_list(
|
|
104
123
|
pending_elements: list[Capture | ContentElement], parent: ContentElement | None = None
|
|
105
124
|
) -> list[ContentElement]:
|
|
106
125
|
"""Dispatches all captured parsers in the list."""
|
|
107
126
|
elements = []
|
|
108
127
|
for item in pending_elements:
|
|
109
128
|
if isinstance(item, Capture):
|
|
110
|
-
captured_elements: list[ContentElement] =
|
|
129
|
+
captured_elements: list[ContentElement] = _dispatch_list(item.dispatch())
|
|
111
130
|
elements.extend(captured_elements)
|
|
112
131
|
elif item != parent:
|
|
113
132
|
elements.append(item)
|
|
114
133
|
return elements
|
|
115
134
|
|
|
116
135
|
|
|
136
|
+
def _str_to_list(input: str | list[str]) -> list[str]:
|
|
137
|
+
if isinstance(input, str):
|
|
138
|
+
return [input] if input else []
|
|
139
|
+
else:
|
|
140
|
+
return input
|
|
141
|
+
|
|
142
|
+
|
|
117
143
|
class ContentElement:
|
|
118
|
-
"""The
|
|
144
|
+
"""The parsed grammar element."""
|
|
119
145
|
|
|
120
146
|
def __init__(
|
|
121
147
|
self,
|
|
@@ -125,15 +151,23 @@ class ContentElement:
|
|
|
125
151
|
characters: dict[POS, str],
|
|
126
152
|
children: list[Capture | ContentElement] | None = None,
|
|
127
153
|
) -> None:
|
|
154
|
+
"""
|
|
155
|
+
Initialize a new instance of the Element class.
|
|
156
|
+
|
|
157
|
+
:param token: The token associated with the element.
|
|
158
|
+
:param grammar: The grammar associated with the element.
|
|
159
|
+
:param content: The content associated with the element.
|
|
160
|
+
:param characters: The characters associated with the element.
|
|
161
|
+
:param children: The children associated with the element. Defaults to None.
|
|
162
|
+
"""
|
|
128
163
|
if children is None:
|
|
129
164
|
children = []
|
|
130
165
|
self.token = token
|
|
131
166
|
self.grammar = grammar
|
|
132
167
|
self.content = content
|
|
133
168
|
self.characters = characters
|
|
134
|
-
self.
|
|
135
|
-
self.
|
|
136
|
-
self._dispatched_children: bool = False
|
|
169
|
+
self._children_captures = children
|
|
170
|
+
self._dispatched: bool = False
|
|
137
171
|
|
|
138
172
|
@property
|
|
139
173
|
def _subelements(self) -> list[ContentElement]:
|
|
@@ -141,52 +175,54 @@ class ContentElement:
|
|
|
141
175
|
|
|
142
176
|
@property
|
|
143
177
|
def children(self) -> list[ContentElement]:
|
|
144
|
-
"
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
178
|
+
"""
|
|
179
|
+
Returns a list of children elements.
|
|
180
|
+
|
|
181
|
+
If the elements have not been dispatched yet, this method will dispatch them before returning.
|
|
182
|
+
|
|
183
|
+
:return: A list of ContentElement objects representing the children elements.
|
|
184
|
+
"""
|
|
185
|
+
if not self._dispatched:
|
|
186
|
+
self._dispatch()
|
|
187
|
+
return self._children
|
|
188
|
+
|
|
189
|
+
def _dispatch(self, nested: bool = False):
|
|
190
|
+
"""
|
|
191
|
+
Dispatches the content element and its children.
|
|
192
|
+
|
|
193
|
+
:param nested: Indicates whether the dispatch is nested within another dispatch.
|
|
194
|
+
:type nested: bool
|
|
195
|
+
:return: None
|
|
196
|
+
"""
|
|
197
|
+
if self._dispatched:
|
|
198
|
+
return
|
|
199
|
+
self._dispatched = True
|
|
200
|
+
self._children: list[ContentElement] = _dispatch_list(self._children_captures, parent=self)
|
|
201
|
+
self._children_captures = []
|
|
202
|
+
if nested:
|
|
203
|
+
for child in self._children:
|
|
204
|
+
child._dispatch(True)
|
|
152
205
|
|
|
153
206
|
def __eq__(self, other):
|
|
154
207
|
if not isinstance(other, ContentElement):
|
|
155
208
|
return False
|
|
156
209
|
return bool(self.grammar == other.grammar and self.characters == other.characters)
|
|
157
210
|
|
|
158
|
-
def
|
|
159
|
-
"Converts the object to dictionary."
|
|
160
|
-
out_dict = {"token": self.token}
|
|
161
|
-
if all_content or not self.children:
|
|
162
|
-
out_dict["content"] = self.content
|
|
163
|
-
if self.children:
|
|
164
|
-
out_dict["children"] = (
|
|
165
|
-
self._list_property_to_dict(
|
|
166
|
-
"children", verbosity=verbosity - 1, all_content=all_content
|
|
167
|
-
)
|
|
168
|
-
if verbosity
|
|
169
|
-
else self.children
|
|
170
|
-
)
|
|
171
|
-
return out_dict
|
|
172
|
-
|
|
173
|
-
def find(
|
|
211
|
+
def _find(
|
|
174
212
|
self,
|
|
175
213
|
tokens: str | list[str],
|
|
214
|
+
start_tokens: str | list[str] = "",
|
|
215
|
+
hide_tokens: str | list[str] = "",
|
|
176
216
|
stop_tokens: str | list[str] = "",
|
|
177
|
-
|
|
178
|
-
stack: list[str] | None = None,
|
|
217
|
+
depth: int = -1,
|
|
179
218
|
attribute: str = "_subelements",
|
|
219
|
+
stack: list[str] | None = None,
|
|
180
220
|
) -> Generator[tuple[ContentElement, list[str]], None, None]:
|
|
181
|
-
|
|
221
|
+
tokens = _str_to_list(tokens)
|
|
222
|
+
start_tokens = _str_to_list(start_tokens)
|
|
223
|
+
hide_tokens = _str_to_list(hide_tokens)
|
|
224
|
+
stop_tokens = _str_to_list(stop_tokens)
|
|
182
225
|
|
|
183
|
-
The find method will return a generator that globs though the element-tree, searching for the next
|
|
184
|
-
subelement that matches the given token.
|
|
185
|
-
"""
|
|
186
|
-
if isinstance(tokens, str):
|
|
187
|
-
tokens = [tokens]
|
|
188
|
-
if isinstance(stop_tokens, str):
|
|
189
|
-
stop_tokens = [stop_tokens] if stop_tokens else []
|
|
190
226
|
if not set(tokens).isdisjoint(set(stop_tokens)):
|
|
191
227
|
raise ValueError("Input tokens and stop_tokens must be disjoint")
|
|
192
228
|
|
|
@@ -194,8 +230,10 @@ class ContentElement:
|
|
|
194
230
|
stack = []
|
|
195
231
|
stack += [self.token]
|
|
196
232
|
|
|
197
|
-
|
|
198
|
-
|
|
233
|
+
start_found = not start_tokens
|
|
234
|
+
|
|
235
|
+
if depth:
|
|
236
|
+
depth -= 1
|
|
199
237
|
children: list[ContentElement] = getattr(self, attribute, self._subelements)
|
|
200
238
|
for child in children:
|
|
201
239
|
if stop_tokens and (
|
|
@@ -204,29 +242,126 @@ class ContentElement:
|
|
|
204
242
|
):
|
|
205
243
|
return None
|
|
206
244
|
|
|
207
|
-
if child.token in
|
|
245
|
+
if not start_found and child.token in start_tokens:
|
|
246
|
+
start_found = True
|
|
247
|
+
start_tokens = []
|
|
248
|
+
|
|
249
|
+
if (
|
|
250
|
+
start_found
|
|
251
|
+
and (child.token in tokens or tokens == ["*"])
|
|
252
|
+
and child.token not in hide_tokens
|
|
253
|
+
):
|
|
208
254
|
yield child, [e for e in stack]
|
|
209
|
-
if
|
|
210
|
-
nested_generator = child.
|
|
211
|
-
tokens,
|
|
255
|
+
if depth:
|
|
256
|
+
nested_generator = child._find(
|
|
257
|
+
tokens,
|
|
258
|
+
start_tokens=start_tokens,
|
|
259
|
+
hide_tokens=hide_tokens,
|
|
260
|
+
stop_tokens=stop_tokens,
|
|
261
|
+
depth=depth - 1,
|
|
262
|
+
stack=[e for e in stack],
|
|
212
263
|
)
|
|
213
264
|
yield from nested_generator
|
|
214
265
|
return None
|
|
215
266
|
|
|
267
|
+
def find(
|
|
268
|
+
self,
|
|
269
|
+
tokens: str | list[str],
|
|
270
|
+
start_tokens: str | list[str] = "",
|
|
271
|
+
hide_tokens: str | list[str] = "",
|
|
272
|
+
stop_tokens: str | list[str] = "",
|
|
273
|
+
depth: int = -1,
|
|
274
|
+
attribute: str = "_subelements",
|
|
275
|
+
) -> Generator[tuple[ContentElement, list[str]], None, None]:
|
|
276
|
+
"""
|
|
277
|
+
Find content elements based on the given criteria.
|
|
278
|
+
|
|
279
|
+
The find method will return a generator that globs though the element-tree, searching for the next
|
|
280
|
+
subelement that matches the given token.
|
|
281
|
+
|
|
282
|
+
:param tokens: The tokens to search for. Can be a single token or a list of tokens.
|
|
283
|
+
:param start_tokens: The tokens that mark the start of the search. Can be a single token or a list of tokens.
|
|
284
|
+
:param hide_tokens: The tokens to hide from the search results. Can be a single token or a list of tokens.
|
|
285
|
+
:param stop_tokens: The tokens that mark the end of the search. Can be a single token or a list of tokens.
|
|
286
|
+
:param depth: The maximum depth to search. Defaults to -1 (unlimited depth).
|
|
287
|
+
:param attribute: The attribute name to access the subelements. Defaults to "_subelements".
|
|
288
|
+
|
|
289
|
+
:yield: A tuple containing the found content element and the stack of tokens encountered.
|
|
290
|
+
|
|
291
|
+
:raises ValueError: If the input tokens and stop_tokens are not disjoint.
|
|
292
|
+
|
|
293
|
+
:return: None if no matching content elements are found.
|
|
294
|
+
"""
|
|
295
|
+
return self._find(
|
|
296
|
+
tokens,
|
|
297
|
+
start_tokens=start_tokens,
|
|
298
|
+
hide_tokens=hide_tokens,
|
|
299
|
+
stop_tokens=stop_tokens,
|
|
300
|
+
depth=depth,
|
|
301
|
+
attribute=attribute,
|
|
302
|
+
)
|
|
303
|
+
|
|
216
304
|
def findall(
|
|
217
305
|
self,
|
|
218
306
|
tokens: str | list[str],
|
|
307
|
+
start_tokens: str | list[str] = "",
|
|
308
|
+
hide_tokens: str | list[str] = "",
|
|
219
309
|
stop_tokens: str | list[str] = "",
|
|
220
|
-
|
|
310
|
+
depth: int = -1,
|
|
221
311
|
attribute: str = "_subelements",
|
|
222
312
|
) -> list[tuple[ContentElement, list[str]]]:
|
|
223
|
-
"""
|
|
313
|
+
"""
|
|
314
|
+
Find all occurrences of the specified tokens within the content element.
|
|
315
|
+
|
|
316
|
+
:param tokens: The tokens to search for.
|
|
317
|
+
:param start_tokens: The tokens that must appear before the found tokens. Defaults to "".
|
|
318
|
+
:param hide_tokens: The tokens that should be hidden from the search. Defaults to "".
|
|
319
|
+
:param stop_tokens: The tokens that, if found, should stop the search. Defaults to "".
|
|
320
|
+
:param depth: The maximum depth to search. Defaults to -1 (unlimited depth).
|
|
321
|
+
:param attribute: The attribute to search within. Defaults to "_subelements".
|
|
322
|
+
|
|
323
|
+
:return: A list of tuples containing the content element and the found tokens.
|
|
324
|
+
"""
|
|
224
325
|
return list(
|
|
225
|
-
self.
|
|
326
|
+
self._find(
|
|
327
|
+
tokens,
|
|
328
|
+
start_tokens=start_tokens,
|
|
329
|
+
hide_tokens=hide_tokens,
|
|
330
|
+
stop_tokens=stop_tokens,
|
|
331
|
+
depth=depth,
|
|
332
|
+
attribute=attribute,
|
|
333
|
+
)
|
|
226
334
|
)
|
|
227
335
|
|
|
336
|
+
def to_dict(self, depth: int = -1, all_content: bool = False, **kwargs) -> dict:
|
|
337
|
+
"""
|
|
338
|
+
Converts the object to a dictionary.
|
|
339
|
+
|
|
340
|
+
:param depth: The depth of the conversion. Defaults to -1.
|
|
341
|
+
:param all_content: Whether to include all content or only the top-level content. Defaults to False.
|
|
342
|
+
|
|
343
|
+
:return: The converted dictionary representation of the object.
|
|
344
|
+
"""
|
|
345
|
+
out_dict = {"token": self.token}
|
|
346
|
+
if all_content or not self.children:
|
|
347
|
+
out_dict["content"] = self.content
|
|
348
|
+
if self.children:
|
|
349
|
+
out_dict["children"] = (
|
|
350
|
+
self._list_property_to_dict("children", depth=depth - 1, all_content=all_content)
|
|
351
|
+
if depth
|
|
352
|
+
else self.children
|
|
353
|
+
)
|
|
354
|
+
return out_dict
|
|
355
|
+
|
|
228
356
|
def flatten(self) -> list[tuple[tuple[int, int], str, list[str]]]:
|
|
229
|
-
"""
|
|
357
|
+
"""
|
|
358
|
+
Converts the object to a flattened array of tokens per index, similarly to vscode-textmate.
|
|
359
|
+
|
|
360
|
+
:return: A list of tuples representing the flattened tokens. Each tuple contains:
|
|
361
|
+
- A tuple representing the starting and ending index of the token.
|
|
362
|
+
- The content of the token.
|
|
363
|
+
- A list of keys associated with the token.
|
|
364
|
+
"""
|
|
230
365
|
token_dict = self._token_by_index(defaultdict(list))
|
|
231
366
|
tokens = []
|
|
232
367
|
for (_, key), group in groupby(sorted(token_dict.items()), lambda x: (x[0][0], x[1])):
|
|
@@ -242,11 +377,20 @@ class ContentElement:
|
|
|
242
377
|
def print(
|
|
243
378
|
self,
|
|
244
379
|
flatten: bool = False,
|
|
245
|
-
|
|
380
|
+
depth: int = -1,
|
|
246
381
|
all_content: bool = False,
|
|
247
382
|
**kwargs,
|
|
248
383
|
) -> None:
|
|
249
|
-
"""
|
|
384
|
+
"""
|
|
385
|
+
Prints the current object recursively by converting it to a dictionary or a flattened array.
|
|
386
|
+
|
|
387
|
+
:param flatten: If True, flattens the object before printing. Defaults to False.
|
|
388
|
+
:param depth: The maximum depth to print. Defaults to -1 (unlimited depth).
|
|
389
|
+
:param all_content: If True, includes all content in the printout. Defaults to False.
|
|
390
|
+
:param **kwargs: Additional keyword arguments to be passed to the pprint function.
|
|
391
|
+
|
|
392
|
+
:return: None
|
|
393
|
+
"""
|
|
250
394
|
if flatten:
|
|
251
395
|
pprint(
|
|
252
396
|
self.flatten(**kwargs),
|
|
@@ -256,14 +400,23 @@ class ContentElement:
|
|
|
256
400
|
)
|
|
257
401
|
else:
|
|
258
402
|
pprint(
|
|
259
|
-
self.to_dict(
|
|
403
|
+
self.to_dict(depth=depth, all_content=all_content, **kwargs),
|
|
260
404
|
sort_dicts=False,
|
|
261
405
|
width=kwargs.pop("width", 150),
|
|
262
406
|
**kwargs,
|
|
263
407
|
)
|
|
264
408
|
|
|
265
409
|
def _token_by_index(self, token_dict: TOKEN_DICT | None = None) -> TOKEN_DICT:
|
|
266
|
-
"""Recursively tokenize every index between start and close.
|
|
410
|
+
"""Recursively tokenize every index between start and close.
|
|
411
|
+
|
|
412
|
+
This method recursively tokenizes every index between the start and close positions of the element.
|
|
413
|
+
It populates a dictionary, `token_dict`, with the tokens corresponding to each index.
|
|
414
|
+
|
|
415
|
+
:param token_dict: A dictionary to store the tokens. If None, a new dictionary is created.
|
|
416
|
+
:type token_dict: dict | None
|
|
417
|
+
:return: A dictionary containing the tokens for each index.
|
|
418
|
+
:rtype: dict
|
|
419
|
+
"""
|
|
267
420
|
if token_dict is None:
|
|
268
421
|
token_dict = defaultdict(list)
|
|
269
422
|
for pos in self.characters:
|
|
@@ -291,21 +444,27 @@ class ContentBlockElement(ContentElement):
|
|
|
291
444
|
|
|
292
445
|
def __init__(
|
|
293
446
|
self,
|
|
447
|
+
*args,
|
|
294
448
|
begin: list[Capture | ContentElement] | None = None,
|
|
295
449
|
end: list[Capture | ContentElement] | None = None,
|
|
296
450
|
**kwargs,
|
|
297
451
|
) -> None:
|
|
452
|
+
"""
|
|
453
|
+
Initialize a new instance of the Element class.
|
|
454
|
+
|
|
455
|
+
:param begin: A list of Capture or ContentElement objects representing the beginning captures of the element. Defaults to None.
|
|
456
|
+
:param end: A list of Capture or ContentElement objects representing the ending captures of the element. Defaults to None.
|
|
457
|
+
:param **kwargs: Additional keyword arguments to be passed to the parent class constructor.
|
|
458
|
+
|
|
459
|
+
:return: None
|
|
460
|
+
"""
|
|
298
461
|
if end is None:
|
|
299
462
|
end = []
|
|
300
463
|
if begin is None:
|
|
301
464
|
begin = []
|
|
302
|
-
super().__init__(**kwargs)
|
|
303
|
-
self.
|
|
304
|
-
self.
|
|
305
|
-
self._begin_dispached: list[ContentElement] = []
|
|
306
|
-
self._end_dispached: list[ContentElement] = []
|
|
307
|
-
self._dispatched_begin: bool = False
|
|
308
|
-
self._dispatched_end: bool = False
|
|
465
|
+
super().__init__(*args, **kwargs)
|
|
466
|
+
self._begin_captures = begin
|
|
467
|
+
self._end_captures = end
|
|
309
468
|
|
|
310
469
|
@property
|
|
311
470
|
def _subelements(self) -> list[ContentElement]:
|
|
@@ -313,39 +472,63 @@ class ContentBlockElement(ContentElement):
|
|
|
313
472
|
|
|
314
473
|
@property
|
|
315
474
|
def begin(self) -> list[ContentElement]:
|
|
316
|
-
"
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
475
|
+
"""
|
|
476
|
+
Returns the list of begin elements.
|
|
477
|
+
|
|
478
|
+
If the elements have not been dispatched yet, this method will dispatch them before returning.
|
|
479
|
+
|
|
480
|
+
:return: The list of begin elements.
|
|
481
|
+
"""
|
|
482
|
+
if not self._dispatched:
|
|
483
|
+
self._dispatch()
|
|
484
|
+
return self._begin
|
|
324
485
|
|
|
325
486
|
@property
|
|
326
487
|
def end(self) -> list[ContentElement]:
|
|
327
|
-
"
|
|
328
|
-
|
|
329
|
-
if not self._dispatched_end:
|
|
330
|
-
self._end_dispached = dispatch_list(self._end_pending, parent=self)
|
|
331
|
-
self._dispatched_end = True
|
|
332
|
-
return self._end_dispached
|
|
333
|
-
else:
|
|
334
|
-
return []
|
|
488
|
+
"""
|
|
489
|
+
Returns the end elements.
|
|
335
490
|
|
|
336
|
-
|
|
337
|
-
|
|
491
|
+
If the elements have not been dispatched yet, this method will dispatch them before returning.
|
|
492
|
+
|
|
493
|
+
:return: A list of end elements.
|
|
494
|
+
"""
|
|
495
|
+
if not self._dispatched:
|
|
496
|
+
self._dispatch()
|
|
497
|
+
return self._end
|
|
498
|
+
|
|
499
|
+
def _dispatch(self, nested: bool = False):
|
|
500
|
+
if self._dispatched:
|
|
501
|
+
return
|
|
502
|
+
super()._dispatch(nested)
|
|
503
|
+
self._begin: list[ContentElement] = _dispatch_list(self._begin_captures, parent=self)
|
|
504
|
+
self._end: list[ContentElement] = _dispatch_list(self._end_captures, parent=self)
|
|
505
|
+
self._begin_captures, self._end_captures = [], []
|
|
506
|
+
if nested:
|
|
507
|
+
for item in self._begin:
|
|
508
|
+
item._dispatch(True)
|
|
509
|
+
for item in self._end:
|
|
510
|
+
item._dispatch(True)
|
|
511
|
+
|
|
512
|
+
def to_dict(self, depth: int = -1, all_content: bool = False, **kwargs) -> dict:
|
|
513
|
+
"""
|
|
514
|
+
Converts the element to a dictionary representation.
|
|
515
|
+
|
|
516
|
+
:param depth: The depth of the conversion. Defaults to -1.
|
|
517
|
+
:param all_content: Whether to include all content. Defaults to False.
|
|
518
|
+
:param **kwargs: Additional keyword arguments.
|
|
519
|
+
|
|
520
|
+
:return: The dictionary representation of the element.
|
|
521
|
+
"""
|
|
522
|
+
out_dict = super().to_dict(depth=depth, all_content=all_content, **kwargs)
|
|
338
523
|
if self.begin:
|
|
339
524
|
out_dict["begin"] = (
|
|
340
|
-
self._list_property_to_dict("begin",
|
|
341
|
-
if
|
|
525
|
+
self._list_property_to_dict("begin", depth=depth - 1, **kwargs)
|
|
526
|
+
if depth
|
|
342
527
|
else self.begin
|
|
343
528
|
)
|
|
344
529
|
if self.end:
|
|
345
530
|
out_dict["end"] = (
|
|
346
|
-
self._list_property_to_dict("end",
|
|
347
|
-
if verbosity
|
|
348
|
-
else self.end
|
|
531
|
+
self._list_property_to_dict("end", depth=depth - 1, **kwargs) if depth else self.end
|
|
349
532
|
)
|
|
350
533
|
|
|
351
534
|
ordered_keys = [
|
textmate_grammar/language.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
3
|
from .elements import Capture, ContentElement
|
|
4
|
-
from .exceptions import IncompatibleFileType
|
|
5
|
-
from .handler import POS, ContentHandler
|
|
6
|
-
from .logger import LOGGER
|
|
7
4
|
from .parser import GrammarParser, PatternsParser
|
|
5
|
+
from .utils.cache import TextmateCache, init_cache
|
|
6
|
+
from .utils.exceptions import IncompatibleFileType
|
|
7
|
+
from .utils.handler import POS, ContentHandler
|
|
8
|
+
from .utils.logger import LOGGER
|
|
8
9
|
|
|
9
10
|
LANGUAGE_PARSERS = {}
|
|
10
11
|
|
|
@@ -27,6 +28,20 @@ class LanguageParser(PatternsParser):
|
|
|
27
28
|
"""The parser of a language grammar."""
|
|
28
29
|
|
|
29
30
|
def __init__(self, grammar: dict, **kwargs):
|
|
31
|
+
"""
|
|
32
|
+
Initialize a Language object.
|
|
33
|
+
|
|
34
|
+
:param grammar: The grammar definition for the language.
|
|
35
|
+
:param kwargs: Additional keyword arguments.
|
|
36
|
+
|
|
37
|
+
:ivar name: The name of the language.
|
|
38
|
+
:ivar uuid: The UUID of the language.
|
|
39
|
+
:ivar file_types: The file types associated with the language.
|
|
40
|
+
:ivar token: The scope name of the language.
|
|
41
|
+
:ivar repository: The repository of grammar rules for the language.
|
|
42
|
+
:ivar injections: The list of injection rules for the language.
|
|
43
|
+
:ivar _cache: The cache object for the language.
|
|
44
|
+
"""
|
|
30
45
|
super().__init__(grammar, key=grammar.get("name", "myLanguage"), language=self, **kwargs)
|
|
31
46
|
|
|
32
47
|
self.name = grammar.get("name", "")
|
|
@@ -35,9 +50,10 @@ class LanguageParser(PatternsParser):
|
|
|
35
50
|
self.token = grammar.get("scopeName", "myScope")
|
|
36
51
|
self.repository = {}
|
|
37
52
|
self.injections: list[dict] = []
|
|
53
|
+
self._cache: TextmateCache = init_cache()
|
|
38
54
|
|
|
39
|
-
#
|
|
40
|
-
for repo in
|
|
55
|
+
# Initialize grammars in repository
|
|
56
|
+
for repo in _gen_repositories(grammar):
|
|
41
57
|
for key, parser_grammar in repo.items():
|
|
42
58
|
self.repository[key] = GrammarParser.initialize(
|
|
43
59
|
parser_grammar, key=key, language=self
|
|
@@ -80,35 +96,62 @@ class LanguageParser(PatternsParser):
|
|
|
80
96
|
|
|
81
97
|
super()._initialize_repository()
|
|
82
98
|
|
|
83
|
-
def parse_file(self, filePath: str | Path, **kwargs) ->
|
|
84
|
-
"""
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
def parse_file(self, filePath: str | Path, **kwargs) -> ContentElement | None:
|
|
100
|
+
"""
|
|
101
|
+
Parses an entire file with the current grammar.
|
|
102
|
+
|
|
103
|
+
:param filePath: The path to the file to be parsed.
|
|
104
|
+
:param kwargs: Additional keyword arguments to be passed to the parser.
|
|
105
|
+
:return: The parsed element if successful, None otherwise.
|
|
106
|
+
"""
|
|
107
|
+
if not isinstance(filePath, Path):
|
|
108
|
+
filePath = Path(filePath).resolve()
|
|
87
109
|
|
|
88
110
|
if filePath.suffix.split(".")[-1] not in self.file_types:
|
|
89
111
|
raise IncompatibleFileType(extensions=self.file_types)
|
|
90
112
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
113
|
+
if self._cache.cache_valid(filePath):
|
|
114
|
+
element = self._cache.load(filePath)
|
|
115
|
+
else:
|
|
116
|
+
handler = ContentHandler.from_path(filePath)
|
|
117
|
+
if handler.source == "":
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
# Configure logger
|
|
121
|
+
LOGGER.configure(self, height=len(handler.lines), width=max(handler.line_lengths))
|
|
122
|
+
element = self._parse_language(handler, **kwargs) # type: ignore
|
|
123
|
+
|
|
124
|
+
if element is not None:
|
|
125
|
+
self._cache.save(filePath, element)
|
|
126
|
+
return element
|
|
127
|
+
|
|
128
|
+
def parse_string(self, input: str, **kwargs) -> ContentElement | None:
|
|
129
|
+
"""
|
|
130
|
+
Parses an input string.
|
|
131
|
+
|
|
132
|
+
:param input: The input string to be parsed.
|
|
133
|
+
:param kwargs: Additional keyword arguments.
|
|
134
|
+
:return: The result of parsing the input string.
|
|
135
|
+
"""
|
|
136
|
+
handler = ContentHandler(input)
|
|
95
137
|
# Configure logger
|
|
96
138
|
LOGGER.configure(self, height=len(handler.lines), width=max(handler.line_lengths))
|
|
97
139
|
|
|
98
|
-
|
|
140
|
+
element = self._parse_language(handler, **kwargs)
|
|
99
141
|
|
|
100
|
-
|
|
101
|
-
"""Parses an input string"""
|
|
102
|
-
handler = ContentHandler(input)
|
|
103
|
-
# Configure logger
|
|
104
|
-
LOGGER.configure(self, height=len(handler.lines), width=max(handler.line_lengths))
|
|
105
|
-
return self._parse_language(handler, **kwargs)
|
|
142
|
+
return element
|
|
106
143
|
|
|
107
|
-
def _parse_language(self, handler: ContentHandler, **kwargs) ->
|
|
144
|
+
def _parse_language(self, handler: ContentHandler, **kwargs) -> ContentElement | None:
|
|
108
145
|
"""Parses the current stream with the language scope."""
|
|
109
146
|
|
|
110
147
|
parsed, elements, _ = self.parse(handler, (0, 0), **kwargs)
|
|
111
|
-
|
|
148
|
+
|
|
149
|
+
if parsed:
|
|
150
|
+
element = elements[0]
|
|
151
|
+
element._dispatch(nested=True) # type: ignore
|
|
152
|
+
else:
|
|
153
|
+
element = None
|
|
154
|
+
return element # type: ignore
|
|
112
155
|
|
|
113
156
|
def _parse(
|
|
114
157
|
self, handler: ContentHandler, starting: POS, **kwargs
|
|
@@ -117,16 +160,16 @@ class LanguageParser(PatternsParser):
|
|
|
117
160
|
return super()._parse(handler, starting, find_one=False, **kwargs)
|
|
118
161
|
|
|
119
162
|
|
|
120
|
-
def
|
|
163
|
+
def _gen_repositories(grammar, key="repository"):
|
|
121
164
|
"""Recursively gets all repositories from a grammar dictionary"""
|
|
122
165
|
if hasattr(grammar, "items"):
|
|
123
166
|
for k, v in grammar.items():
|
|
124
167
|
if k == key:
|
|
125
168
|
yield v
|
|
126
169
|
if isinstance(v, dict):
|
|
127
|
-
for result in
|
|
170
|
+
for result in _gen_repositories(v, key):
|
|
128
171
|
yield result
|
|
129
172
|
elif isinstance(v, list):
|
|
130
173
|
for d in v:
|
|
131
|
-
for result in
|
|
174
|
+
for result in _gen_repositories(d, key):
|
|
132
175
|
yield result
|