mkdocstrings-matlab 0.9.6__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.
Files changed (45) hide show
  1. mkdocstrings_handlers/matlab/__init__.py +26 -3
  2. mkdocstrings_handlers/matlab/config.py +885 -0
  3. mkdocstrings_handlers/matlab/handler.py +155 -299
  4. mkdocstrings_handlers/matlab/logger.py +111 -0
  5. mkdocstrings_handlers/matlab/rendering.py +818 -0
  6. mkdocstrings_handlers/matlab/templates/material/attributes.html.jinja +25 -0
  7. mkdocstrings_handlers/matlab/templates/material/backlinks.html.jinja +17 -0
  8. mkdocstrings_handlers/matlab/templates/material/children.html.jinja +70 -62
  9. mkdocstrings_handlers/matlab/templates/material/class.html.jinja +236 -0
  10. mkdocstrings_handlers/matlab/templates/material/docstring/admonition.html.jinja +20 -0
  11. mkdocstrings_handlers/matlab/templates/material/docstring/classes.html.jinja +85 -0
  12. mkdocstrings_handlers/matlab/templates/material/docstring/examples.html.jinja +27 -0
  13. mkdocstrings_handlers/matlab/templates/material/docstring/functions.html.jinja +91 -0
  14. mkdocstrings_handlers/matlab/templates/material/docstring/input_arguments.html.jinja +171 -0
  15. mkdocstrings_handlers/matlab/templates/material/docstring/name_value_arguments.html.jinja +166 -0
  16. mkdocstrings_handlers/matlab/templates/material/docstring/namespaces.html.jinja +5 -6
  17. mkdocstrings_handlers/matlab/templates/material/docstring/output_arguments.html.jinja +152 -0
  18. mkdocstrings_handlers/matlab/templates/material/docstring/properties.html.jinja +25 -26
  19. mkdocstrings_handlers/matlab/templates/material/docstring.html.jinja +53 -0
  20. mkdocstrings_handlers/matlab/templates/material/expression.html.jinja +55 -0
  21. mkdocstrings_handlers/matlab/templates/material/folder.html.jinja +31 -39
  22. mkdocstrings_handlers/matlab/templates/material/function.html.jinja +148 -0
  23. mkdocstrings_handlers/matlab/templates/material/language.html.jinja +18 -0
  24. mkdocstrings_handlers/matlab/templates/material/languages/en.html.jinja +38 -0
  25. mkdocstrings_handlers/matlab/templates/material/languages/ja.html.jinja +38 -0
  26. mkdocstrings_handlers/matlab/templates/material/languages/zh.html.jinja +38 -0
  27. mkdocstrings_handlers/matlab/templates/material/namespace.html.jinja +32 -38
  28. mkdocstrings_handlers/matlab/templates/material/property.html.jinja +39 -35
  29. mkdocstrings_handlers/matlab/templates/material/script.html.jinja +3 -25
  30. mkdocstrings_handlers/matlab/templates/material/signature.html.jinja +105 -0
  31. mkdocstrings_handlers/matlab/templates/material/style.css +179 -4
  32. mkdocstrings_handlers/matlab/templates/material/summary/classes.html.jinja +25 -0
  33. mkdocstrings_handlers/matlab/templates/material/summary/functions.html.jinja +25 -0
  34. mkdocstrings_handlers/matlab/templates/material/summary/namespaces.html.jinja +17 -13
  35. mkdocstrings_handlers/matlab/templates/material/summary/properties.html.jinja +17 -13
  36. mkdocstrings_handlers/matlab/templates/material/summary.html.jinja +6 -6
  37. {mkdocstrings_matlab-0.9.6.dist-info → mkdocstrings_matlab-1.0.0.dist-info}/METADATA +17 -19
  38. mkdocstrings_matlab-1.0.0.dist-info/RECORD +41 -0
  39. mkdocstrings_handlers/matlab/collect.py +0 -783
  40. mkdocstrings_handlers/matlab/enums.py +0 -54
  41. mkdocstrings_handlers/matlab/models.py +0 -633
  42. mkdocstrings_handlers/matlab/treesitter.py +0 -707
  43. mkdocstrings_matlab-0.9.6.dist-info/RECORD +0 -22
  44. {mkdocstrings_matlab-0.9.6.dist-info → mkdocstrings_matlab-1.0.0.dist-info}/WHEEL +0 -0
  45. {mkdocstrings_matlab-0.9.6.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
+ )