1""" 2 sphinx.ext.napoleon.docstring 3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 5 6 Classes for docstring parsing and formatting. 7 8 9 :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. 10 :license: BSD, see LICENSE for details. 11""" 12 13import collections 14import inspect 15import re 16from functools import partial 17from typing import Any, Callable, Dict, List, Tuple, Union 18 19from sphinx.application import Sphinx 20from sphinx.config import Config as SphinxConfig 21from sphinx.ext.napoleon.iterators import modify_iter 22from sphinx.locale import _, __ 23from sphinx.util import logging 24from sphinx.util.inspect import stringify_annotation 25from sphinx.util.typing import get_type_hints 26 27if False: 28 # For type annotation 29 from typing import Type # for python3.5.1 30 31 32logger = logging.getLogger(__name__) 33 34_directive_regex = re.compile(r'\.\. \S+::') 35_google_section_regex = re.compile(r'^(\s|\w)+:\s*$') 36_google_typed_arg_regex = re.compile(r'(.+?)\(\s*(.*[^\s]+)\s*\)') 37_numpy_section_regex = re.compile(r'^[=\-`:\'"~^_*+#<>]{2,}\s*$') 38_single_colon_regex = re.compile(r'(?<!:):(?!:)') 39_xref_or_code_regex = re.compile( 40 r'((?::(?:[a-zA-Z0-9]+[\-_+:.])*[a-zA-Z0-9]+:`.+?`)|' 41 r'(?:``.+?``))') 42_xref_regex = re.compile( 43 r'(?:(?::(?:[a-zA-Z0-9]+[\-_+:.])*[a-zA-Z0-9]+:)?`.+?`)' 44) 45_bullet_list_regex = re.compile(r'^(\*|\+|\-)(\s+\S|\s*$)') 46_enumerated_list_regex = re.compile( 47 r'^(?P<paren>\()?' 48 r'(\d+|#|[ivxlcdm]+|[IVXLCDM]+|[a-zA-Z])' 49 r'(?(paren)\)|\.)(\s+\S|\s*$)') 50_token_regex = re.compile( 51 r"(,\sor\s|\sor\s|\sof\s|:\s|\sto\s|,\sand\s|\sand\s|,\s" 52 r"|[{]|[}]" 53 r'|"(?:\\"|[^"])*"' 54 r"|'(?:\\'|[^'])*')" 55) 56_default_regex = re.compile( 57 r"^default[^_0-9A-Za-z].*$", 58) 59_SINGLETONS = ("None", "True", "False", "Ellipsis") 60 61 62def _convert_type_spec(_type: str, translations: Dict[str, str] = {}) -> str: 63 """Convert type specification to reference in reST.""" 64 if _type in translations: 65 return translations[_type] 66 else: 67 if _type == 'None': 68 return ':obj:`None`' 69 else: 70 return ':class:`%s`' % _type 71 72 return _type 73 74 75class GoogleDocstring: 76 """Convert Google style docstrings to reStructuredText. 77 78 Parameters 79 ---------- 80 docstring : :obj:`str` or :obj:`list` of :obj:`str` 81 The docstring to parse, given either as a string or split into 82 individual lines. 83 config: :obj:`sphinx.ext.napoleon.Config` or :obj:`sphinx.config.Config` 84 The configuration settings to use. If not given, defaults to the 85 config object on `app`; or if `app` is not given defaults to the 86 a new :class:`sphinx.ext.napoleon.Config` object. 87 88 89 Other Parameters 90 ---------------- 91 app : :class:`sphinx.application.Sphinx`, optional 92 Application object representing the Sphinx process. 93 what : :obj:`str`, optional 94 A string specifying the type of the object to which the docstring 95 belongs. Valid values: "module", "class", "exception", "function", 96 "method", "attribute". 97 name : :obj:`str`, optional 98 The fully qualified name of the object. 99 obj : module, class, exception, function, method, or attribute 100 The object to which the docstring belongs. 101 options : :class:`sphinx.ext.autodoc.Options`, optional 102 The options given to the directive: an object with attributes 103 inherited_members, undoc_members, show_inheritance and noindex that 104 are True if the flag option of same name was given to the auto 105 directive. 106 107 108 Example 109 ------- 110 >>> from sphinx.ext.napoleon import Config 111 >>> config = Config(napoleon_use_param=True, napoleon_use_rtype=True) 112 >>> docstring = '''One line summary. 113 ... 114 ... Extended description. 115 ... 116 ... Args: 117 ... arg1(int): Description of `arg1` 118 ... arg2(str): Description of `arg2` 119 ... Returns: 120 ... str: Description of return value. 121 ... ''' 122 >>> print(GoogleDocstring(docstring, config)) 123 One line summary. 124 <BLANKLINE> 125 Extended description. 126 <BLANKLINE> 127 :param arg1: Description of `arg1` 128 :type arg1: int 129 :param arg2: Description of `arg2` 130 :type arg2: str 131 <BLANKLINE> 132 :returns: Description of return value. 133 :rtype: str 134 <BLANKLINE> 135 136 """ 137 138 _name_rgx = re.compile(r"^\s*((?::(?P<role>\S+):)?`(?P<name>~?[a-zA-Z0-9_.-]+)`|" 139 r" (?P<name2>~?[a-zA-Z0-9_.-]+))\s*", re.X) 140 141 def __init__(self, docstring: Union[str, List[str]], config: SphinxConfig = None, 142 app: Sphinx = None, what: str = '', name: str = '', 143 obj: Any = None, options: Any = None) -> None: 144 self._config = config 145 self._app = app 146 147 if not self._config: 148 from sphinx.ext.napoleon import Config 149 self._config = self._app.config if self._app else Config() # type: ignore 150 151 if not what: 152 if inspect.isclass(obj): 153 what = 'class' 154 elif inspect.ismodule(obj): 155 what = 'module' 156 elif callable(obj): 157 what = 'function' 158 else: 159 what = 'object' 160 161 self._what = what 162 self._name = name 163 self._obj = obj 164 self._opt = options 165 if isinstance(docstring, str): 166 lines = docstring.splitlines() 167 else: 168 lines = docstring 169 self._line_iter = modify_iter(lines, modifier=lambda s: s.rstrip()) 170 self._parsed_lines = [] # type: List[str] 171 self._is_in_section = False 172 self._section_indent = 0 173 if not hasattr(self, '_directive_sections'): 174 self._directive_sections = [] # type: List[str] 175 if not hasattr(self, '_sections'): 176 self._sections = { 177 'args': self._parse_parameters_section, 178 'arguments': self._parse_parameters_section, 179 'attention': partial(self._parse_admonition, 'attention'), 180 'attributes': self._parse_attributes_section, 181 'caution': partial(self._parse_admonition, 'caution'), 182 'danger': partial(self._parse_admonition, 'danger'), 183 'error': partial(self._parse_admonition, 'error'), 184 'example': self._parse_examples_section, 185 'examples': self._parse_examples_section, 186 'hint': partial(self._parse_admonition, 'hint'), 187 'important': partial(self._parse_admonition, 'important'), 188 'keyword args': self._parse_keyword_arguments_section, 189 'keyword arguments': self._parse_keyword_arguments_section, 190 'methods': self._parse_methods_section, 191 'note': partial(self._parse_admonition, 'note'), 192 'notes': self._parse_notes_section, 193 'other parameters': self._parse_other_parameters_section, 194 'parameters': self._parse_parameters_section, 195 'receive': self._parse_receives_section, 196 'receives': self._parse_receives_section, 197 'return': self._parse_returns_section, 198 'returns': self._parse_returns_section, 199 'raise': self._parse_raises_section, 200 'raises': self._parse_raises_section, 201 'references': self._parse_references_section, 202 'see also': self._parse_see_also_section, 203 'tip': partial(self._parse_admonition, 'tip'), 204 'todo': partial(self._parse_admonition, 'todo'), 205 'warning': partial(self._parse_admonition, 'warning'), 206 'warnings': partial(self._parse_admonition, 'warning'), 207 'warn': self._parse_warns_section, 208 'warns': self._parse_warns_section, 209 'yield': self._parse_yields_section, 210 'yields': self._parse_yields_section, 211 } # type: Dict[str, Callable] 212 213 self._load_custom_sections() 214 215 self._parse() 216 217 def __str__(self) -> str: 218 """Return the parsed docstring in reStructuredText format. 219 220 Returns 221 ------- 222 unicode 223 Unicode version of the docstring. 224 225 """ 226 return '\n'.join(self.lines()) 227 228 def lines(self) -> List[str]: 229 """Return the parsed lines of the docstring in reStructuredText format. 230 231 Returns 232 ------- 233 list(str) 234 The lines of the docstring in a list. 235 236 """ 237 return self._parsed_lines 238 239 def _consume_indented_block(self, indent: int = 1) -> List[str]: 240 lines = [] 241 line = self._line_iter.peek() 242 while(not self._is_section_break() and 243 (not line or self._is_indented(line, indent))): 244 lines.append(next(self._line_iter)) 245 line = self._line_iter.peek() 246 return lines 247 248 def _consume_contiguous(self) -> List[str]: 249 lines = [] 250 while (self._line_iter.has_next() and 251 self._line_iter.peek() and 252 not self._is_section_header()): 253 lines.append(next(self._line_iter)) 254 return lines 255 256 def _consume_empty(self) -> List[str]: 257 lines = [] 258 line = self._line_iter.peek() 259 while self._line_iter.has_next() and not line: 260 lines.append(next(self._line_iter)) 261 line = self._line_iter.peek() 262 return lines 263 264 def _consume_field(self, parse_type: bool = True, prefer_type: bool = False 265 ) -> Tuple[str, str, List[str]]: 266 line = next(self._line_iter) 267 268 before, colon, after = self._partition_field_on_colon(line) 269 _name, _type, _desc = before, '', after 270 271 if parse_type: 272 match = _google_typed_arg_regex.match(before) 273 if match: 274 _name = match.group(1).strip() 275 _type = match.group(2) 276 277 _name = self._escape_args_and_kwargs(_name) 278 279 if prefer_type and not _type: 280 _type, _name = _name, _type 281 282 if _type and self._config.napoleon_preprocess_types: 283 _type = _convert_type_spec(_type, self._config.napoleon_type_aliases or {}) 284 285 indent = self._get_indent(line) + 1 286 _descs = [_desc] + self._dedent(self._consume_indented_block(indent)) 287 _descs = self.__class__(_descs, self._config).lines() 288 return _name, _type, _descs 289 290 def _consume_fields(self, parse_type: bool = True, prefer_type: bool = False, 291 multiple: bool = False) -> List[Tuple[str, str, List[str]]]: 292 self._consume_empty() 293 fields = [] 294 while not self._is_section_break(): 295 _name, _type, _desc = self._consume_field(parse_type, prefer_type) 296 if multiple and _name: 297 for name in _name.split(","): 298 fields.append((name.strip(), _type, _desc)) 299 elif _name or _type or _desc: 300 fields.append((_name, _type, _desc,)) 301 return fields 302 303 def _consume_inline_attribute(self) -> Tuple[str, List[str]]: 304 line = next(self._line_iter) 305 _type, colon, _desc = self._partition_field_on_colon(line) 306 if not colon or not _desc: 307 _type, _desc = _desc, _type 308 _desc += colon 309 _descs = [_desc] + self._dedent(self._consume_to_end()) 310 _descs = self.__class__(_descs, self._config).lines() 311 return _type, _descs 312 313 def _consume_returns_section(self, preprocess_types: bool = False 314 ) -> List[Tuple[str, str, List[str]]]: 315 lines = self._dedent(self._consume_to_next_section()) 316 if lines: 317 before, colon, after = self._partition_field_on_colon(lines[0]) 318 _name, _type, _desc = '', '', lines 319 320 if colon: 321 if after: 322 _desc = [after] + lines[1:] 323 else: 324 _desc = lines[1:] 325 326 _type = before 327 328 if (_type and preprocess_types and 329 self._config.napoleon_preprocess_types): 330 _type = _convert_type_spec(_type, self._config.napoleon_type_aliases or {}) 331 332 _desc = self.__class__(_desc, self._config).lines() 333 return [(_name, _type, _desc,)] 334 else: 335 return [] 336 337 def _consume_usage_section(self) -> List[str]: 338 lines = self._dedent(self._consume_to_next_section()) 339 return lines 340 341 def _consume_section_header(self) -> str: 342 section = next(self._line_iter) 343 stripped_section = section.strip(':') 344 if stripped_section.lower() in self._sections: 345 section = stripped_section 346 return section 347 348 def _consume_to_end(self) -> List[str]: 349 lines = [] 350 while self._line_iter.has_next(): 351 lines.append(next(self._line_iter)) 352 return lines 353 354 def _consume_to_next_section(self) -> List[str]: 355 self._consume_empty() 356 lines = [] 357 while not self._is_section_break(): 358 lines.append(next(self._line_iter)) 359 return lines + self._consume_empty() 360 361 def _dedent(self, lines: List[str], full: bool = False) -> List[str]: 362 if full: 363 return [line.lstrip() for line in lines] 364 else: 365 min_indent = self._get_min_indent(lines) 366 return [line[min_indent:] for line in lines] 367 368 def _escape_args_and_kwargs(self, name: str) -> str: 369 if name.endswith('_') and getattr(self._config, 'strip_signature_backslash', False): 370 name = name[:-1] + r'\_' 371 372 if name[:2] == '**': 373 return r'\*\*' + name[2:] 374 elif name[:1] == '*': 375 return r'\*' + name[1:] 376 else: 377 return name 378 379 def _fix_field_desc(self, desc: List[str]) -> List[str]: 380 if self._is_list(desc): 381 desc = [''] + desc 382 elif desc[0].endswith('::'): 383 desc_block = desc[1:] 384 indent = self._get_indent(desc[0]) 385 block_indent = self._get_initial_indent(desc_block) 386 if block_indent > indent: 387 desc = [''] + desc 388 else: 389 desc = ['', desc[0]] + self._indent(desc_block, 4) 390 return desc 391 392 def _format_admonition(self, admonition: str, lines: List[str]) -> List[str]: 393 lines = self._strip_empty(lines) 394 if len(lines) == 1: 395 return ['.. %s:: %s' % (admonition, lines[0].strip()), ''] 396 elif lines: 397 lines = self._indent(self._dedent(lines), 3) 398 return ['.. %s::' % admonition, ''] + lines + [''] 399 else: 400 return ['.. %s::' % admonition, ''] 401 402 def _format_block(self, prefix: str, lines: List[str], padding: str = None) -> List[str]: 403 if lines: 404 if padding is None: 405 padding = ' ' * len(prefix) 406 result_lines = [] 407 for i, line in enumerate(lines): 408 if i == 0: 409 result_lines.append((prefix + line).rstrip()) 410 elif line: 411 result_lines.append(padding + line) 412 else: 413 result_lines.append('') 414 return result_lines 415 else: 416 return [prefix] 417 418 def _format_docutils_params(self, fields: List[Tuple[str, str, List[str]]], 419 field_role: str = 'param', type_role: str = 'type' 420 ) -> List[str]: 421 lines = [] 422 for _name, _type, _desc in fields: 423 _desc = self._strip_empty(_desc) 424 if any(_desc): 425 _desc = self._fix_field_desc(_desc) 426 field = ':%s %s: ' % (field_role, _name) 427 lines.extend(self._format_block(field, _desc)) 428 else: 429 lines.append(':%s %s:' % (field_role, _name)) 430 431 if _type: 432 lines.append(':%s %s: %s' % (type_role, _name, _type)) 433 return lines + [''] 434 435 def _format_field(self, _name: str, _type: str, _desc: List[str]) -> List[str]: 436 _desc = self._strip_empty(_desc) 437 has_desc = any(_desc) 438 separator = ' -- ' if has_desc else '' 439 if _name: 440 if _type: 441 if '`' in _type: 442 field = '**%s** (%s)%s' % (_name, _type, separator) 443 else: 444 field = '**%s** (*%s*)%s' % (_name, _type, separator) 445 else: 446 field = '**%s**%s' % (_name, separator) 447 elif _type: 448 if '`' in _type: 449 field = '%s%s' % (_type, separator) 450 else: 451 field = '*%s*%s' % (_type, separator) 452 else: 453 field = '' 454 455 if has_desc: 456 _desc = self._fix_field_desc(_desc) 457 if _desc[0]: 458 return [field + _desc[0]] + _desc[1:] 459 else: 460 return [field] + _desc 461 else: 462 return [field] 463 464 def _format_fields(self, field_type: str, fields: List[Tuple[str, str, List[str]]] 465 ) -> List[str]: 466 field_type = ':%s:' % field_type.strip() 467 padding = ' ' * len(field_type) 468 multi = len(fields) > 1 469 lines = [] # type: List[str] 470 for _name, _type, _desc in fields: 471 field = self._format_field(_name, _type, _desc) 472 if multi: 473 if lines: 474 lines.extend(self._format_block(padding + ' * ', field)) 475 else: 476 lines.extend(self._format_block(field_type + ' * ', field)) 477 else: 478 lines.extend(self._format_block(field_type + ' ', field)) 479 if lines and lines[-1]: 480 lines.append('') 481 return lines 482 483 def _get_current_indent(self, peek_ahead: int = 0) -> int: 484 line = self._line_iter.peek(peek_ahead + 1)[peek_ahead] 485 while line != self._line_iter.sentinel: 486 if line: 487 return self._get_indent(line) 488 peek_ahead += 1 489 line = self._line_iter.peek(peek_ahead + 1)[peek_ahead] 490 return 0 491 492 def _get_indent(self, line: str) -> int: 493 for i, s in enumerate(line): 494 if not s.isspace(): 495 return i 496 return len(line) 497 498 def _get_initial_indent(self, lines: List[str]) -> int: 499 for line in lines: 500 if line: 501 return self._get_indent(line) 502 return 0 503 504 def _get_min_indent(self, lines: List[str]) -> int: 505 min_indent = None 506 for line in lines: 507 if line: 508 indent = self._get_indent(line) 509 if min_indent is None: 510 min_indent = indent 511 elif indent < min_indent: 512 min_indent = indent 513 return min_indent or 0 514 515 def _indent(self, lines: List[str], n: int = 4) -> List[str]: 516 return [(' ' * n) + line for line in lines] 517 518 def _is_indented(self, line: str, indent: int = 1) -> bool: 519 for i, s in enumerate(line): 520 if i >= indent: 521 return True 522 elif not s.isspace(): 523 return False 524 return False 525 526 def _is_list(self, lines: List[str]) -> bool: 527 if not lines: 528 return False 529 if _bullet_list_regex.match(lines[0]): 530 return True 531 if _enumerated_list_regex.match(lines[0]): 532 return True 533 if len(lines) < 2 or lines[0].endswith('::'): 534 return False 535 indent = self._get_indent(lines[0]) 536 next_indent = indent 537 for line in lines[1:]: 538 if line: 539 next_indent = self._get_indent(line) 540 break 541 return next_indent > indent 542 543 def _is_section_header(self) -> bool: 544 section = self._line_iter.peek().lower() 545 match = _google_section_regex.match(section) 546 if match and section.strip(':') in self._sections: 547 header_indent = self._get_indent(section) 548 section_indent = self._get_current_indent(peek_ahead=1) 549 return section_indent > header_indent 550 elif self._directive_sections: 551 if _directive_regex.match(section): 552 for directive_section in self._directive_sections: 553 if section.startswith(directive_section): 554 return True 555 return False 556 557 def _is_section_break(self) -> bool: 558 line = self._line_iter.peek() 559 return (not self._line_iter.has_next() or 560 self._is_section_header() or 561 (self._is_in_section and 562 line and 563 not self._is_indented(line, self._section_indent))) 564 565 def _load_custom_sections(self) -> None: 566 if self._config.napoleon_custom_sections is not None: 567 for entry in self._config.napoleon_custom_sections: 568 if isinstance(entry, str): 569 # if entry is just a label, add to sections list, 570 # using generic section logic. 571 self._sections[entry.lower()] = self._parse_custom_generic_section 572 else: 573 # otherwise, assume entry is container; 574 if entry[1] == "params_style": 575 self._sections[entry[0].lower()] = \ 576 self._parse_custom_params_style_section 577 elif entry[1] == "returns_style": 578 self._sections[entry[0].lower()] = \ 579 self._parse_custom_returns_style_section 580 else: 581 # [0] is new section, [1] is the section to alias. 582 # in the case of key mismatch, just handle as generic section. 583 self._sections[entry[0].lower()] = \ 584 self._sections.get(entry[1].lower(), 585 self._parse_custom_generic_section) 586 587 def _parse(self) -> None: 588 self._parsed_lines = self._consume_empty() 589 590 if self._name and self._what in ('attribute', 'data', 'property'): 591 # Implicit stop using StopIteration no longer allowed in 592 # Python 3.7; see PEP 479 593 res = [] # type: List[str] 594 try: 595 res = self._parse_attribute_docstring() 596 except StopIteration: 597 pass 598 self._parsed_lines.extend(res) 599 return 600 601 while self._line_iter.has_next(): 602 if self._is_section_header(): 603 try: 604 section = self._consume_section_header() 605 self._is_in_section = True 606 self._section_indent = self._get_current_indent() 607 if _directive_regex.match(section): 608 lines = [section] + self._consume_to_next_section() 609 else: 610 lines = self._sections[section.lower()](section) 611 finally: 612 self._is_in_section = False 613 self._section_indent = 0 614 else: 615 if not self._parsed_lines: 616 lines = self._consume_contiguous() + self._consume_empty() 617 else: 618 lines = self._consume_to_next_section() 619 self._parsed_lines.extend(lines) 620 621 def _parse_admonition(self, admonition: str, section: str) -> List[str]: 622 # type (str, str) -> List[str] 623 lines = self._consume_to_next_section() 624 return self._format_admonition(admonition, lines) 625 626 def _parse_attribute_docstring(self) -> List[str]: 627 _type, _desc = self._consume_inline_attribute() 628 lines = self._format_field('', '', _desc) 629 if _type: 630 lines.extend(['', ':type: %s' % _type]) 631 return lines 632 633 def _parse_attributes_section(self, section: str) -> List[str]: 634 lines = [] 635 for _name, _type, _desc in self._consume_fields(): 636 if not _type: 637 _type = self._lookup_annotation(_name) 638 if self._config.napoleon_use_ivar: 639 _name = self._qualify_name(_name, self._obj) 640 field = ':ivar %s: ' % _name 641 lines.extend(self._format_block(field, _desc)) 642 if _type: 643 lines.append(':vartype %s: %s' % (_name, _type)) 644 else: 645 lines.append('.. attribute:: ' + _name) 646 if self._opt and 'noindex' in self._opt: 647 lines.append(' :noindex:') 648 lines.append('') 649 650 fields = self._format_field('', '', _desc) 651 lines.extend(self._indent(fields, 3)) 652 if _type: 653 lines.append('') 654 lines.extend(self._indent([':type: %s' % _type], 3)) 655 lines.append('') 656 if self._config.napoleon_use_ivar: 657 lines.append('') 658 return lines 659 660 def _parse_examples_section(self, section: str) -> List[str]: 661 labels = { 662 'example': _('Example'), 663 'examples': _('Examples'), 664 } 665 use_admonition = self._config.napoleon_use_admonition_for_examples 666 label = labels.get(section.lower(), section) 667 return self._parse_generic_section(label, use_admonition) 668 669 def _parse_custom_generic_section(self, section: str) -> List[str]: 670 # for now, no admonition for simple custom sections 671 return self._parse_generic_section(section, False) 672 673 def _parse_custom_params_style_section(self, section: str) -> List[str]: 674 return self._format_fields(section, self._consume_fields()) 675 676 def _parse_custom_returns_style_section(self, section: str) -> List[str]: 677 fields = self._consume_returns_section(preprocess_types=True) 678 return self._format_fields(section, fields) 679 680 def _parse_usage_section(self, section: str) -> List[str]: 681 header = ['.. rubric:: Usage:', ''] 682 block = ['.. code-block:: python', ''] 683 lines = self._consume_usage_section() 684 lines = self._indent(lines, 3) 685 return header + block + lines + [''] 686 687 def _parse_generic_section(self, section: str, use_admonition: bool) -> List[str]: 688 lines = self._strip_empty(self._consume_to_next_section()) 689 lines = self._dedent(lines) 690 if use_admonition: 691 header = '.. admonition:: %s' % section 692 lines = self._indent(lines, 3) 693 else: 694 header = '.. rubric:: %s' % section 695 if lines: 696 return [header, ''] + lines + [''] 697 else: 698 return [header, ''] 699 700 def _parse_keyword_arguments_section(self, section: str) -> List[str]: 701 fields = self._consume_fields() 702 if self._config.napoleon_use_keyword: 703 return self._format_docutils_params( 704 fields, 705 field_role="keyword", 706 type_role="kwtype") 707 else: 708 return self._format_fields(_('Keyword Arguments'), fields) 709 710 def _parse_methods_section(self, section: str) -> List[str]: 711 lines = [] # type: List[str] 712 for _name, _type, _desc in self._consume_fields(parse_type=False): 713 lines.append('.. method:: %s' % _name) 714 if self._opt and 'noindex' in self._opt: 715 lines.append(' :noindex:') 716 if _desc: 717 lines.extend([''] + self._indent(_desc, 3)) 718 lines.append('') 719 return lines 720 721 def _parse_notes_section(self, section: str) -> List[str]: 722 use_admonition = self._config.napoleon_use_admonition_for_notes 723 return self._parse_generic_section(_('Notes'), use_admonition) 724 725 def _parse_other_parameters_section(self, section: str) -> List[str]: 726 return self._format_fields(_('Other Parameters'), self._consume_fields()) 727 728 def _parse_parameters_section(self, section: str) -> List[str]: 729 if self._config.napoleon_use_param: 730 # Allow to declare multiple parameters at once (ex: x, y: int) 731 fields = self._consume_fields(multiple=True) 732 return self._format_docutils_params(fields) 733 else: 734 fields = self._consume_fields() 735 return self._format_fields(_('Parameters'), fields) 736 737 def _parse_raises_section(self, section: str) -> List[str]: 738 fields = self._consume_fields(parse_type=False, prefer_type=True) 739 lines = [] # type: List[str] 740 for _name, _type, _desc in fields: 741 m = self._name_rgx.match(_type) 742 if m and m.group('name'): 743 _type = m.group('name') 744 elif _xref_regex.match(_type): 745 pos = _type.find('`') 746 _type = _type[pos + 1:-1] 747 _type = ' ' + _type if _type else '' 748 _desc = self._strip_empty(_desc) 749 _descs = ' ' + '\n '.join(_desc) if any(_desc) else '' 750 lines.append(':raises%s:%s' % (_type, _descs)) 751 if lines: 752 lines.append('') 753 return lines 754 755 def _parse_receives_section(self, section: str) -> List[str]: 756 if self._config.napoleon_use_param: 757 # Allow to declare multiple parameters at once (ex: x, y: int) 758 fields = self._consume_fields(multiple=True) 759 return self._format_docutils_params(fields) 760 else: 761 fields = self._consume_fields() 762 return self._format_fields(_('Receives'), fields) 763 764 def _parse_references_section(self, section: str) -> List[str]: 765 use_admonition = self._config.napoleon_use_admonition_for_references 766 return self._parse_generic_section(_('References'), use_admonition) 767 768 def _parse_returns_section(self, section: str) -> List[str]: 769 fields = self._consume_returns_section() 770 multi = len(fields) > 1 771 if multi: 772 use_rtype = False 773 else: 774 use_rtype = self._config.napoleon_use_rtype 775 776 lines = [] # type: List[str] 777 for _name, _type, _desc in fields: 778 if use_rtype: 779 field = self._format_field(_name, '', _desc) 780 else: 781 field = self._format_field(_name, _type, _desc) 782 783 if multi: 784 if lines: 785 lines.extend(self._format_block(' * ', field)) 786 else: 787 lines.extend(self._format_block(':returns: * ', field)) 788 else: 789 lines.extend(self._format_block(':returns: ', field)) 790 if _type and use_rtype: 791 lines.extend([':rtype: %s' % _type, '']) 792 if lines and lines[-1]: 793 lines.append('') 794 return lines 795 796 def _parse_see_also_section(self, section: str) -> List[str]: 797 return self._parse_admonition('seealso', section) 798 799 def _parse_warns_section(self, section: str) -> List[str]: 800 return self._format_fields(_('Warns'), self._consume_fields()) 801 802 def _parse_yields_section(self, section: str) -> List[str]: 803 fields = self._consume_returns_section(preprocess_types=True) 804 return self._format_fields(_('Yields'), fields) 805 806 def _partition_field_on_colon(self, line: str) -> Tuple[str, str, str]: 807 before_colon = [] 808 after_colon = [] 809 colon = '' 810 found_colon = False 811 for i, source in enumerate(_xref_or_code_regex.split(line)): 812 if found_colon: 813 after_colon.append(source) 814 else: 815 m = _single_colon_regex.search(source) 816 if (i % 2) == 0 and m: 817 found_colon = True 818 colon = source[m.start(): m.end()] 819 before_colon.append(source[:m.start()]) 820 after_colon.append(source[m.end():]) 821 else: 822 before_colon.append(source) 823 824 return ("".join(before_colon).strip(), 825 colon, 826 "".join(after_colon).strip()) 827 828 def _qualify_name(self, attr_name: str, klass: "Type") -> str: 829 if klass and '.' not in attr_name: 830 if attr_name.startswith('~'): 831 attr_name = attr_name[1:] 832 try: 833 q = klass.__qualname__ 834 except AttributeError: 835 q = klass.__name__ 836 return '~%s.%s' % (q, attr_name) 837 return attr_name 838 839 def _strip_empty(self, lines: List[str]) -> List[str]: 840 if lines: 841 start = -1 842 for i, line in enumerate(lines): 843 if line: 844 start = i 845 break 846 if start == -1: 847 lines = [] 848 end = -1 849 for i in reversed(range(len(lines))): 850 line = lines[i] 851 if line: 852 end = i 853 break 854 if start > 0 or end + 1 < len(lines): 855 lines = lines[start:end + 1] 856 return lines 857 858 def _lookup_annotation(self, _name: str) -> str: 859 if self._config.napoleon_attr_annotations: 860 if self._what in ("module", "class", "exception") and self._obj: 861 # cache the class annotations 862 if not hasattr(self, "_annotations"): 863 localns = getattr(self._config, "autodoc_type_aliases", {}) 864 localns.update(getattr( 865 self._config, "napoleon_type_aliases", {} 866 ) or {}) 867 self._annotations = get_type_hints(self._obj, None, localns) 868 if _name in self._annotations: 869 return stringify_annotation(self._annotations[_name]) 870 # No annotation found 871 return "" 872 873 874def _recombine_set_tokens(tokens: List[str]) -> List[str]: 875 token_queue = collections.deque(tokens) 876 keywords = ("optional", "default") 877 878 def takewhile_set(tokens): 879 open_braces = 0 880 previous_token = None 881 while True: 882 try: 883 token = tokens.popleft() 884 except IndexError: 885 break 886 887 if token == ", ": 888 previous_token = token 889 continue 890 891 if not token.strip(): 892 continue 893 894 if token in keywords: 895 tokens.appendleft(token) 896 if previous_token is not None: 897 tokens.appendleft(previous_token) 898 break 899 900 if previous_token is not None: 901 yield previous_token 902 previous_token = None 903 904 if token == "{": 905 open_braces += 1 906 elif token == "}": 907 open_braces -= 1 908 909 yield token 910 911 if open_braces == 0: 912 break 913 914 def combine_set(tokens): 915 while True: 916 try: 917 token = tokens.popleft() 918 except IndexError: 919 break 920 921 if token == "{": 922 tokens.appendleft("{") 923 yield "".join(takewhile_set(tokens)) 924 else: 925 yield token 926 927 return list(combine_set(token_queue)) 928 929 930def _tokenize_type_spec(spec: str) -> List[str]: 931 def postprocess(item): 932 if _default_regex.match(item): 933 default = item[:7] 934 # can't be separated by anything other than a single space 935 # for now 936 other = item[8:] 937 938 return [default, " ", other] 939 else: 940 return [item] 941 942 tokens = list( 943 item 944 for raw_token in _token_regex.split(spec) 945 for item in postprocess(raw_token) 946 if item 947 ) 948 return tokens 949 950 951def _token_type(token: str, location: str = None) -> str: 952 def is_numeric(token): 953 try: 954 # use complex to make sure every numeric value is detected as literal 955 complex(token) 956 except ValueError: 957 return False 958 else: 959 return True 960 961 if token.startswith(" ") or token.endswith(" "): 962 type_ = "delimiter" 963 elif ( 964 is_numeric(token) or 965 (token.startswith("{") and token.endswith("}")) or 966 (token.startswith('"') and token.endswith('"')) or 967 (token.startswith("'") and token.endswith("'")) 968 ): 969 type_ = "literal" 970 elif token.startswith("{"): 971 logger.warning( 972 __("invalid value set (missing closing brace): %s"), 973 token, 974 location=location, 975 ) 976 type_ = "literal" 977 elif token.endswith("}"): 978 logger.warning( 979 __("invalid value set (missing opening brace): %s"), 980 token, 981 location=location, 982 ) 983 type_ = "literal" 984 elif token.startswith("'") or token.startswith('"'): 985 logger.warning( 986 __("malformed string literal (missing closing quote): %s"), 987 token, 988 location=location, 989 ) 990 type_ = "literal" 991 elif token.endswith("'") or token.endswith('"'): 992 logger.warning( 993 __("malformed string literal (missing opening quote): %s"), 994 token, 995 location=location, 996 ) 997 type_ = "literal" 998 elif token in ("optional", "default"): 999 # default is not a official keyword (yet) but supported by the 1000 # reference implementation (numpydoc) and widely used 1001 type_ = "control" 1002 elif _xref_regex.match(token): 1003 type_ = "reference" 1004 else: 1005 type_ = "obj" 1006 1007 return type_ 1008 1009 1010def _convert_numpy_type_spec(_type: str, location: str = None, translations: dict = {}) -> str: 1011 def convert_obj(obj, translations, default_translation): 1012 translation = translations.get(obj, obj) 1013 1014 # use :class: (the default) only if obj is not a standard singleton 1015 if translation in _SINGLETONS and default_translation == ":class:`%s`": 1016 default_translation = ":obj:`%s`" 1017 elif translation == "..." and default_translation == ":class:`%s`": 1018 # allow referencing the builtin ... 1019 default_translation = ":obj:`%s <Ellipsis>`" 1020 1021 if _xref_regex.match(translation) is None: 1022 translation = default_translation % translation 1023 1024 return translation 1025 1026 tokens = _tokenize_type_spec(_type) 1027 combined_tokens = _recombine_set_tokens(tokens) 1028 types = [ 1029 (token, _token_type(token, location)) 1030 for token in combined_tokens 1031 ] 1032 1033 converters = { 1034 "literal": lambda x: "``%s``" % x, 1035 "obj": lambda x: convert_obj(x, translations, ":class:`%s`"), 1036 "control": lambda x: "*%s*" % x, 1037 "delimiter": lambda x: x, 1038 "reference": lambda x: x, 1039 } 1040 1041 converted = "".join(converters.get(type_)(token) for token, type_ in types) 1042 1043 return converted 1044 1045 1046class NumpyDocstring(GoogleDocstring): 1047 """Convert NumPy style docstrings to reStructuredText. 1048 1049 Parameters 1050 ---------- 1051 docstring : :obj:`str` or :obj:`list` of :obj:`str` 1052 The docstring to parse, given either as a string or split into 1053 individual lines. 1054 config: :obj:`sphinx.ext.napoleon.Config` or :obj:`sphinx.config.Config` 1055 The configuration settings to use. If not given, defaults to the 1056 config object on `app`; or if `app` is not given defaults to the 1057 a new :class:`sphinx.ext.napoleon.Config` object. 1058 1059 1060 Other Parameters 1061 ---------------- 1062 app : :class:`sphinx.application.Sphinx`, optional 1063 Application object representing the Sphinx process. 1064 what : :obj:`str`, optional 1065 A string specifying the type of the object to which the docstring 1066 belongs. Valid values: "module", "class", "exception", "function", 1067 "method", "attribute". 1068 name : :obj:`str`, optional 1069 The fully qualified name of the object. 1070 obj : module, class, exception, function, method, or attribute 1071 The object to which the docstring belongs. 1072 options : :class:`sphinx.ext.autodoc.Options`, optional 1073 The options given to the directive: an object with attributes 1074 inherited_members, undoc_members, show_inheritance and noindex that 1075 are True if the flag option of same name was given to the auto 1076 directive. 1077 1078 1079 Example 1080 ------- 1081 >>> from sphinx.ext.napoleon import Config 1082 >>> config = Config(napoleon_use_param=True, napoleon_use_rtype=True) 1083 >>> docstring = '''One line summary. 1084 ... 1085 ... Extended description. 1086 ... 1087 ... Parameters 1088 ... ---------- 1089 ... arg1 : int 1090 ... Description of `arg1` 1091 ... arg2 : str 1092 ... Description of `arg2` 1093 ... Returns 1094 ... ------- 1095 ... str 1096 ... Description of return value. 1097 ... ''' 1098 >>> print(NumpyDocstring(docstring, config)) 1099 One line summary. 1100 <BLANKLINE> 1101 Extended description. 1102 <BLANKLINE> 1103 :param arg1: Description of `arg1` 1104 :type arg1: int 1105 :param arg2: Description of `arg2` 1106 :type arg2: str 1107 <BLANKLINE> 1108 :returns: Description of return value. 1109 :rtype: str 1110 <BLANKLINE> 1111 1112 Methods 1113 ------- 1114 __str__() 1115 Return the parsed docstring in reStructuredText format. 1116 1117 Returns 1118 ------- 1119 str 1120 UTF-8 encoded version of the docstring. 1121 1122 __unicode__() 1123 Return the parsed docstring in reStructuredText format. 1124 1125 Returns 1126 ------- 1127 unicode 1128 Unicode version of the docstring. 1129 1130 lines() 1131 Return the parsed lines of the docstring in reStructuredText format. 1132 1133 Returns 1134 ------- 1135 list(str) 1136 The lines of the docstring in a list. 1137 1138 """ 1139 def __init__(self, docstring: Union[str, List[str]], config: SphinxConfig = None, 1140 app: Sphinx = None, what: str = '', name: str = '', 1141 obj: Any = None, options: Any = None) -> None: 1142 self._directive_sections = ['.. index::'] 1143 super().__init__(docstring, config, app, what, name, obj, options) 1144 1145 def _get_location(self) -> str: 1146 try: 1147 filepath = inspect.getfile(self._obj) if self._obj is not None else None 1148 except TypeError: 1149 filepath = None 1150 name = self._name 1151 1152 if filepath is None and name is None: 1153 return None 1154 elif filepath is None: 1155 filepath = "" 1156 1157 return ":".join([filepath, "docstring of %s" % name]) 1158 1159 def _escape_args_and_kwargs(self, name: str) -> str: 1160 func = super()._escape_args_and_kwargs 1161 1162 if ", " in name: 1163 return ", ".join(func(param) for param in name.split(", ")) 1164 else: 1165 return func(name) 1166 1167 def _consume_field(self, parse_type: bool = True, prefer_type: bool = False 1168 ) -> Tuple[str, str, List[str]]: 1169 line = next(self._line_iter) 1170 if parse_type: 1171 _name, _, _type = self._partition_field_on_colon(line) 1172 else: 1173 _name, _type = line, '' 1174 _name, _type = _name.strip(), _type.strip() 1175 _name = self._escape_args_and_kwargs(_name) 1176 1177 if parse_type and not _type: 1178 _type = self._lookup_annotation(_name) 1179 1180 if prefer_type and not _type: 1181 _type, _name = _name, _type 1182 1183 if self._config.napoleon_preprocess_types: 1184 _type = _convert_numpy_type_spec( 1185 _type, 1186 location=self._get_location(), 1187 translations=self._config.napoleon_type_aliases or {}, 1188 ) 1189 1190 indent = self._get_indent(line) + 1 1191 _desc = self._dedent(self._consume_indented_block(indent)) 1192 _desc = self.__class__(_desc, self._config).lines() 1193 return _name, _type, _desc 1194 1195 def _consume_returns_section(self, preprocess_types: bool = False 1196 ) -> List[Tuple[str, str, List[str]]]: 1197 return self._consume_fields(prefer_type=True) 1198 1199 def _consume_section_header(self) -> str: 1200 section = next(self._line_iter) 1201 if not _directive_regex.match(section): 1202 # Consume the header underline 1203 next(self._line_iter) 1204 return section 1205 1206 def _is_section_break(self) -> bool: 1207 line1, line2 = self._line_iter.peek(2) 1208 return (not self._line_iter.has_next() or 1209 self._is_section_header() or 1210 ['', ''] == [line1, line2] or 1211 (self._is_in_section and 1212 line1 and 1213 not self._is_indented(line1, self._section_indent))) 1214 1215 def _is_section_header(self) -> bool: 1216 section, underline = self._line_iter.peek(2) 1217 section = section.lower() 1218 if section in self._sections and isinstance(underline, str): 1219 return bool(_numpy_section_regex.match(underline)) 1220 elif self._directive_sections: 1221 if _directive_regex.match(section): 1222 for directive_section in self._directive_sections: 1223 if section.startswith(directive_section): 1224 return True 1225 return False 1226 1227 def _parse_see_also_section(self, section: str) -> List[str]: 1228 lines = self._consume_to_next_section() 1229 try: 1230 return self._parse_numpydoc_see_also_section(lines) 1231 except ValueError: 1232 return self._format_admonition('seealso', lines) 1233 1234 def _parse_numpydoc_see_also_section(self, content: List[str]) -> List[str]: 1235 """ 1236 Derived from the NumpyDoc implementation of _parse_see_also. 1237 1238 See Also 1239 -------- 1240 func_name : Descriptive text 1241 continued text 1242 another_func_name : Descriptive text 1243 func_name1, func_name2, :meth:`func_name`, func_name3 1244 1245 """ 1246 items = [] 1247 1248 def parse_item_name(text: str) -> Tuple[str, str]: 1249 """Match ':role:`name`' or 'name'""" 1250 m = self._name_rgx.match(text) 1251 if m: 1252 g = m.groups() 1253 if g[1] is None: 1254 return g[3], None 1255 else: 1256 return g[2], g[1] 1257 raise ValueError("%s is not a item name" % text) 1258 1259 def push_item(name: str, rest: List[str]) -> None: 1260 if not name: 1261 return 1262 name, role = parse_item_name(name) 1263 items.append((name, list(rest), role)) 1264 del rest[:] 1265 1266 def translate(func, description, role): 1267 translations = self._config.napoleon_type_aliases 1268 if role is not None or not translations: 1269 return func, description, role 1270 1271 translated = translations.get(func, func) 1272 match = self._name_rgx.match(translated) 1273 if not match: 1274 return translated, description, role 1275 1276 groups = match.groupdict() 1277 role = groups["role"] 1278 new_func = groups["name"] or groups["name2"] 1279 1280 return new_func, description, role 1281 1282 current_func = None 1283 rest = [] # type: List[str] 1284 1285 for line in content: 1286 if not line.strip(): 1287 continue 1288 1289 m = self._name_rgx.match(line) 1290 if m and line[m.end():].strip().startswith(':'): 1291 push_item(current_func, rest) 1292 current_func, line = line[:m.end()], line[m.end():] 1293 rest = [line.split(':', 1)[1].strip()] 1294 if not rest[0]: 1295 rest = [] 1296 elif not line.startswith(' '): 1297 push_item(current_func, rest) 1298 current_func = None 1299 if ',' in line: 1300 for func in line.split(','): 1301 if func.strip(): 1302 push_item(func, []) 1303 elif line.strip(): 1304 current_func = line 1305 elif current_func is not None: 1306 rest.append(line.strip()) 1307 push_item(current_func, rest) 1308 1309 if not items: 1310 return [] 1311 1312 # apply type aliases 1313 items = [ 1314 translate(func, description, role) 1315 for func, description, role in items 1316 ] 1317 1318 lines = [] # type: List[str] 1319 last_had_desc = True 1320 for name, desc, role in items: 1321 if role: 1322 link = ':%s:`%s`' % (role, name) 1323 else: 1324 link = ':obj:`%s`' % name 1325 if desc or last_had_desc: 1326 lines += [''] 1327 lines += [link] 1328 else: 1329 lines[-1] += ", %s" % link 1330 if desc: 1331 lines += self._indent([' '.join(desc)]) 1332 last_had_desc = True 1333 else: 1334 last_had_desc = False 1335 lines += [''] 1336 1337 return self._format_admonition('seealso', lines) 1338