Closes #1429: Adds smarter Args parsing for Google style docstrings.

This commit is contained in:
Rob Ruana 2014-03-21 19:21:18 -04:00
parent 3e7ce5d3a1
commit c430f7f4c6
2 changed files with 130 additions and 62 deletions

View File

@ -24,7 +24,8 @@ if sys.version_info[0] >= 3:
_directive_regex = re.compile(r'\.\. \S+::')
_field_parens_regex = re.compile(r'\s*(\w+)\s*\(\s*(.+?)\s*\)')
_google_untyped_arg_regex = re.compile(r'\s*(\w+)\s*:\s*(.*)')
_google_typed_arg_regex = re.compile(r'\s*(\w+)\s*\(\s*(.+?)\s*\)\s*:\s*(.*)')
class GoogleDocstring(object):
@ -213,12 +214,22 @@ class GoogleDocstring(object):
def _consume_field(self, parse_type=True, prefer_type=False):
line = self._line_iter.next()
_name, _, _desc = line.partition(':')
_name, _type, _desc = _name.strip(), '', _desc.strip()
match = _field_parens_regex.match(_name)
if parse_type and match:
_name = match.group(1)
_type = match.group(2)
match = None
_name, _type, _desc = line.strip(), '', ''
if parse_type:
match = _google_typed_arg_regex.match(line)
if match:
_name = match.group(1)
_type = match.group(2)
_desc = match.group(3)
if not match:
match = _google_untyped_arg_regex.match(line)
if match:
_name = match.group(1)
_desc = match.group(2)
if prefer_type and not _type:
_type, _name = _name, _type
indent = self._get_indent(line) + 1
@ -238,17 +249,21 @@ class GoogleDocstring(object):
def _consume_returns_section(self):
lines = self._dedent(self._consume_to_next_section())
if lines:
if ':' in lines[0]:
_type, _, _desc = lines[0].partition(':')
_name, _type, _desc = '', _type.strip(), _desc.strip()
match = _field_parens_regex.match(_type)
_name, _type, _desc = '', '', lines
match = _google_typed_arg_regex.match(lines[0])
if match:
_name = match.group(1)
_type = match.group(2)
_desc = match.group(3)
else:
match = _google_untyped_arg_regex.match(lines[0])
if match:
_name = match.group(1)
_type = match.group(2)
_type = match.group(1)
_desc = match.group(2)
if match:
lines[0] = _desc
_desc = lines
else:
_name, _type, _desc = '', '', lines
_desc = self.__class__(_desc, self._config).lines()
return [(_name, _type, _desc,)]
else:

View File

