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.
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -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
- ) -> None:
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 dispatch_list(
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] = dispatch_list(item.dispatch())
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 base grammar element object."""
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._children_pending = children
135
- self._children_dispached: list[ContentElement] = []
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
- "Children elements"
145
- if self._children_pending:
146
- if not self._dispatched_children:
147
- self._children_dispached = dispatch_list(self._children_pending, parent=self)
148
- self._dispatched_children = True
149
- return self._children_dispached
150
- else:
151
- return []
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 to_dict(self, verbosity: int = -1, all_content: bool = False, **kwargs) -> dict:
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
- verbosity: int = -1,
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
- """Find the next subelement that match the input token(s).
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
- if verbosity:
198
- verbosity -= 1
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 tokens or tokens == ["*"]:
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 verbosity:
210
- nested_generator = child.find(
211
- tokens, verbosity=verbosity - 1, stack=[e for e in stack]
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
- verbosity: int = -1,
310
+ depth: int = -1,
221
311
  attribute: str = "_subelements",
222
312
  ) -> list[tuple[ContentElement, list[str]]]:
223
- """Returns subelements that match the input token(s)."""
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.find(tokens, stop_tokens=stop_tokens, verbosity=verbosity, attribute=attribute)
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
- """Converts the object to a flattened array of tokens per index."""
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
- verbosity: int = -1,
380
+ depth: int = -1,
246
381
  all_content: bool = False,
247
382
  **kwargs,
248
383
  ) -> None:
249
- """Prints the current object recursively by converting to dictionary."""
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(verbosity=verbosity, all_content=all_content, **kwargs),
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._begin_pending = begin
304
- self._end_pending = end
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
- "Begin elements"
317
- if self._begin_pending:
318
- if not self._dispatched_begin:
319
- self._begin_dispached = dispatch_list(self._begin_pending, parent=self)
320
- self._dispatched_begin = True
321
- return self._begin_dispached
322
- else:
323
- return []
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
- "End elements"
328
- if self._end_pending:
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
- def to_dict(self, verbosity: int = -1, all_content: bool = False, **kwargs) -> dict:
337
- out_dict = super().to_dict(verbosity=verbosity, all_content=all_content, **kwargs)
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", verbosity=verbosity - 1, **kwargs)
341
- if verbosity
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", verbosity=verbosity - 1, **kwargs)
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 = [
@@ -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
- # Initalize grammars in repository
40
- for repo in gen_repositories(grammar):
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) -> Capture | ContentElement | None:
84
- """Parses an entire file with the current grammar"""
85
- if type(filePath) != Path:
86
- filePath = Path(filePath)
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
- handler = ContentHandler.from_path(filePath)
92
- if handler.source == "":
93
- return None
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
- return self._parse_language(handler, **kwargs)
140
+ element = self._parse_language(handler, **kwargs)
99
141
 
100
- def parse_string(self, input: str, **kwargs):
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) -> Capture | ContentElement | None:
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
- return elements[0] if parsed else None
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 gen_repositories(grammar, key="repository"):
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 gen_repositories(v, key):
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 gen_repositories(d, key):
174
+ for result in _gen_repositories(d, key):
132
175
  yield result