summaryrefslogtreecommitdiff
path: root/sphinx/ext/napoleon/docstring.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/ext/napoleon/docstring.py')
-rw-r--r--sphinx/ext/napoleon/docstring.py169
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: