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