Fix #2227: [Napoleon] Fixes issue in which bulleted lists in parameter descriptions could cause the sphinx builder to fail

This commit is contained in:
Rob Ruana
2016-01-27 10:16:12 -08:00
parent a66041b541
commit 678f6066f5
2 changed files with 582 additions and 7 deletions

View File

@@ -27,6 +27,11 @@ _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+:`.+?`|`.+?`)')
_bullet_list_regex = re.compile(r'^(\*|\+|\-)(\s+\S|\s*$)')
_enumerated_list_regex = re.compile(
r'^(?P<paren>\()?'
r'(\d+|#|[ivxlcdm]+|[IVXLCDM]+|[a-zA-Z])'
r'(?(paren)\)|\.)(\s+\S|\s*$)')
class GoogleDocstring(UnicodeMixin):
@@ -349,6 +354,8 @@ class GoogleDocstring(UnicodeMixin):
field = ''
if has_desc:
if self._is_list(_desc):
return [field, ''] + _desc
return [field + _desc[0]] + _desc[1:]
else:
return [field]
@@ -408,6 +415,23 @@ class GoogleDocstring(UnicodeMixin):
return False
return False
def _is_list(self, lines):
if not lines:
return False
if _bullet_list_regex.match(lines[0]):
return True
if _enumerated_list_regex.match(lines[0]):
return True
if len(lines) < 2 or lines[0].endswith('::'):
return False
indent = self._get_indent(lines[0])
next_indent = indent
for line in lines[1:]:
if line:
next_indent = self._get_indent(line)
break
return next_indent > indent
def _is_section_header(self):
section = self._line_iter.peek().lower()
match = _google_section_regex.match(section)
@@ -530,10 +554,18 @@ class GoogleDocstring(UnicodeMixin):
if self._config.napoleon_use_param:
lines = []
for _name, _type, _desc in fields:
_desc = self._strip_empty(_desc)
if any(_desc):
if self._is_list(_desc):
_desc = [''] + _desc
field = ':param %s: ' % _name
lines.extend(self._format_block(field, _desc))
else:
lines.append(':param %s:' % _name)
if _type:
lines.append(':type %s: %s' % (_name, _type))
return lines + ['']
else:
return self._format_fields('Parameters', fields)
@@ -777,10 +809,11 @@ class NumpyDocstring(GoogleDocstring):
_name, _type = line, ''
_name, _type = _name.strip(), _type.strip()
_name = self._escape_args_and_kwargs(_name)
if prefer_type and not _type:
_type, _name = _name, _type
indent = self._get_indent(line)
_desc = self._dedent(self._consume_indented_block(indent + 1))
indent = self._get_indent(line) + 1
_desc = self._dedent(self._consume_indented_block(indent))
_desc = self.__class__(_desc, self._config).lines()
return _name, _type, _desc

View File

