diff options
Diffstat (limited to 'sphinx/ext/napoleon/docstring.py')
| -rw-r--r-- | sphinx/ext/napoleon/docstring.py | 254 |
1 files changed, 120 insertions, 134 deletions
diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index f8141278f..00a41afe8 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ sphinx.ext.napoleon.docstring ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -11,21 +10,16 @@ :license: BSD, see LICENSE for details. """ -import collections import inspect import re from functools import partial -from six import string_types, u -from six.moves import range - from sphinx.ext.napoleon.iterators import modify_iter from sphinx.locale import _ -from sphinx.util.pycompat import UnicodeMixin if False: # For type annotation - from typing import Any, Callable, Dict, List, Tuple, Union # NOQA + from typing import Any, Callable, Dict, List, Tuple, Type, Union # NOQA from sphinx.application import Sphinx # NOQA from sphinx.config import Config as SphinxConfig # NOQA @@ -43,7 +37,7 @@ _enumerated_list_regex = re.compile( r'(?(paren)\)|\.)(\s+\S|\s*$)') -class GoogleDocstring(UnicodeMixin): +class GoogleDocstring: """Convert Google style docstrings to reStructuredText. Parameters @@ -105,9 +99,13 @@ class GoogleDocstring(UnicodeMixin): <BLANKLINE> """ + + _name_rgx = re.compile(r"^\s*(:(?P<role>\w+):`(?P<name>[a-zA-Z0-9_.-]+)`|" + r" (?P<name2>[a-zA-Z0-9_.-]+))\s*", re.X) + def __init__(self, docstring, config=None, app=None, what='', name='', obj=None, options=None): - # type: (Union[unicode, List[unicode]], SphinxConfig, Sphinx, unicode, unicode, Any, Any) -> None # NOQA + # type: (Union[str, List[str]], SphinxConfig, Sphinx, str, str, Any, Any) -> None self._config = config self._app = app @@ -120,7 +118,7 @@ class GoogleDocstring(UnicodeMixin): what = 'class' elif inspect.ismodule(obj): what = 'module' - elif isinstance(obj, collections.Callable): # type: ignore + elif callable(obj): what = 'function' else: what = 'object' @@ -129,15 +127,16 @@ class GoogleDocstring(UnicodeMixin): self._name = name self._obj = obj self._opt = options - if isinstance(docstring, string_types): - docstring = docstring.splitlines() - self._lines = docstring - self._line_iter = modify_iter(docstring, modifier=lambda s: s.rstrip()) - self._parsed_lines = [] # type: List[unicode] + if isinstance(docstring, str): + lines = docstring.splitlines() + else: + lines = docstring + self._line_iter = modify_iter(lines, modifier=lambda s: s.rstrip()) + self._parsed_lines = [] # type: List[str] self._is_in_section = False self._section_indent = 0 if not hasattr(self, '_directive_sections'): - self._directive_sections = [] # type: List[unicode] + self._directive_sections = [] # type: List[str] if not hasattr(self, '_sections'): self._sections = { 'args': self._parse_parameters_section, @@ -170,14 +169,14 @@ class GoogleDocstring(UnicodeMixin): 'warns': self._parse_warns_section, 'yield': self._parse_yields_section, 'yields': self._parse_yields_section, - } # type: Dict[unicode, Callable] + } # type: Dict[str, Callable] self._load_custom_sections() self._parse() - def __unicode__(self): - # type: () -> unicode + def __str__(self): + # type: () -> str """Return the parsed docstring in reStructuredText format. Returns @@ -186,10 +185,10 @@ class GoogleDocstring(UnicodeMixin): Unicode version of the docstring. """ - return u('\n').join(self.lines()) + return '\n'.join(self.lines()) def lines(self): - # type: () -> List[unicode] + # type: () -> List[str] """Return the parsed lines of the docstring in reStructuredText format. Returns @@ -201,7 +200,7 @@ class GoogleDocstring(UnicodeMixin): return self._parsed_lines def _consume_indented_block(self, indent=1): - # type: (int) -> List[unicode] + # type: (int) -> List[str] lines = [] line = self._line_iter.peek() while(not self._is_section_break() and @@ -211,7 +210,7 @@ class GoogleDocstring(UnicodeMixin): return lines def _consume_contiguous(self): - # type: () -> List[unicode] + # type: () -> List[str] lines = [] while (self._line_iter.has_next() and self._line_iter.peek() and @@ -220,7 +219,7 @@ class GoogleDocstring(UnicodeMixin): return lines def _consume_empty(self): - # type: () -> List[unicode] + # type: () -> List[str] lines = [] line = self._line_iter.peek() while self._line_iter.has_next() and not line: @@ -229,11 +228,11 @@ class GoogleDocstring(UnicodeMixin): return lines def _consume_field(self, parse_type=True, prefer_type=False): - # type: (bool, bool) -> Tuple[unicode, unicode, List[unicode]] + # type: (bool, bool) -> Tuple[str, str, List[str]] line = next(self._line_iter) before, colon, after = self._partition_field_on_colon(line) - _name, _type, _desc = before, '', after # type: unicode, unicode, unicode + _name, _type, _desc = before, '', after if parse_type: match = _google_typed_arg_regex.match(before) @@ -251,7 +250,7 @@ class GoogleDocstring(UnicodeMixin): return _name, _type, _descs def _consume_fields(self, parse_type=True, prefer_type=False): - # type: (bool, bool) -> List[Tuple[unicode, unicode, List[unicode]]] + # type: (bool, bool) -> List[Tuple[str, str, List[str]]] self._consume_empty() fields = [] while not self._is_section_break(): @@ -261,21 +260,22 @@ class GoogleDocstring(UnicodeMixin): return fields def _consume_inline_attribute(self): - # type: () -> Tuple[unicode, List[unicode]] + # type: () -> Tuple[str, List[str]] line = next(self._line_iter) _type, colon, _desc = self._partition_field_on_colon(line) - if not colon: + if not colon or not _desc: _type, _desc = _desc, _type + _desc += colon _descs = [_desc] + self._dedent(self._consume_to_end()) _descs = self.__class__(_descs, self._config).lines() return _type, _descs def _consume_returns_section(self): - # type: () -> List[Tuple[unicode, unicode, List[unicode]]] + # type: () -> List[Tuple[str, str, List[str]]] lines = self._dedent(self._consume_to_next_section()) if lines: before, colon, after = self._partition_field_on_colon(lines[0]) - _name, _type, _desc = '', '', lines # type: unicode, unicode, List[unicode] + _name, _type, _desc = '', '', lines if colon: if after: @@ -291,12 +291,12 @@ class GoogleDocstring(UnicodeMixin): return [] def _consume_usage_section(self): - # type: () -> List[unicode] + # type: () -> List[str] lines = self._dedent(self._consume_to_next_section()) return lines def _consume_section_header(self): - # type: () -> unicode + # type: () -> str section = next(self._line_iter) stripped_section = section.strip(':') if stripped_section.lower() in self._sections: @@ -304,14 +304,14 @@ class GoogleDocstring(UnicodeMixin): return section def _consume_to_end(self): - # type: () -> List[unicode] + # type: () -> List[str] lines = [] while self._line_iter.has_next(): lines.append(next(self._line_iter)) return lines def _consume_to_next_section(self): - # type: () -> List[unicode] + # type: () -> List[str] self._consume_empty() lines = [] while not self._is_section_break(): @@ -319,7 +319,7 @@ class GoogleDocstring(UnicodeMixin): return lines + self._consume_empty() def _dedent(self, lines, full=False): - # type: (List[unicode], bool) -> List[unicode] + # type: (List[str], bool) -> List[str] if full: return [line.lstrip() for line in lines] else: @@ -327,7 +327,7 @@ class GoogleDocstring(UnicodeMixin): return [line[min_indent:] for line in lines] def _escape_args_and_kwargs(self, name): - # type: (unicode) -> unicode + # type: (str) -> str if name[:2] == '**': return r'\*\*' + name[2:] elif name[:1] == '*': @@ -336,32 +336,32 @@ class GoogleDocstring(UnicodeMixin): return name def _fix_field_desc(self, desc): - # type: (List[unicode]) -> List[unicode] + # type: (List[str]) -> List[str] if self._is_list(desc): - desc = [u''] + desc + desc = [''] + desc elif desc[0].endswith('::'): desc_block = desc[1:] indent = self._get_indent(desc[0]) block_indent = self._get_initial_indent(desc_block) if block_indent > indent: - desc = [u''] + desc + desc = [''] + desc else: desc = ['', desc[0]] + self._indent(desc_block, 4) return desc def _format_admonition(self, admonition, lines): - # type: (unicode, List[unicode]) -> List[unicode] + # type: (str, List[str]) -> List[str] lines = self._strip_empty(lines) if len(lines) == 1: return ['.. %s:: %s' % (admonition, lines[0].strip()), ''] elif lines: lines = self._indent(self._dedent(lines), 3) - return [u'.. %s::' % admonition, u''] + lines + [u''] + return ['.. %s::' % admonition, ''] + lines + [''] else: - return [u'.. %s::' % admonition, u''] + return ['.. %s::' % admonition, ''] def _format_block(self, prefix, lines, padding=None): - # type: (unicode, List[unicode], unicode) -> List[unicode] + # type: (str, List[str], str) -> List[str] if lines: if padding is None: padding = ' ' * len(prefix) @@ -379,7 +379,7 @@ class GoogleDocstring(UnicodeMixin): def _format_docutils_params(self, fields, field_role='param', type_role='type'): - # type: (List[Tuple[unicode, unicode, List[unicode]]], unicode, unicode) -> List[unicode] # NOQA + # type: (List[Tuple[str, str, List[str]]], str, str) -> List[str] lines = [] for _name, _type, _desc in fields: _desc = self._strip_empty(_desc) @@ -395,14 +395,14 @@ class GoogleDocstring(UnicodeMixin): return lines + [''] def _format_field(self, _name, _type, _desc): - # type: (unicode, unicode, List[unicode]) -> List[unicode] + # type: (str, str, List[str]) -> List[str] _desc = self._strip_empty(_desc) has_desc = any(_desc) separator = has_desc and ' -- ' or '' if _name: if _type: if '`' in _type: - field = '**%s** (%s)%s' % (_name, _type, separator) # type: unicode + field = '**%s** (%s)%s' % (_name, _type, separator) else: field = '**%s** (*%s*)%s' % (_name, _type, separator) else: @@ -425,11 +425,11 @@ class GoogleDocstring(UnicodeMixin): return [field] def _format_fields(self, field_type, fields): - # type: (unicode, List[Tuple[unicode, unicode, List[unicode]]]) -> List[unicode] + # type: (str, List[Tuple[str, str, List[str]]]) -> List[str] field_type = ':%s:' % field_type.strip() padding = ' ' * len(field_type) multi = len(fields) > 1 - lines = [] # type: List[unicode] + lines = [] # type: List[str] for _name, _type, _desc in fields: field = self._format_field(_name, _type, _desc) if multi: @@ -454,21 +454,21 @@ class GoogleDocstring(UnicodeMixin): return 0 def _get_indent(self, line): - # type: (unicode) -> int + # type: (str) -> int for i, s in enumerate(line): if not s.isspace(): return i return len(line) def _get_initial_indent(self, lines): - # type: (List[unicode]) -> int + # type: (List[str]) -> int for line in lines: if line: return self._get_indent(line) return 0 def _get_min_indent(self, lines): - # type: (List[unicode]) -> int + # type: (List[str]) -> int min_indent = None for line in lines: if line: @@ -480,11 +480,11 @@ class GoogleDocstring(UnicodeMixin): return min_indent or 0 def _indent(self, lines, n=4): - # type: (List[unicode], int) -> List[unicode] + # type: (List[str], int) -> List[str] return [(' ' * n) + line for line in lines] def _is_indented(self, line, indent=1): - # type: (unicode, int) -> bool + # type: (str, int) -> bool for i, s in enumerate(line): if i >= indent: return True @@ -493,7 +493,7 @@ class GoogleDocstring(UnicodeMixin): return False def _is_list(self, lines): - # type: (List[unicode]) -> bool + # type: (List[str]) -> bool if not lines: return False if _bullet_list_regex.match(lines[0]): @@ -539,7 +539,7 @@ class GoogleDocstring(UnicodeMixin): if self._config.napoleon_custom_sections is not None: for entry in self._config.napoleon_custom_sections: - if isinstance(entry, string_types): + if isinstance(entry, str): # if entry is just a label, add to sections list, # using generic section logic. self._sections[entry.lower()] = self._parse_custom_generic_section @@ -558,7 +558,7 @@ class GoogleDocstring(UnicodeMixin): if self._name and (self._what == 'attribute' or self._what == 'data'): # Implicit stop using StopIteration no longer allowed in # Python 3.7; see PEP 479 - res = [] # type: List[unicode] + res = [] # type: List[str] try: res = self._parse_attribute_docstring() except StopIteration: @@ -587,12 +587,12 @@ class GoogleDocstring(UnicodeMixin): self._parsed_lines.extend(lines) def _parse_admonition(self, admonition, section): - # type (unicode, unicode) -> List[unicode] + # type (str, str) -> List[str] lines = self._consume_to_next_section() return self._format_admonition(admonition, lines) def _parse_attribute_docstring(self): - # type: () -> List[unicode] + # type: () -> List[str] _type, _desc = self._consume_inline_attribute() lines = self._format_field('', '', _desc) if _type: @@ -600,11 +600,12 @@ class GoogleDocstring(UnicodeMixin): return lines def _parse_attributes_section(self, section): - # type: (unicode) -> List[unicode] + # type: (str) -> List[str] lines = [] for _name, _type, _desc in self._consume_fields(): if self._config.napoleon_use_ivar: - field = ':ivar %s: ' % _name # type: unicode + _name = self._qualify_name(_name, self._obj) + field = ':ivar %s: ' % _name lines.extend(self._format_block(field, _desc)) if _type: lines.append(':vartype %s: %s' % (_name, _type)) @@ -621,11 +622,11 @@ class GoogleDocstring(UnicodeMixin): return lines def _parse_examples_section(self, section): - # type: (unicode) -> List[unicode] + # type: (str) -> List[str] labels = { 'example': _('Example'), 'examples': _('Examples'), - } # type: Dict[unicode, unicode] + } use_admonition = self._config.napoleon_use_admonition_for_examples label = labels.get(section.lower(), section) return self._parse_generic_section(label, use_admonition) @@ -635,19 +636,19 @@ class GoogleDocstring(UnicodeMixin): return self._parse_generic_section(section, False) def _parse_usage_section(self, section): - # type: (unicode) -> List[unicode] - header = ['.. rubric:: Usage:', ''] # type: List[unicode] - block = ['.. code-block:: python', ''] # type: List[unicode] + # type: (str) -> List[str] + header = ['.. rubric:: Usage:', ''] + block = ['.. code-block:: python', ''] lines = self._consume_usage_section() lines = self._indent(lines, 3) return header + block + lines + [''] def _parse_generic_section(self, section, use_admonition): - # type: (unicode, bool) -> List[unicode] + # type: (str, bool) -> List[str] lines = self._strip_empty(self._consume_to_next_section()) lines = self._dedent(lines) if use_admonition: - header = '.. admonition:: %s' % section # type: unicode + header = '.. admonition:: %s' % section lines = self._indent(lines, 3) else: header = '.. rubric:: %s' % section @@ -657,7 +658,7 @@ class GoogleDocstring(UnicodeMixin): return [header, ''] def _parse_keyword_arguments_section(self, section): - # type: (unicode) -> List[unicode] + # type: (str) -> List[str] fields = self._consume_fields() if self._config.napoleon_use_keyword: return self._format_docutils_params( @@ -668,26 +669,26 @@ class GoogleDocstring(UnicodeMixin): return self._format_fields(_('Keyword Arguments'), fields) def _parse_methods_section(self, section): - # type: (unicode) -> List[unicode] - lines = [] # type: List[unicode] + # type: (str) -> List[str] + lines = [] # type: List[str] for _name, _type, _desc in self._consume_fields(parse_type=False): lines.append('.. method:: %s' % _name) if _desc: - lines.extend([u''] + self._indent(_desc, 3)) + lines.extend([''] + self._indent(_desc, 3)) lines.append('') return lines def _parse_notes_section(self, section): - # type: (unicode) -> List[unicode] + # type: (str) -> List[str] use_admonition = self._config.napoleon_use_admonition_for_notes return self._parse_generic_section(_('Notes'), use_admonition) def _parse_other_parameters_section(self, section): - # type: (unicode) -> List[unicode] + # type: (str) -> List[str] return self._format_fields(_('Other Parameters'), self._consume_fields()) def _parse_parameters_section(self, section): - # type: (unicode) -> List[unicode] + # type: (str) -> List[str] fields = self._consume_fields() if self._config.napoleon_use_param: return self._format_docutils_params(fields) @@ -695,51 +696,28 @@ class GoogleDocstring(UnicodeMixin): return self._format_fields(_('Parameters'), fields) def _parse_raises_section(self, section): - # type: (unicode) -> List[unicode] + # type: (str) -> List[str] fields = self._consume_fields(parse_type=False, prefer_type=True) - field_type = ':raises:' - padding = ' ' * len(field_type) - multi = len(fields) > 1 - lines = [] # type: List[unicode] + lines = [] # type: List[str] for _name, _type, _desc in fields: + m = self._name_rgx.match(_type).groupdict() + if m['role']: + _type = m['name'] + _type = ' ' + _type if _type else '' _desc = self._strip_empty(_desc) - has_desc = any(_desc) - separator = has_desc and ' -- ' or '' - if _type: - has_refs = '`' in _type or ':' in _type - has_space = any(c in ' \t\n\v\f ' for c in _type) - - if not has_refs and not has_space: - _type = ':exc:`%s`%s' % (_type, separator) - elif has_desc and has_space: - _type = '*%s*%s' % (_type, separator) - else: - _type = '%s%s' % (_type, separator) - - if has_desc: - field = [_type + _desc[0]] + _desc[1:] - else: - field = [_type] - else: - field = _desc - if multi: - if lines: - lines.extend(self._format_block(padding + ' * ', field)) - else: - lines.extend(self._format_block(field_type + ' * ', field)) - else: - lines.extend(self._format_block(field_type + ' ', field)) - if lines and lines[-1]: + _descs = ' ' + '\n '.join(_desc) if any(_desc) else '' + lines.append(':raises%s:%s' % (_type, _descs)) + if lines: lines.append('') return lines def _parse_references_section(self, section): - # type: (unicode) -> List[unicode] + # type: (str) -> List[str] use_admonition = self._config.napoleon_use_admonition_for_references return self._parse_generic_section(_('References'), use_admonition) def _parse_returns_section(self, section): - # type: (unicode) -> List[unicode] + # type: (str) -> List[str] fields = self._consume_returns_section() multi = len(fields) > 1 if multi: @@ -747,7 +725,7 @@ class GoogleDocstring(UnicodeMixin): else: use_rtype = self._config.napoleon_use_rtype - lines = [] # type: List[unicode] + lines = [] # type: List[str] for _name, _type, _desc in fields: if use_rtype: field = self._format_field(_name, '', _desc) @@ -768,23 +746,23 @@ class GoogleDocstring(UnicodeMixin): return lines def _parse_see_also_section(self, section): - # type (unicode) -> List[unicode] + # type (str) -> List[str] return self._parse_admonition('seealso', section) def _parse_warns_section(self, section): - # type: (unicode) -> List[unicode] + # type: (str) -> List[str] return self._format_fields(_('Warns'), self._consume_fields()) def _parse_yields_section(self, section): - # type: (unicode) -> List[unicode] + # type: (str) -> List[str] fields = self._consume_returns_section() return self._format_fields(_('Yields'), fields) def _partition_field_on_colon(self, line): - # type: (unicode) -> Tuple[unicode, unicode, unicode] + # type: (str) -> Tuple[str, str, str] before_colon = [] after_colon = [] - colon = '' # type: unicode + colon = '' found_colon = False for i, source in enumerate(_xref_regex.split(line)): if found_colon: @@ -803,8 +781,20 @@ class GoogleDocstring(UnicodeMixin): colon, "".join(after_colon).strip()) + def _qualify_name(self, attr_name, klass): + # type: (str, Type) -> str + if klass and '.' not in attr_name: + if attr_name.startswith('~'): + attr_name = attr_name[1:] + try: + q = klass.__qualname__ + except AttributeError: + q = klass.__name__ + return '~%s.%s' % (q, attr_name) + return attr_name + def _strip_empty(self, lines): - # type: (List[unicode]) -> List[unicode] + # type: (List[str]) -> List[str] if lines: start = -1 for i, line in enumerate(lines): @@ -919,13 +909,12 @@ class NumpyDocstring(GoogleDocstring): """ def __init__(self, docstring, config=None, app=None, what='', name='', obj=None, options=None): - # type: (Union[unicode, List[unicode]], SphinxConfig, Sphinx, unicode, unicode, Any, Any) -> None # NOQA + # type: (Union[str, List[str]], SphinxConfig, Sphinx, str, str, Any, Any) -> None self._directive_sections = ['.. index::'] - super(NumpyDocstring, self).__init__(docstring, config, app, what, - name, obj, options) + super().__init__(docstring, config, app, what, name, obj, options) def _consume_field(self, parse_type=True, prefer_type=False): - # type: (bool, bool) -> Tuple[unicode, unicode, List[unicode]] + # type: (bool, bool) -> Tuple[str, str, List[str]] line = next(self._line_iter) if parse_type: _name, _, _type = self._partition_field_on_colon(line) @@ -942,11 +931,11 @@ class NumpyDocstring(GoogleDocstring): return _name, _type, _desc def _consume_returns_section(self): - # type: () -> List[Tuple[unicode, unicode, List[unicode]]] + # type: () -> List[Tuple[str, str, List[str]]] return self._consume_fields(prefer_type=True) def _consume_section_header(self): - # type: () -> unicode + # type: () -> str section = next(self._line_iter) if not _directive_regex.match(section): # Consume the header underline @@ -967,7 +956,7 @@ class NumpyDocstring(GoogleDocstring): # type: () -> bool section, underline = self._line_iter.peek(2) section = section.lower() - if section in self._sections and isinstance(underline, string_types): + if section in self._sections and isinstance(underline, str): return bool(_numpy_section_regex.match(underline)) elif self._directive_sections: if _directive_regex.match(section): @@ -976,11 +965,8 @@ class NumpyDocstring(GoogleDocstring): return True return False - _name_rgx = re.compile(r"^\s*(:(?P<role>\w+):`(?P<name>[a-zA-Z0-9_.-]+)`|" - r" (?P<name2>[a-zA-Z0-9_.-]+))\s*", re.X) - def _parse_see_also_section(self, section): - # type: (unicode) -> List[unicode] + # type: (str) -> List[str] lines = self._consume_to_next_section() try: return self._parse_numpydoc_see_also_section(lines) @@ -988,7 +974,7 @@ class NumpyDocstring(GoogleDocstring): return self._format_admonition('seealso', lines) def _parse_numpydoc_see_also_section(self, content): - # type: (List[unicode]) -> List[unicode] + # type: (List[str]) -> List[str] """ Derived from the NumpyDoc implementation of _parse_see_also. @@ -1003,7 +989,7 @@ class NumpyDocstring(GoogleDocstring): items = [] def parse_item_name(text): - # type: (unicode) -> Tuple[unicode, unicode] + # type: (str) -> Tuple[str, str] """Match ':role:`name`' or 'name'""" m = self._name_rgx.match(text) if m: @@ -1015,7 +1001,7 @@ class NumpyDocstring(GoogleDocstring): raise ValueError("%s is not a item name" % text) def push_item(name, rest): - # type: (unicode, List[unicode]) -> None + # type: (str, List[str]) -> None if not name: return name, role = parse_item_name(name) @@ -1023,7 +1009,7 @@ class NumpyDocstring(GoogleDocstring): del rest[:] current_func = None - rest = [] # type: List[unicode] + rest = [] # type: List[str] for line in content: if not line.strip(): @@ -1069,12 +1055,12 @@ class NumpyDocstring(GoogleDocstring): 'const': 'const', 'attribute': 'attr', 'attr': 'attr' - } # type: Dict[unicode, unicode] + } if self._what is None: - func_role = 'obj' # type: unicode + func_role = 'obj' else: func_role = roles.get(self._what, '') - lines = [] # type: List[unicode] + lines = [] # type: List[str] last_had_desc = True for func, desc, role in items: if role: |
