From c48c68eac9d51eb7f3337392fc75208eaf7fd639 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 1cd32bcd0..4c2348507 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+:`.+?`|`.+?`)') @@ -402,7 +404,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 @@ -793,8 +796,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 0e98a8fdc..7da361512 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -561,6 +561,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 = [( @@ -1068,3 +1122,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)