mkdocstrings-matlab 0.1.0__py2.py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) hide show
  1. mkdocstrings_handlers/matlab/__init__.py +5 -0
  2. mkdocstrings_handlers/matlab/handler.py +383 -0
  3. mkdocstrings_handlers/matlab/matlab/+docstring/+case/builtin.m +8 -0
  4. mkdocstrings_handlers/matlab/matlab/+docstring/+case/class.m +12 -0
  5. mkdocstrings_handlers/matlab/matlab/+docstring/+case/func.m +5 -0
  6. mkdocstrings_handlers/matlab/matlab/+docstring/+case/method.m +6 -0
  7. mkdocstrings_handlers/matlab/matlab/+docstring/+case/namespace.m +28 -0
  8. mkdocstrings_handlers/matlab/matlab/+docstring/+metadata/argument.m +58 -0
  9. mkdocstrings_handlers/matlab/matlab/+docstring/+metadata/class.m +44 -0
  10. mkdocstrings_handlers/matlab/matlab/+docstring/+metadata/func.m +14 -0
  11. mkdocstrings_handlers/matlab/matlab/+docstring/+metadata/namespace.m +14 -0
  12. mkdocstrings_handlers/matlab/matlab/+docstring/+metadata/property.m +41 -0
  13. mkdocstrings_handlers/matlab/matlab/+docstring/+metadata/script.m +15 -0
  14. mkdocstrings_handlers/matlab/matlab/+docstring/+utils/dedent.m +34 -0
  15. mkdocstrings_handlers/matlab/matlab/+docstring/+utils/get_namespace_path.m +26 -0
  16. mkdocstrings_handlers/matlab/matlab/+docstring/+utils/parse_doc.m +21 -0
  17. mkdocstrings_handlers/matlab/matlab/+docstring/exception.m +7 -0
  18. mkdocstrings_handlers/matlab/matlab/+docstring/resolve.m +94 -0
  19. mkdocstrings_handlers/matlab/matlab/matlab_startup.m +21 -0
  20. mkdocstrings_handlers/matlab/models.py +127 -0
  21. mkdocstrings_handlers/matlab/py.typed +0 -0
  22. mkdocstrings_matlab-0.1.0.dist-info/METADATA +17 -0
  23. mkdocstrings_matlab-0.1.0.dist-info/RECORD +25 -0
  24. mkdocstrings_matlab-0.1.0.dist-info/WHEEL +5 -0
  25. mkdocstrings_matlab-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,5 @@
