Skip to content
75 changes: 73 additions & 2 deletions fire/docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,22 @@ def parse(docstring):
state.returns.lines = []
state.yields.lines = []
state.raises.lines = []
state.line1 = None
state.line1_length = None
state.line2_first_word_length = None
state.line2_length = None
state.line3_first_word_length = None
state.max_line_length = 0

for index, line in enumerate(lines):
has_next = index + 1 < lines_len
previous_line = lines[index - 1] if index > 0 else None
next_line = lines[index + 1] if has_next else None
line_info = _create_line_info(line, next_line, previous_line)
_consume_line(line_info, state)

if state.line2_length:
_merge_if_long_arg(state)

summary = ' '.join(state.summary.lines) if state.summary.lines else None
state.description.lines = _strip_blank_lines(state.description.lines)
Expand Down Expand Up @@ -253,7 +262,7 @@ def _join_lines(lines):
# TODO(dbieber): Add parameters for variations in whitespace handling.
if not lines:
return None

started = False
group_texts = [] # Full text of each section.
group_lines = [] # Lines within the current section.
Expand All @@ -269,7 +278,7 @@ def _join_lines(lines):
group_lines = []

if group_lines: # Process the final group.
group_text = ' '.join(group_lines)
group_text = '\n'.join(group_lines)
group_texts.append(group_text)

return '\n\n'.join(group_texts)
Expand Down Expand Up @@ -391,6 +400,9 @@ def _consume_google_args_line(line_info, state):
"""Consume a single line from a Google args section."""
split_line = line_info.remaining.split(':', 1)
if len(split_line) > 1:
state.line1 = line_info.line
state.line1_length = len(line_info.line)
state.max_line_length = max(state.max_line_length, state.line1_length)
first, second = split_line # first is either the "arg" or "arg (type)"
if _is_arg_name(first.strip()):
arg = _get_or_create_arg_by_name(state, first.strip())
Expand All @@ -410,6 +422,65 @@ def _consume_google_args_line(line_info, state):
else:
if state.current_arg:
state.current_arg.description.lines.append(split_line[0])
state.max_line_length = max(state.max_line_length, len(line_info.line))
if line_info.previous.line == state.line1: # check for line2
state.line2_first_word_length = len(line_info.line.strip().split(' ')[0])
state.line2_length = len(line_info.line)
if line_info.next.line: #check for line3
state.line3_first_word_length = len(line_info.next.line.strip().split(' ')[0])


def _merge_if_long_arg(state):
"""Merges first two lines of the description if the arg name is too long.

Args:
state: The state of the docstring parser.
"""
apparent_max_line_length = roundup(state.max_line_length)
long_arg_name = roundup(len(state.current_arg.name), 5) >= 0.5 * apparent_max_line_length
if long_arg_name and state.line2_first_word_length and state.line3_first_word_length:
line1_intentionally_short = (state.line1_length + state.line2_first_word_length) <= apparent_max_line_length
line2_intentionally_short = (state.line2_length + state.line3_first_word_length) <= apparent_max_line_length
line1_intentionally_long = state.line1_length >= 1.05 * apparent_max_line_length
line2_intentionally_long = state.line2_length >= 1.05 * apparent_max_line_length
if not line1_intentionally_short and not line1_intentionally_long and not line2_intentionally_short and not line2_intentionally_long:
_merge_line1_line2(state.current_arg.description.lines)


def _merge_line1_line2(lines):
"""Merges the first two lines of a list of strings.

Example:
_merge_line1_line2(["oh","no","bro"]) == ["oh no","bro"]

Args:
lines: a list of strings representing each line.
Returns:
the same list but with the first two lines of the list now merged as one line.
"""
merged_line = lines[0] + " " + lines[1]
lines[0] = merged_line
lines.pop(1)
return lines


def roundup(number, multiple=10):
"""Rounds a number to the nearst multiple.

Example:
roundup(72) == 80

Args:
number: an interger type variable.
multiple: nearst multiple to round up to
Returns:
An interger value.
"""
remainder = number % multiple
if remainder == 0:
return number #already rounded
else:
return number + (multiple - remainder)


def _consume_line(line_info, state):
Expand Down
6 changes: 3 additions & 3 deletions fire/docstrings_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def test_google_format_multiline_arg_description(self):
description='The first parameter.'),
ArgInfo(name='param2', type='str',
description='The second parameter. This has a lot of text, '
'enough to cover two lines.'),
'enough to\ncover two lines.'),
],
)
self.assertEqual(expected_docstring_info, docstring_info)
Expand Down Expand Up @@ -229,7 +229,7 @@ def test_numpy_format_typed_args_and_returns(self):
description='The second parameter.'),
],
# TODO(dbieber): Support return type.
returns='bool True if successful, False otherwise.',
returns='bool\nTrue if successful, False otherwise.',
)
self.assertEqual(expected_docstring_info, docstring_info)

Expand Down Expand Up @@ -257,7 +257,7 @@ def test_numpy_format_multiline_arg_description(self):
description='The first parameter.'),
ArgInfo(name='param2', type='str',
description='The second parameter. This has a lot of text, '
'enough to cover two lines.'),
'enough to cover two\nlines.'),
],
)
self.assertEqual(expected_docstring_info, docstring_info)
Expand Down