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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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.