1
+ """MATLAB handler for mkdocstrings."""
2
+
3
+ from mkdocstrings_handlers.matlab.handler import get_handler
4
+
5
+ __all__ = ["get_handler"]
@@ -0,0 +1,383 @@
1
+ from pathlib import Path
2
+ from collections import ChainMap
3
+ from markdown import Markdown
4
+ from mkdocstrings.handlers.base import BaseHandler, CollectorItem, CollectionError
5
+ from mkdocstrings_handlers.python import rendering
6
+ from typing import Any, ClassVar, Mapping
7
+ from griffe import Docstring, Parameters, Parameter, ParameterKind
8
+ from pprint import pprint
9
+
10
+
11
+ import json
12
+
13
+
14
+ from mkdocstrings_handlers.matlab_engine import MatlabEngine, MatlabExecutionError
15
+ from mkdocstrings_handlers.matlab.models import Function, Class, Classfolder, Namespace, Property
16
+
17
+
18
+ ROOT_NAMESPACE = Namespace("", filepath="")
19
+ MODELS = {}
20
+
21
+
22
+ class MatlabHandler(BaseHandler):
23
+ """The `MatlabHandler` class is a handler for processing Matlab code documentation.
24
+
25
+ Attributes:
26
+ name (str): The handler's name.
27
+ domain (str): The cross-documentation domain/language for this handler.
28
+ enable_inventory (bool): Whether this handler is interested in enabling the creation of the `objects.inv` Sphinx inventory file.
29
+ fallback_theme (str): The fallback theme.
30
+ fallback_config (ClassVar[dict]): The configuration used to collect item during autorefs fallback.
31
+ default_config (ClassVar[dict]): The default configuration for the handler.
32
+
33
+ Methods:
34
+ __init__(self, *args, config_file_path=None, paths=None, startup_expression="", locale="en", **kwargs): Initializes a new instance of the `MatlabHandler` class.
35
+ get_templates_dir(self, handler=None): Returns the templates directory for the handler.
36
+ collect(self, identifier, config): Collects the documentation for the given identifier.
37
+ render(self, data, config): Renders the collected documentation data.
38
+ update_env(self, md, config): Updates the Jinja environment with custom filters and tests.
39
+ """
40
+
41
+ name: str = "matlab"
42
+ """The handler's name."""
43
+ domain: str = "mat" # to match Sphinx's default domain
44
+ """The cross-documentation domain/language for this handler."""
45
+ enable_inventory: bool = True
46
+ """Whether this handler is interested in enabling the creation of the `objects.inv` Sphinx inventory file."""
47
+ fallback_theme = "material"
48
+ """The fallback theme."""
49
+ fallback_config: ClassVar[dict] = {"fallback": True}
50
+ """The configuration used to collect item during autorefs fallback."""
51
+ default_config: ClassVar[dict] = {
52
+ # https://mkdocstrings.github.io/python/usage/
53
+ # General options
54
+ # "find_stubs_package": False,
55
+ # "allow_inspection": True,
56
+ "show_bases": True,
57
+ "show_inheritance_diagram": False, # not implemented
58
+ "show_source": False, # not implemented
59
+ # "preload_modules": None,
60
+ # Heading options
61
+ "heading_level": 2,
62
+ "parameter_headings": False,
63
+ "show_root_heading": False,
64
+ "show_root_toc_entry": True,
65
+ "show_root_full_path": True,
66
+ "show_root_members_full_path": False,
67
+ "show_object_full_path": False,
68
+ "show_category_heading": False,
69
+ "show_symbol_type_heading": False,
70
+ "show_symbol_type_toc": False,
71
+ # Member options
72
+ "inherited_members": False,
73
+ "members": None,
74
+ "members_order": rendering.Order.alphabetical.value,
75
+ "filters": ["!^_[^_]"],
76
+ "group_by_category": True,
77
+ "show_submodules": False,
78
+ "summary": False,
79
+ "show_labels": True,
80
+ # Docstring options
81
+ "docstring_style": "google",
82
+ "docstring_options": {},
83
+ "docstring_section_style": "table",
84
+ "merge_init_into_class": False,
85
+ "show_if_no_docstring": False,
86
+ "show_docstring_attributes": True,
87
+ "show_docstring_functions": True,
88
+ "show_docstring_classes": True,
89
+ "show_docstring_modules": True,
90
+ "show_docstring_description": True,
91
+ "show_docstring_examples": True,
92
+ "show_docstring_other_parameters": True,
93
+ "show_docstring_parameters": True,
94
+ "show_docstring_raises": True,
95
+ "show_docstring_receives": True,
96
+ "show_docstring_returns": True,
97
+ "show_docstring_warns": True,
98
+ "show_docstring_yields": True,
99
+ # Signature options
100
+ "annotations_path": "brief",
101
+ "line_length": 60,
102
+ "show_signature": True,
103
+ "show_signature_annotations": False,
104
+ "signature_crossrefs": False,
105
+ "separate_signature": True,
106
+ "unwrap_annotated": False,
107
+ "modernize_annotations": False,
108
+ }
109
+
110
+ def __init__(
111
+ self,
112
+ *args: Any,
113
+ config_file_path: str | None = None,
114
+ paths: list[str] | None = None,
115
+ startup_expression: str = "",
116
+ locale: str = "en",
117
+ **kwargs: Any,
118
+ ) -> None:
119
+ super().__init__(*args, **kwargs)
120
+
121
+ if paths is None:
122
+ paths = ""
123
+
124
+ self.engine = MatlabEngine()
125
+ self.engine.addpath(str(Path(__file__).parent / "matlab"))
126
+ self.engine.matlab_startup(paths, startup_expression)
127
+ self._locale = locale
128
+
129
+ def get_templates_dir(self, handler: str | None = None) -> Path:
130
+ # use the python handler templates
131
+ # (it assumes the python handler is installed)
132
+ return super().get_templates_dir("python")
133
+
134
+ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem:
135
+ """Collect data given an identifier and user configuration.
136
+
137
+ In the implementation, you typically call a subprocess that returns JSON, and load that JSON again into
138
+ a Python dictionary for example, though the implementation is completely free.
139
+
140
+ Arguments:
141
+ identifier: An identifier for which to collect data.
142
+ config: The handler's configuration options.
143
+
144
+ Returns:
145
+ CollectorItem
146
+ """
147
+ final_config = ChainMap(config, self.default_config) # type: ignore[arg-type]
148
+ try:
149
+ ast_json = self.engine.docstring.resolve(identifier)
150
+ except MatlabExecutionError as error:
151
+ raise CollectionError(error.args[0].strip()) from error
152
+ ast_dict = json.loads(ast_json)
153
+
154
+ match ast_dict["type"]:
155
+ case "function":
156
+ return collect_function(ast_dict, final_config)
157
+ case "method":
158
+ return collect_function(ast_dict, final_config)
159
+ case "class":
160
+ return self.collect_class(ast_dict, final_config)
161
+ case _:
162
+ return None
163
+
164
+ def render(self, data: CollectorItem, config: Mapping[str, Any]) -> str:
165
+ """Render a template using provided data and configuration options.
166
+
167
+ Arguments:
168
+ data: The collected data to render.
169
+ config: The handler's configuration options.
170
+
171
+ Returns:
172
+ The rendered template as HTML.
173
+ """
174
+ final_config = ChainMap(config, self.default_config) # type: ignore[arg-type]
175
+
176
+ template_name = rendering.do_get_template(self.env, data)
177
+ template = self.env.get_template(template_name)
178
+
179
+ heading_level = final_config["heading_level"]
180
+
181
+ return template.render(
182
+ **{
183
+ "config": final_config,
184
+ data.kind.value: data,
185
+ "heading_level": heading_level,
186
+ "root": True,
187
+ "locale": self._locale,
188
+ },
189
+ )
190
+
191
+ def get_anchors(self, data: CollectorItem) -> tuple[str, ...]:
192
+ """Return the possible identifiers (HTML anchors) for a collected item.
193
+
194
+ Arguments:
195
+ data: The collected data.
196
+
197
+ Returns:
198
+ The HTML anchors (without '#'), or an empty tuple if this item doesn't have an anchor.
199
+ """
200
+ anchors = [data.path]
201
+ return tuple(anchors)
202
+
203
+ def update_env(self, md: Markdown, config: dict) -> None:
204
+ """Update the Jinja environment with custom filters and tests.
205
+
206
+ Parameters:
207
+ md: The Markdown instance.
208
+ config: The configuration dictionary.
209
+ """
210
+ super().update_env(md, config)
211
+ self.env.trim_blocks = True
212
+ self.env.lstrip_blocks = True
213
+ self.env.keep_trailing_newline = False
214
+ self.env.filters["split_path"] = rendering.do_split_path
215
+ self.env.filters["crossref"] = rendering.do_crossref
216
+ self.env.filters["multi_crossref"] = rendering.do_multi_crossref
217
+ self.env.filters["order_members"] = rendering.do_order_members
218
+ self.env.filters["format_code"] = rendering.do_format_code
219
+ self.env.filters["format_signature"] = rendering.do_format_signature
220
+ self.env.filters["format_attribute"] = rendering.do_format_attribute
221
+ self.env.filters["filter_objects"] = rendering.do_filter_objects
222
+ self.env.filters["stash_crossref"] = lambda ref, length: ref
223
+ self.env.filters["get_template"] = rendering.do_get_template
224
+ self.env.filters["as_attributes_section"] = rendering.do_as_attributes_section
225
+ self.env.filters["as_functions_section"] = rendering.do_as_functions_section
226
+ self.env.filters["as_classes_section"] = rendering.do_as_classes_section
227
+ self.env.filters["as_modules_section"] = rendering.do_as_modules_section
228
+ self.env.tests["existing_template"] = (
229
+ lambda template_name: template_name in self.env.list_templates()
230
+ )
231
+
232
+
233
+ def collect_class(self, ast_dict: dict, config: Mapping) -> Class:
234
+ docstring = (
235
+ Docstring(ast_dict["docstring"], parser=config["docstring_style"])
236
+ if ast_dict["docstring"]
237
+ else None
238
+ )
239
+ object = Class(
240
+ ast_dict["name"],
241
+ docstring=docstring,
242
+ parent=get_parent(Path(ast_dict["path"]).parent),
243
+ hidden=ast_dict["hidden"],
244
+ sealed=ast_dict["sealed"],
245
+ abstract=ast_dict["abstract"],
246
+ enumeration=ast_dict["enumeration"],
247
+ handle=ast_dict["handle"],
248
+ )
249
+
250
+
251
+ for property_dict in ast_dict["properties"]:
252
+ name = property_dict.pop("name")
253
+ defining_class = property_dict.pop("class")
254
+ property_doc = property_dict.pop("docstring")
255
+ docstring = (
256
+ Docstring(property_doc, parser=config["docstring_style"])
257
+ if property_doc
258
+ else None
259
+ )
260
+ if defining_class != object.canonical_path and not config["inherited_members"]:
261
+ continue
262
+
263
+ object.members[name] = Property(name, docstring=docstring, **property_dict)
264
+
265
+ for method_dict in ast_dict["methods"]:
266
+ name = method_dict.pop("name")
267
+ defining_class = method_dict.pop("class")
268
+ if defining_class != object.canonical_path and not config["inherited_members"]:
269
+ continue
270
+
271
+ method = self.collect(f"{defining_class}.{name}", config)
272
+ method._access = method_dict["access"]
273
+ method._static = method_dict["static"]
274
+ method._abstract = method_dict["abstract"]
275
+ method._sealed = method_dict["sealed"]
276
+ method._hidden = method_dict["hidden"]
277
+
278
+ object.members[name] = method
279
+
280
+ return object
281
+
282
+ def get_parent(path: Path) -> Namespace | Classfolder:
283
+ if path.stem[0] == "+":
284
+ if path in MODELS:
285
+ parent = MODELS[path]
286
+ else:
287
+ parent = Namespace(
288
+ path.stem[1:], filepath=str(path), parent=get_parent(path.parent)
289
+ )
290
+ MODELS[path] = parent
291
+ elif path.stem[0] == "@":
292
+ if path in MODELS:
293
+ parent = MODELS[path]
294
+ else:
295
+ parent = Classfolder(
296
+ path.stem[1:], filepath=str(path), parent=get_parent(path.parent)
297
+ )
298
+ MODELS[path] = parent
299
+ else:
300
+ parent = ROOT_NAMESPACE
301
+ return parent
302
+
303
+
304
+
305
+
306
+
307
+ def collect_function(ast_dict: dict, config: Mapping) -> Function:
308
+ parameters = []
309
+
310
+ inputs = (
311
+ ast_dict["inputs"]
312
+ if isinstance(ast_dict["inputs"], list)
313
+ else [ast_dict["inputs"]]
314
+ )
315
+ for input_dict in inputs:
316
+ if input_dict["name"] == "varargin":
317
+ parameter_kind = ParameterKind.var_positional
318
+ elif input_dict["kind"] == "positional":
319
+ parameter_kind = ParameterKind.positional_only
320
+ else:
321
+ parameter_kind = ParameterKind.keyword_only
322
+
323
+ parameters.append(
324
+ Parameter(
325
+ input_dict["name"],
326
+ kind=parameter_kind,
327
+ annotation=input_dict["class"],
328
+ default=input_dict["default"] if input_dict["default"] else None,
329
+ )
330
+ )
331
+
332
+ func = Function(
333
+ ast_dict["name"],
334
+ parameters=Parameters(*parameters),
335
+ docstring=Docstring(
336
+ ast_dict["docstring"],
337
+ parser=config["docstring_style"],
338
+ parser_options=config["docstring_options"],
339
+ )
340
+ if ast_dict["docstring"]
341
+ else None,
342
+ parent=get_parent(Path(ast_dict["path"]).parent),
343
+ )
344
+
345
+ return func
346
+
347
+
348
+ def get_handler(
349
+ *,
350
+ theme: str,
351
+ custom_templates: str | None = None,
352
+ config_file_path: str | None = None,
353
+ paths: list[str] | None = None,
354
+ startup_expression: str = "",
355
+ **config: Any,
356
+ ) -> MatlabHandler:
357
+ """
358
+ Returns a MatlabHandler object.
359
+
360
+ Parameters:
361
+ theme (str): The theme to use.
362
+ custom_templates (str | None, optional): Path to custom templates. Defaults to None.
363
+ config_file_path (str | None, optional): Path to configuration file. Defaults to None.
364
+ paths (list[str] | None, optional): List of paths to include. Defaults to None.
365
+ startup_expression (str, optional): Startup expression. Defaults to "".
366
+ **config (Any): Additional configuration options.
367
+
368
+ Returns:
369
+ MatlabHandler: The created MatlabHandler object.
370
+ """
371
+ return MatlabHandler(
372
+ handler="matlab",
373
+ theme=theme,
374
+ custom_templates=custom_templates,
375
+ config_file_path=config_file_path,
376
+ paths=paths,
377
+ startup_expression=startup_expression,
378
+ )
379
+
380
+
381
+ if __name__ == "__main__":
382
+ handler = get_handler(theme="material")
383
+ pprint(handler.collect("matlab_startup", {}).docstring.parse("google"))
@@ -0,0 +1,8 @@
1
+ function data = builtin(identifier)
2
+ data.name = identifier;
3
+ data.type = 'function';
4
+ data.doc = help(identifier);
5
+ data.location = string(which(identifier));
6
+ data.inputs = struct.empty();
7
+ data.outputs = struct.empty();
8
+ end
@@ -0,0 +1,12 @@
1
+ function data = class(identifier)
2
+
3
+ if isMATLABReleaseOlderThan('R2024a')
4
+ metaclass = @meta.class.fromName;
5
+ else
6
+ metaclass = @matlab.metadata.Class.fromName;
7
+ end
8
+
9
+ object = metaclass(identifier);
10
+ data = docstring.metadata.class(object);
11
+
12
+ end
@@ -0,0 +1,5 @@
1
+ function data = func(identifier)
2
+
3
+ object = matlab.internal.metafunction(identifier);
4
+ data = docstring.metadata.func(object);
5
+ end
@@ -0,0 +1,6 @@
1
+ function data = method(identifier)
2
+
3
+ object = matlab.internal.metafunction(identifier);
4
+ data = docstring.metadata.func(object);
5
+ data.type = 'method';
6
+ end
@@ -0,0 +1,28 @@
1
+ function data = namespace(identifier)
2
+
3
+ currentpath = split(pwd, filesep);
4
+
5
+ name = identifier(2:end);
6
+
7
+ if isfolder(identifier)
8
+ for i = numel(currentpath):-1:1
9
+ directory = currentpath(i);
10
+ if isempty(directory)
11
+ break
12
+ elseif strcmp(directory(1), '+')
13
+ name = sprintf("%s.%s", directory(2:end), name);
14
+ else
15
+ break
16
+ end
17
+ end
18
+ end
19
+
20
+ object = matlab.metadata.Namespace.fromName(name);
21
+
22
+ if isempty(object)
23
+ docstring.exception(identifier)
24
+ end
25
+
26
+ data = docstring.metadata.namespace(object);
27
+ data.name = name;
28
+ end
@@ -0,0 +1,58 @@
1
+ function data = argument(object)
2
+ arguments
3
+ object (1,:) matlab.internal.metadata.Argument
4
+ end
5
+
6
+ if isempty(object)
7
+ data = struct.empty();
8
+ return
9
+ elseif numel(object) > 1
10
+ for iArg = numel(object):-1:1
11
+ data(iArg) = docstring.metadata.argument(object(iArg));
12
+ end
13
+ return
14
+ end
15
+
16
+ data.name = object.Name;
17
+ data.kind = string(object.Kind);
18
+ data.presence = string(object.Presence);
19
+
20
+ if isempty(object.DefaultValue)
21
+ data.default = "";
22
+ elseif object.DefaultValue.IsConstant
23
+ data.default = string(object.DefaultValue.Value);
24
+ else
25
+ data.default = "*expression*";
26
+ end
27
+
28
+ if ~isempty(object.Validation)
29
+ if ~isempty(object.Validation.Class)
30
+ data.class = string(object.Validation.Class.Name);
31
+ else
32
+ data.class = "";
33
+ end
34
+ if ~isempty(object.Validation.Size)
35
+ for iSize = numel(object.Validation.Size):-1:1
36
+
37
+ if isprop(object.Validation.Size(iSize), 'Length')
38
+ data.size(iSize) = object.Validation.Size(iSize).Length;
39
+ else
40
+ data.size(iSize) = ":";
41
+ end
42
+ end
43
+ else
44
+ data.size = "";
45
+ end
46
+
47
+ if ~isempty(object.Validation.Functions)
48
+ data.validators = arrayfun(@(f) string(f.Name), object.Validation.Functions);
49
+ else
50
+ data.validators = "";
51
+ end
52
+ else
53
+ data.class = "";
54
+ data.size = "";
55
+ data.validators = "";
56
+ end
57
+
58
+ end
@@ -0,0 +1,44 @@
1
+ function data = class(object)
2
+ arguments
3
+ object (1,1) matlab.metadata.Class
4
+ end
5
+ data.type = 'class';
6
+
7
+ namespaces = split(object.Name, '.');
8
+ data.name = namespaces{end};
9
+
10
+ data.docstring = docstring.utils.parse_doc(object, true);
11
+ data.path = matlab.internal.metafunction(object.Name).Location;
12
+ data.hidden = object.Hidden;
13
+ data.sealed = object.Sealed;
14
+ data.abstract = object.Abstract;
15
+ data.enumeration = object.Enumeration;
16
+ data.superclasses = arrayfun(@(o) string(o.Name), object.SuperclassList);
17
+ data.handle = object.HandleCompatible;
18
+ data.aliases = object.Aliases;
19
+
20
+ data.methods = [];
21
+ for methodObject = object.MethodList'
22
+ if any(strcmp(methodObject.Name, {'empty', 'forInteractiveUse'}))
23
+ break
24
+ else
25
+ method.name = string(methodObject.Name);
26
+ method.class = string(methodObject.DefiningClass.Name);
27
+ method.access = methodObject.Access;
28
+ method.static = methodObject.Static;
29
+ method.abstract = methodObject.Abstract;
30
+ method.sealed = methodObject.Sealed;
31
+ method.hidden = methodObject.Hidden;
32
+ data.methods = [data.methods, method];
33
+ end
34
+ end
35
+
36
+ numProp = numel(object.PropertyList);
37
+ for iProp = numProp:-1:1
38
+ data.properties(iProp) = docstring.metadata.property(object.PropertyList(iProp));
39
+ end
40
+
41
+ nameparts = split(object.Name, '.');
42
+ data.constructor = any(strcmp(nameparts(end), [data.methods.name]));
43
+
44
+ end
@@ -0,0 +1,14 @@
1
+ function data = func(object)
2
+
3
+ arguments
4
+ object (1,1) matlab.metadata.MetaData
5
+ end
6
+
7
+ data.type = 'function';
8
+ data.name = object.Name;
9
+ data.docstring = docstring.utils.parse_doc(object);
10
+ data.path = object.Location;
11
+
12
+ data.inputs = docstring.metadata.argument(object.Signature.Inputs);
13
+ data.outputs = docstring.metadata.argument(object.Signature.Outputs);
14
+ end
@@ -0,0 +1,14 @@
1
+ function data = namespace(object)
2
+
3
+ arguments
4
+ object (1,1) matlab.metadata.Namespace
5
+ end
6
+
7
+ data.type = 'namespace';
8
+ data.docstring = docstring.utils.parse_doc(object);
9
+
10
+ data.classes = arrayfun(@(o) string(o.Name), object.ClassList);
11
+ data.functions = arrayfun(@(o) string(o.Name), object.FunctionList);
12
+ data.namespaces = arrayfun(@(o) string(o.Name), object.InnerNamespaces);
13
+ data.path = docstring.utils.get_namespace_path(object);
14
+ end
@@ -0,0 +1,41 @@
1
+ function data = argument(object)
2
+ arguments
3
+ object (1,:) meta.property
4
+ end
5
+
6
+ data.name = string(object.Name);
7
+ data.class = string(object.DefiningClass.Name);
8
+ data.docstring = docstring.utils.parse_doc(object);
9
+ data.get_access = object.GetAccess;
10
+ data.set_access = object.SetAccess;
11
+ data.dependent = object.Dependent;
12
+ data.constant = object.Constant;
13
+ data.abstract = object.Abstract;
14
+ data.transient = object.Transient;
15
+ data.hidden = object.Hidden;
16
+
17
+ if ~isempty(object.Validation) && ~isempty(object.Validation.Class)
18
+ data.annotation = string(object.Validation.Class.Name);
19
+ else
20
+ data.annotation = "";
21
+ end
22
+
23
+ if ~isempty(object.Validation) && ~isempty(object.Validation.Size)
24
+ for iSize = numel(object.Validation.Size):-1:1
25
+ if isprop(object.Validation.Size(iSize), 'Length')
26
+ sizeStr(iSize) = string(object.Validation.Size(iSize).Length);
27
+ else
28
+ sizeStr(iSize) = ":";
29
+ end
30
+ end
31
+ data.size = sprintf("[%s]", join(sizeStr, ","));
32
+ else
33
+ data.size = "";
34
+ end
35
+
36
+ if ~isempty(object.Validation) && ~isempty(object.Validation.ValidatorFunctions)
37
+ data.validation = join(cellfun(@(f) string(char(f)), object.Validation.ValidatorFunctions), ", ");
38
+ else
39
+ data.validation = "";
40
+ end
41
+ end
@@ -0,0 +1,15 @@
1
+ function data = script(object)
2
+ %SCRIPT Summary of this function goes here
3
+ % Detailed explanation goes here
4
+
5
+ arguments
6
+ object (1,1) matlab.metadata.MetaData
7
+ end
8
+
9
+ data.type = 'script';
10
+ data.Name = object.Name;
11
+ data.docstring = docstring.utils.parse_doc(object);
12
+ data.path = object.Location;
13
+
14
+ end
15
+
@@ -0,0 +1,34 @@
1
+ function parsedDoc = dedent(doc)
2
+ %DEDENT Summary of this function goes here
3
+ % Detailed explanation goes here
4
+ arguments
5
+ doc (1,1) string
6
+ end
7
+
8
+ lines = cellstr(splitlines(doc));
9
+
10
+
11
+ for iLine = numel(lines):-1:1
12
+ line = lines{iLine};
13
+ if ~isempty(line) && ~all(line == ' ')
14
+ indentations(iLine) = find(line~= ' ', 1);
15
+ else
16
+ indentations(iLine) = inf;
17
+ end
18
+ end
19
+
20
+ indentation = min(indentations);
21
+
22
+ for iLine = 1:numel(lines)
23
+ line = lines{iLine};
24
+ if isempty(line) || numel(line) <= indentation
25
+ lines{iLine} = '';
26
+ else
27
+ lines{iLine} = line(indentation:end);
28
+ end
29
+ end
30
+
31
+ parsedDoc = string(strjoin(lines, '\n'));
32
+
33
+ end
34
+
@@ -0,0 +1,26 @@
1
+ function path = get_namespace_path(object, parents)
2
+
3
+ arguments
4
+ object (1,1) matlab.metadata.Namespace
5
+ parents (1,1) double {mustBeInteger, mustBeNonnegative} = 0
6
+ end
7
+
8
+ if ~isempty(object.ClassList)
9
+
10
+ function_path = which(sprintf("%s.%s", object.Name, object.ClassList(1).Name));
11
+ path = fileparts(function_path);
12
+ elseif ~isempty(object.FunctionList)
13
+
14
+ function_path = which(sprintf("%s.%s", object.Name, object.FunctionList(1).Name));
15
+ path = fileparts(function_path);
16
+ elseif ~isempty(object.InnerNamespaces)
17
+ path = docstring.utils.get_namespace_path(...
18
+ object.InnerNamespaces(1), parents + 1);
19
+ else
20
+ error("Cannot get path of namespace %s", object.Name);
21
+ end
22
+
23
+ for i = 1:parents
24
+ path = fileparts(path);
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ function doc = parse_doc(object, combine)
2
+
3
+ arguments
4
+ object (1,1) matlab.metadata.MetaData
5
+ combine (1,1) logical = false
6
+ end
7
+
8
+ if isempty(object.DetailedDescription)
9
+ doc = object.Description;
10
+ else
11
+ if combine
12
+ doc = sprintf("%s\n%s", object.Description, ...
13
+ docstring.utils.dedent(object.DetailedDescription));
14
+ else
15
+ doc = docstring.utils.dedent(object.DetailedDescription);
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
@@ -0,0 +1,7 @@
1
+ function exception(identifier)
2
+
3
+ notFoundException = MException('mkdocs:callerInput', '%s %s %s', ...
4
+ 'The input', identifier, 'could not be found on the MATLAB path');
5
+ throwAsCaller(notFoundException)
6
+
7
+ end
@@ -0,0 +1,94 @@
1
+ function [jsonString, data] = resolve(identifier, cwd)
2
+ % Resolve the docstring for a given MATLAB entity
3
+ %
4
+ % ```matlab
5
+ % jsonString = resolve(name, cwd)
6
+ % ```
7
+ %
8
+ % Parameters:
9
+ % identifier (string): Name of the MATLAB entity
10
+ % cwd (string): Current working directory
11
+ %
12
+ % Returns:
13
+ % jsonString(string): JSON-encoded metadata object
14
+ % data(struct): metadata object
15
+
16
+ arguments
17
+ identifier (1, :) char
18
+ cwd (1,1) string {mustBeFolder} = pwd()
19
+ end
20
+
21
+ cd(cwd);
22
+
23
+ % Check if namespace
24
+ if strcmp(identifier(1), '+')
25
+ data = docstring.case.namespace(identifier);
26
+ jsonString = jsonencode(data);
27
+ return;
28
+ end
29
+
30
+ % Try namespace functions
31
+ if contains(identifier, '.')
32
+ object = matlab.internal.metafunction(identifier);
33
+ if ~isempty(object) && isa(object, 'matlab.internal.metadata.Function')
34
+ if isempty(object.Signature)
35
+ data = docstring.metadata.script(object);
36
+ else
37
+ data = docstring.metadata.func(object);
38
+ end
39
+ jsonString = jsonencode(data);
40
+ return;
41
+ end
42
+ end
43
+
44
+ % Try built-in aliases with which
45
+ if contains(which(identifier), ' built-in ')
46
+ data = docstring.case.builtin(identifier);
47
+ jsonString = jsonencode(data);
48
+ return
49
+ end
50
+
51
+ switch exist(identifier) %#ok<EXIST>
52
+ case 2
53
+ % if NAME is a file with extension .m, .mlx, .mlapp, or .sfx, or NAME
54
+ % is the name of a file with a non-registered file extension
55
+ % (.mat, .fig, .txt).
56
+
57
+ % Try with metafunction
58
+ object = matlab.internal.metafunction(identifier);
59
+ if ~isempty(object) && isa(object, 'matlab.internal.metadata.Function')
60
+ if isempty(object.Signature)
61
+ data = docstring.metadata.script(object);
62
+ else
63
+ data = docstring.metadata.func(object);
64
+ end
65
+ data.name = identifier;
66
+ else
67
+ docstring.exception(identifier);
68
+ end
69
+ case 5
70
+ % if NAME is a built-in MATLAB function. This does not include classes
71
+ data = docstring.case.builtin(identifier);
72
+ case 8
73
+ % if NAME is a class (exist returns 0 for Java classes if you
74
+ % start MATLAB with the -nojvm option.)
75
+ data = docstring.case.class(identifier);
76
+ case 0
77
+ if contains(identifier, '.')
78
+ nameparts = split(identifier, '.');
79
+ tryclassname = strjoin(nameparts(1:end-1), '.');
80
+ if exist(tryclassname, 'class')
81
+ data = docstring.case.method(identifier);
82
+ else
83
+ docstring.exception(identifier);
84
+ end
85
+ else
86
+ docstring.exception(identifier);
87
+ end
88
+ otherwise
89
+ docstring.exception(identifier);
90
+ end
91
+
92
+ jsonString = jsonencode(data);
93
+
94
+ end
@@ -0,0 +1,21 @@
1
+ function result = matlab_startup(paths, expression)
2
+ % Add paths and evaluate an expression
3
+ %
4
+ % Parameters:
5
+ % paths (string): Paths to add to the MATLAB path
6
+ % expression (string): MATLAB startup expression
7
+
8
+ arguments
9
+ paths (1, :) string
10
+ expression (1, 1) string = string.empty()
11
+ end
12
+ for path = paths
13
+ addpath(genpath(path));
14
+ end
15
+
16
+ if ~isempty(expression)
17
+ eval(expression);
18
+ end
19
+
20
+ result = nan;
21
+ end
@@ -0,0 +1,127 @@
1
+ from enum import Enum
2
+ from typing import Any
3
+ from griffe import Function as GriffeFunction, Class as GriffeClass, Module, Attribute
4
+ from _griffe.exceptions import BuiltinModuleError
5
+
6
+
7
+ class Access(Enum):
8
+ PUBLIC = "public"
9
+ PROTECTED = "protected"
10
+ PRIVATE = "private"
11
+ IMMUTABLE = "immutable"
12
+
13
+
14
+ class CanonicalPathMixin:
15
+ @property
16
+ def canonical_path(self) -> str:
17
+ """The full dotted path of this object.
18
+
19
+ The canonical path is the path where the object was defined (not imported).
20
+ """
21
+ if self.parent is None or self.parent.path == "":
22
+ return self.name
23
+ return f"{self.parent.path}.{self.name}"
24
+
25
+
26
+ class Class(CanonicalPathMixin, GriffeClass):
27
+ def __init__(
28
+ self,
29
+ *args: Any,
30
+ hidden: bool = False,
31
+ sealed: bool = False,
32
+ abstract: bool = False,
33
+ enumeration: bool = False,
34
+ handle: bool = False,
35
+ **kwargs: Any,
36
+ ) -> None:
37
+ super().__init__(*args, **kwargs)
38
+ self._hidden: bool = hidden
39
+ self._sealed: bool = sealed
40
+ self._abstract: bool = abstract
41
+ self._enumeration: bool = enumeration
42
+ self._handle: bool = handle
43
+
44
+ @property
45
+ def is_private(self) -> bool:
46
+ return self._hidden
47
+
48
+ @property
49
+ def canonical_path(self) -> str:
50
+ if isinstance(self.parent, Classfolder):
51
+ return self.parent.canonical_path
52
+ else:
53
+ return super().canonical_path
54
+
55
+
56
+ class Property(CanonicalPathMixin, Attribute):
57
+ def __init__(
58
+ self,
59
+ *args: Any,
60
+ get_access: Access = Access.PUBLIC,
61
+ set_access: Access = Access.PUBLIC,
62
+ dependent: bool = False,
63
+ constant: bool = False,
64
+ abstract: bool = False,
65
+ transient: bool = False,
66
+ hidden: bool = False,
67
+ get_observable: bool = False,
68
+ set_observable: bool = False,
69
+ abort_set: bool = False,
70
+ non_copyable: bool = False,
71
+ has_default: bool = False,
72
+ size: str | None = None,
73
+ validation: str | None = None,
74
+ **kwargs: Any,
75
+ ) -> None:
76
+ super().__init__(*args, **kwargs)
77
+ self._get_access: Access = get_access
78
+ self._set_access: Access = set_access
79
+ self._dependent: bool = dependent
80
+ self._constant: bool = constant
81
+ self._abstract: bool = abstract
82
+ self._transient: bool = transient
83
+ self._hidden: bool = hidden
84
+ self._get_observable: bool = get_observable
85
+ self._set_observable: bool = set_observable
86
+ self._abort_set: bool = abort_set
87
+ self._non_copyable: bool = non_copyable
88
+ self._has_default: bool = has_default
89
+ self._size: str | None = size
90
+ self._validation: str | None = validation
91
+
92
+ @property
93
+ def is_private(self) -> bool:
94
+ set_public = self._access == Access.PUBLIC | self._access == Access.IMMUTABLE
95
+ get_public = self._access == Access.PUBLIC
96
+ return (set_public or get_public) and not self._hidden
97
+
98
+
99
+ class Function(CanonicalPathMixin, GriffeFunction):
100
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
101
+ super().__init__(*args, **kwargs)
102
+ self._access: Access = Access.PUBLIC
103
+ self._static: bool = False
104
+ self._abstract: bool = False
105
+ self._sealed: bool = False
106
+ self._hidden: bool = False
107
+
108
+ @property
109
+ def is_private(self) -> bool:
110
+ public = self._access == Access.PUBLIC | self._access == Access.IMMUTABLE
111
+ return public and not self._hidden
112
+
113
+
114
+ class Namespace(CanonicalPathMixin, Module):
115
+ def __repr__(self) -> str:
116
+ try:
117
+ return f"Namespace({self.filepath!r})"
118
+ except BuiltinModuleError:
119
+ return f"Namespace({self.name!r})"
120
+
121
+
122
+ class Classfolder(CanonicalPathMixin, Module):
123
+ def __repr__(self) -> str:
124
+ try:
125
+ return f"Classfolder({self.filepath!r})"
126
+ except BuiltinModuleError:
127
+ return f"Classfolder({self.name!r})"
File without changes
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.3
2
+ Name: mkdocstrings-matlab
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ Author-email: Mark Hu <mark.hu@asml.com>
6
+ License-File: LICENSE
7
+ Requires-Dist: griffe>=1.2.0
8
+ Requires-Dist: markdown>=3.7
9
+ Requires-Dist: mkdocs-material>=9.5.33
10
+ Requires-Dist: mkdocs>=1.6.0
11
+ Requires-Dist: mkdocstrings>=0.25.2
12
+ Requires-Dist: mkdocstrings[python]>=0.18
13
+ Description-Content-Type: text/markdown
14
+
15
+ # mkdocstrings-matlab
16
+
17
+ Describe your project here.
@@ -0,0 +1,25 @@
1
+ mkdocstrings_handlers/matlab/__init__.py,sha256=laA2bEP5rxdIWBLmfLiKKu7ChZ_HzCe-VeRvxmUTqww,128
2
+ mkdocstrings_handlers/matlab/handler.py,sha256=GuFjY7Jrv9QnHZELWghzAXEBf6Z67xvYr7aLPxMGvAg,14294
3
+ mkdocstrings_handlers/matlab/models.py,sha256=-k5V6Usho9D8KAynvEnMz7B6Fyrf1TrkJ1P8NhRdyzQ,3997
4
+ mkdocstrings_handlers/matlab/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ mkdocstrings_handlers/matlab/matlab/matlab_startup.m,sha256=rGXiR8fI2GW7yWmHxgzE5Un66n5ws3ogYnMvS2oWO8U,464
6
+ mkdocstrings_handlers/matlab/matlab/+docstring/exception.m,sha256=sj2ycqMpknn_OG5A5X_b5e4WcAoR2oPoqNnDEHJsi7c,222
7
+ mkdocstrings_handlers/matlab/matlab/+docstring/resolve.m,sha256=ZHHz6ujW1godzDcGtC3p0SsVARv-roseUh85e2eTBrE,2736
8
+ mkdocstrings_handlers/matlab/matlab/+docstring/+case/builtin.m,sha256=IioiokiaLnsi_6VXraaGdU2AgjCNApYhgVEmp2Lg1eA,245
9
+ mkdocstrings_handlers/matlab/matlab/+docstring/+case/class.m,sha256=MXeRhQibRQSOYwbrmc9tHLS723baStzSf9yXfzSEri0,275
10
+ mkdocstrings_handlers/matlab/matlab/+docstring/+case/func.m,sha256=QneDYSICtXqdPNq8sYNieirXUisuDKRM9rxPx-0osDU,137
11
+ mkdocstrings_handlers/matlab/matlab/+docstring/+case/method.m,sha256=Bw8Mb96OP7-AnbGnvRMySOIbYSTtEC1BGBnqHMEIhsM,165
12
+ mkdocstrings_handlers/matlab/matlab/+docstring/+case/namespace.m,sha256=ZpYrgZHLIdGvgg3F6210gDTska9YyASn63ZVYkBq41A,667
13
+ mkdocstrings_handlers/matlab/matlab/+docstring/+metadata/argument.m,sha256=l6UIQ4uolz533vo5DDWxl0R5PRkbb6hpRxpMPu5SNm8,1583
14
+ mkdocstrings_handlers/matlab/matlab/+docstring/+metadata/class.m,sha256=KurAMy-D_-47CYCdrqvzlSDXIl_dB2gwIQPLzjai35s,1542
15
+ mkdocstrings_handlers/matlab/matlab/+docstring/+metadata/func.m,sha256=urjYQauSRZ9bXg4co4gurOmiKzj5Sc4IS8FRKU8eEHM,408
16
+ mkdocstrings_handlers/matlab/matlab/+docstring/+metadata/namespace.m,sha256=kMzfAoWIpMXH76rrkyUqauJSRIaylsfnu0Cds4pYnJc,481
17
+ mkdocstrings_handlers/matlab/matlab/+docstring/+metadata/property.m,sha256=Ro5jciF60k8YRjJ_GWSm0kJNVZPfh4kyyLvYOwhrL2o,1379
18
+ mkdocstrings_handlers/matlab/matlab/+docstring/+metadata/script.m,sha256=pmuazWBqlugNEEojfQFkAg1ioErizoiEZ9RcFXecCP4,329
19
+ mkdocstrings_handlers/matlab/matlab/+docstring/+utils/dedent.m,sha256=r02mWQkRP9uuoEl-f-02h1-ja17a_29LTeyJvK-kazI,669
20
+ mkdocstrings_handlers/matlab/matlab/+docstring/+utils/get_namespace_path.m,sha256=4NlQ7-RjqIFZAn6P-9JzCrm2FvfGRKiM2M_leINj9i4,835
21
+ mkdocstrings_handlers/matlab/matlab/+docstring/+utils/parse_doc.m,sha256=z1voyYnASblb2i98n4dySOmBIbCa3n9moQKtbR5WE-k,442
22
+ mkdocstrings_matlab-0.1.0.dist-info/METADATA,sha256=R5KTt_IHagwweU_hC78Od6DXPiTfNOkE6S0IiDqqNeU,457
23
+ mkdocstrings_matlab-0.1.0.dist-info/WHEEL,sha256=fl6v0VwpzfGBVsGtkAkhILUlJxROXbA3HvRL6Fe3140,105
24
+ mkdocstrings_matlab-0.1.0.dist-info/licenses/LICENSE,sha256=14xA0OIYNpfmdeuq8-Yyqg7-3IJ4qhu3BJhknent-cY,1069
25
+ mkdocstrings_matlab-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.25.0
3
+ Root-Is-Purelib: true
4
+ Tag: py2-none-any
5
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Mark Shui Hu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.