mkdocstrings-matlab 0.9.7__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mkdocstrings_handlers/matlab/__init__.py +26 -3
- mkdocstrings_handlers/matlab/config.py +885 -0
- mkdocstrings_handlers/matlab/handler.py +155 -296
- mkdocstrings_handlers/matlab/logger.py +111 -0
- mkdocstrings_handlers/matlab/rendering.py +818 -0
- mkdocstrings_handlers/matlab/templates/material/attributes.html.jinja +25 -0
- mkdocstrings_handlers/matlab/templates/material/backlinks.html.jinja +17 -0
- mkdocstrings_handlers/matlab/templates/material/children.html.jinja +70 -62
- mkdocstrings_handlers/matlab/templates/material/class.html.jinja +236 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/admonition.html.jinja +20 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/classes.html.jinja +85 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/examples.html.jinja +27 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/functions.html.jinja +91 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/input_arguments.html.jinja +171 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/name_value_arguments.html.jinja +166 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/namespaces.html.jinja +5 -6
- mkdocstrings_handlers/matlab/templates/material/docstring/output_arguments.html.jinja +152 -0
- mkdocstrings_handlers/matlab/templates/material/docstring/properties.html.jinja +25 -26
- mkdocstrings_handlers/matlab/templates/material/docstring.html.jinja +53 -0
- mkdocstrings_handlers/matlab/templates/material/expression.html.jinja +55 -0
- mkdocstrings_handlers/matlab/templates/material/folder.html.jinja +31 -39
- mkdocstrings_handlers/matlab/templates/material/function.html.jinja +148 -0
- mkdocstrings_handlers/matlab/templates/material/language.html.jinja +18 -0
- mkdocstrings_handlers/matlab/templates/material/languages/en.html.jinja +38 -0
- mkdocstrings_handlers/matlab/templates/material/languages/ja.html.jinja +38 -0
- mkdocstrings_handlers/matlab/templates/material/languages/zh.html.jinja +38 -0
- mkdocstrings_handlers/matlab/templates/material/namespace.html.jinja +32 -38
- mkdocstrings_handlers/matlab/templates/material/property.html.jinja +39 -35
- mkdocstrings_handlers/matlab/templates/material/script.html.jinja +3 -25
- mkdocstrings_handlers/matlab/templates/material/signature.html.jinja +105 -0
- mkdocstrings_handlers/matlab/templates/material/style.css +179 -4
- mkdocstrings_handlers/matlab/templates/material/summary/classes.html.jinja +25 -0
- mkdocstrings_handlers/matlab/templates/material/summary/functions.html.jinja +25 -0
- mkdocstrings_handlers/matlab/templates/material/summary/namespaces.html.jinja +17 -13
- mkdocstrings_handlers/matlab/templates/material/summary/properties.html.jinja +17 -13
- mkdocstrings_handlers/matlab/templates/material/summary.html.jinja +6 -6
- {mkdocstrings_matlab-0.9.7.dist-info → mkdocstrings_matlab-1.0.0.dist-info}/METADATA +17 -19
- mkdocstrings_matlab-1.0.0.dist-info/RECORD +41 -0
- mkdocstrings_handlers/matlab/collect.py +0 -783
- mkdocstrings_handlers/matlab/enums.py +0 -54
- mkdocstrings_handlers/matlab/models.py +0 -633
- mkdocstrings_handlers/matlab/treesitter.py +0 -707
- mkdocstrings_matlab-0.9.7.dist-info/RECORD +0 -22
- {mkdocstrings_matlab-0.9.7.dist-info → mkdocstrings_matlab-1.0.0.dist-info}/WHEEL +0 -0
- {mkdocstrings_matlab-0.9.7.dist-info → mkdocstrings_matlab-1.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,818 @@
|
|
1
|
+
# This module implements rendering utilities.
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import random
|
6
|
+
import re
|
7
|
+
import string
|
8
|
+
import sys
|
9
|
+
from contextlib import suppress
|
10
|
+
from dataclasses import replace
|
11
|
+
from re import Pattern
|
12
|
+
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Literal
|
13
|
+
|
14
|
+
from _griffe.docstrings.models import (
|
15
|
+
DocstringParameter,
|
16
|
+
DocstringReturn,
|
17
|
+
DocstringSectionOtherParameters,
|
18
|
+
DocstringSectionParameters,
|
19
|
+
DocstringSectionReturns,
|
20
|
+
)
|
21
|
+
from griffe import (
|
22
|
+
AliasResolutionError,
|
23
|
+
CyclicAliasError,
|
24
|
+
DocstringAttribute,
|
25
|
+
DocstringClass,
|
26
|
+
DocstringFunction,
|
27
|
+
DocstringModule,
|
28
|
+
DocstringSection,
|
29
|
+
DocstringSectionAttributes,
|
30
|
+
DocstringSectionClasses,
|
31
|
+
DocstringSectionFunctions,
|
32
|
+
DocstringSectionModules,
|
33
|
+
DocstringSectionText,
|
34
|
+
)
|
35
|
+
from jinja2 import pass_context
|
36
|
+
from markupsafe import Markup
|
37
|
+
from maxx.enums import ArgumentKind
|
38
|
+
from maxx.objects import (
|
39
|
+
Alias,
|
40
|
+
Class,
|
41
|
+
Function,
|
42
|
+
Namespace,
|
43
|
+
Object,
|
44
|
+
Property,
|
45
|
+
)
|
46
|
+
from mkdocs_autorefs import AutorefsHookInterface
|
47
|
+
from mkdocstrings import get_logger
|
48
|
+
|
49
|
+
if TYPE_CHECKING:
|
50
|
+
from collections.abc import Iterator, Sequence
|
51
|
+
|
52
|
+
from jinja2.runtime import Context
|
53
|
+
from mkdocstrings import CollectorItem
|
54
|
+
|
55
|
+
_logger = get_logger(__name__)
|
56
|
+
|
57
|
+
|
58
|
+
def _sort_key_alphabetical(item: CollectorItem) -> str:
|
59
|
+
"""
|
60
|
+
Sort key function to order items alphabetically by name.
|
61
|
+
|
62
|
+
Args:
|
63
|
+
item: The collector item to get sort key for.
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
A string representing the sort key (the item's name).
|
67
|
+
"""
|
68
|
+
# `chr(sys.maxunicode)` is a string that contains the final unicode character,
|
69
|
+
# so if `name` isn't found on the object, the item will go to the end of the list.
|
70
|
+
return item.name or chr(sys.maxunicode)
|
71
|
+
|
72
|
+
|
73
|
+
def _sort_key_source(item: CollectorItem) -> float:
|
74
|
+
"""
|
75
|
+
Sort key function to order items by their position in the source file.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
item: The collector item to get sort key for.
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
A float representing the line number of the item in the source file.
|
82
|
+
"""
|
83
|
+
# If `lineno` is none, the item will go to the end of the list.
|
84
|
+
return item.lineno if item.lineno is not None else float("inf")
|
85
|
+
|
86
|
+
|
87
|
+
Order = Literal["alphabetical", "source"]
|
88
|
+
"""Ordering methods.
|
89
|
+
|
90
|
+
- `alphabetical`: order members alphabetically;
|
91
|
+
- `source`: order members as they appear in the source file.
|
92
|
+
"""
|
93
|
+
|
94
|
+
_order_map: dict[str, Callable[[Object | Alias], str | float]] = {
|
95
|
+
"alphabetical": _sort_key_alphabetical,
|
96
|
+
"source": _sort_key_source,
|
97
|
+
}
|
98
|
+
|
99
|
+
|
100
|
+
class _StashCrossRefFilter:
|
101
|
+
stash: ClassVar[dict[str, str]] = {}
|
102
|
+
|
103
|
+
@staticmethod
|
104
|
+
def _gen_key(length: int) -> str:
|
105
|
+
return "_" + "".join(
|
106
|
+
random.choice(string.ascii_letters + string.digits) for _ in range(max(1, length - 1))
|
107
|
+
) # noqa: S311
|
108
|
+
|
109
|
+
def _gen_stash_key(self, length: int) -> str:
|
110
|
+
key = self._gen_key(length)
|
111
|
+
while key in self.stash:
|
112
|
+
key = self._gen_key(length)
|
113
|
+
return key
|
114
|
+
|
115
|
+
def __call__(self, crossref: str, *, length: int) -> str:
|
116
|
+
key = self._gen_stash_key(length)
|
117
|
+
self.stash[key] = crossref
|
118
|
+
return key
|
119
|
+
|
120
|
+
|
121
|
+
do_stash_crossref = _StashCrossRefFilter()
|
122
|
+
"""Filter to stash cross-references (and restore them after formatting and highlighting)."""
|
123
|
+
|
124
|
+
|
125
|
+
@pass_context
|
126
|
+
def do_format_signature(
|
127
|
+
context: Context,
|
128
|
+
callable_path: Markup,
|
129
|
+
function: Function,
|
130
|
+
line_length: int,
|
131
|
+
*,
|
132
|
+
annotations: bool | None = None,
|
133
|
+
crossrefs: bool = False, # noqa: ARG001
|
134
|
+
) -> str:
|
135
|
+
"""Format a signature.
|
136
|
+
|
137
|
+
Parameters:
|
138
|
+
context: Jinja context, passed automatically.
|
139
|
+
callable_path: The path of the callable we render the signature of.
|
140
|
+
function: The function we render the signature of.
|
141
|
+
line_length: The line length.
|
142
|
+
annotations: Whether to show type annotations.
|
143
|
+
crossrefs: Whether to cross-reference types in the signature.
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
The same code, formatted.
|
147
|
+
"""
|
148
|
+
env = context.environment
|
149
|
+
template = env.get_template("signature.html.jinja")
|
150
|
+
|
151
|
+
if annotations is None:
|
152
|
+
new_context = context.parent
|
153
|
+
else:
|
154
|
+
new_context = dict(context.parent)
|
155
|
+
new_context["config"] = replace(new_context["config"], show_signature_types=annotations)
|
156
|
+
|
157
|
+
signature = template.render(new_context, function=function, signature=True)
|
158
|
+
signature = str(
|
159
|
+
env.filters["highlight"](
|
160
|
+
Markup.escape(signature),
|
161
|
+
language="matlab",
|
162
|
+
inline=False,
|
163
|
+
classes=["doc-signature"],
|
164
|
+
linenums=False,
|
165
|
+
),
|
166
|
+
)
|
167
|
+
|
168
|
+
if stash := env.filters["stash_crossref"].stash:
|
169
|
+
for key, value in stash.items():
|
170
|
+
signature = re.sub(rf"\b{key}\b", value, signature)
|
171
|
+
stash.clear()
|
172
|
+
|
173
|
+
return signature
|
174
|
+
|
175
|
+
|
176
|
+
@pass_context
|
177
|
+
def do_format_arguments(
|
178
|
+
context: Context,
|
179
|
+
section_kind: str,
|
180
|
+
section: DocstringSectionParameters | DocstringSectionOtherParameters | DocstringSectionReturns,
|
181
|
+
*,
|
182
|
+
crossrefs: bool = False,
|
183
|
+
) -> str:
|
184
|
+
env = context.environment
|
185
|
+
|
186
|
+
match str(section_kind).strip():
|
187
|
+
case "parameters":
|
188
|
+
template = env.get_template("docstring/input_arguments.html.jinja")
|
189
|
+
case "other parameters":
|
190
|
+
template = env.get_template("docstring/name_value_arguments.html.jinja")
|
191
|
+
case "returns":
|
192
|
+
template = env.get_template("docstring/output_arguments.html.jinja")
|
193
|
+
case _:
|
194
|
+
raise ValueError(f"Unknown section kind: {section_kind}")
|
195
|
+
|
196
|
+
html = template.render(context.parent, section=section)
|
197
|
+
|
198
|
+
if stash := env.filters["stash_crossref"].stash:
|
199
|
+
for key, value in stash.items():
|
200
|
+
html = re.sub(rf"\b{key}\b", value, html)
|
201
|
+
stash.clear()
|
202
|
+
|
203
|
+
return html
|
204
|
+
|
205
|
+
|
206
|
+
@pass_context
|
207
|
+
def do_format_property(
|
208
|
+
context: Context,
|
209
|
+
property_path: Markup,
|
210
|
+
property: Property,
|
211
|
+
line_length: int,
|
212
|
+
*,
|
213
|
+
crossrefs: bool = False, # noqa: ARG001
|
214
|
+
) -> str:
|
215
|
+
"""Format an property.
|
216
|
+
|
217
|
+
Parameters:
|
218
|
+
context: Jinja context, passed automatically.
|
219
|
+
property_path: The path of the callable we render the signature of.
|
220
|
+
property: The property we render the signature of.
|
221
|
+
line_length: The line length.
|
222
|
+
crossrefs: Whether to cross-reference types in the signature.
|
223
|
+
|
224
|
+
Returns:
|
225
|
+
The same code, formatted.
|
226
|
+
"""
|
227
|
+
env = context.environment
|
228
|
+
template = env.get_template("expression.html.jinja")
|
229
|
+
annotations = context.parent["config"].show_signature_types
|
230
|
+
|
231
|
+
signature = str(property_path).strip()
|
232
|
+
if annotations and property.type:
|
233
|
+
annotation = template.render(
|
234
|
+
context.parent,
|
235
|
+
expression=property.type,
|
236
|
+
signature=True,
|
237
|
+
backlink_type="returned-by",
|
238
|
+
)
|
239
|
+
signature += f": {annotation}"
|
240
|
+
if property.default:
|
241
|
+
value = template.render(
|
242
|
+
context.parent,
|
243
|
+
expression=property.default,
|
244
|
+
signature=True,
|
245
|
+
backlink_type="used-by",
|
246
|
+
)
|
247
|
+
signature += f" = {value}"
|
248
|
+
|
249
|
+
signature = str(
|
250
|
+
env.filters["highlight"](
|
251
|
+
Markup.escape(signature),
|
252
|
+
language="python",
|
253
|
+
inline=False,
|
254
|
+
classes=["doc-signature"],
|
255
|
+
linenums=False,
|
256
|
+
),
|
257
|
+
)
|
258
|
+
|
259
|
+
if stash := env.filters["stash_crossref"].stash:
|
260
|
+
for key, value in stash.items():
|
261
|
+
signature = re.sub(rf"\b{key}\b", value, signature)
|
262
|
+
stash.clear()
|
263
|
+
|
264
|
+
return signature
|
265
|
+
|
266
|
+
|
267
|
+
def do_order_members(
|
268
|
+
members: Sequence[Object | Alias],
|
269
|
+
order: Order | list[Order],
|
270
|
+
members_list: bool | list[str] | None,
|
271
|
+
) -> Sequence[Object | Alias]:
|
272
|
+
"""Order members given an ordering method.
|
273
|
+
|
274
|
+
Parameters:
|
275
|
+
members: The members to order.
|
276
|
+
order: The ordering method.
|
277
|
+
members_list: An optional member list (manual ordering).
|
278
|
+
|
279
|
+
Returns:
|
280
|
+
The same members, ordered.
|
281
|
+
"""
|
282
|
+
if isinstance(members_list, list) and members_list:
|
283
|
+
sorted_members = []
|
284
|
+
members_dict = {member.name: member for member in members}
|
285
|
+
for name in members_list:
|
286
|
+
if name in members_dict:
|
287
|
+
sorted_members.append(members_dict[name])
|
288
|
+
return sorted_members
|
289
|
+
if isinstance(order, str):
|
290
|
+
order = [order]
|
291
|
+
for method in order:
|
292
|
+
with suppress(ValueError):
|
293
|
+
return sorted(members, key=_order_map[method])
|
294
|
+
return members
|
295
|
+
|
296
|
+
|
297
|
+
def _keep_object(name: str, filters: Sequence[tuple[Pattern, bool]]) -> bool:
|
298
|
+
"""
|
299
|
+
Determine if an object should be kept based on filter patterns.
|
300
|
+
|
301
|
+
Args:
|
302
|
+
name: The name of the object.
|
303
|
+
filters: A sequence of tuple pairs of (pattern, exclude_flag).
|
304
|
+
|
305
|
+
Returns:
|
306
|
+
True if the object should be kept, False otherwise.
|
307
|
+
"""
|
308
|
+
keep = None
|
309
|
+
rules = set()
|
310
|
+
for regex, exclude in filters:
|
311
|
+
rules.add(exclude)
|
312
|
+
if regex.search(name):
|
313
|
+
keep = not exclude
|
314
|
+
if keep is None:
|
315
|
+
# When we only include stuff, no match = reject.
|
316
|
+
# When we only exclude stuff, or include and exclude stuff, no match = keep.
|
317
|
+
return rules != {False}
|
318
|
+
return keep
|
319
|
+
|
320
|
+
|
321
|
+
def _parents(obj: Alias) -> set[str]:
|
322
|
+
"""
|
323
|
+
Get the full set of parent paths for an object.
|
324
|
+
|
325
|
+
This function collects all parent paths in the inheritance hierarchy
|
326
|
+
including paths for both direct parents and their alias targets.
|
327
|
+
|
328
|
+
Args:
|
329
|
+
obj: The alias object to get parents for.
|
330
|
+
|
331
|
+
Returns:
|
332
|
+
A set of parent path strings.
|
333
|
+
"""
|
334
|
+
parent: Object | Alias = obj.parent # type: ignore[assignment]
|
335
|
+
parents = {parent.path}
|
336
|
+
while parent.parent:
|
337
|
+
parent = parent.parent
|
338
|
+
parents.add(parent.path)
|
339
|
+
return parents
|
340
|
+
|
341
|
+
|
342
|
+
def _remove_cycles(objects: list[Object | Alias]) -> Iterator[Object | Alias]:
|
343
|
+
"""
|
344
|
+
Filter objects to remove those that create cycles in the inheritance graph.
|
345
|
+
|
346
|
+
Args:
|
347
|
+
objects: List of objects to check for cycles.
|
348
|
+
|
349
|
+
Returns:
|
350
|
+
An iterator yielding objects that don't create inheritance cycles.
|
351
|
+
"""
|
352
|
+
suppress_errors = suppress(AliasResolutionError, CyclicAliasError)
|
353
|
+
for obj in objects:
|
354
|
+
if obj.is_alias:
|
355
|
+
with suppress_errors:
|
356
|
+
if obj.parent and obj.path in _parents(obj): # type: ignore[arg-type,union-attr]
|
357
|
+
continue
|
358
|
+
yield obj
|
359
|
+
|
360
|
+
|
361
|
+
def do_filter_objects(
|
362
|
+
objects_dictionary: dict[str, Object | Alias],
|
363
|
+
*,
|
364
|
+
filters: Sequence[tuple[Pattern, bool]] | None = None,
|
365
|
+
members_list: bool | list[str] | None = None,
|
366
|
+
inherited_members: bool | list[str] = False,
|
367
|
+
private_members: bool | list[str] = False,
|
368
|
+
hidden_members: bool | list[str] = False,
|
369
|
+
keep_no_docstrings: bool = True,
|
370
|
+
) -> list[Object | Alias]:
|
371
|
+
"""Filter a dictionary of objects based on their docstrings.
|
372
|
+
|
373
|
+
Parameters:
|
374
|
+
objects_dictionary: The dictionary of objects.
|
375
|
+
filters: Filters to apply, based on members' names.
|
376
|
+
Each element is a tuple: a pattern, and a boolean indicating whether
|
377
|
+
to reject the object if the pattern matches.
|
378
|
+
members_list: An optional, explicit list of members to keep.
|
379
|
+
When given and empty, return an empty list.
|
380
|
+
When given and not empty, ignore filters and docstrings presence/absence.
|
381
|
+
inherited_members: Whether to keep inherited members or exclude them.
|
382
|
+
private_members: Whether to keep private members or exclude them.
|
383
|
+
hidden_members: Whether to keep hidden members or exclude them.
|
384
|
+
keep_no_docstrings: Whether to keep objects with no/empty docstrings (recursive check).
|
385
|
+
|
386
|
+
Returns:
|
387
|
+
A list of objects.
|
388
|
+
"""
|
389
|
+
inherited_members_specified = False
|
390
|
+
if inherited_members is True:
|
391
|
+
# Include all inherited members.
|
392
|
+
objects = list(objects_dictionary.values())
|
393
|
+
elif inherited_members is False:
|
394
|
+
# Include no inherited members.
|
395
|
+
objects = [obj for obj in objects_dictionary.values() if not obj.inherited]
|
396
|
+
else:
|
397
|
+
# Include specific inherited members.
|
398
|
+
inherited_members_specified = True
|
399
|
+
objects = [
|
400
|
+
obj
|
401
|
+
for obj in objects_dictionary.values()
|
402
|
+
if not obj.inherited or obj.name in set(inherited_members)
|
403
|
+
]
|
404
|
+
|
405
|
+
if isinstance(private_members, bool) and not private_members:
|
406
|
+
objects = [obj for obj in objects if not obj.is_private]
|
407
|
+
elif isinstance(private_members, list):
|
408
|
+
objects = [
|
409
|
+
obj
|
410
|
+
for obj in objects
|
411
|
+
if not obj.is_private or (obj.is_private and obj.name in private_members)
|
412
|
+
]
|
413
|
+
|
414
|
+
if isinstance(hidden_members, bool) and not hidden_members:
|
415
|
+
objects = [obj for obj in objects if not obj.is_hidden]
|
416
|
+
elif isinstance(hidden_members, list):
|
417
|
+
objects = [
|
418
|
+
obj
|
419
|
+
for obj in objects
|
420
|
+
if not obj.is_hidden or (obj.is_hidden and obj.name in hidden_members)
|
421
|
+
]
|
422
|
+
|
423
|
+
if members_list is True:
|
424
|
+
# Return all pre-selected members.
|
425
|
+
return objects
|
426
|
+
|
427
|
+
if members_list is False or members_list == []:
|
428
|
+
# Return selected inherited members, if any.
|
429
|
+
return [obj for obj in objects if obj.inherited]
|
430
|
+
|
431
|
+
if members_list is not None:
|
432
|
+
# Return selected members (keeping any pre-selected inherited members).
|
433
|
+
return [
|
434
|
+
obj
|
435
|
+
for obj in objects
|
436
|
+
if obj.name in set(members_list) or (inherited_members_specified and obj.inherited)
|
437
|
+
]
|
438
|
+
|
439
|
+
# Use filters and docstrings.
|
440
|
+
if filters:
|
441
|
+
objects = [
|
442
|
+
obj
|
443
|
+
for obj in objects
|
444
|
+
if _keep_object(obj.name, filters) or (inherited_members_specified and obj.inherited)
|
445
|
+
]
|
446
|
+
if not keep_no_docstrings:
|
447
|
+
objects = [
|
448
|
+
obj
|
449
|
+
for obj in objects
|
450
|
+
if obj.has_docstring or (inherited_members_specified and obj.inherited)
|
451
|
+
]
|
452
|
+
|
453
|
+
# Prevent infinite recursion.
|
454
|
+
if objects:
|
455
|
+
objects = list(_remove_cycles(objects))
|
456
|
+
|
457
|
+
return objects
|
458
|
+
|
459
|
+
|
460
|
+
def do_get_template(obj: Object) -> str:
|
461
|
+
"""Get the template name used to render an object.
|
462
|
+
|
463
|
+
Parameters:
|
464
|
+
env: The Jinja environment, passed automatically.
|
465
|
+
obj: A Griffe object, or a template name.
|
466
|
+
|
467
|
+
Returns:
|
468
|
+
A template name.
|
469
|
+
"""
|
470
|
+
name = obj
|
471
|
+
if isinstance(obj, (Alias, Object)):
|
472
|
+
extra_data = getattr(obj, "extra", {}).get("mkdocstrings", {})
|
473
|
+
if name := extra_data.get("template", ""):
|
474
|
+
return name
|
475
|
+
name = obj.kind.value
|
476
|
+
return f"{name}.html.jinja"
|
477
|
+
|
478
|
+
|
479
|
+
@pass_context
|
480
|
+
def do_as_properties_section(
|
481
|
+
context: Context, # noqa: ARG001
|
482
|
+
properties: Sequence[Property],
|
483
|
+
*,
|
484
|
+
check_public: bool = True,
|
485
|
+
) -> DocstringSectionAttributes:
|
486
|
+
"""Build an properties section from a list of properties.
|
487
|
+
|
488
|
+
Parameters:
|
489
|
+
properties: The properties to build the section from.
|
490
|
+
check_public: Whether to check if the property is public.
|
491
|
+
|
492
|
+
Returns:
|
493
|
+
An properties docstring section (attributes in Python)
|
494
|
+
"""
|
495
|
+
|
496
|
+
def _parse_docstring_summary(property: Property) -> str:
|
497
|
+
if property.docstring is None:
|
498
|
+
return ""
|
499
|
+
line = property.docstring.value.split("\n", 1)[0]
|
500
|
+
if ":" in line and property.docstring.parser_options.get(
|
501
|
+
"returns_type_in_property_summary", False
|
502
|
+
):
|
503
|
+
_, line = line.split(":", 1)
|
504
|
+
return line
|
505
|
+
|
506
|
+
return DocstringSectionAttributes(
|
507
|
+
[
|
508
|
+
DocstringAttribute(
|
509
|
+
name=property.name,
|
510
|
+
description=_parse_docstring_summary(property),
|
511
|
+
annotation=str(property.type),
|
512
|
+
value=property.default, # type: ignore[arg-type]
|
513
|
+
)
|
514
|
+
for property in properties
|
515
|
+
if not check_public or not property.is_private
|
516
|
+
],
|
517
|
+
)
|
518
|
+
|
519
|
+
|
520
|
+
@pass_context
|
521
|
+
def do_as_functions_section(
|
522
|
+
context: Context,
|
523
|
+
functions: Sequence[Function],
|
524
|
+
*,
|
525
|
+
check_public: bool = True,
|
526
|
+
) -> DocstringSectionFunctions:
|
527
|
+
"""Build a functions section from a list of functions.
|
528
|
+
|
529
|
+
Parameters:
|
530
|
+
functions: The functions to build the section from.
|
531
|
+
check_public: Whether to check if the function is public.
|
532
|
+
|
533
|
+
Returns:
|
534
|
+
A functions docstring section.
|
535
|
+
"""
|
536
|
+
keep_constructor_method = not context.parent["config"].merge_constructor_into_class
|
537
|
+
return DocstringSectionFunctions(
|
538
|
+
[
|
539
|
+
DocstringFunction(
|
540
|
+
name=function.name,
|
541
|
+
description=function.docstring.value.split("\n", 1)[0]
|
542
|
+
if function.docstring
|
543
|
+
else "",
|
544
|
+
)
|
545
|
+
for function in functions
|
546
|
+
if (not check_public or not function.is_private)
|
547
|
+
and (
|
548
|
+
keep_constructor_method
|
549
|
+
or not function.parent
|
550
|
+
or not function.parent.is_class
|
551
|
+
or function.name != function.parent.name
|
552
|
+
)
|
553
|
+
],
|
554
|
+
)
|
555
|
+
|
556
|
+
|
557
|
+
@pass_context
|
558
|
+
def do_as_classes_section(
|
559
|
+
context: Context, # noqa: ARG001
|
560
|
+
classes: Sequence[Class],
|
561
|
+
*,
|
562
|
+
check_public: bool = True,
|
563
|
+
) -> DocstringSectionClasses:
|
564
|
+
"""Build a classes section from a list of classes.
|
565
|
+
|
566
|
+
Parameters:
|
567
|
+
classes: The classes to build the section from.
|
568
|
+
check_public: Whether to check if the class is public.
|
569
|
+
|
570
|
+
Returns:
|
571
|
+
A classes docstring section.
|
572
|
+
"""
|
573
|
+
return DocstringSectionClasses(
|
574
|
+
[
|
575
|
+
DocstringClass(
|
576
|
+
name=cls.name,
|
577
|
+
description=cls.docstring.value.split("\n", 1)[0] if cls.docstring else "",
|
578
|
+
)
|
579
|
+
for cls in classes
|
580
|
+
if not check_public or not cls.is_private
|
581
|
+
],
|
582
|
+
)
|
583
|
+
|
584
|
+
|
585
|
+
@pass_context
|
586
|
+
def do_as_namespaces_section(
|
587
|
+
context: Context, # noqa: ARG001
|
588
|
+
namespaces: Sequence[Namespace],
|
589
|
+
*,
|
590
|
+
check_public: bool = True,
|
591
|
+
) -> DocstringSectionModules:
|
592
|
+
"""Build a namespaces section from a list of namespaces.
|
593
|
+
|
594
|
+
Parameters:
|
595
|
+
namespaces: The namespaces to build the section from.
|
596
|
+
check_public: Whether to check if the namespace is public.
|
597
|
+
|
598
|
+
Returns:
|
599
|
+
A namespaces docstring section. (modules in Python)
|
600
|
+
"""
|
601
|
+
return DocstringSectionModules(
|
602
|
+
[
|
603
|
+
DocstringModule(
|
604
|
+
name=namespace.name,
|
605
|
+
description=namespace.docstring.value.split("\n", 1)[0]
|
606
|
+
if namespace.docstring
|
607
|
+
else "",
|
608
|
+
)
|
609
|
+
for namespace in namespaces
|
610
|
+
if not check_public or not namespace.is_internal
|
611
|
+
],
|
612
|
+
)
|
613
|
+
|
614
|
+
|
615
|
+
def do_function_docstring(
|
616
|
+
function: Function,
|
617
|
+
parse_arguments: bool,
|
618
|
+
show_docstring_input_arguments: bool,
|
619
|
+
show_docstring_name_value_arguments: bool,
|
620
|
+
show_docstring_output_arguments: bool,
|
621
|
+
) -> list[DocstringSection]:
|
622
|
+
if function.docstring is None:
|
623
|
+
return []
|
624
|
+
|
625
|
+
docstring_sections = [section for section in function.docstring.parsed]
|
626
|
+
if not parse_arguments or not (
|
627
|
+
show_docstring_input_arguments
|
628
|
+
or show_docstring_name_value_arguments
|
629
|
+
or show_docstring_output_arguments
|
630
|
+
):
|
631
|
+
return docstring_sections
|
632
|
+
|
633
|
+
docstring_arguments = any(
|
634
|
+
isinstance(doc, DocstringSectionParameters) for doc in docstring_sections
|
635
|
+
)
|
636
|
+
docstring_returns = any(isinstance(doc, DocstringSectionReturns) for doc in docstring_sections)
|
637
|
+
|
638
|
+
if not docstring_arguments and function.arguments:
|
639
|
+
arguments_arguments = any(param.docstring is not None for param in function.arguments)
|
640
|
+
else:
|
641
|
+
arguments_arguments = False
|
642
|
+
|
643
|
+
if not docstring_returns and function.returns:
|
644
|
+
arguments_returns = any(ret.docstring is not None for ret in function.returns)
|
645
|
+
else:
|
646
|
+
arguments_returns = False
|
647
|
+
|
648
|
+
document_arguments = not docstring_arguments and arguments_arguments
|
649
|
+
document_returns = not docstring_returns and arguments_returns
|
650
|
+
|
651
|
+
standard_arguments = [
|
652
|
+
param for param in function.arguments if param.kind is not ArgumentKind.keyword_only
|
653
|
+
]
|
654
|
+
|
655
|
+
keyword_arguments = [
|
656
|
+
param for param in function.arguments if param.kind is ArgumentKind.keyword_only
|
657
|
+
]
|
658
|
+
|
659
|
+
if show_docstring_input_arguments and document_arguments and standard_arguments:
|
660
|
+
docstring_sections.append(
|
661
|
+
DocstringSectionParameters(
|
662
|
+
[
|
663
|
+
DocstringParameter(
|
664
|
+
name=param.name,
|
665
|
+
value=str(param.default) if param.default is not None else None,
|
666
|
+
annotation=param.type,
|
667
|
+
description=param.docstring.value if param.docstring is not None else "",
|
668
|
+
)
|
669
|
+
for param in standard_arguments
|
670
|
+
],
|
671
|
+
title="",
|
672
|
+
)
|
673
|
+
)
|
674
|
+
|
675
|
+
if show_docstring_name_value_arguments and document_arguments and keyword_arguments:
|
676
|
+
docstring_sections.append(
|
677
|
+
DocstringSectionOtherParameters(
|
678
|
+
[
|
679
|
+
DocstringParameter(
|
680
|
+
name=param.name,
|
681
|
+
value=str(param.default) if param.default is not None else None,
|
682
|
+
annotation=param.type,
|
683
|
+
description=param.docstring.value if param.docstring is not None else "",
|
684
|
+
)
|
685
|
+
for param in keyword_arguments
|
686
|
+
],
|
687
|
+
title="",
|
688
|
+
)
|
689
|
+
)
|
690
|
+
|
691
|
+
if show_docstring_output_arguments and document_returns:
|
692
|
+
returns = DocstringSectionReturns(
|
693
|
+
[
|
694
|
+
DocstringReturn(
|
695
|
+
name=param.name,
|
696
|
+
value=str(param.default) if param.default is not None else None,
|
697
|
+
annotation=param.type,
|
698
|
+
description=param.docstring.value if param.docstring is not None else "",
|
699
|
+
)
|
700
|
+
for param in function.returns or []
|
701
|
+
],
|
702
|
+
title="",
|
703
|
+
)
|
704
|
+
docstring_sections.append(returns)
|
705
|
+
|
706
|
+
return docstring_sections
|
707
|
+
|
708
|
+
|
709
|
+
def do_as_inheritance_diagram_section(model: Class) -> DocstringSectionText | None:
|
710
|
+
"""Generate an inheritance diagram section for a class.
|
711
|
+
|
712
|
+
Args:
|
713
|
+
model: The class model to create an inheritance diagram for.
|
714
|
+
|
715
|
+
Returns:
|
716
|
+
A docstring section with a Mermaid diagram, or None if there's no inheritance.
|
717
|
+
"""
|
718
|
+
|
719
|
+
if not hasattr(model, "bases") or not model.bases:
|
720
|
+
return None
|
721
|
+
|
722
|
+
def get_id(str: str) -> str:
|
723
|
+
return str.replace(".", "_")
|
724
|
+
|
725
|
+
def get_nodes(model: Class, nodes: set[str] = set()) -> set[str]:
|
726
|
+
nodes.add(f" {get_id(model.name)}[{model.name}]")
|
727
|
+
for base in [str(base) for base in model.bases]:
|
728
|
+
super = model.paths_collection.get_member(base) if model.paths_collection else None
|
729
|
+
if super is None:
|
730
|
+
nodes.add(f" {get_id(base)}[{base}]")
|
731
|
+
else:
|
732
|
+
if isinstance(super, Class):
|
733
|
+
get_nodes(super, nodes)
|
734
|
+
return nodes
|
735
|
+
|
736
|
+
def get_links(model: Class, links: set[str] = set()) -> set[str]:
|
737
|
+
for base in [str(base) for base in model.bases]:
|
738
|
+
super = model.paths_collection.get_member(base) if model.paths_collection else None
|
739
|
+
if super is None:
|
740
|
+
links.add(f" {get_id(base)} --> {get_id(model.name)}")
|
741
|
+
else:
|
742
|
+
if isinstance(super, Class):
|
743
|
+
links.add(f" {get_id(super.name)} --> {get_id(model.name)}")
|
744
|
+
get_links(super, links)
|
745
|
+
return links
|
746
|
+
|
747
|
+
nodes = get_nodes(model)
|
748
|
+
if len(nodes) == 1:
|
749
|
+
return None
|
750
|
+
|
751
|
+
nodes_str = "\n".join(list(nodes))
|
752
|
+
links_str = "\n".join(list(get_links(model)))
|
753
|
+
section = f"```mermaid\nflowchart TB\n{nodes_str}\n{links_str}\n```"
|
754
|
+
|
755
|
+
return DocstringSectionText(section, title="Inheritance Diagram")
|
756
|
+
|
757
|
+
|
758
|
+
class AutorefsHook(AutorefsHookInterface):
|
759
|
+
"""Autorefs hook.
|
760
|
+
|
761
|
+
With this hook, we're able to add context to autorefs (cross-references),
|
762
|
+
such as originating file path and line number, to improve error reporting.
|
763
|
+
"""
|
764
|
+
|
765
|
+
def __init__(self, current_object: Object | Alias, config: dict[str, Any]) -> None:
|
766
|
+
"""Initialize the hook.
|
767
|
+
|
768
|
+
Parameters:
|
769
|
+
current_object: The object being rendered.
|
770
|
+
config: The configuration dictionary.
|
771
|
+
"""
|
772
|
+
self.current_object = current_object
|
773
|
+
"""The current object being rendered."""
|
774
|
+
self.config = config
|
775
|
+
"""The configuration options."""
|
776
|
+
|
777
|
+
def expand_identifier(self, identifier: str) -> str:
|
778
|
+
"""Expand an identifier.
|
779
|
+
|
780
|
+
Parameters:
|
781
|
+
identifier: The identifier to expand.
|
782
|
+
|
783
|
+
Returns:
|
784
|
+
The expanded identifier.
|
785
|
+
"""
|
786
|
+
return identifier
|
787
|
+
|
788
|
+
def get_context(self) -> AutorefsHookInterface.Context:
|
789
|
+
"""Get the context for the current object.
|
790
|
+
|
791
|
+
Returns:
|
792
|
+
The context.
|
793
|
+
"""
|
794
|
+
role = {
|
795
|
+
"property": "data"
|
796
|
+
if self.current_object.parent and self.current_object.parent.is_module
|
797
|
+
else "attr",
|
798
|
+
"class": "class",
|
799
|
+
"function": "meth"
|
800
|
+
if self.current_object.parent and self.current_object.parent.is_class
|
801
|
+
else "func",
|
802
|
+
"module": "mod",
|
803
|
+
}.get(self.current_object.kind.value.lower(), "obj")
|
804
|
+
origin = self.current_object.path
|
805
|
+
try:
|
806
|
+
filepath = self.current_object.docstring.parent.filepath # type: ignore[union-attr]
|
807
|
+
lineno = self.current_object.docstring.lineno or 0 # type: ignore[union-attr]
|
808
|
+
except AttributeError:
|
809
|
+
filepath = self.current_object.filepath
|
810
|
+
lineno = 0
|
811
|
+
|
812
|
+
return AutorefsHookInterface.Context(
|
813
|
+
domain="py",
|
814
|
+
role=role,
|
815
|
+
origin=origin,
|
816
|
+
filepath=str(filepath),
|
817
|
+
lineno=lineno,
|
818
|
+
)
|