summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Ruana <rob@relentlessidiot.com>2014-03-21 19:21:18 -0400
committerRob Ruana <rob@relentlessidiot.com>2014-03-21 19:21:18 -0400
commitd2e0fb667cad9f93b77de93e66b8d184514fcfca (patch)
treed13554686286e7ec821e42fb677d1287397deff3
parentae1e88428e8729940a1bf9c83c373c19172092e3 (diff)
downloadsphinx-d2e0fb667cad9f93b77de93e66b8d184514fcfca.tar.gz
Closes #1429: Adds smarter Args parsing for Google style docstrings.
-rw-r--r--sphinx/ext/napoleon/docstring.py45
-rw-r--r--tests/test_napoleon_docstring.py147
2 files changed, 130 insertions, 62 deletions
diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py
index 24f4423e..1f41b6b6 100644
--- a/sphinx/ext/napoleon/docstring.py
+++ b/sphinx/ext/napoleon/docstring.py
@@ -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:
diff --git a/tests/test_napoleon_docstring.py b/tests/test_napoleon_docstring.py
index 268fc1eb..34ba4e41 100644
--- a/tests/test_napoleon_docstring.py
+++ b/tests/test_napoleon_docstring.py
@@ -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)