Closes #1904: [Napoleon] parses restructuredtext references in fields/params BEFORE splitting on colon

This commit is contained in:
Rob Ruana
2015-05-27 10:57:04 -07:00
parent d966317464
commit eade1e797e
2 changed files with 81 additions and 27 deletions

View File

@@ -23,8 +23,8 @@ from sphinx.util.pycompat import UnicodeMixin
_directive_regex = re.compile(r'\.\. \S+::') _directive_regex = re.compile(r'\.\. \S+::')
_google_untyped_arg_regex = re.compile(r'(.+)\s*(?<!:):(?!:)\s*(.*)') _google_typed_arg_regex = re.compile(r'\s*(.+?)\s*\(\s*(.+?)\s*\)')
_google_typed_arg_regex = re.compile(r'(.+)\((.+)\)\s*(?<!:):(?!:)\s*(.*)') _xref_regex = re.compile(r'(:\w+:\S+:`.+?`|:\S+:`.+?`|`.+?`)')
class GoogleDocstring(UnicodeMixin): class GoogleDocstring(UnicodeMixin):
@@ -202,20 +202,14 @@ class GoogleDocstring(UnicodeMixin):
def _consume_field(self, parse_type=True, prefer_type=False): def _consume_field(self, parse_type=True, prefer_type=False):
line = next(self._line_iter) line = next(self._line_iter)
match = None before, colon, after = self._partition_field_on_colon(line)
_name, _type, _desc = line.strip(), '', '' _name, _type, _desc = before, '', after
if parse_type:
match = _google_typed_arg_regex.match(line)
if match:
_name = match.group(1).strip()
_type = match.group(2).strip()
_desc = match.group(3).strip()
if not match: if parse_type:
match = _google_untyped_arg_regex.match(line) match = _google_typed_arg_regex.match(before)
if match: if match:
_name = match.group(1).strip() _name = match.group(1)
_desc = match.group(2).strip() _type = match.group(2)
if _name[:2] == '**': if _name[:2] == '**':
_name = r'\*\*'+_name[2:] _name = r'\*\*'+_name[2:]
@@ -241,20 +235,21 @@ class GoogleDocstring(UnicodeMixin):
def _consume_returns_section(self): def _consume_returns_section(self):
lines = self._dedent(self._consume_to_next_section()) lines = self._dedent(self._consume_to_next_section())
if lines: if lines:
before, colon, after = self._partition_field_on_colon(lines[0])
_name, _type, _desc = '', '', lines _name, _type, _desc = '', '', lines
match = _google_typed_arg_regex.match(lines[0])
if match: if colon:
_name = match.group(1).strip() if after:
_type = match.group(2).strip() _desc = [after] + lines[1:]
_desc = match.group(3).strip() else:
else: _desc = lines[1:]
match = _google_untyped_arg_regex.match(lines[0])
match = _google_typed_arg_regex.match(before)
if match: if match:
_type = match.group(1).strip() _name = match.group(1)
_desc = match.group(2).strip() _type = match.group(2)
if match: else:
lines[0] = _desc _type = before
_desc = lines
_desc = self.__class__(_desc, self._config).lines() _desc = self.__class__(_desc, self._config).lines()
return [(_name, _type, _desc,)] return [(_name, _type, _desc,)]
@@ -593,6 +588,27 @@ class GoogleDocstring(UnicodeMixin):
fields = self._consume_returns_section() fields = self._consume_returns_section()
return self._format_fields('Yields', fields) return self._format_fields('Yields', fields)
def _partition_field_on_colon(self, line):
before_colon = []
after_colon = []
colon = ''
found_colon = False
for i, source in enumerate(_xref_regex.split(line)):
if found_colon:
after_colon.append(source)
else:
if (i % 2) == 0 and ":" in source:
found_colon = True
before, colon, after = source.partition(":")
before_colon.append(before)
after_colon.append(after)
else:
before_colon.append(source)
return ("".join(before_colon).strip(),
colon,
"".join(after_colon).strip())
def _strip_empty(self, lines): def _strip_empty(self, lines):
if lines: if lines:
start = -1 start = -1
@@ -719,7 +735,7 @@ class NumpyDocstring(GoogleDocstring):
def _consume_field(self, parse_type=True, prefer_type=False): def _consume_field(self, parse_type=True, prefer_type=False):
line = next(self._line_iter) line = next(self._line_iter)
if parse_type: if parse_type:
_name, _, _type = line.partition(':') _name, _, _type = self._partition_field_on_colon(line)
if not _name: if not _name:
_type = line _type = line
else: else:

View File

@@ -356,6 +356,22 @@ Returns:
:returns: an example instance :returns: an example instance
if available, None if not available. if available, None if not available.
:rtype: :py:class:`~.module.submodule.SomeClass` :rtype: :py:class:`~.module.submodule.SomeClass`
"""
actual = str(GoogleDocstring(docstring))
self.assertEqual(expected, actual)
def test_xrefs_in_return_type(self):
docstring = """Example Function
Returns:
:class:`numpy.ndarray`: A :math:`n \\times 2` array containing
a bunch of math items
"""
expected = """Example Function
:returns: A :math:`n \\times 2` array containing
a bunch of math items
:rtype: :class:`numpy.ndarray`
""" """
actual = str(GoogleDocstring(docstring)) actual = str(GoogleDocstring(docstring))
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
@@ -696,3 +712,25 @@ arg_ : type
actual = str(NumpyDocstring(docstring, config, app, "class")) actual = str(NumpyDocstring(docstring, config, app, "class"))
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
def test_xrefs_in_return_type(self):
docstring = """
Example Function
Returns
-------
:class:`numpy.ndarray`
A :math:`n \\times 2` array containing
a bunch of math items
"""
expected = """
Example Function
:returns: A :math:`n \\times 2` array containing
a bunch of math items
:rtype: :class:`numpy.ndarray`
"""
config = Config()
app = mock.Mock()
actual = str(NumpyDocstring(docstring, config, app, "method"))
self.assertEqual(expected, actual)