2014-01-18 12:56:23 -06:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
|
|
sphinx.ext.napoleon.docstring
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
|
|
Classes for docstring parsing and formatting.
|
|
|
|
|
|
|
|
|
2017-12-31 10:06:58 -06:00
|
|
|
:copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
|
2014-01-18 12:56:23 -06:00
|
|
|
:license: BSD, see LICENSE for details.
|
|
|
|
"""
|
|
|
|
|
2014-03-09 21:07:21 -05:00
|
|
|
import inspect
|
2014-01-18 12:56:23 -06:00
|
|
|
import re
|
2018-09-23 08:36:41 -05:00
|
|
|
from collections.abc import Callable
|
2018-02-13 10:38:44 -06:00
|
|
|
from functools import partial
|
2014-04-28 21:46:47 -05:00
|
|
|
|
2015-11-18 12:37:00 -06:00
|
|
|
from six import string_types, u
|
2014-04-28 21:46:47 -05:00
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
from sphinx.ext.napoleon.iterators import modify_iter
|
2018-05-21 10:33:47 -05:00
|
|
|
from sphinx.locale import _
|
2014-04-29 10:31:09 -05:00
|
|
|
from sphinx.util.pycompat import UnicodeMixin
|
2014-01-18 12:56:23 -06:00
|
|
|
|
2018-03-13 09:01:11 -05:00
|
|
|
if False:
|
|
|
|
# For type annotation
|
2018-09-23 08:36:41 -05:00
|
|
|
from typing import Any, Dict, List, Tuple, Type, Union # NOQA
|
2016-11-11 04:37:14 -06:00
|
|
|
from sphinx.application import Sphinx # NOQA
|
|
|
|
from sphinx.config import Config as SphinxConfig # NOQA
|
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
_directive_regex = re.compile(r'\.\. \S+::')
|
2015-07-24 13:30:35 -05:00
|
|
|
_google_section_regex = re.compile(r'^(\s|\w)+:\s*$')
|
2016-08-02 15:29:07 -05:00
|
|
|
_google_typed_arg_regex = re.compile(r'\s*(.+?)\s*\(\s*(.*[^\s]+)\s*\)')
|
2015-07-24 13:30:35 -05:00
|
|
|
_numpy_section_regex = re.compile(r'^[=\-`:\'"~^_*+#<>]{2,}\s*$')
|
2016-06-12 00:55:55 -05:00
|
|
|
_single_colon_regex = re.compile(r'(?<!:):(?!:)')
|
2016-11-21 12:11:04 -06:00
|
|
|
_xref_regex = re.compile(r'(:(?:[a-zA-Z0-9]+[\-_+:.])*[a-zA-Z0-9]+:`.+?`)')
|
2016-01-27 12:16:12 -06:00
|
|
|
_bullet_list_regex = re.compile(r'^(\*|\+|\-)(\s+\S|\s*$)')
|
|
|
|
_enumerated_list_regex = re.compile(
|
|
|
|
r'^(?P<paren>\()?'
|
|
|
|
r'(\d+|#|[ivxlcdm]+|[IVXLCDM]+|[a-zA-Z])'
|
|
|
|
r'(?(paren)\)|\.)(\s+\S|\s*$)')
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
|
2014-04-29 10:31:09 -05:00
|
|
|
class GoogleDocstring(UnicodeMixin):
|
2015-06-09 20:18:22 -05:00
|
|
|
"""Convert Google style docstrings to reStructuredText.
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
2016-06-23 14:25:00 -05:00
|
|
|
docstring : :obj:`str` or :obj:`list` of :obj:`str`
|
2014-01-18 12:56:23 -06:00
|
|
|
The docstring to parse, given either as a string or split into
|
|
|
|
individual lines.
|
2016-06-23 14:25:00 -05:00
|
|
|
config: :obj:`sphinx.ext.napoleon.Config` or :obj:`sphinx.config.Config`
|
2014-01-18 12:56:23 -06:00
|
|
|
The configuration settings to use. If not given, defaults to the
|
|
|
|
config object on `app`; or if `app` is not given defaults to the
|
2016-06-23 14:25:00 -05:00
|
|
|
a new :class:`sphinx.ext.napoleon.Config` object.
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
|
|
|
|
Other Parameters
|
|
|
|
----------------
|
2016-06-23 14:25:00 -05:00
|
|
|
app : :class:`sphinx.application.Sphinx`, optional
|
2014-01-18 12:56:23 -06:00
|
|
|
Application object representing the Sphinx process.
|
2016-06-23 14:25:00 -05:00
|
|
|
what : :obj:`str`, optional
|
2014-01-18 12:56:23 -06:00
|
|
|
A string specifying the type of the object to which the docstring
|
|
|
|
belongs. Valid values: "module", "class", "exception", "function",
|
|
|
|
"method", "attribute".
|
2016-06-23 14:25:00 -05:00
|
|
|
name : :obj:`str`, optional
|
2014-01-18 12:56:23 -06:00
|
|
|
The fully qualified name of the object.
|
|
|
|
obj : module, class, exception, function, method, or attribute
|
|
|
|
The object to which the docstring belongs.
|
2016-06-23 14:25:00 -05:00
|
|
|
options : :class:`sphinx.ext.autodoc.Options`, optional
|
2014-01-18 12:56:23 -06:00
|
|
|
The options given to the directive: an object with attributes
|
|
|
|
inherited_members, undoc_members, show_inheritance and noindex that
|
|
|
|
are True if the flag option of same name was given to the auto
|
|
|
|
directive.
|
|
|
|
|
2016-08-06 13:37:17 -05:00
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
Example
|
|
|
|
-------
|
|
|
|
>>> from sphinx.ext.napoleon import Config
|
|
|
|
>>> config = Config(napoleon_use_param=True, napoleon_use_rtype=True)
|
|
|
|
>>> docstring = '''One line summary.
|
|
|
|
...
|
|
|
|
... Extended description.
|
|
|
|
...
|
|
|
|
... Args:
|
|
|
|
... arg1(int): Description of `arg1`
|
|
|
|
... arg2(str): Description of `arg2`
|
|
|
|
... Returns:
|
|
|
|
... str: Description of return value.
|
|
|
|
... '''
|
|
|
|
>>> print(GoogleDocstring(docstring, config))
|
|
|
|
One line summary.
|
|
|
|
<BLANKLINE>
|
|
|
|
Extended description.
|
|
|
|
<BLANKLINE>
|
|
|
|
:param arg1: Description of `arg1`
|
|
|
|
:type arg1: int
|
|
|
|
:param arg2: Description of `arg2`
|
|
|
|
:type arg2: str
|
|
|
|
<BLANKLINE>
|
|
|
|
:returns: Description of return value.
|
|
|
|
:rtype: str
|
2015-01-12 16:08:51 -06:00
|
|
|
<BLANKLINE>
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
"""
|
2018-09-23 07:31:27 -05:00
|
|
|
|
|
|
|
_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)
|
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
def __init__(self, docstring, config=None, app=None, what='', name='',
|
|
|
|
obj=None, options=None):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (Union[unicode, List[unicode]], SphinxConfig, Sphinx, unicode, unicode, Any, Any) -> None # NOQA
|
2014-01-18 12:56:23 -06:00
|
|
|
self._config = config
|
|
|
|
self._app = app
|
2014-03-09 21:07:21 -05:00
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
if not self._config:
|
|
|
|
from sphinx.ext.napoleon import Config
|
2016-11-11 04:37:14 -06:00
|
|
|
self._config = self._app and self._app.config or Config() # type: ignore
|
2014-03-09 21:07:21 -05:00
|
|
|
|
|
|
|
if not what:
|
|
|
|
if inspect.isclass(obj):
|
|
|
|
what = 'class'
|
|
|
|
elif inspect.ismodule(obj):
|
|
|
|
what = 'module'
|
2018-09-23 08:36:41 -05:00
|
|
|
elif isinstance(obj, Callable):
|
2014-03-09 21:07:21 -05:00
|
|
|
what = 'function'
|
|
|
|
else:
|
|
|
|
what = 'object'
|
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
self._what = what
|
|
|
|
self._name = name
|
|
|
|
self._obj = obj
|
|
|
|
self._opt = options
|
2014-04-30 07:30:46 -05:00
|
|
|
if isinstance(docstring, string_types):
|
2017-02-05 21:03:32 -06:00
|
|
|
docstring = docstring.splitlines()
|
2014-01-18 12:56:23 -06:00
|
|
|
self._lines = docstring
|
|
|
|
self._line_iter = modify_iter(docstring, modifier=lambda s: s.rstrip())
|
2016-11-11 04:37:14 -06:00
|
|
|
self._parsed_lines = [] # type: List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
self._is_in_section = False
|
|
|
|
self._section_indent = 0
|
|
|
|
if not hasattr(self, '_directive_sections'):
|
2016-11-11 04:37:14 -06:00
|
|
|
self._directive_sections = [] # type: List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
if not hasattr(self, '_sections'):
|
|
|
|
self._sections = {
|
|
|
|
'args': self._parse_parameters_section,
|
|
|
|
'arguments': self._parse_parameters_section,
|
2018-02-13 10:38:44 -06:00
|
|
|
'attention': partial(self._parse_admonition, 'attention'),
|
2014-01-18 12:56:23 -06:00
|
|
|
'attributes': self._parse_attributes_section,
|
2018-02-13 10:38:44 -06:00
|
|
|
'caution': partial(self._parse_admonition, 'caution'),
|
|
|
|
'danger': partial(self._parse_admonition, 'danger'),
|
|
|
|
'error': partial(self._parse_admonition, 'error'),
|
2014-01-18 12:56:23 -06:00
|
|
|
'example': self._parse_examples_section,
|
|
|
|
'examples': self._parse_examples_section,
|
2018-02-13 10:38:44 -06:00
|
|
|
'hint': partial(self._parse_admonition, 'hint'),
|
|
|
|
'important': partial(self._parse_admonition, 'important'),
|
2014-01-18 12:56:23 -06:00
|
|
|
'keyword args': self._parse_keyword_arguments_section,
|
|
|
|
'keyword arguments': self._parse_keyword_arguments_section,
|
|
|
|
'methods': self._parse_methods_section,
|
2018-02-13 10:38:44 -06:00
|
|
|
'note': partial(self._parse_admonition, 'note'),
|
2014-01-18 12:56:23 -06:00
|
|
|
'notes': self._parse_notes_section,
|
|
|
|
'other parameters': self._parse_other_parameters_section,
|
|
|
|
'parameters': self._parse_parameters_section,
|
|
|
|
'return': self._parse_returns_section,
|
|
|
|
'returns': self._parse_returns_section,
|
|
|
|
'raises': self._parse_raises_section,
|
|
|
|
'references': self._parse_references_section,
|
|
|
|
'see also': self._parse_see_also_section,
|
2018-02-13 10:38:44 -06:00
|
|
|
'tip': partial(self._parse_admonition, 'tip'),
|
|
|
|
'todo': partial(self._parse_admonition, 'todo'),
|
|
|
|
'warning': partial(self._parse_admonition, 'warning'),
|
|
|
|
'warnings': partial(self._parse_admonition, 'warning'),
|
2014-01-18 12:56:23 -06:00
|
|
|
'warns': self._parse_warns_section,
|
2015-02-09 02:49:31 -06:00
|
|
|
'yield': self._parse_yields_section,
|
2014-01-18 12:56:23 -06:00
|
|
|
'yields': self._parse_yields_section,
|
2016-11-11 04:37:14 -06:00
|
|
|
} # type: Dict[unicode, Callable]
|
2018-02-15 15:16:23 -06:00
|
|
|
|
|
|
|
self._load_custom_sections()
|
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
self._parse()
|
|
|
|
|
|
|
|
def __unicode__(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> unicode
|
2014-01-18 12:56:23 -06:00
|
|
|
"""Return the parsed docstring in reStructuredText format.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
unicode
|
|
|
|
Unicode version of the docstring.
|
|
|
|
|
|
|
|
"""
|
2015-11-18 12:37:00 -06:00
|
|
|
return u('\n').join(self.lines())
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
def lines(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
"""Return the parsed lines of the docstring in reStructuredText format.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
2016-08-02 15:29:07 -05:00
|
|
|
list(str)
|
2014-01-18 12:56:23 -06:00
|
|
|
The lines of the docstring in a list.
|
|
|
|
|
|
|
|
"""
|
|
|
|
return self._parsed_lines
|
|
|
|
|
|
|
|
def _consume_indented_block(self, indent=1):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (int) -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
lines = []
|
|
|
|
line = self._line_iter.peek()
|
2015-02-15 17:51:29 -06:00
|
|
|
while(not self._is_section_break() and
|
|
|
|
(not line or self._is_indented(line, indent))):
|
2017-12-15 23:51:55 -06:00
|
|
|
lines.append(next(self._line_iter))
|
2014-01-18 12:56:23 -06:00
|
|
|
line = self._line_iter.peek()
|
|
|
|
return lines
|
|
|
|
|
|
|
|
def _consume_contiguous(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
lines = []
|
2015-02-15 17:51:29 -06:00
|
|
|
while (self._line_iter.has_next() and
|
|
|
|
self._line_iter.peek() and
|
|
|
|
not self._is_section_header()):
|
2017-12-15 23:51:55 -06:00
|
|
|
lines.append(next(self._line_iter))
|
2014-01-18 12:56:23 -06:00
|
|
|
return lines
|
|
|
|
|
|
|
|
def _consume_empty(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
lines = []
|
|
|
|
line = self._line_iter.peek()
|
|
|
|
while self._line_iter.has_next() and not line:
|
2017-12-15 23:51:55 -06:00
|
|
|
lines.append(next(self._line_iter))
|
2014-01-18 12:56:23 -06:00
|
|
|
line = self._line_iter.peek()
|
|
|
|
return lines
|
|
|
|
|
|
|
|
def _consume_field(self, parse_type=True, prefer_type=False):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (bool, bool) -> Tuple[unicode, unicode, List[unicode]]
|
2017-12-15 23:51:55 -06:00
|
|
|
line = next(self._line_iter)
|
2014-03-21 18:21:18 -05:00
|
|
|
|
2015-05-27 12:57:04 -05:00
|
|
|
before, colon, after = self._partition_field_on_colon(line)
|
2016-11-11 04:37:14 -06:00
|
|
|
_name, _type, _desc = before, '', after # type: unicode, unicode, unicode
|
2014-03-21 18:21:18 -05:00
|
|
|
|
2015-05-27 12:57:04 -05:00
|
|
|
if parse_type:
|
2018-10-15 12:17:32 -05:00
|
|
|
match = _google_typed_arg_regex.match(before)
|
2014-03-21 18:21:18 -05:00
|
|
|
if match:
|
2015-05-27 12:57:04 -05:00
|
|
|
_name = match.group(1)
|
|
|
|
_type = match.group(2)
|
2014-03-21 18:21:18 -05:00
|
|
|
|
2015-11-20 18:39:59 -06:00
|
|
|
_name = self._escape_args_and_kwargs(_name)
|
2015-01-12 16:08:51 -06:00
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
if prefer_type and not _type:
|
|
|
|
_type, _name = _name, _type
|
|
|
|
indent = self._get_indent(line) + 1
|
2017-03-02 21:19:09 -06:00
|
|
|
_descs = [_desc] + self._dedent(self._consume_indented_block(indent))
|
|
|
|
_descs = self.__class__(_descs, self._config).lines()
|
|
|
|
return _name, _type, _descs
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
def _consume_fields(self, parse_type=True, prefer_type=False):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (bool, bool) -> List[Tuple[unicode, unicode, List[unicode]]]
|
2014-01-18 12:56:23 -06:00
|
|
|
self._consume_empty()
|
|
|
|
fields = []
|
|
|
|
while not self._is_section_break():
|
|
|
|
_name, _type, _desc = self._consume_field(parse_type, prefer_type)
|
|
|
|
if _name or _type or _desc:
|
|
|
|
fields.append((_name, _type, _desc,))
|
|
|
|
return fields
|
|
|
|
|
2015-06-09 20:18:22 -05:00
|
|
|
def _consume_inline_attribute(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> Tuple[unicode, List[unicode]]
|
2017-12-15 23:51:55 -06:00
|
|
|
line = next(self._line_iter)
|
2015-06-09 20:18:22 -05:00
|
|
|
_type, colon, _desc = self._partition_field_on_colon(line)
|
2018-09-23 08:26:17 -05:00
|
|
|
if not colon or not _desc:
|
2015-06-09 20:18:22 -05:00
|
|
|
_type, _desc = _desc, _type
|
2018-09-23 08:26:17 -05:00
|
|
|
_desc += colon
|
2017-03-02 21:19:09 -06:00
|
|
|
_descs = [_desc] + self._dedent(self._consume_to_end())
|
|
|
|
_descs = self.__class__(_descs, self._config).lines()
|
|
|
|
return _type, _descs
|
2015-06-09 20:18:22 -05:00
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
def _consume_returns_section(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> List[Tuple[unicode, unicode, List[unicode]]]
|
2014-01-18 12:56:23 -06:00
|
|
|
lines = self._dedent(self._consume_to_next_section())
|
|
|
|
if lines:
|
2015-05-27 12:57:04 -05:00
|
|
|
before, colon, after = self._partition_field_on_colon(lines[0])
|
2016-11-11 04:37:14 -06:00
|
|
|
_name, _type, _desc = '', '', lines # type: unicode, unicode, List[unicode]
|
2015-05-27 12:57:04 -05:00
|
|
|
|
|
|
|
if colon:
|
|
|
|
if after:
|
|
|
|
_desc = [after] + lines[1:]
|
|
|
|
else:
|
|
|
|
_desc = lines[1:]
|
|
|
|
|
2016-08-02 15:29:07 -05:00
|
|
|
_type = before
|
2014-03-21 18:21:18 -05:00
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
_desc = self.__class__(_desc, self._config).lines()
|
|
|
|
return [(_name, _type, _desc,)]
|
|
|
|
else:
|
|
|
|
return []
|
|
|
|
|
2015-02-04 06:52:47 -06:00
|
|
|
def _consume_usage_section(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> List[unicode]
|
2015-02-04 06:52:47 -06:00
|
|
|
lines = self._dedent(self._consume_to_next_section())
|
|
|
|
return lines
|
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
def _consume_section_header(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> unicode
|
2017-12-15 23:51:55 -06:00
|
|
|
section = next(self._line_iter)
|
2014-01-18 12:56:23 -06:00
|
|
|
stripped_section = section.strip(':')
|
|
|
|
if stripped_section.lower() in self._sections:
|
|
|
|
section = stripped_section
|
|
|
|
return section
|
|
|
|
|
2015-06-09 20:18:22 -05:00
|
|
|
def _consume_to_end(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> List[unicode]
|
2015-06-09 20:18:22 -05:00
|
|
|
lines = []
|
|
|
|
while self._line_iter.has_next():
|
2017-12-15 23:51:55 -06:00
|
|
|
lines.append(next(self._line_iter))
|
2015-06-09 20:18:22 -05:00
|
|
|
return lines
|
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
def _consume_to_next_section(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
self._consume_empty()
|
|
|
|
lines = []
|
|
|
|
while not self._is_section_break():
|
2017-12-15 23:51:55 -06:00
|
|
|
lines.append(next(self._line_iter))
|
2014-01-18 12:56:23 -06:00
|
|
|
return lines + self._consume_empty()
|
|
|
|
|
|
|
|
def _dedent(self, lines, full=False):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (List[unicode], bool) -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
if full:
|
|
|
|
return [line.lstrip() for line in lines]
|
|
|
|
else:
|
|
|
|
min_indent = self._get_min_indent(lines)
|
|
|
|
return [line[min_indent:] for line in lines]
|
|
|
|
|
2015-11-20 18:39:59 -06:00
|
|
|
def _escape_args_and_kwargs(self, name):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> unicode
|
2015-11-20 18:39:59 -06:00
|
|
|
if name[:2] == '**':
|
|
|
|
return r'\*\*' + name[2:]
|
|
|
|
elif name[:1] == '*':
|
|
|
|
return r'\*' + name[1:]
|
|
|
|
else:
|
|
|
|
return name
|
|
|
|
|
2016-06-12 00:55:55 -05:00
|
|
|
def _fix_field_desc(self, desc):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (List[unicode]) -> List[unicode]
|
2016-06-12 00:55:55 -05:00
|
|
|
if self._is_list(desc):
|
2017-03-02 21:19:09 -06:00
|
|
|
desc = [u''] + desc
|
2016-06-12 00:55:55 -05:00
|
|
|
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:
|
2017-03-02 21:19:09 -06:00
|
|
|
desc = [u''] + desc
|
2016-06-12 00:55:55 -05:00
|
|
|
else:
|
|
|
|
desc = ['', desc[0]] + self._indent(desc_block, 4)
|
|
|
|
return desc
|
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
def _format_admonition(self, admonition, lines):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode, List[unicode]) -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
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)
|
2017-03-02 21:19:09 -06:00
|
|
|
return [u'.. %s::' % admonition, u''] + lines + [u'']
|
2014-01-18 12:56:23 -06:00
|
|
|
else:
|
2017-03-02 21:19:09 -06:00
|
|
|
return [u'.. %s::' % admonition, u'']
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
def _format_block(self, prefix, lines, padding=None):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode, List[unicode], unicode) -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
if lines:
|
|
|
|
if padding is None:
|
|
|
|
padding = ' ' * len(prefix)
|
|
|
|
result_lines = []
|
|
|
|
for i, line in enumerate(lines):
|
2015-01-08 01:12:17 -06:00
|
|
|
if i == 0:
|
2015-01-09 11:41:09 -06:00
|
|
|
result_lines.append((prefix + line).rstrip())
|
2015-01-08 01:12:17 -06:00
|
|
|
elif line:
|
|
|
|
result_lines.append(padding + line)
|
2014-01-18 12:56:23 -06:00
|
|
|
else:
|
|
|
|
result_lines.append('')
|
|
|
|
return result_lines
|
|
|
|
else:
|
|
|
|
return [prefix]
|
|
|
|
|
2016-06-12 00:55:55 -05:00
|
|
|
def _format_docutils_params(self, fields, field_role='param',
|
|
|
|
type_role='type'):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (List[Tuple[unicode, unicode, List[unicode]]], unicode, unicode) -> List[unicode] # NOQA
|
2016-06-12 00:55:55 -05:00
|
|
|
lines = []
|
|
|
|
for _name, _type, _desc in fields:
|
|
|
|
_desc = self._strip_empty(_desc)
|
|
|
|
if any(_desc):
|
|
|
|
_desc = self._fix_field_desc(_desc)
|
|
|
|
field = ':%s %s: ' % (field_role, _name)
|
|
|
|
lines.extend(self._format_block(field, _desc))
|
|
|
|
else:
|
|
|
|
lines.append(':%s %s:' % (field_role, _name))
|
|
|
|
|
|
|
|
if _type:
|
|
|
|
lines.append(':%s %s: %s' % (type_role, _name, _type))
|
|
|
|
return lines + ['']
|
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
def _format_field(self, _name, _type, _desc):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode, unicode, List[unicode]) -> List[unicode]
|
2015-06-11 14:50:05 -05:00
|
|
|
_desc = self._strip_empty(_desc)
|
|
|
|
has_desc = any(_desc)
|
|
|
|
separator = has_desc and ' -- ' or ''
|
2014-01-18 12:56:23 -06:00
|
|
|
if _name:
|
|
|
|
if _type:
|
2014-03-09 00:56:56 -06:00
|
|
|
if '`' in _type:
|
2016-11-11 04:37:14 -06:00
|
|
|
field = '**%s** (%s)%s' % (_name, _type, separator) # type: unicode
|
2014-03-09 00:56:56 -06:00
|
|
|
else:
|
2015-06-11 14:50:05 -05:00
|
|
|
field = '**%s** (*%s*)%s' % (_name, _type, separator)
|
2014-01-18 12:56:23 -06:00
|
|
|
else:
|
2015-06-11 14:50:05 -05:00
|
|
|
field = '**%s**%s' % (_name, separator)
|
2014-01-18 12:56:23 -06:00
|
|
|
elif _type:
|
2014-03-09 00:56:56 -06:00
|
|
|
if '`' in _type:
|
2015-06-11 14:50:05 -05:00
|
|
|
field = '%s%s' % (_type, separator)
|
2014-03-09 00:56:56 -06:00
|
|
|
else:
|
2015-06-11 14:50:05 -05:00
|
|
|
field = '*%s*%s' % (_type, separator)
|
2014-01-18 12:56:23 -06:00
|
|
|
else:
|
2015-06-11 14:50:05 -05:00
|
|
|
field = ''
|
|
|
|
|
|
|
|
if has_desc:
|
2016-06-12 00:55:55 -05:00
|
|
|
_desc = self._fix_field_desc(_desc)
|
|
|
|
if _desc[0]:
|
|
|
|
return [field + _desc[0]] + _desc[1:]
|
|
|
|
else:
|
|
|
|
return [field] + _desc
|
2015-06-11 14:50:05 -05:00
|
|
|
else:
|
|
|
|
return [field]
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
def _format_fields(self, field_type, fields):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode, List[Tuple[unicode, unicode, List[unicode]]]) -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
field_type = ':%s:' % field_type.strip()
|
|
|
|
padding = ' ' * len(field_type)
|
|
|
|
multi = len(fields) > 1
|
2016-11-11 04:37:14 -06:00
|
|
|
lines = [] # type: List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
for _name, _type, _desc in fields:
|
|
|
|
field = self._format_field(_name, _type, _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))
|
2015-06-11 14:50:05 -05:00
|
|
|
if lines and lines[-1]:
|
|
|
|
lines.append('')
|
2014-01-18 12:56:23 -06:00
|
|
|
return lines
|
|
|
|
|
|
|
|
def _get_current_indent(self, peek_ahead=0):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (int) -> int
|
2014-01-18 12:56:23 -06:00
|
|
|
line = self._line_iter.peek(peek_ahead + 1)[peek_ahead]
|
|
|
|
while line != self._line_iter.sentinel:
|
|
|
|
if line:
|
|
|
|
return self._get_indent(line)
|
|
|
|
peek_ahead += 1
|
|
|
|
line = self._line_iter.peek(peek_ahead + 1)[peek_ahead]
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def _get_indent(self, line):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> int
|
2014-01-18 12:56:23 -06:00
|
|
|
for i, s in enumerate(line):
|
|
|
|
if not s.isspace():
|
|
|
|
return i
|
|
|
|
return len(line)
|
|
|
|
|
2016-06-12 00:55:55 -05:00
|
|
|
def _get_initial_indent(self, lines):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (List[unicode]) -> int
|
2016-06-12 00:55:55 -05:00
|
|
|
for line in lines:
|
|
|
|
if line:
|
|
|
|
return self._get_indent(line)
|
|
|
|
return 0
|
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
def _get_min_indent(self, lines):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (List[unicode]) -> int
|
2014-01-18 12:56:23 -06:00
|
|
|
min_indent = None
|
|
|
|
for line in lines:
|
|
|
|
if line:
|
|
|
|
indent = self._get_indent(line)
|
|
|
|
if min_indent is None:
|
|
|
|
min_indent = indent
|
|
|
|
elif indent < min_indent:
|
|
|
|
min_indent = indent
|
|
|
|
return min_indent or 0
|
|
|
|
|
|
|
|
def _indent(self, lines, n=4):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (List[unicode], int) -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
return [(' ' * n) + line for line in lines]
|
|
|
|
|
|
|
|
def _is_indented(self, line, indent=1):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode, int) -> bool
|
2014-01-18 12:56:23 -06:00
|
|
|
for i, s in enumerate(line):
|
|
|
|
if i >= indent:
|
|
|
|
return True
|
|
|
|
elif not s.isspace():
|
|
|
|
return False
|
|
|
|
return False
|
|
|
|
|
2016-01-27 12:16:12 -06:00
|
|
|
def _is_list(self, lines):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (List[unicode]) -> bool
|
2016-01-27 12:16:12 -06:00
|
|
|
if not lines:
|
|
|
|
return False
|
2018-10-15 12:17:32 -05:00
|
|
|
if _bullet_list_regex.match(lines[0]):
|
2016-01-27 12:16:12 -06:00
|
|
|
return True
|
2018-10-15 12:17:32 -05:00
|
|
|
if _enumerated_list_regex.match(lines[0]):
|
2016-01-27 12:16:12 -06:00
|
|
|
return True
|
|
|
|
if len(lines) < 2 or lines[0].endswith('::'):
|
|
|
|
return False
|
|
|
|
indent = self._get_indent(lines[0])
|
|
|
|
next_indent = indent
|
|
|
|
for line in lines[1:]:
|
|
|
|
if line:
|
|
|
|
next_indent = self._get_indent(line)
|
|
|
|
break
|
|
|
|
return next_indent > indent
|
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
def _is_section_header(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> bool
|
2014-01-18 12:56:23 -06:00
|
|
|
section = self._line_iter.peek().lower()
|
2015-07-24 13:30:35 -05:00
|
|
|
match = _google_section_regex.match(section)
|
|
|
|
if match and section.strip(':') in self._sections:
|
2014-01-18 12:56:23 -06:00
|
|
|
header_indent = self._get_indent(section)
|
|
|
|
section_indent = self._get_current_indent(peek_ahead=1)
|
|
|
|
return section_indent > header_indent
|
|
|
|
elif self._directive_sections:
|
|
|
|
if _directive_regex.match(section):
|
|
|
|
for directive_section in self._directive_sections:
|
|
|
|
if section.startswith(directive_section):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _is_section_break(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> bool
|
2014-01-18 12:56:23 -06:00
|
|
|
line = self._line_iter.peek()
|
2015-02-15 17:51:29 -06:00
|
|
|
return (not self._line_iter.has_next() or
|
|
|
|
self._is_section_header() or
|
|
|
|
(self._is_in_section and
|
|
|
|
line and
|
|
|
|
not self._is_indented(line, self._section_indent)))
|
2014-01-18 12:56:23 -06:00
|
|
|
|
2018-02-15 15:16:23 -06:00
|
|
|
def _load_custom_sections(self):
|
|
|
|
# type: () -> None
|
|
|
|
|
|
|
|
if self._config.napoleon_custom_sections is not None:
|
|
|
|
for entry in self._config.napoleon_custom_sections:
|
|
|
|
if isinstance(entry, string_types):
|
|
|
|
# if entry is just a label, add to sections list,
|
|
|
|
# using generic section logic.
|
|
|
|
self._sections[entry.lower()] = self._parse_custom_generic_section
|
|
|
|
else:
|
|
|
|
# otherwise, assume entry is container;
|
|
|
|
# [0] is new section, [1] is the section to alias.
|
|
|
|
# in the case of key mismatch, just handle as generic section.
|
|
|
|
self._sections[entry[0].lower()] = \
|
|
|
|
self._sections.get(entry[1].lower(),
|
|
|
|
self._parse_custom_generic_section)
|
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
def _parse(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> None
|
2014-01-18 12:56:23 -06:00
|
|
|
self._parsed_lines = self._consume_empty()
|
2015-06-09 20:18:22 -05:00
|
|
|
|
|
|
|
if self._name and (self._what == 'attribute' or self._what == 'data'):
|
2018-07-03 11:28:54 -05:00
|
|
|
# Implicit stop using StopIteration no longer allowed in
|
|
|
|
# Python 3.7; see PEP 479
|
|
|
|
res = [] # type: List[unicode]
|
|
|
|
try:
|
|
|
|
res = self._parse_attribute_docstring()
|
|
|
|
except StopIteration:
|
|
|
|
pass
|
|
|
|
self._parsed_lines.extend(res)
|
2015-06-09 20:18:22 -05:00
|
|
|
return
|
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
while self._line_iter.has_next():
|
|
|
|
if self._is_section_header():
|
|
|
|
try:
|
|
|
|
section = self._consume_section_header()
|
|
|
|
self._is_in_section = True
|
|
|
|
self._section_indent = self._get_current_indent()
|
2018-10-15 12:17:32 -05:00
|
|
|
if _directive_regex.match(section):
|
2014-01-18 12:56:23 -06:00
|
|
|
lines = [section] + self._consume_to_next_section()
|
|
|
|
else:
|
|
|
|
lines = self._sections[section.lower()](section)
|
|
|
|
finally:
|
|
|
|
self._is_in_section = False
|
|
|
|
self._section_indent = 0
|
|
|
|
else:
|
|
|
|
if not self._parsed_lines:
|
|
|
|
lines = self._consume_contiguous() + self._consume_empty()
|
|
|
|
else:
|
|
|
|
lines = self._consume_to_next_section()
|
|
|
|
self._parsed_lines.extend(lines)
|
|
|
|
|
2018-02-13 10:38:44 -06:00
|
|
|
def _parse_admonition(self, admonition, section):
|
|
|
|
# type (unicode, unicode) -> List[unicode]
|
|
|
|
lines = self._consume_to_next_section()
|
|
|
|
return self._format_admonition(admonition, lines)
|
|
|
|
|
2015-06-09 20:18:22 -05:00
|
|
|
def _parse_attribute_docstring(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> List[unicode]
|
2015-06-09 20:18:22 -05:00
|
|
|
_type, _desc = self._consume_inline_attribute()
|
2017-09-06 17:29:41 -05:00
|
|
|
lines = self._format_field('', '', _desc)
|
|
|
|
if _type:
|
|
|
|
lines.extend(['', ':type: %s' % _type])
|
|
|
|
return lines
|
2015-06-09 20:18:22 -05:00
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
def _parse_attributes_section(self, section):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
lines = []
|
|
|
|
for _name, _type, _desc in self._consume_fields():
|
|
|
|
if self._config.napoleon_use_ivar:
|
2018-09-23 08:25:31 -05:00
|
|
|
_name = self._qualify_name(_name, self._obj)
|
2016-11-11 04:37:14 -06:00
|
|
|
field = ':ivar %s: ' % _name # type: unicode
|
2014-01-18 12:56:23 -06:00
|
|
|
lines.extend(self._format_block(field, _desc))
|
|
|
|
if _type:
|
|
|
|
lines.append(':vartype %s: %s' % (_name, _type))
|
|
|
|
else:
|
2015-06-09 20:18:22 -05:00
|
|
|
lines.extend(['.. attribute:: ' + _name, ''])
|
2017-09-06 17:29:41 -05:00
|
|
|
fields = self._format_field('', '', _desc)
|
2017-03-02 21:19:09 -06:00
|
|
|
lines.extend(self._indent(fields, 3))
|
2017-09-06 17:29:41 -05:00
|
|
|
if _type:
|
|
|
|
lines.append('')
|
|
|
|
lines.extend(self._indent([':type: %s' % _type], 3))
|
2014-01-18 12:56:23 -06:00
|
|
|
lines.append('')
|
|
|
|
if self._config.napoleon_use_ivar:
|
|
|
|
lines.append('')
|
|
|
|
return lines
|
|
|
|
|
|
|
|
def _parse_examples_section(self, section):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> List[unicode]
|
2018-05-21 10:33:47 -05:00
|
|
|
labels = {
|
|
|
|
'example': _('Example'),
|
|
|
|
'examples': _('Examples'),
|
|
|
|
} # type: Dict[unicode, unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
use_admonition = self._config.napoleon_use_admonition_for_examples
|
2018-05-21 10:33:47 -05:00
|
|
|
label = labels.get(section.lower(), section)
|
|
|
|
return self._parse_generic_section(label, use_admonition)
|
2014-01-18 12:56:23 -06:00
|
|
|
|
2018-02-15 15:16:23 -06:00
|
|
|
def _parse_custom_generic_section(self, section):
|
|
|
|
# for now, no admonition for simple custom sections
|
|
|
|
return self._parse_generic_section(section, False)
|
|
|
|
|
2015-02-04 06:52:47 -06:00
|
|
|
def _parse_usage_section(self, section):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> List[unicode]
|
|
|
|
header = ['.. rubric:: Usage:', ''] # type: List[unicode]
|
|
|
|
block = ['.. code-block:: python', ''] # type: List[unicode]
|
2015-02-04 06:52:47 -06:00
|
|
|
lines = self._consume_usage_section()
|
|
|
|
lines = self._indent(lines, 3)
|
|
|
|
return header + block + lines + ['']
|
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
def _parse_generic_section(self, section, use_admonition):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode, bool) -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
lines = self._strip_empty(self._consume_to_next_section())
|
|
|
|
lines = self._dedent(lines)
|
|
|
|
if use_admonition:
|
2016-11-11 04:37:14 -06:00
|
|
|
header = '.. admonition:: %s' % section # type: unicode
|
2014-01-18 12:56:23 -06:00
|
|
|
lines = self._indent(lines, 3)
|
|
|
|
else:
|
|
|
|
header = '.. rubric:: %s' % section
|
|
|
|
if lines:
|
|
|
|
return [header, ''] + lines + ['']
|
|
|
|
else:
|
|
|
|
return [header, '']
|
|
|
|
|
|
|
|
def _parse_keyword_arguments_section(self, section):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> List[unicode]
|
2016-03-15 15:24:16 -05:00
|
|
|
fields = self._consume_fields()
|
|
|
|
if self._config.napoleon_use_keyword:
|
2016-06-12 00:55:55 -05:00
|
|
|
return self._format_docutils_params(
|
2016-03-15 15:24:16 -05:00
|
|
|
fields,
|
|
|
|
field_role="keyword",
|
2016-06-12 00:55:55 -05:00
|
|
|
type_role="kwtype")
|
2016-03-15 15:24:16 -05:00
|
|
|
else:
|
2018-05-21 10:33:47 -05:00
|
|
|
return self._format_fields(_('Keyword Arguments'), fields)
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
def _parse_methods_section(self, section):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> List[unicode]
|
|
|
|
lines = [] # type: List[unicode]
|
2018-05-21 10:33:47 -05:00
|
|
|
for _name, _type, _desc in self._consume_fields(parse_type=False):
|
2014-01-18 12:56:23 -06:00
|
|
|
lines.append('.. method:: %s' % _name)
|
|
|
|
if _desc:
|
2017-03-02 21:19:09 -06:00
|
|
|
lines.extend([u''] + self._indent(_desc, 3))
|
2014-01-18 12:56:23 -06:00
|
|
|
lines.append('')
|
|
|
|
return lines
|
|
|
|
|
|
|
|
def _parse_notes_section(self, section):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
use_admonition = self._config.napoleon_use_admonition_for_notes
|
2018-05-21 10:33:47 -05:00
|
|
|
return self._parse_generic_section(_('Notes'), use_admonition)
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
def _parse_other_parameters_section(self, section):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> List[unicode]
|
2018-05-21 10:33:47 -05:00
|
|
|
return self._format_fields(_('Other Parameters'), self._consume_fields())
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
def _parse_parameters_section(self, section):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
fields = self._consume_fields()
|
|
|
|
if self._config.napoleon_use_param:
|
2016-06-12 00:55:55 -05:00
|
|
|
return self._format_docutils_params(fields)
|
2014-01-18 12:56:23 -06:00
|
|
|
else:
|
2018-05-21 10:33:47 -05:00
|
|
|
return self._format_fields(_('Parameters'), fields)
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
def _parse_raises_section(self, section):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> List[unicode]
|
2015-06-11 03:15:52 -05:00
|
|
|
fields = self._consume_fields(parse_type=False, prefer_type=True)
|
2016-11-11 04:37:14 -06:00
|
|
|
lines = [] # type: List[unicode]
|
2018-09-23 08:11:10 -05:00
|
|
|
for _name, _type, _desc in fields:
|
2018-10-15 19:58:25 -05:00
|
|
|
m = self._name_rgx.match(_type).groupdict()
|
2018-09-23 07:31:27 -05:00
|
|
|
if m['role']:
|
|
|
|
_type = m['name']
|
|
|
|
_type = ' ' + _type if _type else ''
|
2015-06-11 14:50:05 -05:00
|
|
|
_desc = self._strip_empty(_desc)
|
2018-09-23 08:11:10 -05:00
|
|
|
_descs = ' ' + '\n '.join(_desc) if any(_desc) else ''
|
|
|
|
lines.append(':raises%s:%s' % (_type, _descs))
|
2018-09-23 07:31:27 -05:00
|
|
|
if lines:
|
2015-06-11 03:15:52 -05:00
|
|
|
lines.append('')
|
2014-01-18 12:56:23 -06:00
|
|
|
return lines
|
|
|
|
|
|
|
|
def _parse_references_section(self, section):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
use_admonition = self._config.napoleon_use_admonition_for_references
|
2018-05-21 10:33:47 -05:00
|
|
|
return self._parse_generic_section(_('References'), use_admonition)
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
def _parse_returns_section(self, section):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
fields = self._consume_returns_section()
|
|
|
|
multi = len(fields) > 1
|
|
|
|
if multi:
|
|
|
|
use_rtype = False
|
|
|
|
else:
|
|
|
|
use_rtype = self._config.napoleon_use_rtype
|
|
|
|
|
2016-11-11 04:37:14 -06:00
|
|
|
lines = [] # type: List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
for _name, _type, _desc in fields:
|
|
|
|
if use_rtype:
|
|
|
|
field = self._format_field(_name, '', _desc)
|
|
|
|
else:
|
|
|
|
field = self._format_field(_name, _type, _desc)
|
|
|
|
|
|
|
|
if multi:
|
|
|
|
if lines:
|
|
|
|
lines.extend(self._format_block(' * ', field))
|
|
|
|
else:
|
|
|
|
lines.extend(self._format_block(':returns: * ', field))
|
|
|
|
else:
|
|
|
|
lines.extend(self._format_block(':returns: ', field))
|
|
|
|
if _type and use_rtype:
|
2015-06-11 14:50:05 -05:00
|
|
|
lines.extend([':rtype: %s' % _type, ''])
|
|
|
|
if lines and lines[-1]:
|
|
|
|
lines.append('')
|
2014-01-18 12:56:23 -06:00
|
|
|
return lines
|
|
|
|
|
|
|
|
def _parse_see_also_section(self, section):
|
2018-02-13 10:38:44 -06:00
|
|
|
# type (unicode) -> List[unicode]
|
|
|
|
return self._parse_admonition('seealso', section)
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
def _parse_warns_section(self, section):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> List[unicode]
|
2018-05-21 10:33:47 -05:00
|
|
|
return self._format_fields(_('Warns'), self._consume_fields())
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
def _parse_yields_section(self, section):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> List[unicode]
|
2015-02-09 02:49:31 -06:00
|
|
|
fields = self._consume_returns_section()
|
2018-05-21 10:33:47 -05:00
|
|
|
return self._format_fields(_('Yields'), fields)
|
2014-01-18 12:56:23 -06:00
|
|
|
|
2015-05-27 12:57:04 -05:00
|
|
|
def _partition_field_on_colon(self, line):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> Tuple[unicode, unicode, unicode]
|
2015-05-27 12:57:04 -05:00
|
|
|
before_colon = []
|
|
|
|
after_colon = []
|
2018-10-15 12:17:32 -05:00
|
|
|
colon = '' # type: unicode
|
2015-05-27 12:57:04 -05:00
|
|
|
found_colon = False
|
2018-10-15 12:17:32 -05:00
|
|
|
for i, source in enumerate(_xref_regex.split(line)):
|
2015-05-27 12:57:04 -05:00
|
|
|
if found_colon:
|
|
|
|
after_colon.append(source)
|
|
|
|
else:
|
2016-06-12 00:55:55 -05:00
|
|
|
m = _single_colon_regex.search(source)
|
|
|
|
if (i % 2) == 0 and m:
|
2015-05-27 12:57:04 -05:00
|
|
|
found_colon = True
|
2016-06-12 00:55:55 -05:00
|
|
|
colon = source[m.start(): m.end()]
|
|
|
|
before_colon.append(source[:m.start()])
|
|
|
|
after_colon.append(source[m.end():])
|
2015-05-27 12:57:04 -05:00
|
|
|
else:
|
|
|
|
before_colon.append(source)
|
|
|
|
|
|
|
|
return ("".join(before_colon).strip(),
|
|
|
|
colon,
|
|
|
|
"".join(after_colon).strip())
|
|
|
|
|
2018-09-23 08:25:31 -05:00
|
|
|
def _qualify_name(self, attr_name, klass):
|
2018-09-23 08:36:41 -05:00
|
|
|
# type: (unicode, Type) -> unicode
|
2018-09-23 08:25:31 -05:00
|
|
|
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
|
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
def _strip_empty(self, lines):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (List[unicode]) -> List[unicode]
|
2014-01-18 12:56:23 -06:00
|
|
|
if lines:
|
|
|
|
start = -1
|
|
|
|
for i, line in enumerate(lines):
|
|
|
|
if line:
|
|
|
|
start = i
|
|
|
|
break
|
|
|
|
if start == -1:
|
|
|
|
lines = []
|
|
|
|
end = -1
|
2014-04-29 05:59:58 -05:00
|
|
|
for i in reversed(range(len(lines))):
|
2014-01-18 12:56:23 -06:00
|
|
|
line = lines[i]
|
|
|
|
if line:
|
|
|
|
end = i
|
|
|
|
break
|
|
|
|
if start > 0 or end + 1 < len(lines):
|
|
|
|
lines = lines[start:end + 1]
|
|
|
|
return lines
|
|
|
|
|
|
|
|
|
|
|
|
class NumpyDocstring(GoogleDocstring):
|
2015-06-09 20:18:22 -05:00
|
|
|
"""Convert NumPy style docstrings to reStructuredText.
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
2016-06-23 14:25:00 -05:00
|
|
|
docstring : :obj:`str` or :obj:`list` of :obj:`str`
|
2014-01-18 12:56:23 -06:00
|
|
|
The docstring to parse, given either as a string or split into
|
|
|
|
individual lines.
|
2016-06-23 14:25:00 -05:00
|
|
|
config: :obj:`sphinx.ext.napoleon.Config` or :obj:`sphinx.config.Config`
|
2014-01-18 12:56:23 -06:00
|
|
|
The configuration settings to use. If not given, defaults to the
|
|
|
|
config object on `app`; or if `app` is not given defaults to the
|
2016-06-23 14:25:00 -05:00
|
|
|
a new :class:`sphinx.ext.napoleon.Config` object.
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
|
|
|
|
Other Parameters
|
|
|
|
----------------
|
2016-06-23 14:25:00 -05:00
|
|
|
app : :class:`sphinx.application.Sphinx`, optional
|
2014-01-18 12:56:23 -06:00
|
|
|
Application object representing the Sphinx process.
|
2016-06-23 14:25:00 -05:00
|
|
|
what : :obj:`str`, optional
|
2014-01-18 12:56:23 -06:00
|
|
|
A string specifying the type of the object to which the docstring
|
|
|
|
belongs. Valid values: "module", "class", "exception", "function",
|
|
|
|
"method", "attribute".
|
2016-06-23 14:25:00 -05:00
|
|
|
name : :obj:`str`, optional
|
2014-01-18 12:56:23 -06:00
|
|
|
The fully qualified name of the object.
|
|
|
|
obj : module, class, exception, function, method, or attribute
|
|
|
|
The object to which the docstring belongs.
|
2016-06-23 14:25:00 -05:00
|
|
|
options : :class:`sphinx.ext.autodoc.Options`, optional
|
2014-01-18 12:56:23 -06:00
|
|
|
The options given to the directive: an object with attributes
|
|
|
|
inherited_members, undoc_members, show_inheritance and noindex that
|
|
|
|
are True if the flag option of same name was given to the auto
|
|
|
|
directive.
|
|
|
|
|
2016-08-06 13:37:17 -05:00
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
Example
|
|
|
|
-------
|
|
|
|
>>> from sphinx.ext.napoleon import Config
|
|
|
|
>>> config = Config(napoleon_use_param=True, napoleon_use_rtype=True)
|
|
|
|
>>> docstring = '''One line summary.
|
|
|
|
...
|
|
|
|
... Extended description.
|
|
|
|
...
|
|
|
|
... Parameters
|
|
|
|
... ----------
|
|
|
|
... arg1 : int
|
|
|
|
... Description of `arg1`
|
|
|
|
... arg2 : str
|
|
|
|
... Description of `arg2`
|
|
|
|
... Returns
|
|
|
|
... -------
|
|
|
|
... str
|
|
|
|
... Description of return value.
|
|
|
|
... '''
|
|
|
|
>>> print(NumpyDocstring(docstring, config))
|
|
|
|
One line summary.
|
|
|
|
<BLANKLINE>
|
|
|
|
Extended description.
|
|
|
|
<BLANKLINE>
|
|
|
|
:param arg1: Description of `arg1`
|
|
|
|
:type arg1: int
|
|
|
|
:param arg2: Description of `arg2`
|
|
|
|
:type arg2: str
|
|
|
|
<BLANKLINE>
|
|
|
|
:returns: Description of return value.
|
|
|
|
:rtype: str
|
2015-01-12 16:08:51 -06:00
|
|
|
<BLANKLINE>
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
Methods
|
|
|
|
-------
|
|
|
|
__str__()
|
|
|
|
Return the parsed docstring in reStructuredText format.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
str
|
|
|
|
UTF-8 encoded version of the docstring.
|
|
|
|
|
|
|
|
__unicode__()
|
|
|
|
Return the parsed docstring in reStructuredText format.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
unicode
|
|
|
|
Unicode version of the docstring.
|
|
|
|
|
|
|
|
lines()
|
|
|
|
Return the parsed lines of the docstring in reStructuredText format.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
2016-08-02 15:29:07 -05:00
|
|
|
list(str)
|
2014-01-18 12:56:23 -06:00
|
|
|
The lines of the docstring in a list.
|
|
|
|
|
|
|
|
"""
|
|
|
|
def __init__(self, docstring, config=None, app=None, what='', name='',
|
|
|
|
obj=None, options=None):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (Union[unicode, List[unicode]], SphinxConfig, Sphinx, unicode, unicode, Any, Any) -> None # NOQA
|
2014-01-18 12:56:23 -06:00
|
|
|
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):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (bool, bool) -> Tuple[unicode, unicode, List[unicode]]
|
2017-12-15 23:51:55 -06:00
|
|
|
line = next(self._line_iter)
|
2014-01-18 12:56:23 -06:00
|
|
|
if parse_type:
|
2015-05-27 12:57:04 -05:00
|
|
|
_name, _, _type = self._partition_field_on_colon(line)
|
2014-01-18 12:56:23 -06:00
|
|
|
else:
|
|
|
|
_name, _type = line, ''
|
|
|
|
_name, _type = _name.strip(), _type.strip()
|
2015-11-20 18:39:59 -06:00
|
|
|
_name = self._escape_args_and_kwargs(_name)
|
2016-01-27 12:16:12 -06:00
|
|
|
|
2014-01-18 12:56:23 -06:00
|
|
|
if prefer_type and not _type:
|
|
|
|
_type, _name = _name, _type
|
2016-01-27 12:16:12 -06:00
|
|
|
indent = self._get_indent(line) + 1
|
|
|
|
_desc = self._dedent(self._consume_indented_block(indent))
|
2014-01-18 12:56:23 -06:00
|
|
|
_desc = self.__class__(_desc, self._config).lines()
|
|
|
|
return _name, _type, _desc
|
|
|
|
|
|
|
|
def _consume_returns_section(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> List[Tuple[unicode, unicode, List[unicode]]]
|
2014-01-18 12:56:23 -06:00
|
|
|
return self._consume_fields(prefer_type=True)
|
|
|
|
|
|
|
|
def _consume_section_header(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> unicode
|
2017-12-15 23:51:55 -06:00
|
|
|
section = next(self._line_iter)
|
2014-01-18 12:56:23 -06:00
|
|
|
if not _directive_regex.match(section):
|
|
|
|
# Consume the header underline
|
2017-12-15 23:51:55 -06:00
|
|
|
next(self._line_iter)
|
2014-01-18 12:56:23 -06:00
|
|
|
return section
|
|
|
|
|
|
|
|
def _is_section_break(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> bool
|
2014-01-18 12:56:23 -06:00
|
|
|
line1, line2 = self._line_iter.peek(2)
|
2015-02-15 17:51:29 -06:00
|
|
|
return (not self._line_iter.has_next() or
|
|
|
|
self._is_section_header() or
|
|
|
|
['', ''] == [line1, line2] or
|
|
|
|
(self._is_in_section and
|
|
|
|
line1 and
|
|
|
|
not self._is_indented(line1, self._section_indent)))
|
2014-01-18 12:56:23 -06:00
|
|
|
|
|
|
|
def _is_section_header(self):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: () -> bool
|
2014-01-18 12:56:23 -06:00
|
|
|
section, underline = self._line_iter.peek(2)
|
|
|
|
section = section.lower()
|
2014-04-30 07:30:46 -05:00
|
|
|
if section in self._sections and isinstance(underline, string_types):
|
2018-10-15 12:17:32 -05:00
|
|
|
return bool(_numpy_section_regex.match(underline))
|
2014-01-18 12:56:23 -06:00
|
|
|
elif self._directive_sections:
|
|
|
|
if _directive_regex.match(section):
|
|
|
|
for directive_section in self._directive_sections:
|
|
|
|
if section.startswith(directive_section):
|
|
|
|
return True
|
|
|
|
return False
|
2014-03-09 21:07:21 -05:00
|
|
|
|
|
|
|
def _parse_see_also_section(self, section):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (unicode) -> List[unicode]
|
2014-03-11 02:50:49 -05:00
|
|
|
lines = self._consume_to_next_section()
|
|
|
|
try:
|
|
|
|
return self._parse_numpydoc_see_also_section(lines)
|
|
|
|
except ValueError:
|
|
|
|
return self._format_admonition('seealso', lines)
|
|
|
|
|
|
|
|
def _parse_numpydoc_see_also_section(self, content):
|
2016-11-11 04:37:14 -06:00
|
|
|
# type: (List[unicode]) -> List[unicode]
|
2014-03-09 21:07:21 -05:00
|
|
|
"""
|
|
|
|
Derived from the NumpyDoc implementation of _parse_see_also.
|
|
|
|
|
2015-02-09 00:28:57 -06:00
|
|
|
See Also
|
|
|
|
--------
|
2014-03-09 21:07:21 -05:00
|
|
|
func_name : Descriptive text
|
|
|
|
continued text
|
|
|
|
another_func_name : Descriptive text
|
|
|
|
func_name1, func_name2, :meth:`func_name`, func_name3
|
|
|
|
|
|
|
|
"""
|
|
|
|
items = []
|
|
|
|
|
|
|
|
def parse_item_name(text):
|
2018-01-22 07:05:38 -06:00
|
|
|
# type: (unicode) -> Tuple[unicode, unicode]
|
2014-03-09 21:07:21 -05:00
|
|
|
"""Match ':role:`name`' or 'name'"""
|
2018-10-15 12:17:32 -05:00
|
|
|
m = self._name_rgx.match(text)
|
2014-03-09 21:07:21 -05:00
|
|
|
if m:
|
|
|
|
g = m.groups()
|
|
|
|
if g[1] is None:
|
|
|
|
return g[3], None
|
|
|
|
else:
|
|
|
|
return g[2], g[1]
|
|
|
|
raise ValueError("%s is not a item name" % text)
|
|
|
|
|
|
|
|
def push_item(name, rest):
|
2018-01-22 07:05:38 -06:00
|
|
|
# type: (unicode, List[unicode]) -> None
|
2014-03-09 21:07:21 -05:00
|
|
|
if not name:
|
|
|
|
return
|
|
|
|
name, role = parse_item_name(name)
|
|
|
|
items.append((name, list(rest), role))
|
|
|
|
del rest[:]
|
|
|
|
|
|
|
|
current_func = None
|
2016-11-11 04:37:14 -06:00
|
|
|
rest = [] # type: List[unicode]
|
2014-03-09 21:07:21 -05:00
|
|
|
|
|
|
|
for line in content:
|
2014-03-11 02:50:49 -05:00
|
|
|
if not line.strip():
|
|
|
|
continue
|
2014-03-09 21:07:21 -05:00
|
|
|
|
2018-10-15 12:17:32 -05:00
|
|
|
m = self._name_rgx.match(line)
|
2014-03-09 21:07:21 -05:00
|
|
|
if m and line[m.end():].strip().startswith(':'):
|
|
|
|
push_item(current_func, rest)
|
|
|
|
current_func, line = line[:m.end()], line[m.end():]
|
|
|
|
rest = [line.split(':', 1)[1].strip()]
|
|
|
|
if not rest[0]:
|
|
|
|
rest = []
|
|
|
|
elif not line.startswith(' '):
|
|
|
|
push_item(current_func, rest)
|
|
|
|
current_func = None
|
|
|
|
if ',' in line:
|
|
|
|
for func in line.split(','):
|
|
|
|
if func.strip():
|
|
|
|
push_item(func, [])
|
|
|
|
elif line.strip():
|
|
|
|
current_func = line
|
|
|
|
elif current_func is not None:
|
|
|
|
rest.append(line.strip())
|
|
|
|
push_item(current_func, rest)
|
|
|
|
|
|
|
|
if not items:
|
|
|
|
return []
|
|
|
|
|
|
|
|
roles = {
|
|
|
|
'method': 'meth',
|
|
|
|
'meth': 'meth',
|
|
|
|
'function': 'func',
|
|
|
|
'func': 'func',
|
|
|
|
'class': 'class',
|
|
|
|
'exception': 'exc',
|
|
|
|
'exc': 'exc',
|
|
|
|
'object': 'obj',
|
|
|
|
'obj': 'obj',
|
|
|
|
'module': 'mod',
|
|
|
|
'mod': 'mod',
|
|
|
|
'data': 'data',
|
|
|
|
'constant': 'const',
|
|
|
|
'const': 'const',
|
|
|
|
'attribute': 'attr',
|
|
|
|
'attr': 'attr'
|
2016-11-11 04:37:14 -06:00
|
|
|
} # type: Dict[unicode, unicode]
|
2014-03-09 21:07:21 -05:00
|
|
|
if self._what is None:
|
2016-11-11 04:37:14 -06:00
|
|
|
func_role = 'obj' # type: unicode
|
2014-03-09 21:07:21 -05:00
|
|
|
else:
|
|
|
|
func_role = roles.get(self._what, '')
|
2016-11-11 04:37:14 -06:00
|
|
|
lines = [] # type: List[unicode]
|
2014-03-09 21:07:21 -05:00
|
|
|
last_had_desc = True
|
|
|
|
for func, desc, role in items:
|
|
|
|
if role:
|
|
|
|
link = ':%s:`%s`' % (role, func)
|
|
|
|
elif func_role:
|
|
|
|
link = ':%s:`%s`' % (func_role, func)
|
|
|
|
else:
|
|
|
|
link = "`%s`_" % func
|
|
|
|
if desc or last_had_desc:
|
|
|
|
lines += ['']
|
|
|
|
lines += [link]
|
|
|
|
else:
|
|
|
|
lines[-1] += ", %s" % link
|
|
|
|
if desc:
|
|
|
|
lines += self._indent([' '.join(desc)])
|
|
|
|
last_had_desc = True
|
|
|
|
else:
|
|
|
|
last_had_desc = False
|
|
|
|
lines += ['']
|
|
|
|
|
|
|
|
return self._format_admonition('seealso', lines)
|