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.
- mkdocstrings_handlers/matlab/__init__.py +26 -3
- mkdocstrings_handlers/matlab/config.py +885 -0
- mkdocstrings_handlers/matlab/handler.py +155 -296
- mkdocstrings_handlers/matlab/logger.py +111 -0
- mkdocstrings_handlers/matlab/rendering.py +818 -0
- mkdocstrings_handlers/matlab/templates/material/attributes.html.jinja +25 -0
- mkdocstrings_handlers/matlab/templates/material/backlinks.html.jinja +17 -0
- mkdocstrings_handlers/matlab/templates/material/children.html.jinja +70 -62
- mkdocstrings_handlers/matlab/templates/material/class.html.jinja +236 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/admonition.html.jinja +20 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/classes.html.jinja +85 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/examples.html.jinja +27 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/functions.html.jinja +91 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/input_arguments.html.jinja +171 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/name_value_arguments.html.jinja +166 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/namespaces.html.jinja +5 -6
- mkdocstrings_handlers/matlab/templates/material/docstring/output_arguments.html.jinja +152 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/properties.html.jinja +25 -26
- mkdocstrings_handlers/matlab/templates/material/docstring.html.jinja +53 -0
- mkdocstrings_handlers/matlab/templates/material/expression.html.jinja +55 -0
- mkdocstrings_handlers/matlab/templates/material/folder.html.jinja +31 -39
- mkdocstrings_handlers/matlab/templates/material/function.html.jinja +148 -0
- mkdocstrings_handlers/matlab/templates/material/language.html.jinja +18 -0
- mkdocstrings_handlers/matlab/templates/material/languages/en.html.jinja +38 -0
- mkdocstrings_handlers/matlab/templates/material/languages/ja.html.jinja +38 -0
- mkdocstrings_handlers/matlab/templates/material/languages/zh.html.jinja +38 -0
- mkdocstrings_handlers/matlab/templates/material/namespace.html.jinja +32 -38
- mkdocstrings_handlers/matlab/templates/material/property.html.jinja +39 -35
- mkdocstrings_handlers/matlab/templates/material/script.html.jinja +3 -25
- mkdocstrings_handlers/matlab/templates/material/signature.html.jinja +105 -0
- mkdocstrings_handlers/matlab/templates/material/style.css +179 -4
- mkdocstrings_handlers/matlab/templates/material/summary/classes.html.jinja +25 -0
- mkdocstrings_handlers/matlab/templates/material/summary/functions.html.jinja +25 -0
- mkdocstrings_handlers/matlab/templates/material/summary/namespaces.html.jinja +17 -13
- mkdocstrings_handlers/matlab/templates/material/summary/properties.html.jinja +17 -13
- mkdocstrings_handlers/matlab/templates/material/summary.html.jinja +6 -6
- {mkdocstrings_matlab-0.9.7.dist-info → mkdocstrings_matlab-1.0.0.dist-info}/METADATA +17 -19
- mkdocstrings_matlab-1.0.0.dist-info/RECORD +41 -0
- mkdocstrings_handlers/matlab/collect.py +0 -783
- mkdocstrings_handlers/matlab/enums.py +0 -54
- mkdocstrings_handlers/matlab/models.py +0 -633
- mkdocstrings_handlers/matlab/treesitter.py +0 -707
- mkdocstrings_matlab-0.9.7.dist-info/RECORD +0 -22
- {mkdocstrings_matlab-0.9.7.dist-info → mkdocstrings_matlab-1.0.0.dist-info}/WHEEL +0 -0
- {mkdocstrings_matlab-0.9.7.dist-info → mkdocstrings_matlab-1.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,783 +0,0 @@
|
|
1
|
-
"""Functions and classes for collecting MATLAB objects from paths."""
|
2
|
-
|
3
|
-
from collections import defaultdict, deque
|
4
|
-
from copy import copy, deepcopy
|
5
|
-
from pathlib import Path
|
6
|
-
from typing import Any, Callable, Mapping, Sequence, TypeVar
|
7
|
-
|
8
|
-
from _griffe.collections import LinesCollection as GLC
|
9
|
-
from _griffe.collections import ModulesCollection
|
10
|
-
from _griffe.docstrings.models import (
|
11
|
-
DocstringParameter,
|
12
|
-
DocstringReturn,
|
13
|
-
DocstringSectionOtherParameters,
|
14
|
-
DocstringSectionParameters,
|
15
|
-
DocstringSectionReturns,
|
16
|
-
)
|
17
|
-
from _griffe.enumerations import DocstringSectionKind
|
18
|
-
from _griffe.expressions import Expr
|
19
|
-
|
20
|
-
from mkdocstrings_handlers.matlab.enums import ParameterKind
|
21
|
-
from mkdocstrings_handlers.matlab.models import (
|
22
|
-
Class,
|
23
|
-
Classfolder,
|
24
|
-
Docstring,
|
25
|
-
DocstringSectionText,
|
26
|
-
Folder,
|
27
|
-
Function,
|
28
|
-
MatlabMixin,
|
29
|
-
Namespace,
|
30
|
-
PathMixin,
|
31
|
-
_ParentGrabber,
|
32
|
-
)
|
33
|
-
from mkdocstrings_handlers.matlab.treesitter import FileParser
|
34
|
-
|
35
|
-
PathType = TypeVar("PathType", bound=PathMixin)
|
36
|
-
|
37
|
-
__all__ = ["LinesCollection", "PathCollection"]
|
38
|
-
|
39
|
-
|
40
|
-
class LinesCollection(GLC):
|
41
|
-
"""A simple dictionary containing the modules source code lines."""
|
42
|
-
|
43
|
-
def __init__(self) -> None:
|
44
|
-
"""Initialize the collection."""
|
45
|
-
self._data: dict[str, list[str]] = {}
|
46
|
-
|
47
|
-
|
48
|
-
class PathGlobber:
|
49
|
-
"""
|
50
|
-
A class to recursively glob paths as MATLAB would do it.
|
51
|
-
"""
|
52
|
-
|
53
|
-
def __init__(self, path: Path, recursive: bool = False):
|
54
|
-
self._idx = 0
|
55
|
-
self._paths: list[Path] = []
|
56
|
-
self._glob(path, recursive)
|
57
|
-
|
58
|
-
def _glob(self, path: Path, recursive: bool = False):
|
59
|
-
for member in path.iterdir():
|
60
|
-
if (
|
61
|
-
member.is_dir()
|
62
|
-
and recursive
|
63
|
-
and member.stem[0] not in ["+", "@"]
|
64
|
-
and member.stem != "private"
|
65
|
-
):
|
66
|
-
self._glob(member, recursive=True)
|
67
|
-
elif member.is_dir() and member.stem[0] == "+":
|
68
|
-
self._paths.append(member)
|
69
|
-
self._glob(member)
|
70
|
-
elif member.is_dir() and member.stem[0] == "@":
|
71
|
-
self._paths.append(member)
|
72
|
-
elif (
|
73
|
-
member.is_file()
|
74
|
-
and member.suffix == ".m"
|
75
|
-
and member.name != "Contents.m"
|
76
|
-
):
|
77
|
-
self._paths.append(member)
|
78
|
-
|
79
|
-
def max_stem_length(self) -> int:
|
80
|
-
return max(len(path.stem) for path in self._paths)
|
81
|
-
|
82
|
-
def __len__(self):
|
83
|
-
return len(self._paths)
|
84
|
-
|
85
|
-
def __iter__(self):
|
86
|
-
return self
|
87
|
-
|
88
|
-
def __next__(self):
|
89
|
-
try:
|
90
|
-
item = self._paths[self._idx]
|
91
|
-
except IndexError as err:
|
92
|
-
raise StopIteration from err
|
93
|
-
self._idx += 1
|
94
|
-
return item
|
95
|
-
|
96
|
-
|
97
|
-
class PathCollection(ModulesCollection):
|
98
|
-
"""
|
99
|
-
PathCollection is a class that manages a collection of MATLAB paths and their corresponding models.
|
100
|
-
|
101
|
-
Attributes:
|
102
|
-
config (Mapping): Configuration settings for the PathCollection.
|
103
|
-
lines_collection (LinesCollection): An instance of LinesCollection for managing lines.
|
104
|
-
|
105
|
-
Args:
|
106
|
-
matlab_path (Sequence[str | Path]): A list of strings or Path objects representing the MATLAB paths.
|
107
|
-
recursive (bool, optional): If True, recursively adds all subdirectories of the given paths to the search path. Defaults to False.
|
108
|
-
config (Mapping, optional): Configuration settings for the PathCollection. Defaults to {}.
|
109
|
-
config_path (Path | None, optional): The path to the configuration file. Defaults to None.
|
110
|
-
|
111
|
-
Methods:
|
112
|
-
members() -> dict:
|
113
|
-
Returns a dictionary of members with their corresponding models.
|
114
|
-
|
115
|
-
resolve(identifier: str, config: Mapping = {}) -> MatlabMixin | None:
|
116
|
-
Resolves the given identifier to a model object.
|
117
|
-
|
118
|
-
update_model(model: MatlabMixin, config: Mapping) -> MatlabMixin:
|
119
|
-
Updates the given model object with the provided configuration.
|
120
|
-
|
121
|
-
addpath(path: str | Path, to_end: bool = False, recursive: bool = False) -> list[Path]:
|
122
|
-
Adds a path to the search path.
|
123
|
-
|
124
|
-
rm_path(path: str | Path, recursive: bool = False) -> list[Path]:
|
125
|
-
Removes a path from the search path and updates the namespace and database accordingly.
|
126
|
-
|
127
|
-
get_inheritance_diagram(model: Class) -> DocstringSectionText | None:
|
128
|
-
Generates an inheritance diagram for the given class model.
|
129
|
-
"""
|
130
|
-
|
131
|
-
def __init__(
|
132
|
-
self,
|
133
|
-
matlab_path: Sequence[str | Path],
|
134
|
-
recursive: bool = False,
|
135
|
-
config: Mapping = {},
|
136
|
-
config_path: Path | None = None,
|
137
|
-
) -> None:
|
138
|
-
"""
|
139
|
-
Initialize an instance of PathCollection.
|
140
|
-
|
141
|
-
Args:
|
142
|
-
matlab_path (list[str | Path]): A list of strings or Path objects representing the MATLAB paths.
|
143
|
-
|
144
|
-
Raises:
|
145
|
-
TypeError: If any element in matlab_path is not a string or Path object.
|
146
|
-
"""
|
147
|
-
for path in matlab_path:
|
148
|
-
if not isinstance(path, (str, Path)):
|
149
|
-
raise TypeError(f"Expected str or Path, got {type(path)}")
|
150
|
-
|
151
|
-
self._path: deque[Path] = deque()
|
152
|
-
self._mapping: dict[str, deque[Path]] = defaultdict(deque)
|
153
|
-
self._models: dict[Path, LazyModel] = {}
|
154
|
-
self._members: dict[Path, list[tuple[str, Path]]] = defaultdict(list)
|
155
|
-
self._folders: dict[Path, LazyModel] = {}
|
156
|
-
self._config_path = config_path
|
157
|
-
|
158
|
-
self.config = config
|
159
|
-
self.lines_collection = LinesCollection()
|
160
|
-
|
161
|
-
for path in matlab_path:
|
162
|
-
self.addpath(Path(path), to_end=True, recursive=recursive)
|
163
|
-
|
164
|
-
@property
|
165
|
-
def members(self):
|
166
|
-
return {
|
167
|
-
identifier: self._models[paths[0]].model()
|
168
|
-
for identifier, paths in self._mapping.items()
|
169
|
-
}
|
170
|
-
|
171
|
-
def resolve(
|
172
|
-
self,
|
173
|
-
identifier: str,
|
174
|
-
config: Mapping = {},
|
175
|
-
):
|
176
|
-
"""
|
177
|
-
Resolve an identifier to a MatlabMixin model.
|
178
|
-
|
179
|
-
This method attempts to resolve a given identifier to a corresponding
|
180
|
-
MatlabMixin model using the internal mapping and models. If the identifier
|
181
|
-
is not found directly, it will attempt to resolve it by breaking down the
|
182
|
-
identifier into parts and resolving each part recursively.
|
183
|
-
|
184
|
-
Args:
|
185
|
-
identifier (str): The identifier to resolve.
|
186
|
-
config (Mapping, optional): Configuration options to update the model. Defaults to an empty dictionary.
|
187
|
-
|
188
|
-
Returns:
|
189
|
-
MatlabMixin or None: The resolved MatlabMixin model if found, otherwise None.
|
190
|
-
"""
|
191
|
-
|
192
|
-
# Find in global database
|
193
|
-
if identifier in self._mapping:
|
194
|
-
model = self._models[self._mapping[identifier][0]].model()
|
195
|
-
if model is not None:
|
196
|
-
model = self.update_model(model, config)
|
197
|
-
|
198
|
-
elif self._config_path is not None and "/" in identifier:
|
199
|
-
absolute_path = (self._config_path / Path(identifier)).resolve()
|
200
|
-
if absolute_path.exists():
|
201
|
-
if absolute_path.suffix:
|
202
|
-
path, member = absolute_path.parent, absolute_path.stem
|
203
|
-
else:
|
204
|
-
path, member = absolute_path, None
|
205
|
-
lazymodel = self._folders.get(path, None)
|
206
|
-
|
207
|
-
if lazymodel is not None:
|
208
|
-
model = lazymodel.model()
|
209
|
-
if model is not None and member is not None:
|
210
|
-
model = model.members.get(member, None)
|
211
|
-
else:
|
212
|
-
model = None
|
213
|
-
else:
|
214
|
-
model = None
|
215
|
-
|
216
|
-
else:
|
217
|
-
model = None
|
218
|
-
name_parts = identifier.split(".")
|
219
|
-
if len(name_parts) > 1:
|
220
|
-
base = self.resolve(".".join(name_parts[:-1]), config=config)
|
221
|
-
if base is None or name_parts[-1] not in base.members:
|
222
|
-
model = None
|
223
|
-
else:
|
224
|
-
model = base.members[name_parts[-1]]
|
225
|
-
else:
|
226
|
-
model = None
|
227
|
-
|
228
|
-
if isinstance(model, MatlabMixin):
|
229
|
-
return model
|
230
|
-
return None
|
231
|
-
|
232
|
-
def update_model(self, model: MatlabMixin, config: Mapping) -> MatlabMixin:
|
233
|
-
"""
|
234
|
-
Update the given model based on the provided configuration.
|
235
|
-
|
236
|
-
This method updates the docstring parser and parser options for the model,
|
237
|
-
patches return annotations for MATLAB functions, and optionally creates
|
238
|
-
docstring sections from argument blocks. It also recursively updates
|
239
|
-
members of the model and handles special cases for class constructors
|
240
|
-
and inheritance diagrams.
|
241
|
-
|
242
|
-
Args:
|
243
|
-
model (MatlabMixin): The model to update.
|
244
|
-
config (Mapping): The configuration dictionary.
|
245
|
-
|
246
|
-
Returns:
|
247
|
-
MatlabMixin: The updated model.
|
248
|
-
"""
|
249
|
-
|
250
|
-
# Update docstring parser and parser options
|
251
|
-
if hasattr(model, "docstring") and model.docstring is not None:
|
252
|
-
model.docstring.parser = config.get("docstring_style", "google")
|
253
|
-
model.docstring.parser_options = config.get("docstring_options", {})
|
254
|
-
|
255
|
-
# Patch docstring section titles
|
256
|
-
if model.docstring is not None:
|
257
|
-
for section in model.docstring.parsed:
|
258
|
-
match section.kind:
|
259
|
-
case DocstringSectionKind.attributes:
|
260
|
-
section.title = "Properties:"
|
261
|
-
case DocstringSectionKind.modules:
|
262
|
-
section.title = "Namespaces:"
|
263
|
-
case DocstringSectionKind.parameters:
|
264
|
-
section.title = "Input arguments:"
|
265
|
-
case DocstringSectionKind.returns:
|
266
|
-
section.title = "Output arguments:"
|
267
|
-
case DocstringSectionKind.other_parameters:
|
268
|
-
section.title = "Name-Value Arguments:"
|
269
|
-
|
270
|
-
# Patch returns annotation
|
271
|
-
# In _griffe.docstrings.<parser>.py the function _read_returns_section will enforce an annotation
|
272
|
-
# on the return parameter. This annotation is grabbed from the parent. For MATLAB this is invalid.
|
273
|
-
# Thus the return annotation needs to be patched back to a None.
|
274
|
-
if (
|
275
|
-
isinstance(model, Function)
|
276
|
-
and model.docstring is not None
|
277
|
-
and any(
|
278
|
-
isinstance(doc, DocstringSectionReturns)
|
279
|
-
for doc in model.docstring.parsed
|
280
|
-
)
|
281
|
-
):
|
282
|
-
section = next(
|
283
|
-
doc
|
284
|
-
for doc in model.docstring.parsed
|
285
|
-
if isinstance(doc, DocstringSectionReturns)
|
286
|
-
)
|
287
|
-
for returns in section.value:
|
288
|
-
if not isinstance(returns.annotation, Expr):
|
289
|
-
returns.annotation = None
|
290
|
-
|
291
|
-
# previous updates do not edit the model attributes persistently
|
292
|
-
# However, the following updates do edit the model attributes persistently
|
293
|
-
# such as adding new sections to the docstring or editing its members.abs
|
294
|
-
# Thus, we need to copy the model to avoid editing the original model
|
295
|
-
alias = copy(model)
|
296
|
-
alias.docstring = (
|
297
|
-
deepcopy(model.docstring) if model.docstring is not None else None
|
298
|
-
)
|
299
|
-
alias.members = {key: value for key, value in model.members.items()}
|
300
|
-
if isinstance(alias, Class):
|
301
|
-
alias._inherited_members = None
|
302
|
-
|
303
|
-
for name, member in getattr(alias, "members", {}).items():
|
304
|
-
alias.members[name] = self.update_model(member, config)
|
305
|
-
|
306
|
-
# Merge constructor docstring into class
|
307
|
-
if (
|
308
|
-
isinstance(alias, Class)
|
309
|
-
and config.get("merge_constructor_into_class", False)
|
310
|
-
and alias.name in alias.members
|
311
|
-
and alias.members[alias.name].docstring is not None
|
312
|
-
):
|
313
|
-
constructor = alias.members.pop(alias.name)
|
314
|
-
if constructor.docstring is not None:
|
315
|
-
if alias.docstring is None:
|
316
|
-
alias.docstring = Docstring("", parent=alias)
|
317
|
-
|
318
|
-
if config.get("merge_constructor_ignore_summary", False):
|
319
|
-
alias.docstring._suffixes.extend(constructor.docstring.parsed[1:])
|
320
|
-
else:
|
321
|
-
alias.docstring._suffixes.extend(constructor.docstring.parsed)
|
322
|
-
|
323
|
-
# Hide hidden members (methods and properties)
|
324
|
-
hidden_members = config.get("hidden_members", False)
|
325
|
-
if isinstance(hidden_members, bool):
|
326
|
-
filter_hidden = not hidden_members
|
327
|
-
show_hidden = []
|
328
|
-
else:
|
329
|
-
filter_hidden = True
|
330
|
-
show_hidden: list[str] = hidden_members
|
331
|
-
if isinstance(alias, Class) and filter_hidden:
|
332
|
-
alias.members = {
|
333
|
-
key: value
|
334
|
-
for key, value in alias.members.items()
|
335
|
-
if not getattr(value, "Hidden", False)
|
336
|
-
or (
|
337
|
-
show_hidden
|
338
|
-
and getattr(value, "Hidden", False)
|
339
|
-
and key in show_hidden
|
340
|
-
)
|
341
|
-
}
|
342
|
-
alias._inherited_members = {
|
343
|
-
key: value
|
344
|
-
for key, value in alias.inherited_members.items()
|
345
|
-
if not getattr(value, "Hidden", False)
|
346
|
-
or (
|
347
|
-
show_hidden
|
348
|
-
and getattr(value, "Hidden", False)
|
349
|
-
and key in show_hidden
|
350
|
-
)
|
351
|
-
}
|
352
|
-
|
353
|
-
# Hide private members (methods and properties)
|
354
|
-
private_members = config.get("private_members", False)
|
355
|
-
if isinstance(private_members, bool):
|
356
|
-
filter_private = not private_members
|
357
|
-
show_private = []
|
358
|
-
else:
|
359
|
-
filter_private = True
|
360
|
-
show_private: list[str] = private_members
|
361
|
-
if isinstance(alias, Class) and filter_private:
|
362
|
-
alias.members = {
|
363
|
-
key: value
|
364
|
-
for key, value in alias.members.items()
|
365
|
-
if not getattr(value, "Private", False)
|
366
|
-
or (
|
367
|
-
show_private
|
368
|
-
and getattr(value, "Private", False)
|
369
|
-
and key in show_private
|
370
|
-
)
|
371
|
-
}
|
372
|
-
alias._inherited_members = {
|
373
|
-
key: value
|
374
|
-
for key, value in alias.inherited_members.items()
|
375
|
-
if not getattr(value, "Private", False)
|
376
|
-
or (
|
377
|
-
show_private
|
378
|
-
and getattr(value, "Private", False)
|
379
|
-
and key in show_private
|
380
|
-
)
|
381
|
-
}
|
382
|
-
|
383
|
-
# Create parameters and returns sections from argument blocks
|
384
|
-
if (
|
385
|
-
isinstance(alias, Function)
|
386
|
-
and alias.docstring is not None
|
387
|
-
and config.get("parse_arguments", True)
|
388
|
-
and (
|
389
|
-
config.get("show_docstring_input_arguments", True)
|
390
|
-
or config.get("show_docstring_name_value_arguments", True)
|
391
|
-
or config.get("show_docstring_output_arguments", True)
|
392
|
-
)
|
393
|
-
):
|
394
|
-
docstring_parameters = any(
|
395
|
-
isinstance(doc, DocstringSectionParameters)
|
396
|
-
for doc in alias.docstring.parsed
|
397
|
-
)
|
398
|
-
docstring_returns = any(
|
399
|
-
isinstance(doc, DocstringSectionReturns)
|
400
|
-
for doc in alias.docstring.parsed
|
401
|
-
)
|
402
|
-
|
403
|
-
if not docstring_parameters and alias.parameters:
|
404
|
-
arguments_parameters = any(
|
405
|
-
param.docstring is not None for param in alias.parameters
|
406
|
-
)
|
407
|
-
else:
|
408
|
-
arguments_parameters = False
|
409
|
-
|
410
|
-
if not docstring_returns and alias.returns:
|
411
|
-
arguments_returns = any(
|
412
|
-
ret.docstring is not None for ret in alias.returns
|
413
|
-
)
|
414
|
-
else:
|
415
|
-
arguments_returns = False
|
416
|
-
|
417
|
-
document_parameters = not docstring_parameters and arguments_parameters
|
418
|
-
document_returns = not docstring_returns and arguments_returns
|
419
|
-
|
420
|
-
standard_parameters = [
|
421
|
-
param
|
422
|
-
for param in alias.parameters
|
423
|
-
if param.kind is not ParameterKind.keyword_only
|
424
|
-
]
|
425
|
-
|
426
|
-
keyword_parameters = [
|
427
|
-
param
|
428
|
-
for param in alias.parameters
|
429
|
-
if param.kind is ParameterKind.keyword_only
|
430
|
-
]
|
431
|
-
|
432
|
-
if (
|
433
|
-
config.get("show_docstring_input_arguments", True)
|
434
|
-
and document_parameters
|
435
|
-
and standard_parameters
|
436
|
-
):
|
437
|
-
alias.docstring._suffixes.append(
|
438
|
-
DocstringSectionParameters(
|
439
|
-
[
|
440
|
-
DocstringParameter(
|
441
|
-
name=param.name,
|
442
|
-
value=str(param.default)
|
443
|
-
if param.default is not None
|
444
|
-
else None,
|
445
|
-
annotation=param.annotation,
|
446
|
-
description=param.docstring.value
|
447
|
-
if param.docstring is not None
|
448
|
-
else "",
|
449
|
-
)
|
450
|
-
for param in standard_parameters
|
451
|
-
]
|
452
|
-
)
|
453
|
-
)
|
454
|
-
|
455
|
-
if (
|
456
|
-
config.get("show_docstring_name_value_arguments", True)
|
457
|
-
and document_parameters
|
458
|
-
and keyword_parameters
|
459
|
-
):
|
460
|
-
alias.docstring._suffixes.append(
|
461
|
-
DocstringSectionOtherParameters(
|
462
|
-
[
|
463
|
-
DocstringParameter(
|
464
|
-
name=param.name,
|
465
|
-
value=str(param.default)
|
466
|
-
if param.default is not None
|
467
|
-
else None,
|
468
|
-
annotation=param.annotation,
|
469
|
-
description=param.docstring.value
|
470
|
-
if param.docstring is not None
|
471
|
-
else "",
|
472
|
-
)
|
473
|
-
for param in keyword_parameters
|
474
|
-
],
|
475
|
-
title="Name-Value Arguments:",
|
476
|
-
)
|
477
|
-
)
|
478
|
-
|
479
|
-
if config.get("show_docstring_output_arguments", True) and document_returns:
|
480
|
-
returns = DocstringSectionReturns(
|
481
|
-
[
|
482
|
-
DocstringReturn(
|
483
|
-
name=param.name,
|
484
|
-
value=str(param.default)
|
485
|
-
if param.default is not None
|
486
|
-
else None,
|
487
|
-
annotation=param.annotation,
|
488
|
-
description=param.docstring.value
|
489
|
-
if param.docstring is not None
|
490
|
-
else "",
|
491
|
-
)
|
492
|
-
for param in alias.returns or []
|
493
|
-
]
|
494
|
-
)
|
495
|
-
alias.docstring._suffixes.append(returns)
|
496
|
-
|
497
|
-
# Add inheritance diagram to class docstring
|
498
|
-
if (
|
499
|
-
isinstance(alias, Class)
|
500
|
-
and config.get("show_inheritance_diagram", False)
|
501
|
-
and (
|
502
|
-
(
|
503
|
-
alias.docstring is not None
|
504
|
-
and "Inheritance Diagram" not in alias.docstring.parsed
|
505
|
-
)
|
506
|
-
or alias.docstring is None
|
507
|
-
)
|
508
|
-
):
|
509
|
-
diagram = self.get_inheritance_diagram(alias)
|
510
|
-
if diagram is not None:
|
511
|
-
if alias.docstring is None:
|
512
|
-
alias.docstring = Docstring("", parent=alias)
|
513
|
-
alias.docstring._prefixes.append(diagram)
|
514
|
-
|
515
|
-
return alias
|
516
|
-
|
517
|
-
def addpath(self, path: str | Path, to_end: bool = False, recursive: bool = False):
|
518
|
-
"""
|
519
|
-
Add a path to the search path.
|
520
|
-
|
521
|
-
Args:
|
522
|
-
path (str | Path): The path to be added.
|
523
|
-
to_end (bool, optional): Whether to add the path to the end of the search path. Defaults to False.
|
524
|
-
|
525
|
-
Returns:
|
526
|
-
list[Path]: The previous search path before adding the new path.
|
527
|
-
"""
|
528
|
-
if isinstance(path, str):
|
529
|
-
path = Path(path)
|
530
|
-
|
531
|
-
if path in self._path:
|
532
|
-
self._path.remove(path)
|
533
|
-
|
534
|
-
if to_end:
|
535
|
-
self._path.append(path)
|
536
|
-
else:
|
537
|
-
self._path.appendleft(path)
|
538
|
-
|
539
|
-
for member in PathGlobber(path, recursive=recursive):
|
540
|
-
model = LazyModel(member, self)
|
541
|
-
self._models[member] = model
|
542
|
-
self._mapping[model.name].append(member)
|
543
|
-
self._members[path].append((model.name, member))
|
544
|
-
|
545
|
-
if self._config_path is not None and member.parent.stem[0] not in [
|
546
|
-
"+",
|
547
|
-
"@",
|
548
|
-
]:
|
549
|
-
if member.parent.is_relative_to(self._config_path):
|
550
|
-
if member.parent not in self._folders:
|
551
|
-
self._folders[member.parent] = LazyModel(member.parent, self)
|
552
|
-
else:
|
553
|
-
pass # TODO: Issue warning?
|
554
|
-
|
555
|
-
def rm_path(self, path: str | Path, recursive: bool = False):
|
556
|
-
"""
|
557
|
-
Removes a path from the search path and updates the namespace and database accordingly.
|
558
|
-
|
559
|
-
Args:
|
560
|
-
path (str | Path): The path to be removed from the search path.
|
561
|
-
recursive (bool, optional): If True, recursively removes all subdirectories of the given path from the search path. Defaults to False.
|
562
|
-
|
563
|
-
Returns:
|
564
|
-
list[Path]: The previous search path before the removal.
|
565
|
-
|
566
|
-
"""
|
567
|
-
if isinstance(path, str):
|
568
|
-
path = Path(path)
|
569
|
-
|
570
|
-
if path not in self._path:
|
571
|
-
return list(self._path)
|
572
|
-
|
573
|
-
self._path.remove(path)
|
574
|
-
|
575
|
-
for name, member in self._members.pop(path):
|
576
|
-
self._mapping[name].remove(member)
|
577
|
-
self._models.pop(member)
|
578
|
-
|
579
|
-
if recursive:
|
580
|
-
for subdir in [item for item in self._path if _is_subdirectory(path, item)]:
|
581
|
-
self.rm_path(subdir, recursive=False)
|
582
|
-
|
583
|
-
def get_inheritance_diagram(self, model: Class) -> DocstringSectionText | None:
|
584
|
-
def get_id(str: str) -> str:
|
585
|
-
return str.replace(".", "_")
|
586
|
-
|
587
|
-
def get_nodes(model: Class, nodes: set[str] = set()) -> set[str]:
|
588
|
-
nodes.add(f" {get_id(model.name)}[{model.name}]")
|
589
|
-
for base in [str(base) for base in model.bases]:
|
590
|
-
super = self.resolve(base)
|
591
|
-
if super is None:
|
592
|
-
nodes.add(f" {get_id(base)}[{base}]")
|
593
|
-
else:
|
594
|
-
if isinstance(super, Class):
|
595
|
-
get_nodes(super, nodes)
|
596
|
-
return nodes
|
597
|
-
|
598
|
-
def get_links(model: Class, links: set[str] = set()) -> set[str]:
|
599
|
-
for base in [str(base) for base in model.bases]:
|
600
|
-
super = self.resolve(base)
|
601
|
-
if super is None:
|
602
|
-
links.add(f" {get_id(base)} --> {get_id(model.name)}")
|
603
|
-
else:
|
604
|
-
links.add(f" {get_id(super.name)} --> {get_id(model.name)}")
|
605
|
-
if isinstance(super, Class):
|
606
|
-
get_links(super, links)
|
607
|
-
return links
|
608
|
-
|
609
|
-
nodes = get_nodes(model)
|
610
|
-
if len(nodes) == 1:
|
611
|
-
return None
|
612
|
-
|
613
|
-
nodes_str = "\n".join(list(nodes))
|
614
|
-
links_str = "\n".join(list(get_links(model)))
|
615
|
-
section = f"```mermaid\nflowchart TB\n{nodes_str}\n{links_str}\n```"
|
616
|
-
|
617
|
-
return DocstringSectionText(section, title="Inheritance Diagram")
|
618
|
-
|
619
|
-
|
620
|
-
def _is_subdirectory(parent_path: Path, child_path: Path) -> bool:
|
621
|
-
try:
|
622
|
-
child_path.relative_to(parent_path)
|
623
|
-
except ValueError:
|
624
|
-
return False
|
625
|
-
else:
|
626
|
-
return True
|
627
|
-
|
628
|
-
|
629
|
-
class LazyModel:
|
630
|
-
"""
|
631
|
-
A class to lazily collect and model MATLAB objects from a given path.
|
632
|
-
|
633
|
-
Methods:
|
634
|
-
is_class_folder: Checks if the path is a class folder.
|
635
|
-
is_namespace: Checks if the path is a namespace.
|
636
|
-
is_in_namespace: Checks if the path is within a namespace.
|
637
|
-
name: Returns the name of the MATLAB object, including namespace if applicable.
|
638
|
-
model: Collects and returns the MATLAB object model..
|
639
|
-
"""
|
640
|
-
|
641
|
-
def __init__(self, path: Path, path_collection: PathCollection):
|
642
|
-
self._path: Path = path
|
643
|
-
self._model: MatlabMixin | None = None
|
644
|
-
self._path_collection: PathCollection = path_collection
|
645
|
-
self._lines_collection: LinesCollection = path_collection.lines_collection
|
646
|
-
|
647
|
-
@property
|
648
|
-
def is_folder(self) -> bool:
|
649
|
-
return self._path.is_dir() and self._path.name[0] not in ["+", "@"]
|
650
|
-
|
651
|
-
@property
|
652
|
-
def is_class_folder(self) -> bool:
|
653
|
-
return self._path.is_dir() and self._path.name[0] == "@"
|
654
|
-
|
655
|
-
@property
|
656
|
-
def is_namespace(self) -> bool:
|
657
|
-
return self._path.name[0] == "+"
|
658
|
-
|
659
|
-
@property
|
660
|
-
def is_in_namespace(self) -> bool:
|
661
|
-
return self._path.parent.name[0] == "+"
|
662
|
-
|
663
|
-
@property
|
664
|
-
def name(self):
|
665
|
-
if self.is_in_namespace:
|
666
|
-
parts = list(self._path.parts)
|
667
|
-
item = len(parts) - 2
|
668
|
-
nameparts = []
|
669
|
-
while item >= 0:
|
670
|
-
if parts[item][0] != "+":
|
671
|
-
break
|
672
|
-
nameparts.append(parts[item][1:])
|
673
|
-
item -= 1
|
674
|
-
nameparts.reverse()
|
675
|
-
namespace = ".".join(nameparts) + "."
|
676
|
-
else:
|
677
|
-
namespace = ""
|
678
|
-
|
679
|
-
if self.is_class_folder or self.is_namespace:
|
680
|
-
name = namespace + self._path.name[1:]
|
681
|
-
else:
|
682
|
-
name = namespace + self._path.stem
|
683
|
-
|
684
|
-
if self.is_namespace:
|
685
|
-
return "+" + name
|
686
|
-
else:
|
687
|
-
return name
|
688
|
-
|
689
|
-
def model(self) -> MatlabMixin | None:
|
690
|
-
if not self._path.exists():
|
691
|
-
return None
|
692
|
-
|
693
|
-
if self._model is None:
|
694
|
-
if self.is_class_folder:
|
695
|
-
self._model = self._collect_classfolder(self._path)
|
696
|
-
elif self.is_namespace:
|
697
|
-
self._model = self._collect_namespace(self._path)
|
698
|
-
elif self.is_folder:
|
699
|
-
self._model = self._collect_folder(self._path)
|
700
|
-
else:
|
701
|
-
self._model = self._collect_path(self._path)
|
702
|
-
if self._model is not None:
|
703
|
-
self._model.parent = self._collect_parent(self._path.parent)
|
704
|
-
return self._model
|
705
|
-
|
706
|
-
def _collect_parent(self, path: Path) -> _ParentGrabber | None:
|
707
|
-
if self.is_in_namespace:
|
708
|
-
grabber: Callable[[], MatlabMixin | None] = self._path_collection._models[
|
709
|
-
path
|
710
|
-
].model
|
711
|
-
parent = _ParentGrabber(grabber)
|
712
|
-
else:
|
713
|
-
parent = None
|
714
|
-
return parent
|
715
|
-
|
716
|
-
def _collect_path(self, path: Path, **kwargs: Any) -> MatlabMixin:
|
717
|
-
file = FileParser(path)
|
718
|
-
model = file.parse(path_collection=self._path_collection, **kwargs)
|
719
|
-
self._lines_collection[path] = file.content.split("\n")
|
720
|
-
return model
|
721
|
-
|
722
|
-
def _collect_directory(self, path: Path, model: PathType) -> PathType:
|
723
|
-
for member in path.iterdir():
|
724
|
-
if member.is_dir() and member.name[0] in ["+", "@"]:
|
725
|
-
submodel = self._path_collection._models[member].model()
|
726
|
-
if submodel is not None:
|
727
|
-
model.members[submodel.name] = submodel
|
728
|
-
|
729
|
-
elif member.is_file() and member.suffix == ".m":
|
730
|
-
if member.name == "Contents.m":
|
731
|
-
contentsfile = self._collect_path(member)
|
732
|
-
model.docstring = contentsfile.docstring
|
733
|
-
else:
|
734
|
-
submodel = self._path_collection._models[member].model()
|
735
|
-
if submodel is not None:
|
736
|
-
model.members[submodel.name] = submodel
|
737
|
-
|
738
|
-
if model.docstring is None:
|
739
|
-
model.docstring = self._collect_readme_md(path, model)
|
740
|
-
|
741
|
-
return model
|
742
|
-
|
743
|
-
def _collect_classfolder(self, path: Path) -> Classfolder | None:
|
744
|
-
classfile = path / (path.name[1:] + ".m")
|
745
|
-
if not classfile.exists():
|
746
|
-
return None
|
747
|
-
model = self._collect_path(classfile)
|
748
|
-
if not isinstance(model, Classfolder):
|
749
|
-
return None
|
750
|
-
for member in path.iterdir():
|
751
|
-
if member.is_file() and member.suffix == ".m" and member != classfile:
|
752
|
-
if member.name == "Contents.m" and model.docstring is None:
|
753
|
-
contentsfile = self._collect_path(member)
|
754
|
-
model.docstring = contentsfile.docstring
|
755
|
-
else:
|
756
|
-
method = self._collect_path(member)
|
757
|
-
method.parent = model
|
758
|
-
model.members[method.name] = method
|
759
|
-
if model.docstring is None:
|
760
|
-
model.docstring = self._collect_readme_md(path, model)
|
761
|
-
return model
|
762
|
-
|
763
|
-
def _collect_namespace(self, path: Path) -> Namespace:
|
764
|
-
name = self.name[1:].split(".")[-1]
|
765
|
-
model = Namespace(name, filepath=path, path_collection=self._path_collection)
|
766
|
-
return self._collect_directory(path, model)
|
767
|
-
|
768
|
-
def _collect_folder(self, path: Path) -> Folder:
|
769
|
-
name = path.stem
|
770
|
-
model = Folder("/" + name, filepath=path, path_collection=self._path_collection)
|
771
|
-
return self._collect_directory(path, model)
|
772
|
-
|
773
|
-
def _collect_readme_md(self, path, parent: PathMixin) -> Docstring | None:
|
774
|
-
if (path / "README.md").exists():
|
775
|
-
readme = path / "README.md"
|
776
|
-
elif (path / "readme.md").exists():
|
777
|
-
readme = path / "readme.md"
|
778
|
-
else:
|
779
|
-
return None
|
780
|
-
|
781
|
-
with open(readme, "r") as file:
|
782
|
-
content = file.read()
|
783
|
-
return Docstring(content, parent=parent)
|