@ -146,6 +146,19 @@ class GoogleDocstringTest(BaseDocstringTest):
:returns: *str* --
Extended
description of return value"""
), (
"""
Single line summary
Returns:
Extended
description of return value
""",
"""
Single line summary
:returns: Extended
description of return value"""
)]
def test_docstrings(self):
@ -155,6 +168,43 @@ class GoogleDocstringTest(BaseDocstringTest):
expected = textwrap.dedent(expected)
self.assertEqual(expected, actual)
def test_parameters_with_class_reference(self):
docstring = """\
Construct a new XBlock.
This class should only be used by runtimes.
Arguments:
runtime (:class:`Runtime`): Use it to access the environment.
It is available in XBlock code as ``self.runtime``.
field_data (:class:`FieldData`): Interface used by the XBlock
fields to access their data from wherever it is persisted.
scope_ids (:class:`ScopeIds`): Identifiers needed to resolve scopes.
"""
actual = str(GoogleDocstring(docstring))
expected = """\
Construct a new XBlock.
This class should only be used by runtimes.
:param runtime: Use it to access the environment.
It is available in XBlock code as ``self.runtime``.
:type runtime: :class:`Runtime`
:param field_data: Interface used by the XBlock
fields to access their data from wherever it is persisted.
:type field_data: :class:`FieldData`
:param scope_ids: Identifiers needed to resolve scopes.
:type scope_ids: :class:`ScopeIds`
"""
self.assertEqual(expected, actual)
class NumpyDocstringTest(BaseDocstringTest):
docstrings = [(
@ -268,95 +318,98 @@ class NumpyDocstringTest(BaseDocstringTest):
self.assertEqual(expected, actual)
def test_parameters_with_class_reference(self):
docstring = """
Parameters
----------
param1 : :class:`MyClass <name.space.MyClass>` instance
docstring = """\
Parameters
----------
param1 : :class:`MyClass <name.space.MyClass>` instance
"""
"""
config = Config(napoleon_use_param=False)
actual = str(NumpyDocstring(textwrap.dedent(docstring), config))
expected = textwrap.dedent("""
actual = str(NumpyDocstring(docstring, config))
expected = """\
:Parameters: **param1** (:class:`MyClass <name.space.MyClass>` instance)
""")
"""
self.assertEqual(expected, actual)
config = Config(napoleon_use_param=True)
actual = str(NumpyDocstring(textwrap.dedent(docstring), config))
expected = textwrap.dedent("""
actual = str(NumpyDocstring(docstring, config))
expected = """\
:type param1: :class:`MyClass <name.space.MyClass>` instance
""")
:type param1: :class:`MyClass <name.space.MyClass>` instance
"""
self.assertEqual(expected, actual)
def test_parameters_without_class_reference(self):
docstring = """
Parameters
----------
param1 : MyClass instance
docstring = """\
Parameters
----------
param1 : MyClass instance
"""
"""
config = Config(napoleon_use_param=False)
actual = str(NumpyDocstring(textwrap.dedent(docstring), config))
expected = textwrap.dedent("""
:Parameters: **param1** (*MyClass instance*)
""")
actual = str(NumpyDocstring(docstring, config))
expected = """\
:Parameters: **param1** (*MyClass instance*)
"""
self.assertEqual(expected, actual)
config = Config(napoleon_use_param=True)
actual = str(NumpyDocstring(textwrap.dedent(docstring), config))
expected = textwrap.dedent("""
expected = """\
:type param1: MyClass instance
""")
:type param1: MyClass instance
"""
self.assertEqual(expected, actual)
def test_see_also_refs(self):
docstring = """
numpy.multivariate_normal(mean, cov, shape=None, spam=None)
docstring = """\
numpy.multivariate_normal(mean, cov, shape=None, spam=None)
See Also
--------
some, other, funcs
otherfunc : relationship
See Also
--------
some, other, funcs
otherfunc : relationship
"""
"""
actual = str(NumpyDocstring(textwrap.dedent(docstring)))
actual = str(NumpyDocstring(docstring))
expected = """
expected = """\
numpy.multivariate_normal(mean, cov, shape=None, spam=None)
.. seealso::
\n :obj:`some`, :obj:`other`, :obj:`funcs`
\n :obj:`otherfunc`
:obj:`some`, :obj:`other`, :obj:`funcs`
\n\
:obj:`otherfunc`
relationship
"""
self.assertEqual(expected, actual)
docstring = """
numpy.multivariate_normal(mean, cov, shape=None, spam=None)
docstring = """\
numpy.multivariate_normal(mean, cov, shape=None, spam=None)
See Also
--------
some, other, funcs
otherfunc : relationship
See Also
--------
some, other, funcs
otherfunc : relationship
"""
"""
config = Config()
app = Mock()
actual = str(NumpyDocstring(textwrap.dedent(docstring),
config, app, "method"))
actual = str(NumpyDocstring(docstring, config, app, "method"))
expected = """
expected = """\
numpy.multivariate_normal(mean, cov, shape=None, spam=None)
.. seealso::
\n :meth:`some`, :meth:`other`, :meth:`funcs`
\n :meth:`otherfunc`
:meth:`some`, :meth:`other`, :meth:`funcs`
\n\
:meth:`otherfunc`
relationship
"""
self.assertEqual(expected, actual)