mkdocstrings-matlab 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.
- mkdocs_material_matlab/__init__.py +4 -0
- mkdocs_material_matlab/css/style.css +7 -0
- mkdocs_material_matlab/mkdocs_material_matlab.py +20 -0
- mkdocstrings_handlers/matlab/__init__.py +5 -0
- mkdocstrings_handlers/matlab/collect.py +597 -0
- mkdocstrings_handlers/matlab/enums.py +35 -0
- mkdocstrings_handlers/matlab/handler.py +294 -0
- mkdocstrings_handlers/matlab/models.py +560 -0
- mkdocstrings_handlers/matlab/py.typed +0 -0
- mkdocstrings_handlers/matlab/treesitter.py +654 -0
- mkdocstrings_matlab-0.3.0.dist-info/METADATA +82 -0
- mkdocstrings_matlab-0.3.0.dist-info/RECORD +15 -0
- mkdocstrings_matlab-0.3.0.dist-info/WHEEL +4 -0
- mkdocstrings_matlab-0.3.0.dist-info/entry_points.txt +2 -0
- mkdocstrings_matlab-0.3.0.dist-info/licenses/LICENSE +15 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
from mkdocs.plugins import BasePlugin
|
2
|
+
import os
|
3
|
+
|
4
|
+
|
5
|
+
class MkdocsMaterialMatlabPlugin(BasePlugin):
|
6
|
+
def on_config(self, config):
|
7
|
+
# Ensure the custom CSS file is included in the extra_css list
|
8
|
+
css_path = "css/style.css"
|
9
|
+
if css_path not in config["extra_css"]:
|
10
|
+
config["extra_css"].append(css_path)
|
11
|
+
return config
|
12
|
+
|
13
|
+
def on_post_build(self, *, config):
|
14
|
+
# Ensure the custom CSS file is copied to the output directory
|
15
|
+
css_src_path = os.path.join(os.path.dirname(__file__), "css", "style.css")
|
16
|
+
css_dest_path = os.path.join(config["site_dir"], "css", "style.css")
|
17
|
+
os.makedirs(os.path.dirname(css_dest_path), exist_ok=True)
|
18
|
+
with open(css_src_path, "rb") as src_file:
|
19
|
+
with open(css_dest_path, "wb") as dest_file:
|
20
|
+
dest_file.write(src_file.read())
|
@@ -0,0 +1,597 @@
|
|
1
|
+
from collections import defaultdict, deque
|
2
|
+
from copy import deepcopy
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Mapping, Sequence
|
5
|
+
|
6
|
+
from _griffe.collections import LinesCollection as GLC, ModulesCollection
|
7
|
+
from _griffe.docstrings.models import (
|
8
|
+
DocstringSectionParameters,
|
9
|
+
DocstringSectionReturns,
|
10
|
+
DocstringParameter,
|
11
|
+
DocstringReturn,
|
12
|
+
)
|
13
|
+
from _griffe.expressions import Expr
|
14
|
+
|
15
|
+
from mkdocstrings_handlers.matlab.enums import ParameterKind
|
16
|
+
from mkdocstrings_handlers.matlab.models import (
|
17
|
+
_ParentGrabber,
|
18
|
+
Class,
|
19
|
+
Classfolder,
|
20
|
+
Docstring,
|
21
|
+
DocstringSectionText,
|
22
|
+
Function,
|
23
|
+
MatlabMixin,
|
24
|
+
Object,
|
25
|
+
Namespace,
|
26
|
+
ROOT,
|
27
|
+
)
|
28
|
+
from mkdocstrings_handlers.matlab.treesitter import FileParser
|
29
|
+
|
30
|
+
|
31
|
+
__all__ = ["LinesCollection", "PathCollection"]
|
32
|
+
|
33
|
+
|
34
|
+
class LinesCollection(GLC):
|
35
|
+
"""A simple dictionary containing the modules source code lines."""
|
36
|
+
|
37
|
+
def __init__(self) -> None:
|
38
|
+
"""Initialize the collection."""
|
39
|
+
self._data: dict[str, list[str]] = {}
|
40
|
+
|
41
|
+
|
42
|
+
class PathGlobber:
|
43
|
+
"""
|
44
|
+
A class to recursively glob paths as MATLAB would do it.
|
45
|
+
"""
|
46
|
+
|
47
|
+
def __init__(self, path: Path, recursive: bool = False):
|
48
|
+
self._idx = 0
|
49
|
+
self._paths: list[Path] = []
|
50
|
+
self._glob(path, recursive)
|
51
|
+
|
52
|
+
def _glob(self, path: Path, recursive: bool = False):
|
53
|
+
for member in path.iterdir():
|
54
|
+
if (
|
55
|
+
member.is_dir()
|
56
|
+
and recursive
|
57
|
+
and member.stem[0] not in ["+", "@"]
|
58
|
+
and member.stem != "private"
|
59
|
+
):
|
60
|
+
self._glob(member, recursive=True)
|
61
|
+
elif member.is_dir() and member.stem[0] == "+":
|
62
|
+
self._paths.append(member)
|
63
|
+
self._glob(member)
|
64
|
+
elif member.is_dir() and member.stem[0] == "@":
|
65
|
+
self._paths.append(member)
|
66
|
+
elif (
|
67
|
+
member.is_file()
|
68
|
+
and member.suffix == ".m"
|
69
|
+
and member.name != "Contents.m"
|
70
|
+
):
|
71
|
+
self._paths.append(member)
|
72
|
+
|
73
|
+
def max_stem_length(self) -> int:
|
74
|
+
return max(len(path.stem) for path in self._paths)
|
75
|
+
|
76
|
+
def __len__(self):
|
77
|
+
return len(self._paths)
|
78
|
+
|
79
|
+
def __iter__(self):
|
80
|
+
return self
|
81
|
+
|
82
|
+
def __next__(self):
|
83
|
+
try:
|
84
|
+
item = self._paths[self._idx]
|
85
|
+
except IndexError as err:
|
86
|
+
raise StopIteration from err
|
87
|
+
self._idx += 1
|
88
|
+
return item
|
89
|
+
|
90
|
+
|
91
|
+
class PathCollection(ModulesCollection):
|
92
|
+
"""
|
93
|
+
PathCollection is a class that manages a collection of MATLAB paths and their corresponding models.
|
94
|
+
|
95
|
+
Attributes:
|
96
|
+
config (Mapping): Configuration settings for the PathCollection.
|
97
|
+
lines_collection (LinesCollection): An instance of LinesCollection for managing lines.
|
98
|
+
|
99
|
+
Args:
|
100
|
+
matlab_path (Sequence[str | Path]): A list of strings or Path objects representing the MATLAB paths.
|
101
|
+
recursive (bool, optional): If True, recursively adds all subdirectories of the given paths to the search path. Defaults to False.
|
102
|
+
config (Mapping, optional): Configuration settings for the PathCollection. Defaults to {}.
|
103
|
+
|
104
|
+
Methods:
|
105
|
+
members() -> dict:
|
106
|
+
Returns a dictionary of members with their corresponding models.
|
107
|
+
|
108
|
+
resolve(identifier: str, config: Mapping = {}) -> MatlabMixin | None:
|
109
|
+
Resolves the given identifier to a model object.
|
110
|
+
|
111
|
+
update_model(model: MatlabMixin, config: Mapping) -> MatlabMixin:
|
112
|
+
Updates the given model object with the provided configuration.
|
113
|
+
|
114
|
+
addpath(path: str | Path, to_end: bool = False, recursive: bool = False) -> list[Path]:
|
115
|
+
Adds a path to the search path.
|
116
|
+
|
117
|
+
rm_path(path: str | Path, recursive: bool = False) -> list[Path]:
|
118
|
+
Removes a path from the search path and updates the namespace and database accordingly.
|
119
|
+
|
120
|
+
get_inheritance_diagram(model: Class) -> DocstringSectionText | None:
|
121
|
+
Generates an inheritance diagram for the given class model.
|
122
|
+
"""
|
123
|
+
|
124
|
+
def __init__(
|
125
|
+
self,
|
126
|
+
matlab_path: Sequence[str | Path],
|
127
|
+
recursive: bool = False,
|
128
|
+
config: Mapping = {},
|
129
|
+
) -> None:
|
130
|
+
"""
|
131
|
+
Initialize an instance of PathCollection.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
matlab_path (list[str | Path]): A list of strings or Path objects representing the MATLAB paths.
|
135
|
+
|
136
|
+
Raises:
|
137
|
+
TypeError: If any element in matlab_path is not a string or Path object.
|
138
|
+
"""
|
139
|
+
for path in matlab_path:
|
140
|
+
if not isinstance(path, (str, Path)):
|
141
|
+
raise TypeError(f"Expected str or Path, got {type(path)}")
|
142
|
+
|
143
|
+
self._path: deque[Path] = deque()
|
144
|
+
self._mapping: dict[str, deque[Path]] = defaultdict(deque)
|
145
|
+
self._models: dict[Path, LazyModel] = {}
|
146
|
+
self._members: dict[Path, list[tuple[str, Path]]] = defaultdict(list)
|
147
|
+
|
148
|
+
self.config = config
|
149
|
+
self.lines_collection = LinesCollection()
|
150
|
+
|
151
|
+
for path in matlab_path:
|
152
|
+
self.addpath(Path(path), to_end=True, recursive=recursive)
|
153
|
+
|
154
|
+
@property
|
155
|
+
def members(self):
|
156
|
+
return {
|
157
|
+
identifier: self._models[paths[0]].model()
|
158
|
+
for identifier, paths in self._mapping.items()
|
159
|
+
}
|
160
|
+
|
161
|
+
def resolve(
|
162
|
+
self,
|
163
|
+
identifier: str,
|
164
|
+
config: Mapping = {},
|
165
|
+
):
|
166
|
+
"""
|
167
|
+
Resolve an identifier to a MatlabMixin model.
|
168
|
+
|
169
|
+
This method attempts to resolve a given identifier to a corresponding
|
170
|
+
MatlabMixin model using the internal mapping and models. If the identifier
|
171
|
+
is not found directly, it will attempt to resolve it by breaking down the
|
172
|
+
identifier into parts and resolving each part recursively.
|
173
|
+
|
174
|
+
Args:
|
175
|
+
identifier (str): The identifier to resolve.
|
176
|
+
config (Mapping, optional): Configuration options to update the model. Defaults to an empty dictionary.
|
177
|
+
|
178
|
+
Returns:
|
179
|
+
MatlabMixin or None: The resolved MatlabMixin model if found, otherwise None.
|
180
|
+
"""
|
181
|
+
|
182
|
+
# Find in global database
|
183
|
+
if identifier in self._mapping:
|
184
|
+
model = self._models[self._mapping[identifier][0]].model()
|
185
|
+
if model is not None:
|
186
|
+
model = self.update_model(model, config)
|
187
|
+
else:
|
188
|
+
model = None
|
189
|
+
name_parts = identifier.split(".")
|
190
|
+
if len(name_parts) > 1:
|
191
|
+
base = self.resolve(".".join(name_parts[:-1]), config=config)
|
192
|
+
if base is None or name_parts[-1] not in base.members:
|
193
|
+
model = None
|
194
|
+
else:
|
195
|
+
model = base.members[name_parts[-1]]
|
196
|
+
else:
|
197
|
+
model = None
|
198
|
+
|
199
|
+
if isinstance(model, MatlabMixin):
|
200
|
+
return model
|
201
|
+
return None
|
202
|
+
|
203
|
+
def update_model(self, model: MatlabMixin, config: Mapping):
|
204
|
+
"""
|
205
|
+
Update the given model based on the provided configuration.
|
206
|
+
|
207
|
+
This method updates the docstring parser and parser options for the model,
|
208
|
+
patches return annotations for MATLAB functions, and optionally creates
|
209
|
+
docstring sections from argument blocks. It also recursively updates
|
210
|
+
members of the model and handles special cases for class constructors
|
211
|
+
and inheritance diagrams.
|
212
|
+
|
213
|
+
Args:
|
214
|
+
model (MatlabMixin): The model to update.
|
215
|
+
config (Mapping): The configuration dictionary.
|
216
|
+
|
217
|
+
Returns:
|
218
|
+
MatlabMixin: The updated model.
|
219
|
+
"""
|
220
|
+
|
221
|
+
# Update docstring parser and parser options
|
222
|
+
if hasattr(model, "docstring") and model.docstring is not None:
|
223
|
+
model.docstring.parser = config.get("docstring_style", "google")
|
224
|
+
model.docstring.parser_options = config.get("docstring_options", {})
|
225
|
+
|
226
|
+
# Patch returns annotation
|
227
|
+
# In _griffe.docstrings.<parser>.py the function _read_returns_section will enforce an annotation
|
228
|
+
# on the return parameter. This annotation is grabbed from the parent. For MATLAB is is invalid.
|
229
|
+
# Thus the return annotation needs to be patched back to a None.
|
230
|
+
if (
|
231
|
+
isinstance(model, Function)
|
232
|
+
and model.docstring is not None
|
233
|
+
and any(
|
234
|
+
isinstance(doc, DocstringSectionReturns)
|
235
|
+
for doc in model.docstring.parsed
|
236
|
+
)
|
237
|
+
):
|
238
|
+
section = next(
|
239
|
+
doc
|
240
|
+
for doc in model.docstring.parsed
|
241
|
+
if isinstance(doc, DocstringSectionReturns)
|
242
|
+
)
|
243
|
+
for returns in section.value:
|
244
|
+
if not isinstance(returns.annotation, Expr):
|
245
|
+
returns.annotation = None
|
246
|
+
|
247
|
+
# Create parameters and returns sections from argument blocks
|
248
|
+
if (
|
249
|
+
isinstance(model, Function)
|
250
|
+
and model.docstring is not None
|
251
|
+
and config.get("create_from_argument_blocks", True)
|
252
|
+
):
|
253
|
+
docstring_parameters = any(
|
254
|
+
isinstance(doc, DocstringSectionParameters)
|
255
|
+
for doc in model.docstring.parsed
|
256
|
+
)
|
257
|
+
docstring_returns = any(
|
258
|
+
isinstance(doc, DocstringSectionReturns)
|
259
|
+
for doc in model.docstring.parsed
|
260
|
+
)
|
261
|
+
|
262
|
+
if not docstring_parameters and model.parameters:
|
263
|
+
arguments_parameters = any(
|
264
|
+
param.docstring is not None for param in model.parameters
|
265
|
+
)
|
266
|
+
else:
|
267
|
+
arguments_parameters = False
|
268
|
+
|
269
|
+
if not docstring_returns and model.returns:
|
270
|
+
arguments_returns = any(
|
271
|
+
ret.docstring is not None for ret in model.returns
|
272
|
+
)
|
273
|
+
else:
|
274
|
+
arguments_returns = False
|
275
|
+
|
276
|
+
document_parameters = not docstring_parameters and arguments_parameters
|
277
|
+
document_returns = not docstring_returns and arguments_returns
|
278
|
+
|
279
|
+
if document_parameters:
|
280
|
+
parameters = DocstringSectionParameters(
|
281
|
+
[
|
282
|
+
DocstringParameter(
|
283
|
+
name=param.name,
|
284
|
+
value=str(param.default)
|
285
|
+
if param.default is not None
|
286
|
+
else None,
|
287
|
+
annotation=param.annotation,
|
288
|
+
description=param.docstring.value
|
289
|
+
if param.docstring is not None
|
290
|
+
else "",
|
291
|
+
)
|
292
|
+
for param in model.parameters
|
293
|
+
if param.kind is not ParameterKind.keyword_only
|
294
|
+
]
|
295
|
+
)
|
296
|
+
|
297
|
+
keywords = DocstringSectionParameters(
|
298
|
+
[
|
299
|
+
DocstringParameter(
|
300
|
+
name=param.name,
|
301
|
+
value=str(param.default)
|
302
|
+
if param.default is not None
|
303
|
+
else None,
|
304
|
+
annotation=param.annotation,
|
305
|
+
description=param.docstring.value
|
306
|
+
if param.docstring is not None
|
307
|
+
else "",
|
308
|
+
)
|
309
|
+
for param in model.parameters
|
310
|
+
if param.kind is ParameterKind.keyword_only
|
311
|
+
],
|
312
|
+
title="Keyword Arguments:",
|
313
|
+
)
|
314
|
+
model.docstring._extra_sections.append(parameters)
|
315
|
+
model.docstring._extra_sections.append(keywords)
|
316
|
+
|
317
|
+
if document_returns:
|
318
|
+
returns = DocstringSectionReturns(
|
319
|
+
[
|
320
|
+
DocstringReturn(
|
321
|
+
name=param.name,
|
322
|
+
value=str(param.default)
|
323
|
+
if param.default is not None
|
324
|
+
else None,
|
325
|
+
annotation=param.annotation,
|
326
|
+
description=param.docstring.value
|
327
|
+
if param.docstring is not None
|
328
|
+
else "",
|
329
|
+
)
|
330
|
+
for param in model.returns or []
|
331
|
+
]
|
332
|
+
)
|
333
|
+
model.docstring._extra_sections.append(returns)
|
334
|
+
|
335
|
+
for member in getattr(model, "members", {}).values():
|
336
|
+
self.update_model(member, config)
|
337
|
+
|
338
|
+
if (
|
339
|
+
isinstance(model, Class)
|
340
|
+
and config.get("merge_constructor_into_class", False)
|
341
|
+
and model.name in model.members
|
342
|
+
and model.members[model.name].docstring is not None
|
343
|
+
):
|
344
|
+
model = deepcopy(model)
|
345
|
+
constructor = model.members.pop(model.name)
|
346
|
+
if constructor.docstring is not None:
|
347
|
+
if model.docstring is None:
|
348
|
+
model.docstring = Docstring("", parent=model)
|
349
|
+
model.docstring._extra_sections.extend(constructor.docstring.parsed)
|
350
|
+
|
351
|
+
if (
|
352
|
+
isinstance(model, Class)
|
353
|
+
and config.get("show_inheritance_diagram", False)
|
354
|
+
and (
|
355
|
+
(
|
356
|
+
model.docstring is not None
|
357
|
+
and "Inheritance Diagram" not in model.docstring.parsed
|
358
|
+
)
|
359
|
+
or model.docstring is None
|
360
|
+
)
|
361
|
+
):
|
362
|
+
diagram = self.get_inheritance_diagram(model)
|
363
|
+
if diagram is not None:
|
364
|
+
model = deepcopy(model)
|
365
|
+
if model.docstring is None:
|
366
|
+
model.docstring = Docstring("", parent=model)
|
367
|
+
model.docstring._extra_sections.append(diagram)
|
368
|
+
|
369
|
+
return model
|
370
|
+
|
371
|
+
def addpath(self, path: str | Path, to_end: bool = False, recursive: bool = False):
|
372
|
+
"""
|
373
|
+
Add a path to the search path.
|
374
|
+
|
375
|
+
Args:
|
376
|
+
path (str | Path): The path to be added.
|
377
|
+
to_end (bool, optional): Whether to add the path to the end of the search path. Defaults to False.
|
378
|
+
|
379
|
+
Returns:
|
380
|
+
list[Path]: The previous search path before adding the new path.
|
381
|
+
"""
|
382
|
+
if isinstance(path, str):
|
383
|
+
path = Path(path)
|
384
|
+
|
385
|
+
if path in self._path:
|
386
|
+
self._path.remove(path)
|
387
|
+
|
388
|
+
if to_end:
|
389
|
+
self._path.append(path)
|
390
|
+
else:
|
391
|
+
self._path.appendleft(path)
|
392
|
+
|
393
|
+
members = PathGlobber(path, recursive=recursive)
|
394
|
+
for member in members:
|
395
|
+
model = LazyModel(member, self)
|
396
|
+
self._models[member] = model
|
397
|
+
self._mapping[model.name].append(member)
|
398
|
+
self._members[path].append((model.name, member))
|
399
|
+
|
400
|
+
def rm_path(self, path: str | Path, recursive: bool = False):
|
401
|
+
"""
|
402
|
+
Removes a path from the search path and updates the namespace and database accordingly.
|
403
|
+
|
404
|
+
Args:
|
405
|
+
path (str | Path): The path to be removed from the search path.
|
406
|
+
recursive (bool, optional): If True, recursively removes all subdirectories of the given path from the search path. Defaults to False.
|
407
|
+
|
408
|
+
Returns:
|
409
|
+
list[Path]: The previous search path before the removal.
|
410
|
+
|
411
|
+
"""
|
412
|
+
if isinstance(path, str):
|
413
|
+
path = Path(path)
|
414
|
+
|
415
|
+
if path not in self._path:
|
416
|
+
return list(self._path)
|
417
|
+
|
418
|
+
self._path.remove(path)
|
419
|
+
|
420
|
+
for name, member in self._members.pop(path):
|
421
|
+
self._mapping[name].remove(member)
|
422
|
+
self._models.pop(member)
|
423
|
+
|
424
|
+
if recursive:
|
425
|
+
for subdir in [item for item in self._path if _is_subdirectory(path, item)]:
|
426
|
+
self.rm_path(subdir, recursive=False)
|
427
|
+
|
428
|
+
def get_inheritance_diagram(self, model: Class) -> DocstringSectionText | None:
|
429
|
+
def get_id(str: str) -> str:
|
430
|
+
return str.replace(".", "_")
|
431
|
+
|
432
|
+
def get_nodes(model: Class, nodes: set[str] = set()) -> set[str]:
|
433
|
+
nodes.add(f" {get_id(model.name)}[{model.name}]")
|
434
|
+
for base in [str(base) for base in model.bases]:
|
435
|
+
super = self.resolve(base)
|
436
|
+
if super is None:
|
437
|
+
nodes.add(f" {get_id(base)}[{base}]")
|
438
|
+
else:
|
439
|
+
if isinstance(super, Class):
|
440
|
+
get_nodes(super, nodes)
|
441
|
+
return nodes
|
442
|
+
|
443
|
+
def get_links(model: Class, links: set[str] = set()) -> set[str]:
|
444
|
+
for base in [str(base) for base in model.bases]:
|
445
|
+
super = self.resolve(base)
|
446
|
+
if super is None:
|
447
|
+
links.add(f" {get_id(base)} --> {get_id(model.name)}")
|
448
|
+
else:
|
449
|
+
links.add(f" {get_id(super.name)} --> {get_id(model.name)}")
|
450
|
+
if isinstance(super, Class):
|
451
|
+
get_links(super, links)
|
452
|
+
return links
|
453
|
+
|
454
|
+
nodes = get_nodes(model)
|
455
|
+
if len(nodes) == 1:
|
456
|
+
return None
|
457
|
+
|
458
|
+
nodes_str = "\n".join(list(nodes))
|
459
|
+
links_str = "\n".join(list(get_links(model)))
|
460
|
+
section = f"## Inheritance Diagram\n\n```mermaid\nflowchart TB\n{nodes_str}\n{links_str}\n```"
|
461
|
+
|
462
|
+
return DocstringSectionText(section, title="Inheritance Diagram")
|
463
|
+
|
464
|
+
|
465
|
+
def _is_subdirectory(parent_path: Path, child_path: Path) -> bool:
|
466
|
+
try:
|
467
|
+
child_path.relative_to(parent_path)
|
468
|
+
except ValueError:
|
469
|
+
return False
|
470
|
+
else:
|
471
|
+
return True
|
472
|
+
|
473
|
+
|
474
|
+
class LazyModel:
|
475
|
+
"""
|
476
|
+
A class to lazily collect and model MATLAB objects from a given path.
|
477
|
+
|
478
|
+
Methods:
|
479
|
+
is_class_folder: Checks if the path is a class folder.
|
480
|
+
is_namespace: Checks if the path is a namespace.
|
481
|
+
is_in_namespace: Checks if the path is within a namespace.
|
482
|
+
name: Returns the name of the MATLAB object, including namespace if applicable.
|
483
|
+
model: Collects and returns the MATLAB object model..
|
484
|
+
"""
|
485
|
+
|
486
|
+
def __init__(self, path: Path, path_collection: PathCollection):
|
487
|
+
self._path: Path = path
|
488
|
+
self._model: MatlabMixin | None = None
|
489
|
+
self._path_collection: PathCollection = path_collection
|
490
|
+
self._lines_collection: LinesCollection = path_collection.lines_collection
|
491
|
+
|
492
|
+
@property
|
493
|
+
def is_class_folder(self) -> bool:
|
494
|
+
return self._path.is_dir() and self._path.name[0] == "@"
|
495
|
+
|
496
|
+
@property
|
497
|
+
def is_namespace(self) -> bool:
|
498
|
+
return self._path.name[0] == "+"
|
499
|
+
|
500
|
+
@property
|
501
|
+
def is_in_namespace(self) -> bool:
|
502
|
+
return self._path.parent.name[0] == "+"
|
503
|
+
|
504
|
+
@property
|
505
|
+
def name(self):
|
506
|
+
if self.is_in_namespace:
|
507
|
+
parts = list(self._path.parts)
|
508
|
+
item = len(parts) - 2
|
509
|
+
nameparts = []
|
510
|
+
while item >= 0:
|
511
|
+
if parts[item][0] != "+":
|
512
|
+
break
|
513
|
+
nameparts.append(parts[item][1:])
|
514
|
+
item -= 1
|
515
|
+
nameparts.reverse()
|
516
|
+
namespace = ".".join(nameparts) + "."
|
517
|
+
else:
|
518
|
+
namespace = ""
|
519
|
+
|
520
|
+
if self.is_class_folder or self.is_namespace:
|
521
|
+
name = namespace + self._path.name[1:]
|
522
|
+
else:
|
523
|
+
name = namespace + self._path.stem
|
524
|
+
|
525
|
+
if self.is_namespace:
|
526
|
+
return "+" + name
|
527
|
+
else:
|
528
|
+
return name
|
529
|
+
|
530
|
+
def model(self):
|
531
|
+
if not self._path.exists():
|
532
|
+
return None
|
533
|
+
|
534
|
+
if self._model is None:
|
535
|
+
if self.is_class_folder:
|
536
|
+
self._model = self._collect_classfolder(self._path)
|
537
|
+
elif self.is_namespace:
|
538
|
+
self._model = self._collect_namespace(self._path)
|
539
|
+
else:
|
540
|
+
self._model = self._collect_path(self._path)
|
541
|
+
if self._model is not None:
|
542
|
+
self._model.parent = self._collect_parent(self._path.parent)
|
543
|
+
return self._model
|
544
|
+
|
545
|
+
def _collect_parent(self, path: Path) -> Object | _ParentGrabber:
|
546
|
+
if self.is_in_namespace:
|
547
|
+
parent = _ParentGrabber(
|
548
|
+
lambda: self._path_collection._models[path].model() or ROOT
|
549
|
+
)
|
550
|
+
else:
|
551
|
+
parent = ROOT
|
552
|
+
return parent
|
553
|
+
|
554
|
+
def _collect_path(self, path: Path) -> MatlabMixin:
|
555
|
+
file = FileParser(path)
|
556
|
+
model = file.parse(path_collection=self._path_collection)
|
557
|
+
self._lines_collection[path] = file.content.split("\n")
|
558
|
+
return model
|
559
|
+
|
560
|
+
def _collect_classfolder(self, path: Path) -> Classfolder | None:
|
561
|
+
classfile = path / (path.name[1:] + ".m")
|
562
|
+
if not classfile.exists():
|
563
|
+
return None
|
564
|
+
model = self._collect_path(classfile)
|
565
|
+
if not isinstance(model, Classfolder):
|
566
|
+
return None
|
567
|
+
for member in path.iterdir():
|
568
|
+
if (
|
569
|
+
member.is_file()
|
570
|
+
and member.suffix == ".m"
|
571
|
+
and member.name != "Contents.m"
|
572
|
+
and member != classfile
|
573
|
+
):
|
574
|
+
method = self._collect_path(member)
|
575
|
+
method.parent = model
|
576
|
+
model.members[method.name] = method
|
577
|
+
return model
|
578
|
+
|
579
|
+
def _collect_namespace(self, path: Path) -> Namespace | None:
|
580
|
+
name = self.name[1:].split(".")[-1]
|
581
|
+
model = Namespace(name, filepath=path, path_collection=self._path_collection)
|
582
|
+
|
583
|
+
for member in path.iterdir():
|
584
|
+
if member.is_dir() and member.name[0] in ["+", "@"]:
|
585
|
+
submodel = self._path_collection._models[member].model()
|
586
|
+
if submodel is not None:
|
587
|
+
model.members[submodel.name] = submodel
|
588
|
+
|
589
|
+
elif member.is_file() and member.suffix == ".m":
|
590
|
+
if member.name == "Contents.m":
|
591
|
+
contentsfile = self._collect_path(member)
|
592
|
+
contentsfile.docstring = model.docstring
|
593
|
+
else:
|
594
|
+
submodel = self._path_collection._models[member].model()
|
595
|
+
if submodel is not None:
|
596
|
+
model.members[submodel.name] = submodel
|
597
|
+
return model
|
@@ -0,0 +1,35 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
|
3
|
+
|
4
|
+
class ParameterKind(str, Enum):
|
5
|
+
"""
|
6
|
+
An enumeration representing different kinds of function parameters.
|
7
|
+
|
8
|
+
Attributes:
|
9
|
+
positional (str): Positional-only parameter.
|
10
|
+
optional (str): Optional parameter.
|
11
|
+
keyword_only (str): Keyword-only parameter.
|
12
|
+
var_keyword (str): Variadic keyword parameter.
|
13
|
+
"""
|
14
|
+
|
15
|
+
positional_only = "positional-only"
|
16
|
+
optional = "optional"
|
17
|
+
keyword_only = "keyword-only"
|
18
|
+
var_keyword = "variadic keyword"
|
19
|
+
|
20
|
+
|
21
|
+
class AccessEnum(str, Enum):
|
22
|
+
"""
|
23
|
+
An enumeration representing different access levels for MATLAB code elements.
|
24
|
+
|
25
|
+
Attributes:
|
26
|
+
public (str): Represents public access level.
|
27
|
+
protected (str): Represents protected access level.
|
28
|
+
private (str): Represents private access level.
|
29
|
+
immutable (str): Represents immutable access level.
|
30
|
+
"""
|
31
|
+
|
32
|
+
public = "public"
|
33
|
+
protected = "protected"
|
34
|
+
private = "private"
|
35
|
+
immutable = "immutable"
|