@@ -280,14 +280,11 @@ 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)
@@ -615,6 +612,285 @@ Summary line
actual = str(GoogleDocstring(docstring))
self.assertEqual(expected, actual)
def test_list_in_parameter_description(self):
docstring = """One line summary.
Parameters:
no_list (int):
one_bullet_empty (int):
*
one_bullet_single_line (int):
- first line
one_bullet_two_lines (int):
+ first line
continued
two_bullets_single_line (int):
- first line
- second line
two_bullets_two_lines (int):
* first line
continued
* second line
continued
one_enumeration_single_line (int):
1. first line
one_enumeration_two_lines (int):
1) first line
continued
two_enumerations_one_line (int):
(iii) first line
(iv) second line
two_enumerations_two_lines (int):
a. first line
continued
b. second line
continued
one_definition_one_line (int):
item 1
first line
one_definition_two_lines (int):
item 1
first line
continued
two_definitions_one_line (int):
item 1
first line
item 2
second line
two_definitions_two_lines (int):
item 1
first line
continued
item 2
second line
continued
one_definition_blank_line (int):
item 1
first line
extra first line
two_definitions_blank_lines (int):
item 1
first line
extra first line
item 2
second line
extra second line
definition_after_inline_text (int): text line
item 1
first line
definition_after_normal_text (int):
text line
item 1
first line
"""
expected = """One line summary.
:param no_list:
:type no_list: int
:param one_bullet_empty:
*
:type one_bullet_empty: int
:param one_bullet_single_line:
- first line
:type one_bullet_single_line: int
:param one_bullet_two_lines:
+ first line
continued
:type one_bullet_two_lines: int
:param two_bullets_single_line:
- first line
- second line
:type two_bullets_single_line: int
:param two_bullets_two_lines:
* first line
continued
* second line
continued
:type two_bullets_two_lines: int
:param one_enumeration_single_line:
1. first line
:type one_enumeration_single_line: int
:param one_enumeration_two_lines:
1) first line
continued
:type one_enumeration_two_lines: int
:param two_enumerations_one_line:
(iii) first line
(iv) second line
:type two_enumerations_one_line: int
:param two_enumerations_two_lines:
a. first line
continued
b. second line
continued
:type two_enumerations_two_lines: int
:param one_definition_one_line:
item 1
first line
:type one_definition_one_line: int
:param one_definition_two_lines:
item 1
first line
continued
:type one_definition_two_lines: int
:param two_definitions_one_line:
item 1
first line
item 2
second line
:type two_definitions_one_line: int
:param two_definitions_two_lines:
item 1
first line
continued
item 2
second line
continued
:type two_definitions_two_lines: int
:param one_definition_blank_line:
item 1
first line
extra first line
:type one_definition_blank_line: int
:param two_definitions_blank_lines:
item 1
first line
extra first line
item 2
second line
extra second line
:type two_definitions_blank_lines: int
:param definition_after_inline_text: text line
item 1
first line
:type definition_after_inline_text: int
:param definition_after_normal_text: text line
item 1
first line
:type definition_after_normal_text: int
"""
config = Config(napoleon_use_param=True)
actual = str(GoogleDocstring(docstring, config))
self.assertEqual(expected, actual)
expected = """One line summary.
:Parameters: * **no_list** (*int*)
* **one_bullet_empty** (*int*) --
*
* **one_bullet_single_line** (*int*) --
- first line
* **one_bullet_two_lines** (*int*) --
+ first line
continued
* **two_bullets_single_line** (*int*) --
- first line
- second line
* **two_bullets_two_lines** (*int*) --
* first line
continued
* second line
continued
* **one_enumeration_single_line** (*int*) --
1. first line
* **one_enumeration_two_lines** (*int*) --
1) first line
continued
* **two_enumerations_one_line** (*int*) --
(iii) first line
(iv) second line
* **two_enumerations_two_lines** (*int*) --
a. first line
continued
b. second line
continued
* **one_definition_one_line** (*int*) --
item 1
first line
* **one_definition_two_lines** (*int*) --
item 1
first line
continued
* **two_definitions_one_line** (*int*) --
item 1
first line
item 2
second line
* **two_definitions_two_lines** (*int*) --
item 1
first line
continued
item 2
second line
continued
* **one_definition_blank_line** (*int*) --
item 1
first line
extra first line
* **two_definitions_blank_lines** (*int*) --
item 1
first line
extra first line
item 2
second line
extra second line
* **definition_after_inline_text** (*int*) -- text line
item 1
first line
* **definition_after_normal_text** (*int*) -- text line
item 1
first line
"""
config = Config(napoleon_use_param=False)
actual = str(GoogleDocstring(docstring, config))
self.assertEqual(expected, actual)
class NumpyDocstringTest(BaseDocstringTest):
docstrings = [(
@@ -1194,3 +1470,269 @@ body
for docstring, expected in docstrings:
actual = str(NumpyDocstring(docstring))
self.assertEqual(expected, actual)
def test_list_in_parameter_description(self):
docstring = """One line summary.
Parameters
----------
no_list : int
one_bullet_empty : int
*
one_bullet_single_line : int
- first line
one_bullet_two_lines : int
+ first line
continued
two_bullets_single_line : int
- first line
- second line
two_bullets_two_lines : int
* first line
continued
* second line
continued
one_enumeration_single_line : int
1. first line
one_enumeration_two_lines : int
1) first line
continued
two_enumerations_one_line : int
(iii) first line
(iv) second line
two_enumerations_two_lines : int
a. first line
continued
b. second line
continued
one_definition_one_line : int
item 1
first line
one_definition_two_lines : int
item 1
first line
continued
two_definitions_one_line : int
item 1
first line
item 2
second line
two_definitions_two_lines : int
item 1
first line
continued
item 2
second line
continued
one_definition_blank_line : int
item 1
first line
extra first line
two_definitions_blank_lines : int
item 1
first line
extra first line
item 2
second line
extra second line
definition_after_normal_text : int
text line
item 1
first line
"""
expected = """One line summary.
:param no_list:
:type no_list: int
:param one_bullet_empty:
*
:type one_bullet_empty: int
:param one_bullet_single_line:
- first line
:type one_bullet_single_line: int
:param one_bullet_two_lines:
+ first line
continued
:type one_bullet_two_lines: int
:param two_bullets_single_line:
- first line
- second line
:type two_bullets_single_line: int
:param two_bullets_two_lines:
* first line
continued
* second line
continued
:type two_bullets_two_lines: int
:param one_enumeration_single_line:
1. first line
:type one_enumeration_single_line: int
:param one_enumeration_two_lines:
1) first line
continued
:type one_enumeration_two_lines: int
:param two_enumerations_one_line:
(iii) first line
(iv) second line
:type two_enumerations_one_line: int
:param two_enumerations_two_lines:
a. first line
continued
b. second line
continued
:type two_enumerations_two_lines: int
:param one_definition_one_line:
item 1
first line
:type one_definition_one_line: int
:param one_definition_two_lines:
item 1
first line
continued
:type one_definition_two_lines: int
:param two_definitions_one_line:
item 1
first line
item 2
second line
:type two_definitions_one_line: int
:param two_definitions_two_lines:
item 1
first line
continued
item 2
second line
continued
:type two_definitions_two_lines: int
:param one_definition_blank_line:
item 1
first line
extra first line
:type one_definition_blank_line: int
:param two_definitions_blank_lines:
item 1
first line
extra first line
item 2
second line
extra second line
:type two_definitions_blank_lines: int
:param definition_after_normal_text: text line
item 1
first line
:type definition_after_normal_text: int
"""
config = Config(napoleon_use_param=True)
actual = str(NumpyDocstring(docstring, config))
self.assertEqual(expected, actual)
expected = """One line summary.
:Parameters: * **no_list** (*int*)
* **one_bullet_empty** (*int*) --
*
* **one_bullet_single_line** (*int*) --
- first line
* **one_bullet_two_lines** (*int*) --
+ first line
continued
* **two_bullets_single_line** (*int*) --
- first line
- second line
* **two_bullets_two_lines** (*int*) --
* first line
continued
* second line
continued
* **one_enumeration_single_line** (*int*) --
1. first line
* **one_enumeration_two_lines** (*int*) --
1) first line
continued
* **two_enumerations_one_line** (*int*) --
(iii) first line
(iv) second line
* **two_enumerations_two_lines** (*int*) --
a. first line
continued
b. second line
continued
* **one_definition_one_line** (*int*) --
item 1
first line
* **one_definition_two_lines** (*int*) --
item 1
first line
continued
* **two_definitions_one_line** (*int*) --
item 1
first line
item 2
second line
* **two_definitions_two_lines** (*int*) --
item 1
first line
continued
item 2
second line
continued
* **one_definition_blank_line** (*int*) --
item 1
first line
extra first line
* **two_definitions_blank_lines** (*int*) --
item 1
first line
extra first line
item 2
second line
extra second line
* **definition_after_normal_text** (*int*) -- text line
item 1
first line
"""
config = Config(napoleon_use_param=False)
actual = str(NumpyDocstring(docstring, config))
self.assertEqual(expected, actual)