diff options
Diffstat (limited to 'sphinx/ext/napoleon/docstring.py')
| -rw-r--r-- | sphinx/ext/napoleon/docstring.py | 169 |
1 files changed, 118 insertions, 51 deletions
diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index e887e0246..c77598ef1 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -21,6 +21,12 @@ from six.moves import range from sphinx.ext.napoleon.iterators import modify_iter from sphinx.util.pycompat import UnicodeMixin +if False: + # For type annotation + from typing import Any, Callable, Dict, List, Tuple, Union # NOQA + from sphinx.application import Sphinx # NOQA + from sphinx.config import Config as SphinxConfig # NOQA + _directive_regex = re.compile(r'\.\. \S+::') _google_section_regex = re.compile(r'^(\s|\w)+:\s*$') @@ -99,19 +105,20 @@ class GoogleDocstring(UnicodeMixin): """ 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 self._config = config self._app = app if not self._config: from sphinx.ext.napoleon import Config - self._config = self._app and self._app.config or Config() + self._config = self._app and self._app.config or Config() # type: ignore if not what: if inspect.isclass(obj): what = 'class' elif inspect.ismodule(obj): what = 'module' - elif isinstance(obj, collections.Callable): + elif isinstance(obj, collections.Callable): # type: ignore what = 'function' else: what = 'object' @@ -124,11 +131,11 @@ class GoogleDocstring(UnicodeMixin): docstring = docstring.splitlines() self._lines = docstring self._line_iter = modify_iter(docstring, modifier=lambda s: s.rstrip()) - self._parsed_lines = [] + self._parsed_lines = [] # type: List[unicode] self._is_in_section = False self._section_indent = 0 if not hasattr(self, '_directive_sections'): - self._directive_sections = [] + self._directive_sections = [] # type: List[unicode] if not hasattr(self, '_sections'): self._sections = { 'args': self._parse_parameters_section, @@ -154,10 +161,11 @@ class GoogleDocstring(UnicodeMixin): 'warns': self._parse_warns_section, 'yield': self._parse_yields_section, 'yields': self._parse_yields_section, - } + } # type: Dict[unicode, Callable] self._parse() def __unicode__(self): + # type: () -> unicode """Return the parsed docstring in reStructuredText format. Returns @@ -169,6 +177,7 @@ class GoogleDocstring(UnicodeMixin): return u('\n').join(self.lines()) def lines(self): + # type: () -> List[unicode] """Return the parsed lines of the docstring in reStructuredText format. Returns @@ -180,38 +189,42 @@ class GoogleDocstring(UnicodeMixin): return self._parsed_lines def _consume_indented_block(self, indent=1): + # type: (int) -> List[unicode] lines = [] line = self._line_iter.peek() while(not self._is_section_break() and (not line or self._is_indented(line, indent))): - lines.append(next(self._line_iter)) + lines.append(next(self._line_iter)) # type: ignore line = self._line_iter.peek() return lines def _consume_contiguous(self): + # type: () -> List[unicode] lines = [] while (self._line_iter.has_next() and self._line_iter.peek() and not self._is_section_header()): - lines.append(next(self._line_iter)) + lines.append(next(self._line_iter)) # type: ignore return lines def _consume_empty(self): + # type: () -> List[unicode] lines = [] line = self._line_iter.peek() while self._line_iter.has_next() and not line: - lines.append(next(self._line_iter)) + lines.append(next(self._line_iter)) # type: ignore line = self._line_iter.peek() return lines def _consume_field(self, parse_type=True, prefer_type=False): - line = next(self._line_iter) + # type: (bool, bool) -> Tuple[unicode, unicode, List[unicode]] + line = next(self._line_iter) # type: ignore before, colon, after = self._partition_field_on_colon(line) - _name, _type, _desc = before, '', after + _name, _type, _desc = before, '', after # type: unicode, unicode, unicode if parse_type: - match = _google_typed_arg_regex.match(before) + match = _google_typed_arg_regex.match(before) # type: ignore if match: _name = match.group(1) _type = match.group(2) @@ -221,11 +234,12 @@ class GoogleDocstring(UnicodeMixin): if prefer_type and not _type: _type, _name = _name, _type indent = self._get_indent(line) + 1 - _desc = [_desc] + self._dedent(self._consume_indented_block(indent)) - _desc = self.__class__(_desc, self._config).lines() - return _name, _type, _desc + _descs = [_desc] + self._dedent(self._consume_indented_block(indent)) + _descs = self.__class__(_descs, self._config).lines() + return _name, _type, _descs def _consume_fields(self, parse_type=True, prefer_type=False): + # type: (bool, bool) -> List[Tuple[unicode, unicode, List[unicode]]] self._consume_empty() fields = [] while not self._is_section_break(): @@ -235,19 +249,21 @@ class GoogleDocstring(UnicodeMixin): return fields def _consume_inline_attribute(self): - line = next(self._line_iter) + # type: () -> Tuple[unicode, List[unicode]] + line = next(self._line_iter) # type: ignore _type, colon, _desc = self._partition_field_on_colon(line) if not colon: _type, _desc = _desc, _type - _desc = [_desc] + self._dedent(self._consume_to_end()) - _desc = self.__class__(_desc, self._config).lines() - return _type, _desc + _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]]] lines = self._dedent(self._consume_to_next_section()) if lines: before, colon, after = self._partition_field_on_colon(lines[0]) - _name, _type, _desc = '', '', lines + _name, _type, _desc = '', '', lines # type: unicode, unicode, List[unicode] if colon: if after: @@ -263,30 +279,35 @@ class GoogleDocstring(UnicodeMixin): return [] def _consume_usage_section(self): + # type: () -> List[unicode] lines = self._dedent(self._consume_to_next_section()) return lines def _consume_section_header(self): - section = next(self._line_iter) + # type: () -> unicode + section = next(self._line_iter) # type: ignore stripped_section = section.strip(':') if stripped_section.lower() in self._sections: section = stripped_section return section def _consume_to_end(self): + # type: () -> List[unicode] lines = [] while self._line_iter.has_next(): - lines.append(next(self._line_iter)) + lines.append(next(self._line_iter)) # type: ignore return lines def _consume_to_next_section(self): + # type: () -> List[unicode] self._consume_empty() lines = [] while not self._is_section_break(): - lines.append(next(self._line_iter)) + lines.append(next(self._line_iter)) # type: ignore return lines + self._consume_empty() def _dedent(self, lines, full=False): + # type: (List[unicode], bool) -> List[unicode] if full: return [line.lstrip() for line in lines] else: @@ -294,6 +315,7 @@ class GoogleDocstring(UnicodeMixin): return [line[min_indent:] for line in lines] def _escape_args_and_kwargs(self, name): + # type: (unicode) -> unicode if name[:2] == '**': return r'\*\*' + name[2:] elif name[:1] == '*': @@ -302,29 +324,32 @@ class GoogleDocstring(UnicodeMixin): return name def _fix_field_desc(self, desc): + # type: (List[unicode]) -> List[unicode] if self._is_list(desc): - desc = [''] + desc + desc = [u''] + 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 = [''] + desc + desc = [u''] + desc else: desc = ['', desc[0]] + self._indent(desc_block, 4) return desc def _format_admonition(self, admonition, lines): + # type: (unicode, List[unicode]) -> List[unicode] 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 ['.. %s::' % admonition, ''] + lines + [''] + return [u'.. %s::' % admonition, u''] + lines + [u''] else: - return ['.. %s::' % admonition, ''] + return [u'.. %s::' % admonition, u''] def _format_block(self, prefix, lines, padding=None): + # type: (unicode, List[unicode], unicode) -> List[unicode] if lines: if padding is None: padding = ' ' * len(prefix) @@ -342,6 +367,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 lines = [] for _name, _type, _desc in fields: _desc = self._strip_empty(_desc) @@ -357,13 +383,14 @@ class GoogleDocstring(UnicodeMixin): return lines + [''] def _format_field(self, _name, _type, _desc): + # type: (unicode, unicode, List[unicode]) -> List[unicode] _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) + field = '**%s** (%s)%s' % (_name, _type, separator) # type: unicode else: field = '**%s** (*%s*)%s' % (_name, _type, separator) else: @@ -386,10 +413,11 @@ class GoogleDocstring(UnicodeMixin): return [field] def _format_fields(self, field_type, fields): + # type: (unicode, List[Tuple[unicode, unicode, List[unicode]]]) -> List[unicode] field_type = ':%s:' % field_type.strip() padding = ' ' * len(field_type) multi = len(fields) > 1 - lines = [] + lines = [] # type: List[unicode] for _name, _type, _desc in fields: field = self._format_field(_name, _type, _desc) if multi: @@ -404,6 +432,7 @@ class GoogleDocstring(UnicodeMixin): return lines def _get_current_indent(self, peek_ahead=0): + # type: (int) -> int line = self._line_iter.peek(peek_ahead + 1)[peek_ahead] while line != self._line_iter.sentinel: if line: @@ -413,18 +442,21 @@ class GoogleDocstring(UnicodeMixin): return 0 def _get_indent(self, line): + # type: (unicode) -> 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 for line in lines: if line: return self._get_indent(line) return 0 def _get_min_indent(self, lines): + # type: (List[unicode]) -> int min_indent = None for line in lines: if line: @@ -436,9 +468,11 @@ class GoogleDocstring(UnicodeMixin): return min_indent or 0 def _indent(self, lines, n=4): + # type: (List[unicode], int) -> List[unicode] return [(' ' * n) + line for line in lines] def _is_indented(self, line, indent=1): + # type: (unicode, int) -> bool for i, s in enumerate(line): if i >= indent: return True @@ -447,11 +481,12 @@ class GoogleDocstring(UnicodeMixin): return False def _is_list(self, lines): + # type: (List[unicode]) -> bool if not lines: return False - if _bullet_list_regex.match(lines[0]): + if _bullet_list_regex.match(lines[0]): # type: ignore return True - if _enumerated_list_regex.match(lines[0]): + if _enumerated_list_regex.match(lines[0]): # type: ignore return True if len(lines) < 2 or lines[0].endswith('::'): return False @@ -464,6 +499,7 @@ class GoogleDocstring(UnicodeMixin): return next_indent > indent def _is_section_header(self): + # type: () -> bool section = self._line_iter.peek().lower() match = _google_section_regex.match(section) if match and section.strip(':') in self._sections: @@ -478,6 +514,7 @@ class GoogleDocstring(UnicodeMixin): return False def _is_section_break(self): + # type: () -> bool line = self._line_iter.peek() return (not self._line_iter.has_next() or self._is_section_header() or @@ -486,6 +523,7 @@ class GoogleDocstring(UnicodeMixin): not self._is_indented(line, self._section_indent))) def _parse(self): + # type: () -> None self._parsed_lines = self._consume_empty() if self._name and (self._what == 'attribute' or self._what == 'data'): @@ -498,7 +536,7 @@ class GoogleDocstring(UnicodeMixin): section = self._consume_section_header() self._is_in_section = True self._section_indent = self._get_current_indent() - if _directive_regex.match(section): + if _directive_regex.match(section): # type: ignore lines = [section] + self._consume_to_next_section() else: lines = self._sections[section.lower()](section) @@ -513,42 +551,47 @@ class GoogleDocstring(UnicodeMixin): self._parsed_lines.extend(lines) def _parse_attribute_docstring(self): + # type: () -> List[unicode] _type, _desc = self._consume_inline_attribute() return self._format_field('', _type, _desc) def _parse_attributes_section(self, section): + # type: (unicode) -> List[unicode] lines = [] for _name, _type, _desc in self._consume_fields(): if self._config.napoleon_use_ivar: - field = ':ivar %s: ' % _name + field = ':ivar %s: ' % _name # type: unicode lines.extend(self._format_block(field, _desc)) if _type: lines.append(':vartype %s: %s' % (_name, _type)) else: lines.extend(['.. attribute:: ' + _name, '']) - field = self._format_field('', _type, _desc) - lines.extend(self._indent(field, 3)) + fields = self._format_field('', _type, _desc) + lines.extend(self._indent(fields, 3)) lines.append('') if self._config.napoleon_use_ivar: lines.append('') return lines def _parse_examples_section(self, section): + # type: (unicode) -> List[unicode] use_admonition = self._config.napoleon_use_admonition_for_examples return self._parse_generic_section(section, use_admonition) def _parse_usage_section(self, section): - header = ['.. rubric:: Usage:', ''] - block = ['.. code-block:: python', ''] + # type: (unicode) -> List[unicode] + header = ['.. rubric:: Usage:', ''] # type: List[unicode] + block = ['.. code-block:: python', ''] # type: List[unicode] 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] lines = self._strip_empty(self._consume_to_next_section()) lines = self._dedent(lines) if use_admonition: - header = '.. admonition:: %s' % section + header = '.. admonition:: %s' % section # type: unicode lines = self._indent(lines, 3) else: header = '.. rubric:: %s' % section @@ -558,6 +601,7 @@ class GoogleDocstring(UnicodeMixin): return [header, ''] def _parse_keyword_arguments_section(self, section): + # type: (unicode) -> List[unicode] fields = self._consume_fields() if self._config.napoleon_use_keyword: return self._format_docutils_params( @@ -568,26 +612,31 @@ class GoogleDocstring(UnicodeMixin): return self._format_fields('Keyword Arguments', fields) def _parse_methods_section(self, section): - lines = [] + # type: (unicode) -> List[unicode] + lines = [] # type: List[unicode] for _name, _, _desc in self._consume_fields(parse_type=False): lines.append('.. method:: %s' % _name) if _desc: - lines.extend([''] + self._indent(_desc, 3)) + lines.extend([u''] + self._indent(_desc, 3)) lines.append('') return lines def _parse_note_section(self, section): + # type: (unicode) -> List[unicode] lines = self._consume_to_next_section() return self._format_admonition('note', lines) def _parse_notes_section(self, section): + # type: (unicode) -> List[unicode] 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] return self._format_fields('Other Parameters', self._consume_fields()) def _parse_parameters_section(self, section): + # type: (unicode) -> List[unicode] fields = self._consume_fields() if self._config.napoleon_use_param: return self._format_docutils_params(fields) @@ -595,11 +644,12 @@ class GoogleDocstring(UnicodeMixin): return self._format_fields('Parameters', fields) def _parse_raises_section(self, section): + # type: (unicode) -> List[unicode] fields = self._consume_fields(parse_type=False, prefer_type=True) field_type = ':raises:' padding = ' ' * len(field_type) multi = len(fields) > 1 - lines = [] + lines = [] # type: List[unicode] for _, _type, _desc in fields: _desc = self._strip_empty(_desc) has_desc = any(_desc) @@ -633,10 +683,12 @@ class GoogleDocstring(UnicodeMixin): return lines def _parse_references_section(self, section): + # type: (unicode) -> List[unicode] 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] fields = self._consume_returns_section() multi = len(fields) > 1 if multi: @@ -644,7 +696,7 @@ class GoogleDocstring(UnicodeMixin): else: use_rtype = self._config.napoleon_use_rtype - lines = [] + lines = [] # type: List[unicode] for _name, _type, _desc in fields: if use_rtype: field = self._format_field(_name, '', _desc) @@ -665,30 +717,36 @@ class GoogleDocstring(UnicodeMixin): return lines def _parse_see_also_section(self, section): + # type: (unicode) -> List[unicode] lines = self._consume_to_next_section() return self._format_admonition('seealso', lines) def _parse_todo_section(self, section): + # type: (unicode) -> List[unicode] lines = self._consume_to_next_section() return self._format_admonition('todo', lines) def _parse_warning_section(self, section): + # type: (unicode) -> List[unicode] lines = self._consume_to_next_section() return self._format_admonition('warning', lines) def _parse_warns_section(self, section): + # type: (unicode) -> List[unicode] return self._format_fields('Warns', self._consume_fields()) def _parse_yields_section(self, section): + # type: (unicode) -> List[unicode] fields = self._consume_returns_section() return self._format_fields('Yields', fields) def _partition_field_on_colon(self, line): + # type: (unicode) -> Tuple[unicode, unicode, unicode] before_colon = [] after_colon = [] colon = '' found_colon = False - for i, source in enumerate(_xref_regex.split(line)): + for i, source in enumerate(_xref_regex.split(line)): # type: ignore if found_colon: after_colon.append(source) else: @@ -706,6 +764,7 @@ class GoogleDocstring(UnicodeMixin): "".join(after_colon).strip()) def _strip_empty(self, lines): + # type: (List[unicode]) -> List[unicode] if lines: start = -1 for i, line in enumerate(lines): @@ -820,12 +879,14 @@ 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 self._directive_sections = ['.. index::'] super(NumpyDocstring, self).__init__(docstring, config, app, what, name, obj, options) def _consume_field(self, parse_type=True, prefer_type=False): - line = next(self._line_iter) + # type: (bool, bool) -> Tuple[unicode, unicode, List[unicode]] + line = next(self._line_iter) # type: ignore if parse_type: _name, _, _type = self._partition_field_on_colon(line) else: @@ -841,16 +902,19 @@ class NumpyDocstring(GoogleDocstring): return _name, _type, _desc def _consume_returns_section(self): + # type: () -> List[Tuple[unicode, unicode, List[unicode]]] return self._consume_fields(prefer_type=True) def _consume_section_header(self): - section = next(self._line_iter) + # type: () -> unicode + section = next(self._line_iter) # type: ignore if not _directive_regex.match(section): # Consume the header underline - next(self._line_iter) + next(self._line_iter) # type: ignore return section def _is_section_break(self): + # type: () -> bool line1, line2 = self._line_iter.peek(2) return (not self._line_iter.has_next() or self._is_section_header() or @@ -860,10 +924,11 @@ class NumpyDocstring(GoogleDocstring): not self._is_indented(line1, self._section_indent))) def _is_section_header(self): + # type: () -> bool section, underline = self._line_iter.peek(2) section = section.lower() if section in self._sections and isinstance(underline, string_types): - return bool(_numpy_section_regex.match(underline)) + return bool(_numpy_section_regex.match(underline)) # type: ignore elif self._directive_sections: if _directive_regex.match(section): for directive_section in self._directive_sections: @@ -875,6 +940,7 @@ class NumpyDocstring(GoogleDocstring): r" (?P<name2>[a-zA-Z0-9_.-]+))\s*", re.X) def _parse_see_also_section(self, section): + # type: (unicode) -> List[unicode] lines = self._consume_to_next_section() try: return self._parse_numpydoc_see_also_section(lines) @@ -882,6 +948,7 @@ class NumpyDocstring(GoogleDocstring): return self._format_admonition('seealso', lines) def _parse_numpydoc_see_also_section(self, content): + # type: (List[unicode]) -> List[unicode] """ Derived from the NumpyDoc implementation of _parse_see_also. @@ -914,13 +981,13 @@ class NumpyDocstring(GoogleDocstring): del rest[:] current_func = None - rest = [] + rest = [] # type: List[unicode] for line in content: if not line.strip(): continue - m = self._name_rgx.match(line) + m = self._name_rgx.match(line) # type: ignore if m and line[m.end():].strip().startswith(':'): push_item(current_func, rest) current_func, line = line[:m.end()], line[m.end():] @@ -960,12 +1027,12 @@ class NumpyDocstring(GoogleDocstring): 'const': 'const', 'attribute': 'attr', 'attr': 'attr' - } + } # type: Dict[unicode, unicode] if self._what is None: - func_role = 'obj' + func_role = 'obj' # type: unicode else: func_role = roles.get(self._what, '') - lines = [] + lines = [] # type: List[unicode] last_had_desc = True for func, desc, role in items: if role: |
