|
32 | 32 | from mkdocstrings.loggers import get_logger |
33 | 33 |
|
34 | 34 | if TYPE_CHECKING: |
35 | | - from collections.abc import Sequence |
| 35 | + from collections.abc import Iterator, Sequence |
36 | 36 |
|
37 | 37 | from griffe import Attribute, Class, Function, Module |
38 | 38 | from jinja2 import Environment, Template |
@@ -326,26 +326,47 @@ def repl(match: Match) -> str: |
326 | 326 | return Markup(text).format(**variables) |
327 | 327 |
|
328 | 328 |
|
329 | | -def do_split_path(path: str, full_path: str) -> list[tuple[str, str]]: |
| 329 | +_split_path_re = re.compile(r"([.(]?)([\w]+)(\))?") |
| 330 | +_splitable_re = re.compile(r"[().]") |
| 331 | + |
| 332 | + |
| 333 | +def do_split_path(path: str, full_path: str) -> Iterator[tuple[str, str, str, str]]: |
330 | 334 | """Split object paths for building cross-references. |
331 | 335 |
|
332 | 336 | Parameters: |
333 | 337 | path: The path to split. |
| 338 | + full_path: The full path, used to compute correct paths for each part of the path. |
334 | 339 |
|
335 | | - Returns: |
336 | | - A list of pairs (title, full path). |
| 340 | + Yields: |
| 341 | + 4-tuples: prefix, word, full path, suffix. |
337 | 342 | """ |
338 | | - if "." not in path: |
339 | | - return [(path, full_path)] |
340 | | - pairs = [] |
341 | | - full_path = "" |
342 | | - for part in path.split("."): |
343 | | - if full_path: |
344 | | - full_path += f".{part}" |
345 | | - else: |
346 | | - full_path = part |
347 | | - pairs.append((part, full_path)) |
348 | | - return pairs |
| 343 | + # Path is a single word, yield full path directly. |
| 344 | + if not _splitable_re.search(path): |
| 345 | + yield ("", path, full_path, "") |
| 346 | + return |
| 347 | + |
| 348 | + current_path = "" |
| 349 | + if path == full_path: |
| 350 | + # Split full path and yield directly without storing data in a dict. |
| 351 | + for match in _split_path_re.finditer(full_path): |
| 352 | + prefix, word, suffix = match.groups() |
| 353 | + current_path = f"{current_path}{prefix}{word}{suffix or ''}" if current_path else word |
| 354 | + yield prefix or "", word, current_path, suffix or "" |
| 355 | + return |
| 356 | + |
| 357 | + # Split full path first to store tuples in a dict. |
| 358 | + elements = {} |
| 359 | + for match in _split_path_re.finditer(full_path): |
| 360 | + prefix, word, suffix = match.groups() |
| 361 | + current_path = f"{current_path}{prefix}{word}{suffix or ''}" if current_path else word |
| 362 | + elements[word] = (prefix or "", word, current_path, suffix or "") |
| 363 | + |
| 364 | + # Then split path and pick tuples from the dict. |
| 365 | + first = True |
| 366 | + for match in _split_path_re.finditer(path): |
| 367 | + prefix, word, current_path, suffix = elements[match.group(2)] |
| 368 | + yield "" if first else prefix, word, current_path, suffix |
| 369 | + first = False |
349 | 370 |
|
350 | 371 |
|
351 | 372 | def _keep_object(name: str, filters: Sequence[tuple[Pattern, bool]]) -> bool: |
@@ -539,11 +560,20 @@ def do_as_attributes_section( |
539 | 560 | Returns: |
540 | 561 | An attributes docstring section. |
541 | 562 | """ |
| 563 | + |
| 564 | + def _parse_docstring_summary(attribute: Attribute) -> str: |
| 565 | + if attribute.docstring is None: |
| 566 | + return "" |
| 567 | + line = attribute.docstring.value.split("\n", 1)[0] |
| 568 | + if ":" in line and attribute.docstring.parser_options.get("returns_type_in_property_summary", False): |
| 569 | + _, line = line.split(":", 1) |
| 570 | + return line |
| 571 | + |
542 | 572 | return DocstringSectionAttributes( |
543 | 573 | [ |
544 | 574 | DocstringAttribute( |
545 | 575 | name=attribute.name, |
546 | | - description=attribute.docstring.value.split("\n", 1)[0] if attribute.docstring else "", |
| 576 | + description=_parse_docstring_summary(attribute), |
547 | 577 | annotation=attribute.annotation, |
548 | 578 | value=attribute.value, # type: ignore[arg-type] |
549 | 579 | ) |
|
0 commit comments