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.
@@ -0,0 +1 @@
1
+ __version__ = "0.4.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,31 @@ 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)
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 base grammar element object."""
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._children_pending = children
135
- self._children_dispached: list[ContentElement] = []
136
- self._dispatched_children: bool = False
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
- "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 []
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 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(
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
- verbosity: int = -1,
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
- """Find the next subelement that match the input token(s).
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
- if verbosity:
198
- verbosity -= 1
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 tokens or tokens == ["*"]:
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 verbosity:
210
- nested_generator = child.find(
211
- tokens, verbosity=verbosity - 1, stack=[e for e in stack]
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
- verbosity: int = -1,
313
+ depth: int = -1,
221
314
  attribute: str = "_subelements",
222
315
  ) -> list[tuple[ContentElement, list[str]]]:
223
- """Returns subelements that match the input token(s)."""
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.find(tokens, stop_tokens=stop_tokens, verbosity=verbosity, attribute=attribute)
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
- """Converts the object to a flattened array of tokens per index."""
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
- verbosity: int = -1,
383
+ depth: int = -1,
246
384
  all_content: bool = False,
247
385
  **kwargs,
248
386
  ) -> None:
249
- """Prints the current object recursively by converting to dictionary."""
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(verbosity=verbosity, all_content=all_content, **kwargs),
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._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
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
- "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 []
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
- "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 []
491
+ """
492
+ Returns the end elements.
335
493
 
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)
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", verbosity=verbosity - 1, **kwargs)
341
- if verbosity
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", verbosity=verbosity - 1, **kwargs)
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 = [
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import shutil
2
4
  from pathlib import Path
3
5
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import plistlib
2
4
  from pathlib import Path
3
5
 
@@ -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
- # Initalize grammars in repository
40
- for repo in gen_repositories(grammar):
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) -> Capture | ContentElement | None:
84
- """Parses an entire file with the current grammar"""
85
- if type(filePath) != Path:
86
- filePath = Path(filePath)
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
- handler = ContentHandler.from_path(filePath)
92
- if handler.source == "":
93
- return None
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
- return self._parse_language(handler, **kwargs)
142
+ element = self._parse_language(handler, **kwargs)
99
143
 
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)
144
+ return element
106
145
 
107
- def _parse_language(self, handler: ContentHandler, **kwargs) -> Capture | ContentElement | None:
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
- return elements[0] if parsed else None
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 gen_repositories(grammar, key="repository"):
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 gen_repositories(v, key):
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 gen_repositories(d, key):
176
+ for result in _gen_repositories(d, key):
132
177
  yield result