diff options
-rw-r--r-- | sphinx/ext/napoleon/docstring.py | 45 | ||||
-rw-r--r-- | tests/test_napoleon_docstring.py | 147 |
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) |