From 6b6d3dbea9f37602aefed545e6487a5fe87f677b Mon Sep 17 00:00:00 2001 From: Rob Ruana Date: Fri, 24 Jul 2015 11:30:35 -0700 Subject: [PATCH] Closes #1840: [Napoleon] Fixes Google style parsing to only match section headers ending with exactly one colon --- sphinx/ext/napoleon/docstring.py | 8 +- tests/test_ext_napoleon_docstring.py | 126 +++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 3 deletions(-) diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index ae0ab31db..0cc1f96f1 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -23,7 +23,9 @@ from sphinx.util.pycompat import UnicodeMixin _directive_regex = re.compile(r'\.\. \S+::') +_google_section_regex = re.compile(r'^(\s|\w)+:\s*$') _google_typed_arg_regex = re.compile(r'\s*(.+?)\s*\(\s*(.+?)\s*\)') +_numpy_section_regex = re.compile(r'^[=\-`:\'"~^_*+#<>]{2,}\s*$') _xref_regex = re.compile(r'(:\w+:\S+:`.+?`|:\S+:`.+?`|`.+?`)') @@ -381,7 +383,8 @@ class GoogleDocstring(UnicodeMixin): def _is_section_header(self): section = self._line_iter.peek().lower() - if section.strip(':') in self._sections: + match = _google_section_regex.match(section) + if match and section.strip(':') in self._sections: header_indent = self._get_indent(section) section_indent = self._get_current_indent(peek_ahead=1) return section_indent > header_indent @@ -769,8 +772,7 @@ class NumpyDocstring(GoogleDocstring): section, underline = self._line_iter.peek(2) section = section.lower() if section in self._sections and isinstance(underline, string_types): - pattern = r'[=\-`:\'"~^_*+#<>]{' + str(len(section)) + r'}$' - return bool(re.match(pattern, underline)) + return bool(_numpy_section_regex.match(underline)) elif self._directive_sections: if _directive_regex.match(section): for directive_section in self._directive_sections: diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py index 0543d0846..81707c2e1 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -576,6 +576,60 @@ Code sample for usage:: actual = str(GoogleDocstring(docstring)) self.assertEqual(expected, actual) + def test_section_header_formatting(self): + docstrings = [(""" +Summary line + +Example: + Multiline reStructuredText + literal code block + +""", """ +Summary line + +.. rubric:: Example + +Multiline reStructuredText +literal code block +"""), + ################################ + (""" +Summary line + +Example:: + + Multiline reStructuredText + literal code block + +""", """ +Summary line + +Example:: + + Multiline reStructuredText + literal code block +"""), + ################################ + (""" +Summary line + +:Example: + + Multiline reStructuredText + literal code block + +""", """ +Summary line + +:Example: + + Multiline reStructuredText + literal code block +""")] + for docstring, expected in docstrings: + actual = str(GoogleDocstring(docstring)) + self.assertEqual(expected, actual) + class NumpyDocstringTest(BaseDocstringTest): docstrings = [( @@ -1095,3 +1149,75 @@ Example Function app = mock.Mock() actual = str(NumpyDocstring(docstring, config, app, "method")) self.assertEqual(expected, actual) + + def test_section_header_underline_length(self): + docstrings = [(""" +Summary line + +Example +- +Multiline example +body + +""", """ +Summary line + +Example +- +Multiline example +body +"""), + ################################ + (""" +Summary line + +Example +-- +Multiline example +body + +""", """ +Summary line + +.. rubric:: Example + +Multiline example +body +"""), + ################################ + (""" +Summary line + +Example +------- +Multiline example +body + +""", """ +Summary line + +.. rubric:: Example + +Multiline example +body +"""), + ################################ + (""" +Summary line + +Example +------------ +Multiline example +body + +""", """ +Summary line + +.. rubric:: Example + +Multiline example +body +""")] + for docstring, expected in docstrings: + actual = str(NumpyDocstring(docstring)) + self.assertEqual(expected, actual)