diff --git a/fire/docstrings.py b/fire/docstrings.py index e173d192..244f5769 100644 --- a/fire/docstrings.py +++ b/fire/docstrings.py @@ -295,9 +295,8 @@ def _is_arg_name(name): """Returns whether name is a valid arg name. This is used to prevent multiple words (plaintext) from being misinterpreted - as an argument name. So if ":" appears in the middle of a line in a docstring, - we don't accidentally interpret the first half of that line as a single arg - name. + as an argument name. Any line that doesn't match the pattern for a valid + argument is treated as not being an argument. Args: name: The name of the potential arg. @@ -305,9 +304,11 @@ def _is_arg_name(name): True if name looks like an arg name, False otherwise. """ name = name.strip() - return (name - and ' ' not in name - and ':' not in name) + # arg_pattern is a letter or underscore followed by + # zero or more letters, numbers, or underscores. + arg_pattern = r'^[a-zA-Z_]\w*$' + re.match(arg_pattern, name) + return re.match(arg_pattern, name) is not None def _as_arg_name_and_type(text): @@ -390,6 +391,7 @@ def _consume_google_args_line(line_info, state): arg = _get_or_create_arg_by_name(state, arg_name) arg.type.lines.append(type_str) arg.description.lines.append(second.strip()) + state.current_arg = arg else: if state.current_arg: state.current_arg.description.lines.append(split_line[0]) diff --git a/fire/docstrings_test.py b/fire/docstrings_test.py index 8b1d7685..fcb1b115 100644 --- a/fire/docstrings_test.py +++ b/fire/docstrings_test.py @@ -142,6 +142,32 @@ def test_google_format_typed_args_and_returns(self): ) self.assertEqual(expected_docstring_info, docstring_info) + def test_google_format_multiline_arg_description(self): + docstring = """Docstring summary. + + This is a longer description of the docstring. It spans multiple lines, as + is allowed. + + Args: + param1 (int): The first parameter. + param2 (str): The second parameter. This has a lot of text, enough to + cover two lines. + """ + docstring_info = docstrings.parse(docstring) + expected_docstring_info = DocstringInfo( + summary='Docstring summary.', + description='This is a longer description of the docstring. It spans ' + 'multiple lines, as\nis allowed.', + args=[ + ArgInfo(name='param1', type='int', + description='The first parameter.'), + ArgInfo(name='param2', type='str', + description='The second parameter. This has a lot of text, ' + 'enough to cover two lines.'), + ], + ) + self.assertEqual(expected_docstring_info, docstring_info) + def test_rst_format_typed_args_and_returns(self): docstring = """Docstring summary. @@ -205,6 +231,35 @@ def test_numpy_format_typed_args_and_returns(self): ) self.assertEqual(expected_docstring_info, docstring_info) + def test_numpy_format_multiline_arg_description(self): + docstring = """Docstring summary. + + This is a longer description of the docstring. It spans across multiple + lines. + + Parameters + ---------- + param1 : int + The first parameter. + param2 : str + The second parameter. This has a lot of text, enough to cover two + lines. + """ + docstring_info = docstrings.parse(docstring) + expected_docstring_info = DocstringInfo( + summary='Docstring summary.', + description='This is a longer description of the docstring. It spans ' + 'across multiple\nlines.', + args=[ + ArgInfo(name='param1', type='int', + description='The first parameter.'), + ArgInfo(name='param2', type='str', + description='The second parameter. This has a lot of text, ' + 'enough to cover two lines.'), + ], + ) + self.assertEqual(expected_docstring_info, docstring_info) + def test_multisection_docstring(self): docstring = """Docstring summary.