mkdocstrings-matlab 0.3.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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"
|