mkdocstrings-matlab 0.9.7__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. mkdocstrings_handlers/matlab/__init__.py +26 -3
  2. mkdocstrings_handlers/matlab/config.py +885 -0
  3. mkdocstrings_handlers/matlab/handler.py +155 -296
  4. mkdocstrings_handlers/matlab/logger.py +111 -0
  5. mkdocstrings_handlers/matlab/rendering.py +818 -0
  6. mkdocstrings_handlers/matlab/templates/material/attributes.html.jinja +25 -0
  7. mkdocstrings_handlers/matlab/templates/material/backlinks.html.jinja +17 -0
  8. mkdocstrings_handlers/matlab/templates/material/children.html.jinja +70 -62
  9. mkdocstrings_handlers/matlab/templates/material/class.html.jinja +236 -0
  10. mkdocstrings_handlers/matlab/templates/material/docstring/admonition.html.jinja +20 -0
  11. mkdocstrings_handlers/matlab/templates/material/docstring/classes.html.jinja +85 -0
  12. mkdocstrings_handlers/matlab/templates/material/docstring/examples.html.jinja +27 -0
  13. mkdocstrings_handlers/matlab/templates/material/docstring/functions.html.jinja +91 -0
  14. mkdocstrings_handlers/matlab/templates/material/docstring/input_arguments.html.jinja +171 -0
  15. mkdocstrings_handlers/matlab/templates/material/docstring/name_value_arguments.html.jinja +166 -0
  16. mkdocstrings_handlers/matlab/templates/material/docstring/namespaces.html.jinja +5 -6
  17. mkdocstrings_handlers/matlab/templates/material/docstring/output_arguments.html.jinja +152 -0
  18. mkdocstrings_handlers/matlab/templates/material/docstring/properties.html.jinja +25 -26
  19. mkdocstrings_handlers/matlab/templates/material/docstring.html.jinja +53 -0
  20. mkdocstrings_handlers/matlab/templates/material/expression.html.jinja +55 -0
  21. mkdocstrings_handlers/matlab/templates/material/folder.html.jinja +31 -39
  22. mkdocstrings_handlers/matlab/templates/material/function.html.jinja +148 -0
  23. mkdocstrings_handlers/matlab/templates/material/language.html.jinja +18 -0
  24. mkdocstrings_handlers/matlab/templates/material/languages/en.html.jinja +38 -0
  25. mkdocstrings_handlers/matlab/templates/material/languages/ja.html.jinja +38 -0
  26. mkdocstrings_handlers/matlab/templates/material/languages/zh.html.jinja +38 -0
  27. mkdocstrings_handlers/matlab/templates/material/namespace.html.jinja +32 -38
  28. mkdocstrings_handlers/matlab/templates/material/property.html.jinja +39 -35
  29. mkdocstrings_handlers/matlab/templates/material/script.html.jinja +3 -25
  30. mkdocstrings_handlers/matlab/templates/material/signature.html.jinja +105 -0
  31. mkdocstrings_handlers/matlab/templates/material/style.css +179 -4
  32. mkdocstrings_handlers/matlab/templates/material/summary/classes.html.jinja +25 -0
  33. mkdocstrings_handlers/matlab/templates/material/summary/functions.html.jinja +25 -0
  34. mkdocstrings_handlers/matlab/templates/material/summary/namespaces.html.jinja +17 -13
  35. mkdocstrings_handlers/matlab/templates/material/summary/properties.html.jinja +17 -13
  36. mkdocstrings_handlers/matlab/templates/material/summary.html.jinja +6 -6
  37. {mkdocstrings_matlab-0.9.7.dist-info → mkdocstrings_matlab-1.0.0.dist-info}/METADATA +17 -19
  38. mkdocstrings_matlab-1.0.0.dist-info/RECORD +41 -0
  39. mkdocstrings_handlers/matlab/collect.py +0 -783
  40. mkdocstrings_handlers/matlab/enums.py +0 -54
  41. mkdocstrings_handlers/matlab/models.py +0 -633
  42. mkdocstrings_handlers/matlab/treesitter.py +0 -707
  43. mkdocstrings_matlab-0.9.7.dist-info/RECORD +0 -22
  44. {mkdocstrings_matlab-0.9.7.dist-info → mkdocstrings_matlab-1.0.0.dist-info}/WHEEL +0 -0
  45. {mkdocstrings_matlab-0.9.7.dist-info → mkdocstrings_matlab-1.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,707 +0,0 @@
1
- """Tree-sitter queries to extract information from MATLAB files."""
2
-
3
- import textwrap
4
- from collections import OrderedDict
5
- from pathlib import Path
6
- from typing import Any
7
-
8
- import charset_normalizer
9
- import tree_sitter_matlab as tsmatlab
10
- from tree_sitter import Language, Node, Parser
11
-
12
- from mkdocstrings_handlers.matlab.enums import ParameterKind
13
- from mkdocstrings_handlers.matlab.models import (
14
- AccessEnum,
15
- Class,
16
- Classfolder,
17
- Docstring,
18
- Function,
19
- MatlabMixin,
20
- Parameter,
21
- Parameters,
22
- Property,
23
- Script,
24
- )
25
-
26
- __all__ = ["FileParser"]
27
-
28
-
29
- LANGUAGE = Language(tsmatlab.language())
30
-
31
- PARSER = Parser(LANGUAGE)
32
-
33
- FILE_QUERY = LANGUAGE.query("""(source_file .
34
- (comment)* @header .
35
- [
36
- (function_definition) @function
37
- (class_definition) @class
38
- ]?
39
- )
40
- """)
41
-
42
-
43
- FUNCTION_QUERY = LANGUAGE.query("""(function_definition .
44
- ("function")
45
- (function_output .
46
- [
47
- (identifier) @output
48
- (multioutput_variable .
49
- [
50
- (identifier) @output
51
- _
52
- ]*
53
- )
54
- ]
55
- )?
56
- [
57
- ("set.") @setter
58
- ("get.") @getter
59
- ]?
60
- (identifier) @name
61
- (function_arguments .
62
- [
63
- (identifier) @input
64
- _
65
- ]*
66
- )?
67
- (comment)* @docstring
68
- (arguments_statement)* @arguments
69
- )""")
70
-
71
-
72
- ARGUMENTS_QUERY = LANGUAGE.query("""(arguments_statement .
73
- ("arguments")
74
- (attributes
75
- (identifier) @attributes
76
- )?
77
- (comment)?
78
- ("\\n")?
79
- (property)+ @arguments
80
- )""")
81
-
82
-
83
- PROPERTY_QUERY = LANGUAGE.query("""(property .
84
- [
85
- (identifier) @name
86
- (property_name
87
- (identifier) @options .
88
- (".") .
89
- (identifier) @name
90
- )
91
- ]
92
- (dimensions)? @dimensions
93
- (identifier)? @class
94
- (validation_functions)? @validators
95
- (default_value
96
- ("=")
97
- _+ @default
98
- )?
99
- (comment)* @comment
100
- )""")
101
-
102
-
103
- ATTRIBUTE_QUERY = LANGUAGE.query("""(attribute
104
- (identifier) @name
105
- (
106
- ("=")
107
- _+ @value
108
- )?
109
- )""")
110
-
111
-
112
- CLASS_QUERY = LANGUAGE.query("""("classdef" .
113
- (attributes
114
- (attribute) @attributes
115
- )?
116
- (identifier) @name
117
- (superclasses
118
- (property_name) @bases
119
- )? .
120
- (comment)* @docstring
121
- ("\\n")?
122
- [
123
- (comment)
124
- (methods) @methods
125
- (properties) @properties
126
- (enumeration) @enumeration
127
- ]*
128
- )""")
129
-
130
-
131
- METHODS_QUERY = LANGUAGE.query("""("methods" .
132
- (attributes
133
- (attribute) @attributes
134
- )? .
135
- ("\\n")? .
136
- (function_definition)* @methods
137
- )""")
138
-
139
- PROPERTIES_QUERY = LANGUAGE.query("""("properties" .
140
- (attributes
141
- (attribute) @attributes
142
- )? .
143
- ("\\n")? .
144
- (property)* @properties
145
- )""")
146
-
147
-
148
- def _strtobool(value: str) -> bool:
149
- """
150
- Convert a string representation of truth to boolean.
151
-
152
- Args:
153
- value (str): The string to convert. Expected values are "true", "1" for True, and any other value for False.
154
-
155
- Returns:
156
- bool: True if the input string is "true" or "1" (case insensitive), otherwise False.
157
- """
158
- if value.lower() in ["true", "1"]:
159
- return True
160
- else:
161
- return False
162
-
163
-
164
- def _dedent(lines: list[str]) -> list[str]:
165
- """
166
- Remove the common leading whitespace from each line in the given list of lines.
167
-
168
- Args:
169
- lines (list[str]): A list of strings where each string represents a line of text.
170
-
171
- Returns:
172
- list[str]: A list of strings with the common leading whitespace removed from each line.
173
- """
174
- text = "\n".join(lines)
175
- dedented_text = textwrap.dedent(text)
176
- return dedented_text.split("\n")
177
-
178
-
179
- class FileParser(object):
180
- """
181
- A class to parse MATLAB files using Tree-sitter.
182
-
183
- Attributes:
184
- filepath (Path): The path to the MATLAB file.
185
- encoding (str): The encoding of the file content.
186
- content: Returns the decoded content of the file.
187
-
188
- Methods:
189
- parse(**kwargs) -> MatlabMixin: Parses the MATLAB file and returns a MatlabMixin.
190
- """
191
-
192
- def __init__(self, filepath: Path):
193
- """
194
- Initialize the object with the given file path.
195
-
196
- Args:
197
- filepath (Path): The path to the file to be processed.
198
- """
199
- self.filepath: Path = filepath
200
- result = charset_normalizer.from_path(filepath).best()
201
- self.encoding: str = result.encoding if result else "utf-8"
202
- with open(filepath, "rb") as f:
203
- self._content: bytes = f.read()
204
- self._node: Node | None = None
205
-
206
- @property
207
- def content(self):
208
- """
209
- Property that decodes and returns the content using the specified encoding.
210
-
211
- Returns:
212
- str: The decoded content.
213
- """
214
- return self._content.decode(self.encoding)
215
-
216
- def parse(self, **kwargs: Any) -> MatlabMixin:
217
- """
218
- Parse the content of the file and return a MatlabMixin.
219
-
220
- This method uses a tree-sitter parser to parse the content of the file
221
- and extract relevant information to create a MatlabMixin. It handles
222
- different types of Matlab constructs such as functions and classes.
223
-
224
- Args:
225
- **kwargs: Additional keyword arguments to pass to the parsing methods.
226
-
227
- Returns:
228
- MatlabMixin: An instance of MatlabMixin representing the parsed content.
229
-
230
- Raises:
231
- ValueError: If the file could not be parsed.
232
- """
233
- try:
234
- tree = PARSER.parse(self._content)
235
- cursor = tree.walk()
236
-
237
- if cursor.node is None:
238
- raise ValueError(f"The file {self.filepath} could not be parsed.")
239
- captures = FILE_QUERY.captures(cursor.node)
240
- if "function" in captures:
241
- model = self._parse_function(captures["function"][0], **kwargs)
242
- elif "class" in captures:
243
- model = self._parse_class(captures["class"][0], **kwargs)
244
- else:
245
- model = Script(self.filepath.stem, filepath=self.filepath, **kwargs)
246
-
247
- if not model.docstring:
248
- model.docstring = self._comment_docstring(
249
- captures.get("header", None), parent=model
250
- )
251
-
252
- return model
253
- except Exception as ex:
254
- syntax_error = SyntaxError("Error parsing Matlab file")
255
- syntax_error.filename = str(self.filepath)
256
- if self._node is not None:
257
- if self._node.text is not None:
258
- indentation = " " * self._node.start_point.column
259
- syntax_error.text = indentation + self._node.text.decode(
260
- self.encoding
261
- )
262
- syntax_error.lineno = self._node.start_point.row + 1
263
- syntax_error.offset = self._node.start_point.column + 1
264
- syntax_error.end_lineno = self._node.end_point.row + 1
265
- syntax_error.end_offset = self._node.end_point.column + 1
266
- raise syntax_error from ex
267
-
268
- def _parse_class(self, node: Node, **kwargs: Any) -> Class:
269
- """
270
- Parse a class node and return a Class or Classfolder model.
271
-
272
- This method processes a class node captured by the CLASS_QUERY and extracts
273
- its bases, docstring, attributes, properties, and methods. It constructs
274
- and returns a Class or Classfolder model based on the parsed information.
275
-
276
- Args:
277
- node (Node): The class node to parse.
278
- **kwargs: Additional keyword arguments to pass to the Class or Classfolder model.
279
-
280
- Returns:
281
- Class: The parsed Class or Classfolder model.
282
- """
283
- self._node = node
284
- saved_kwargs = {key: value for key, value in kwargs.items()}
285
- captures = CLASS_QUERY.captures(node)
286
-
287
- bases = self._decode_from_capture(captures, "bases")
288
- docstring = self._comment_docstring(captures.get("docstring", None))
289
-
290
- attribute_pairs = [
291
- self._parse_attribute(node) for node in captures.get("attributes", [])
292
- ]
293
- for key, value in attribute_pairs:
294
- if key in ["Sealed", "Abstract", "Hidden"]:
295
- kwargs[key] = value
296
-
297
- if self.filepath.parent.stem[0] == "@":
298
- model = Classfolder(
299
- self.filepath.stem,
300
- lineno=node.range.start_point.row + 1,
301
- endlineno=node.range.end_point.row + 1,
302
- bases=bases,
303
- docstring=docstring,
304
- filepath=self.filepath,
305
- **kwargs,
306
- )
307
- else:
308
- model = Class(
309
- self.filepath.stem,
310
- lineno=node.range.start_point.row + 1,
311
- endlineno=node.range.end_point.row + 1,
312
- bases=bases,
313
- docstring=docstring,
314
- filepath=self.filepath,
315
- **kwargs,
316
- )
317
-
318
- for property_captures in [
319
- PROPERTIES_QUERY.captures(node) for node in captures.get("properties", [])
320
- ]:
321
- property_kwargs = {key: value for key, value in saved_kwargs.items()}
322
- attribute_pairs = [
323
- self._parse_attribute(node)
324
- for node in property_captures.get("attributes", [])
325
- ]
326
- for key, value in attribute_pairs:
327
- if key in [
328
- "AbortSet",
329
- "Abstract",
330
- "Constant",
331
- "Dependant",
332
- "GetObservable",
333
- "Hidden",
334
- "NonCopyable",
335
- "SetObservable",
336
- "Transient",
337
- "WeakHandle",
338
- ]:
339
- property_kwargs[key] = value
340
- elif key in ["Access", "GetAccess", "SetAccess"]:
341
- if value in ["public", "protected", "private", "immutable"]:
342
- property_kwargs[key] = AccessEnum(value)
343
- else:
344
- property_kwargs[key] = AccessEnum.private
345
- for property_node in property_captures.get("properties", []):
346
- property_captures = PROPERTY_QUERY.captures(property_node)
347
-
348
- prop = Property(
349
- self._first_from_capture(property_captures, "name"),
350
- annotation=self._first_from_capture(property_captures, "class"),
351
- value=self._decode_from_capture(property_captures, "default"),
352
- docstring=self._comment_docstring(
353
- property_captures.get("comment", None)
354
- ),
355
- parent=model,
356
- **property_kwargs,
357
- )
358
- model.members[prop.name] = prop
359
-
360
- for method_captures in [
361
- METHODS_QUERY.captures(node) for node in captures.get("methods", [])
362
- ]:
363
- method_kwargs = {key: value for key, value in saved_kwargs.items()}
364
- attribute_pairs = [
365
- self._parse_attribute(node)
366
- for node in method_captures.get("attributes", [])
367
- ]
368
- for key, value in attribute_pairs:
369
- if key in [
370
- "Abstract",
371
- "Hidden",
372
- "Sealed",
373
- "Static",
374
- ]:
375
- method_kwargs[key] = value
376
- elif key == "Access":
377
- if value in ["public", "protected", "private", "immutable"]:
378
- method_kwargs[key] = AccessEnum(value)
379
- else:
380
- method_kwargs[key] = AccessEnum.private
381
- for method_node in method_captures.get("methods", []):
382
- method = self._parse_function(
383
- method_node, method=True, parent=model, **method_kwargs
384
- )
385
- if (
386
- method.name != self.filepath.stem
387
- and not method.Static
388
- and method.parameters
389
- ):
390
- # Remove self from first method argument
391
- method.parameters._params = method.parameters._params[1:]
392
- if method._is_getter and method.name in model.members:
393
- prop = model.members[method.name]
394
- if isinstance(prop, Property):
395
- prop.getter = method
396
- else:
397
- # This can be either an error or that it is a getter in an inherited class
398
- pass
399
- elif method._is_setter and method.name in model.members:
400
- prop = model.members[method.name]
401
- if isinstance(prop, Property):
402
- prop.setter = method
403
- else:
404
- # This can be either an error or that it is a setter in an inherited class
405
- pass
406
- else:
407
- model.members[method.name] = method
408
-
409
- return model
410
-
411
- def _parse_attribute(self, node: Node) -> tuple[str, Any]:
412
- """
413
- Parse an attribute from a given node.
414
-
415
- Args:
416
- node (Node): The node to parse the attribute from.
417
-
418
- Returns:
419
- tuple[str, Any]: A tuple containing the attribute key and its value.
420
- The value is `True` if no value is specified,
421
- otherwise it is the parsed value which can be a boolean or a string.
422
- """
423
- self._node = node
424
- captures = ATTRIBUTE_QUERY.captures(node)
425
-
426
- key = self._first_from_capture(captures, "name")
427
- if "value" not in captures:
428
- value = True
429
- elif captures["value"][0].type == "boolean":
430
- value = _strtobool(self._first_from_capture(captures, "value"))
431
- else:
432
- value = self._first_from_capture(captures, "value")
433
-
434
- return (key, value)
435
-
436
- def _parse_function(
437
- self, node: Node, method: bool = False, **kwargs: Any
438
- ) -> Function:
439
- """
440
- Parse a function node and return a Function model.
441
-
442
- Args:
443
- node (Node): The node representing the function in the syntax tree.
444
- method (bool, optional): Whether the function is a method. Defaults to False.
445
- **kwargs: Additional keyword arguments to pass to the Function model.
446
-
447
- Returns:
448
- Function: The parsed function model.
449
-
450
- Raises:
451
- KeyError: If required captures are missing from the node.
452
-
453
- """
454
- self._node = node
455
- captures: dict = FUNCTION_QUERY.matches(node)[0][1]
456
-
457
- input_names = self._decode_from_capture(captures, "input")
458
- parameters: dict = (
459
- OrderedDict(
460
- (name, Parameter(name, kind=ParameterKind.positional_only))
461
- for name in input_names
462
- )
463
- if input_names
464
- else {}
465
- )
466
- output_names = self._decode_from_capture(captures, "output")
467
- returns: dict = (
468
- OrderedDict(
469
- (name, Parameter(name, kind=ParameterKind.positional_only))
470
- for name in output_names
471
- )
472
- if output_names
473
- else {}
474
- )
475
- if method:
476
- name = self._first_from_capture(captures, "name")
477
- else:
478
- name = self.filepath.stem
479
-
480
- model = Function(
481
- name,
482
- lineno=node.range.start_point.row + 1,
483
- endlineno=node.range.end_point.row + 1,
484
- filepath=self.filepath,
485
- docstring=self._comment_docstring(captures.get("docstring", None)),
486
- getter="getter" in captures,
487
- setter="setter" in captures,
488
- **kwargs,
489
- )
490
-
491
- captures_arguments = [
492
- ARGUMENTS_QUERY.captures(node) for node in captures.get("arguments", [])
493
- ]
494
- for arguments in captures_arguments:
495
- attributes = self._decode_from_capture(arguments, "attributes")
496
- is_input = (
497
- attributes is None
498
- or "Input" in attributes
499
- or "Output" not in attributes
500
- )
501
- # is_repeating = "Repeating" in attributes
502
-
503
- captures_argument = [
504
- PROPERTY_QUERY.captures(node) for node in arguments["arguments"]
505
- ]
506
- for argument in captures_argument:
507
- name = self._first_from_capture(argument, "name")
508
-
509
- if "options" in argument:
510
- options_name = self._first_from_capture(argument, "options")
511
- parameters.pop(options_name, None)
512
- parameter = parameters[name] = Parameter(
513
- name, kind=ParameterKind.keyword_only
514
- )
515
- else:
516
- if is_input:
517
- parameter = parameters.get(name, Parameter(name))
518
- else:
519
- parameter = returns.get(name, Parameter(name))
520
-
521
- if "default" in argument:
522
- parameter.kind = ParameterKind.optional
523
- else:
524
- parameter.kind = ParameterKind.positional_only
525
-
526
- annotation = self._first_from_capture(argument, "class")
527
- if annotation:
528
- parameter.annotation = annotation
529
-
530
- default = self._first_from_capture(argument, "default")
531
- if default:
532
- parameter.default = default
533
-
534
- docstring = self._comment_docstring(
535
- argument.get("comment", None), parent=model
536
- )
537
- if docstring:
538
- parameter.docstring = docstring
539
-
540
- model.parameters = Parameters(*list(parameters.values()))
541
- model.returns = Parameters(*list(returns.values())) if returns else None
542
-
543
- return model
544
-
545
- def _decode(self, node: Node) -> str:
546
- """
547
- Decode the text of a given node using the specified encoding.
548
-
549
- Args:
550
- node (Node): The node whose text needs to be decoded.
551
-
552
- Returns:
553
- str: The decoded text of the node. If the node or its text is None, returns an empty string.
554
- """
555
- self._node = node
556
- return (
557
- node.text.decode(self.encoding)
558
- if node is not None and node.text is not None
559
- else ""
560
- )
561
-
562
- def _decode_from_capture(
563
- self, capture: dict[str, list[Node]], key: str
564
- ) -> list[str]:
565
- """
566
- Decode elements from a capture dictionary based on a specified key.
567
-
568
- Args:
569
- capture (dict[str, list[Node]]): A dictionary where the keys are strings and the values are lists of Node objects.
570
- key (str): The key to look for in the capture dictionary.
571
-
572
- Returns:
573
- list[str]: A list of decoded strings corresponding to the elements associated with the specified key in the capture dictionary.
574
- """
575
- if key not in capture:
576
- return []
577
- else:
578
- return [self._decode(element) for element in capture[key]]
579
-
580
- def _first_from_capture(self, capture: dict[str, list[Node]], key: str) -> str:
581
- """
582
- Retrieve the first decoded string from a capture dictionary for a given key.
583
-
584
- Args:
585
- capture (dict[str, list[Node]]): A dictionary where the key is a string and the value is a list of Node objects.
586
- key (str): The key to look up in the capture dictionary.
587
-
588
- Returns:
589
- str: The first decoded string if available, otherwise an empty string.
590
- """
591
- decoded = self._decode_from_capture(capture, key)
592
- if decoded:
593
- return decoded[0]
594
- else:
595
- return ""
596
-
597
- def _comment_docstring(
598
- self, nodes: list[Node] | Node | None, parent: MatlabMixin | None = None
599
- ) -> Docstring | None:
600
- """
601
- Extract and process a docstring from given nodes.
602
-
603
- This method processes nodes to extract a docstring, handling different
604
- comment styles and blocks. It supports both single-line and multi-line
605
- comments, as well as special comment blocks delimited by `%{` and `%}`.
606
-
607
- Args:
608
- nodes (list[Node] | Node | None): The nodes from which to extract the docstring.
609
- parent (MatlabMixin | None, optional): The parent MatlabMixin. Defaults to None.
610
-
611
- Returns:
612
- Docstring | None: The extracted and processed docstring, or None if no docstring is found.
613
-
614
- Raises:
615
- LookupError: If a line does not start with a comment character.
616
- """
617
- if nodes is None:
618
- return None
619
- elif isinstance(nodes, list):
620
- # Ensure that if there is a gap between subsequent comment nodes, only the first block is considered
621
- if gaps := (
622
- end.start_point.row - start.end_point.row
623
- for (start, end) in zip(nodes[:-1], nodes[1:])
624
- ):
625
- first_gap_index = next(
626
- (i for i, gap in enumerate(gaps) if gap > 1), None
627
- )
628
- nodes = (
629
- nodes[: first_gap_index + 1]
630
- if first_gap_index is not None
631
- else nodes
632
- )
633
-
634
- lineno = nodes[0].range.start_point.row + 1
635
- endlineno = nodes[-1].range.end_point.row + 1
636
- lines = iter(
637
- [
638
- line
639
- for lines in [self._decode(node).splitlines() for node in nodes]
640
- for line in lines
641
- ]
642
- )
643
- else:
644
- lineno = nodes.range.start_point.row + 1
645
- endlineno = nodes.range.end_point.row + 1
646
- lines = iter(self._decode(nodes).splitlines())
647
-
648
- docstring, uncommented = [], []
649
-
650
- while True:
651
- try:
652
- line = next(lines).lstrip()
653
- except StopIteration:
654
- break
655
-
656
- # Exclude all pragma's
657
- if line in [
658
- "%#codegen",
659
- "%#eml",
660
- "%#external",
661
- "%#exclude",
662
- "%#function",
663
- "%#ok",
664
- "%#mex",
665
- ]:
666
- continue
667
-
668
- if "--8<--" in line:
669
- continue
670
-
671
- if line[:2] == "%{" or line[:2] == "%%":
672
- if uncommented:
673
- docstring += _dedent(uncommented)
674
- uncommented = []
675
- if line[:2] == "%%":
676
- docstring.append(line[2:].lstrip())
677
- continue
678
-
679
- comment_block = []
680
- line = line[2:]
681
- while "%}" not in line:
682
- comment_block.append(line)
683
- try:
684
- line = next(lines)
685
- except StopIteration:
686
- break
687
- else:
688
- last_line = line[: line.index("%}")]
689
- if last_line:
690
- comment_block.append(last_line)
691
- docstring.append(comment_block[0])
692
- docstring += _dedent(comment_block[1:])
693
-
694
- elif line[0] == "%":
695
- uncommented.append(line[1:])
696
- else:
697
- raise LookupError
698
-
699
- if uncommented:
700
- docstring += _dedent(uncommented)
701
-
702
- return Docstring(
703
- "\n".join(docstring),
704
- lineno=lineno,
705
- endlineno=endlineno,
706
- parent=parent,
707
- )