From 924f13adb7ed57d221eb2d774f652da2dc05590d Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 May 2023 21:40:12 -0400 Subject: [PATCH 01/16] chore: update version info --- docs/source/conf.py | 2 +- pyproject.toml | 2 +- src/docformatter/__pkginfo__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 2167dba..e87e659 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -9,7 +9,7 @@ project = "docformatter" copyright = "2022-2023, Steven Myint" author = "Steven Myint" -release = "1.7.1" +release = "1.7.2-rc1" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/pyproject.toml b/pyproject.toml index 171a4d2..acc055a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "docformatter" -version = "1.7.1" +version = "1.7.2-rc1" description = "Formats docstrings to follow PEP 257" authors = ["Steven Myint"] maintainers = [ diff --git a/src/docformatter/__pkginfo__.py b/src/docformatter/__pkginfo__.py index e98e7ea..c9c6a72 100644 --- a/src/docformatter/__pkginfo__.py +++ b/src/docformatter/__pkginfo__.py @@ -23,4 +23,4 @@ # SOFTWARE. """Package information for docformatter.""" -__version__ = "1.7.1" +__version__ = "1.7.2-rc1" From 9f2a8541c3c8ef91a9d128c4005414638ad27d96 Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Sun, 28 May 2023 10:50:53 -0400 Subject: [PATCH 02/16] fix: excessive whitespace in middle of line --- .github/workflows/do-prioritize-issues.yml | 9 +- docs/source/conf.py | 2 +- pyproject.toml | 2 +- src/docformatter/__pkginfo__.py | 2 +- src/docformatter/format.py | 1 + src/docformatter/syntax.py | 75 +++--- tests/test_docformatter.py | 254 +++++++++------------ tests/test_format_docstring.py | 2 +- 8 files changed, 164 insertions(+), 183 deletions(-) diff --git a/.github/workflows/do-prioritize-issues.yml b/.github/workflows/do-prioritize-issues.yml index c1a80c5..82e4ed2 100644 --- a/.github/workflows/do-prioritize-issues.yml +++ b/.github/workflows/do-prioritize-issues.yml @@ -24,22 +24,19 @@ jobs: uses: weibullguy/get-labels-action@main - name: Add High Urgency Labels - if: | - ${{ (contains(steps.getlabels.outputs.labels, "C: convention") && contains (steps.getlabels.outputs.labels, "P: bug")) }} + if: '${{ (contains(steps.getlabels.outputs.labels, "C: convention") && contains (steps.getlabels.outputs.labels, "P: bug")) }}' uses: andymckay/labeler@master with: add-labels: "U: high" - name: Add Medium Urgency Labels - if: | - ${{ (contains(steps.getlabels.outputs.labels, "C: style") && contains(steps.getlabels.outputs.labels, "P: bug")) || (contains(steps.getlabels.outputs.labels, "C: stakeholder") && contains(steps.getlabels.outputs.labels, "P: bug")) || (contains(steps.getlabels.outputs.labels, "C: convention") && contains(steps.getlabels.outputs.labels, "P: enhancement")) }} + if: '${{ (contains(steps.getlabels.outputs.labels, "C: style") && contains(steps.getlabels.outputs.labels, "P: bug")) || (contains(steps.getlabels.outputs.labels, "C: stakeholder") && contains(steps.getlabels.outputs.labels, "P: bug")) || (contains(steps.getlabels.outputs.labels, "C: convention") && contains(steps.getlabels.outputs.labels, "P: enhancement")) }}' uses: andymckay/labeler@master with: add-labels: "U: medium" - name: Add Low Urgency Labels - if: | - ${{ (contains(steps.getlabels.outputs.labels, "C: style") && contains(steps.getlabels.outputs.labels, "P: enhancement")) || (contains(steps.getlabels.outputs.labels, "C: stakeholder") && contains(steps.getlabels.outputs.labels, "P: enhancement")) || contains(steps.getlabels.outputs.labels, "doc") || contains(steps.getlabels.outputs.labels, "chore") }} + if: '${{ (contains(steps.getlabels.outputs.labels, "C: style") && contains(steps.getlabels.outputs.labels, "P: enhancement")) || (contains(steps.getlabels.outputs.labels, "C: stakeholder") && contains(steps.getlabels.outputs.labels, "P: enhancement")) || contains(steps.getlabels.outputs.labels, "doc") || contains(steps.getlabels.outputs.labels, "chore") }}' uses: andymckay/labeler@master with: add-labels: "U: low" diff --git a/docs/source/conf.py b/docs/source/conf.py index e87e659..30101bb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -9,7 +9,7 @@ project = "docformatter" copyright = "2022-2023, Steven Myint" author = "Steven Myint" -release = "1.7.2-rc1" +release = "1.7.2-rc2" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/pyproject.toml b/pyproject.toml index acc055a..33db9f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "docformatter" -version = "1.7.2-rc1" +version = "1.7.2-rc2" description = "Formats docstrings to follow PEP 257" authors = ["Steven Myint"] maintainers = [ diff --git a/src/docformatter/__pkginfo__.py b/src/docformatter/__pkginfo__.py index c9c6a72..56d888c 100644 --- a/src/docformatter/__pkginfo__.py +++ b/src/docformatter/__pkginfo__.py @@ -23,4 +23,4 @@ # SOFTWARE. """Package information for docformatter.""" -__version__ = "1.7.2-rc1" +__version__ = "1.7.2-rc2" diff --git a/src/docformatter/format.py b/src/docformatter/format.py index 6731bb9..f287876 100644 --- a/src/docformatter/format.py +++ b/src/docformatter/format.py @@ -470,6 +470,7 @@ def _do_format_docstring( # noqa PLR0911 self.args.style, ) or _syntax.do_find_directives(summary) + or _syntax.do_find_links(summary) ): # Something is probably not right with the splitting. return docstring diff --git a/src/docformatter/syntax.py b/src/docformatter/syntax.py index 5c148e3..8807a46 100644 --- a/src/docformatter/syntax.py +++ b/src/docformatter/syntax.py @@ -379,9 +379,20 @@ def do_split_description( _url_idx = do_find_links(text) # Check if the description contains any field lists. - _parameter_idx, _wrap_parameters = do_find_field_lists(text, style) - - if not _url_idx and not (_parameter_idx and _wrap_parameters): + _field_idx, _wrap_fields = do_find_field_lists(text, style) + + # Field list wrapping takes precedence over URL wrapping. + for _fieldl, _fieldu in _field_idx: + for _key, _value in enumerate(_url_idx): + if ( + _value[0] == _fieldl + or _value[0] == _fieldu + or _value[1] == _fieldl + or _value[1] == _fieldu + ): + _url_idx.pop(_key) + + if not _url_idx and not (_field_idx and _wrap_fields): return description_to_list( text, indentation, @@ -397,10 +408,10 @@ def do_split_description( wrap_length, ) - if _parameter_idx: - _lines, _text_idx = do_wrap_parameter_lists( + if _field_idx: + _lines, _text_idx = do_wrap_field_lists( text, - _parameter_idx, + _field_idx, _lines, _text_idx, indentation, @@ -421,22 +432,22 @@ def do_split_description( return _lines -def do_wrap_parameter_lists( # noqa: PLR0913 +def do_wrap_field_lists( # noqa: PLR0913 text: str, - parameter_idx: List[Tuple[int, int]], + field_idx: List[Tuple[int, int]], lines: List[str], text_idx: int, indentation: str, wrap_length: int, ) -> Tuple[List[str], int]: - """Wrap parameter lists in the long description. + """Wrap field lists in the long description. Parameters ---------- text : str The long description text. - parameter_idx : list - The list of parameter list indices found in the description text. + field_idx : list + The list of field list indices found in the description text. lines : list The list of formatted lines in the description that come before the first parameter list item. @@ -456,26 +467,23 @@ def do_wrap_parameter_lists( # noqa: PLR0913 """ lines.extend( description_to_list( - text[text_idx : parameter_idx[0][0]], + text[text_idx : field_idx[0][0]], indentation, wrap_length, ) ) - for _idx, _parameter in enumerate(parameter_idx): + for _idx, _field in enumerate(field_idx): try: - _parameter_description = text[ - _parameter[1] : parameter_idx[_idx + 1][0] - ].strip() + _field_description = text[_field[1] : field_idx[_idx + 1][0]].strip() except IndexError: - _parameter_description = ( - text[_parameter[1] :].strip().replace(" ", "").replace("\t", "") - ) + _field_description = text[ + _field[1] : + ].strip() # .replace(" ", "").replace("\t", "") - if len(_parameter_description) <= (wrap_length - len(indentation)): + if len(_field_description) <= (wrap_length - len(indentation)): lines.append( - f"{indentation}{text[_parameter[0]: _parameter[1]]} " - f"{_parameter_description}" + f"{indentation}{text[_field[0]: _field[1]]} " f"{_field_description}" ) else: if len(indentation) > DEFAULT_INDENT: @@ -483,19 +491,20 @@ def do_wrap_parameter_lists( # noqa: PLR0913 else: _subsequent = 2 * indentation - lines.extend( - textwrap.wrap( - textwrap.dedent( - f"{text[_parameter[0]:_parameter[1]]} " - f"{_parameter_description.strip()}" - ), - width=wrap_length, - initial_indent=indentation, - subsequent_indent=_subsequent, - ) + _wrapped_fields = textwrap.wrap( + textwrap.dedent( + f"{text[_field[0]:_field[1]]} " f"{_field_description.strip()}" + ), + width=wrap_length, + initial_indent=indentation, + subsequent_indent=_subsequent, ) + for _idx, _f in enumerate(_wrapped_fields): + _indent = indentation if _idx == 0 else _subsequent + _wrapped_fields[_idx] = f"{_indent}{re.sub(' +', ' ', _f.strip())}" + lines.extend(_wrapped_fields) - text_idx = _parameter[1] + text_idx = _field[1] return lines, text_idx diff --git a/tests/test_docformatter.py b/tests/test_docformatter.py index 2a19d29..3c7c7b5 100644 --- a/tests/test_docformatter.py +++ b/tests/test_docformatter.py @@ -97,9 +97,7 @@ def foo(): ''' ], ) - @pytest.mark.parametrize( - "diff", [True, False], ids=["show-diff", "no-diff"] - ) + @pytest.mark.parametrize("diff", [True, False], ids=["show-diff", "no-diff"]) def test_in_place(self, temporary_file, contents, diff): """Should make changes and save back to file.""" output_file = io.StringIO() @@ -179,12 +177,8 @@ def test_io_error_exit_code(self): "contents", ["""Totally fine docstring, do not report anything."""], ) - @pytest.mark.parametrize( - "diff", [True, False], ids=["show-diff", "no-diff"] - ) - def test_check_mode_correct_docstring( - self, temporary_file, contents, diff - ): + @pytest.mark.parametrize("diff", [True, False], ids=["show-diff", "no-diff"]) + def test_check_mode_correct_docstring(self, temporary_file, contents, diff): """""" stdout = io.StringIO() stderr = io.StringIO() @@ -214,12 +208,8 @@ def test_check_mode_correct_docstring( ''' ], ) - @pytest.mark.parametrize( - "diff", [True, False], ids=["show-diff", "no-diff"] - ) - def test_check_mode_incorrect_docstring( - self, temporary_file, contents, diff - ): + @pytest.mark.parametrize("diff", [True, False], ids=["show-diff", "no-diff"]) + def test_check_mode_incorrect_docstring(self, temporary_file, contents, diff): """""" stdout = io.StringIO() stderr = io.StringIO() @@ -273,10 +263,7 @@ def foo(): - """ + """Hello world.""" ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -309,10 +296,7 @@ def foo(): + """Hello world this is a summary + that will get wrapped.""" ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -340,10 +324,7 @@ def test_end_to_end_with_no_wrapping_long_sentences( ): """Long sentences remain long with wrapping turned off.""" assert "" == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -373,10 +354,7 @@ def test_end_to_end_with_no_wrapping_short_sentences( ): """Short sentences remain short with wrapping turned off.""" assert "" == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -406,10 +384,7 @@ def foo(): - the trailing period """ + the trailing period.""" ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -448,10 +423,7 @@ def test_end_to_end_keep_rest_link_one_line( See issue #145. See requirement docformatter_10.1.3.1. """ assert "" == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -462,7 +434,7 @@ def test_end_to_end_keep_rest_link_one_line( def foo(): """Description from issue #150 that was being improperly wrapped. - The text file can be retrieved via the Chrome plugin `Get + The text file can be retrieved via the Chrome plugin `Get Cookies.txt ` while browsing.""" ''' @@ -492,8 +464,8 @@ def test_ignore_already_wrapped_link( @@ -1,6 +1,7 @@ def foo(): """Description from issue #150 that was being improperly wrapped. - -- The text file can be retrieved via the Chrome plugin `Get + +- The text file can be retrieved via the Chrome plugin `Get - Cookies.txt ` while browsing.""" + The text file can be retrieved via the Chrome plugin @@ -501,10 +473,63 @@ def foo(): + cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid>` while browsing. + """ ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] + ) + + @pytest.mark.system + @pytest.mark.parametrize( + "contents", + [ + '''\ +"""Create a wrapper around a WiX install. + + :param tools: ToolCache of available tools. + :param wix_home: The path of the WiX installation. + :param bin_install: Is the install a binaries-only install? A full + MSI install of WiX has a `/bin` folder in the paths; a + binaries-only install does not. + :returns: A valid WiX SDK wrapper. If WiX is not available, and was + not installed, raises MissingToolError. + """\ +''' + ], + ) + @pytest.mark.parametrize( + "arguments", + [["--black"]], + ) + def test_end_to_end_no_excessive_whitespace( + self, + run_docformatter, + temporary_file, + arguments, + contents, + ): + """Remove all excess whitespace in the middle of wrappped lines. + + See issue #222. + """ + assert '''\ +@@ -1,10 +1,9 @@ + """Create a wrapper around a WiX install. + +- :param tools: ToolCache of available tools. +- :param wix_home: The path of the WiX installation. +- :param bin_install: Is the install a binaries-only install? A full +- MSI install of WiX has a `/bin` folder in the paths; a +- binaries-only install does not. +- :returns: A valid WiX SDK wrapper. If WiX is not available, and was +- not installed, raises MissingToolError. +- """ ++:param tools: ToolCache of available tools. ++:param wix_home: The path of the WiX installation. ++:param bin_install: Is the install a binaries-only install? A full MSI install of ++WiX has a `/bin` folder in the paths; a binaries-only install does not. ++:returns: A valid WiX SDK wrapper. If WiX is not available, and was not installed, ++raises MissingToolError. ++""" +''' == "\n".join( + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -551,14 +576,11 @@ def foo(): + - My list item - My list item - + - """ ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -578,15 +600,9 @@ def test_invalid_range( ): """""" if arguments[1] == "0": - assert ( - "must be positive" - in run_docformatter.communicate()[1].decode() - ) + assert "must be positive" in run_docformatter.communicate()[1].decode() if arguments[1] == "3": - assert ( - "should be less than" - in run_docformatter.communicate()[1].decode() - ) + assert "should be less than" in run_docformatter.communicate()[1].decode() @pytest.mark.system @pytest.mark.parametrize("arguments", [[]]) @@ -644,14 +660,10 @@ def test_standard_in_with_invalid_options( assert "cannot mix" in run_docformatter.communicate()[1].decode() if arguments[0] == "--in-place": - assert ( - "cannot be used" in run_docformatter.communicate()[1].decode() - ) + assert "cannot be used" in run_docformatter.communicate()[1].decode() if arguments[0] == "--recursive": - assert ( - "cannot be used" in run_docformatter.communicate()[1].decode() - ) + assert "cannot be used" in run_docformatter.communicate()[1].decode() class TestEndToEndPyproject: @@ -702,12 +714,9 @@ def test_no_pre_summary_space_using_pyproject( @@ -1,3 +1,2 @@ class TestFoo(): """Docstring that should not have a pre-summary space.""" -- +- ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -755,13 +764,10 @@ def test_pre_summary_space_using_pyproject( @@ -1,3 +1,2 @@ class TestFoo(): - """Docstring that should have a pre-summary space.""" -- +- + """ Docstring that should have a pre-summary space.""" ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -771,8 +777,8 @@ class TestFoo(): '''\ class TestFoo(): """Docstring that should not have a pre-summary newline. - - This is a multi-line docstring that should not have a + + This is a multi-line docstring that should not have a newline placed before the summary.""" ''' ], @@ -812,19 +818,16 @@ def test_no_pre_summary_newline_using_pyproject( @@ -1,6 +1,6 @@ class TestFoo(): """Docstring that should not have a pre-summary newline. -- -- This is a multi-line docstring that should not have a +- +- This is a multi-line docstring that should not have a - newline placed before the summary.""" -- +- + + This is a multi-line docstring that should not have a + newline placed before the summary. + """ ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -835,7 +838,7 @@ class TestFoo(): class TestFoo(): """Docstring that should have a pre-summary newline. - This is a multi-line docstring that should have a newline + This is a multi-line docstring that should have a newline placed before the summary.""" ''' ], @@ -877,17 +880,14 @@ class TestFoo(): - """Docstring that should have a pre-summary newline. + """ + Docstring that should have a pre-summary newline. - -- This is a multi-line docstring that should have a newline + +- This is a multi-line docstring that should have a newline - placed before the summary.""" + This is a multi-line docstring that should have a newline placed + before the summary. + """ ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -896,7 +896,7 @@ class TestFoo(): [ '''\ class TestFoo(): - """Really long summary docstring that should not be + """Really long summary docstring that should not be split into a multiline summary.""" ''' ], @@ -935,16 +935,13 @@ def test_no_pre_summary_multiline_using_pyproject( assert '''\ @@ -1,4 +1,3 @@ class TestFoo(): -- """Really long summary docstring that should not be +- """Really long summary docstring that should not be - split into a multiline summary.""" -- +- + """Really long summary docstring that should not be split + into a multiline summary.""" ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -953,7 +950,7 @@ class TestFoo(): [ '''\ class TestFoo(): - """Really long summary docstring that should be + """Really long summary docstring that should be split into a multiline summary.""" ''' ], @@ -992,16 +989,13 @@ def test_pre_summary_multiline_using_pyproject( assert '''\ @@ -1,4 +1,3 @@ class TestFoo(): -- """Really long summary docstring that should be +- """Really long summary docstring that should be - split into a multiline summary.""" -- +- + """Really long summary docstring that should be split + into a multiline summary.""" ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -1012,7 +1006,7 @@ class TestFoo(): class TestFoo(): """Summary docstring that is followed by a description. - This is the description and it shouldn't have a blank line + This is the description and it shouldn't have a blank line inserted after it. """ ''' @@ -1053,17 +1047,14 @@ def test_no_blank_using_pyproject( @@ -1,6 +1,6 @@ class TestFoo(): """Summary docstring that is followed by a description. - -- This is the description and it shouldn\'t have a blank line + +- This is the description and it shouldn\'t have a blank line - inserted after it. + This is the description and it shouldn\'t have a blank line inserted + after it. """ ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -1074,7 +1065,7 @@ class TestFoo(): class TestFoo(): """Summary docstring that is followed by a description. - This is the description and it should have a blank line + This is the description and it should have a blank line inserted after it. """ ''' @@ -1115,18 +1106,15 @@ def test_blank_using_pyproject( @@ -1,6 +1,7 @@ class TestFoo(): """Summary docstring that is followed by a description. - -- This is the description and it should have a blank line + +- This is the description and it should have a blank line - inserted after it. + This is the description and it should have a blank line inserted + after it. + """ ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -1135,7 +1123,7 @@ class TestFoo(): [ '''\ class foo(): - """Hello world is a long sentence that will be wrapped at 12 + """Hello world is a long sentence that will be wrapped at 12 characters because I\'m using that option in pyproject.toml.""" ''' ], @@ -1174,7 +1162,7 @@ def test_format_wrap_using_pyproject( assert '''\ @@ -1,3 +1,18 @@ class foo(): -- """Hello world is a long sentence that will be wrapped at 12 +- """Hello world is a long sentence that will be wrapped at 12 - characters because I\'m using that option in pyproject.toml.""" + """Hello + world is @@ -1194,10 +1182,7 @@ class foo(): + ject.tom + l.""" ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @@ -1251,10 +1236,7 @@ class TestFoo(): - """ Docstring that should not have a pre-summary space.""" + """Docstring that should not have a pre-summary space.""" ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @pytest.mark.system @@ -1301,10 +1283,7 @@ def test_in_place_using_setup_cfg( See issue #122. """ assert "" == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) with open(temporary_file, "r") as f: assert ( @@ -1359,9 +1338,7 @@ def test_check_using_setup_cfg( See issue #122. """ _results = run_docformatter.communicate() - assert "" == "\n".join( - _results[0].decode().replace("\r", "").split("\n")[2:] - ) + assert "" == "\n".join(_results[0].decode().replace("\r", "").split("\n")[2:]) assert temporary_file == _results[1].decode().rstrip("\n") @pytest.mark.system @@ -1412,8 +1389,5 @@ class TestFoo(): - """ Docstring that should not have a pre-summary space.""" + """Docstring that should not have a pre-summary space.""" ''' == "\n".join( - run_docformatter.communicate()[0] - .decode() - .replace("\r", "") - .split("\n")[2:] + run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) diff --git a/tests/test_format_docstring.py b/tests/test_format_docstring.py index 515f89c..077643b 100644 --- a/tests/test_format_docstring.py +++ b/tests/test_format_docstring.py @@ -1931,7 +1931,7 @@ def test_format_docstring_sphinx_style_remove_excess_whitespace( ): """Should remove unneeded whitespace. - See issue #217 + See issue #217 and #222 """ uut = Formatter( test_args, From 2861ffe709cdcd4cde776596056ab7f43aef081b Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Sun, 28 May 2023 19:53:29 -0400 Subject: [PATCH 03/16] test: fix failing tests --- src/docformatter/syntax.py | 7 ++- tests/test_docformatter.py | 113 ++++++++++++++++++------------------- 2 files changed, 59 insertions(+), 61 deletions(-) diff --git a/src/docformatter/syntax.py b/src/docformatter/syntax.py index 8807a46..b6a0c5a 100644 --- a/src/docformatter/syntax.py +++ b/src/docformatter/syntax.py @@ -499,9 +499,12 @@ def do_wrap_field_lists( # noqa: PLR0913 initial_indent=indentation, subsequent_indent=_subsequent, ) - for _idx, _f in enumerate(_wrapped_fields): + for _idx, _wrapped_field in enumerate(_wrapped_fields): _indent = indentation if _idx == 0 else _subsequent - _wrapped_fields[_idx] = f"{_indent}{re.sub(' +', ' ', _f.strip())}" + _wrapped_fields[ + _idx + ] = f"{_indent}{re.sub(' +', ' ', _wrapped_field.strip())}" + lines.extend(_wrapped_fields) text_idx = _field[1] diff --git a/tests/test_docformatter.py b/tests/test_docformatter.py index 3c7c7b5..f67a32a 100644 --- a/tests/test_docformatter.py +++ b/tests/test_docformatter.py @@ -464,7 +464,7 @@ def test_ignore_already_wrapped_link( @@ -1,6 +1,7 @@ def foo(): """Description from issue #150 that was being improperly wrapped. - + - The text file can be retrieved via the Chrome plugin `Get - Cookies.txt ` while browsing.""" @@ -512,7 +512,7 @@ def test_end_to_end_no_excessive_whitespace( assert '''\ @@ -1,10 +1,9 @@ """Create a wrapper around a WiX install. - + - :param tools: ToolCache of available tools. - :param wix_home: The path of the WiX installation. - :param bin_install: Is the install a binaries-only install? A full @@ -576,7 +576,7 @@ def foo(): + - My list item - My list item - + - """ ''' == "\n".join( @@ -714,7 +714,7 @@ def test_no_pre_summary_space_using_pyproject( @@ -1,3 +1,2 @@ class TestFoo(): """Docstring that should not have a pre-summary space.""" -- +- ''' == "\n".join( run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @@ -723,10 +723,10 @@ class TestFoo(): @pytest.mark.parametrize( "contents", [ - '''\ - class TestFoo(): - """Docstring that should have a pre-summary space.""" - ''' +'''\ +class TestFoo(): + """Docstring that should have a pre-summary space.""" +''' ], ) @pytest.mark.parametrize( @@ -761,11 +761,10 @@ def test_pre_summary_space_using_pyproject( See issue #119. """ assert '''\ -@@ -1,3 +1,2 @@ - class TestFoo(): -- """Docstring that should have a pre-summary space.""" -- -+ """ Docstring that should have a pre-summary space.""" +@@ -1,2 +1,2 @@ + class TestFoo(): +- """Docstring that should have a pre-summary space.""" ++ """ Docstring that should have a pre-summary space.""" ''' == "\n".join( run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @@ -774,13 +773,13 @@ class TestFoo(): @pytest.mark.parametrize( "contents", [ - '''\ - class TestFoo(): - """Docstring that should not have a pre-summary newline. +'''\ +class TestFoo(): + """Docstring that should not have a pre-summary newline. - This is a multi-line docstring that should not have a - newline placed before the summary.""" - ''' + This is a multi-line docstring that should not have a + newline placed before the summary.""" +''' ], ) @pytest.mark.parametrize( @@ -815,17 +814,15 @@ def test_no_pre_summary_newline_using_pyproject( See issue #119. """ assert '''\ -@@ -1,6 +1,6 @@ - class TestFoo(): - """Docstring that should not have a pre-summary newline. -- -- This is a multi-line docstring that should not have a -- newline placed before the summary.""" -- -+ -+ This is a multi-line docstring that should not have a -+ newline placed before the summary. -+ """ +@@ -1,5 +1,6 @@ + class TestFoo(): + """Docstring that should not have a pre-summary newline. + +- This is a multi-line docstring that should not have a +- newline placed before the summary.""" ++ This is a multi-line docstring that should not have a newline placed ++ before the summary. ++ """ ''' == "\n".join( run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @@ -880,7 +877,7 @@ class TestFoo(): - """Docstring that should have a pre-summary newline. + """ + Docstring that should have a pre-summary newline. - + - This is a multi-line docstring that should have a newline - placed before the summary.""" + This is a multi-line docstring that should have a newline placed @@ -894,11 +891,11 @@ class TestFoo(): @pytest.mark.parametrize( "contents", [ - '''\ - class TestFoo(): - """Really long summary docstring that should not be - split into a multiline summary.""" - ''' +'''\ + class TestFoo(): + """Really long summary docstring that should not be + split into a multiline summary.""" +''' ], ) @pytest.mark.parametrize( @@ -933,13 +930,12 @@ def test_no_pre_summary_multiline_using_pyproject( See issue #119. """ assert '''\ -@@ -1,4 +1,3 @@ - class TestFoo(): -- """Really long summary docstring that should not be -- split into a multiline summary.""" -- -+ """Really long summary docstring that should not be split -+ into a multiline summary.""" +@@ -1,3 +1,3 @@ + class TestFoo(): +- """Really long summary docstring that should not be +- split into a multiline summary.""" ++ """Really long summary docstring that should not be split into a ++ multiline summary.""" ''' == "\n".join( run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @@ -948,11 +944,11 @@ class TestFoo(): @pytest.mark.parametrize( "contents", [ - '''\ - class TestFoo(): - """Really long summary docstring that should be - split into a multiline summary.""" - ''' +'''\ + class TestFoo(): + """Really long summary docstring that should be + split into a multiline summary.""" +''' ], ) @pytest.mark.parametrize( @@ -987,13 +983,12 @@ def test_pre_summary_multiline_using_pyproject( See issue #119. """ assert '''\ -@@ -1,4 +1,3 @@ - class TestFoo(): -- """Really long summary docstring that should be -- split into a multiline summary.""" -- -+ """Really long summary docstring that should be split -+ into a multiline summary.""" +@@ -1,3 +1,3 @@ + class TestFoo(): +- """Really long summary docstring that should be +- split into a multiline summary.""" ++ """Really long summary docstring that should be split into a multiline ++ summary.""" ''' == "\n".join( run_docformatter.communicate()[0].decode().replace("\r", "").split("\n")[2:] ) @@ -1002,7 +997,7 @@ class TestFoo(): @pytest.mark.parametrize( "contents", [ - '''\ +'''\ class TestFoo(): """Summary docstring that is followed by a description. @@ -1047,7 +1042,7 @@ def test_no_blank_using_pyproject( @@ -1,6 +1,6 @@ class TestFoo(): """Summary docstring that is followed by a description. - + - This is the description and it shouldn\'t have a blank line - inserted after it. + This is the description and it shouldn\'t have a blank line inserted @@ -1061,7 +1056,7 @@ class TestFoo(): @pytest.mark.parametrize( "contents", [ - '''\ +'''\ class TestFoo(): """Summary docstring that is followed by a description. @@ -1106,7 +1101,7 @@ def test_blank_using_pyproject( @@ -1,6 +1,7 @@ class TestFoo(): """Summary docstring that is followed by a description. - + - This is the description and it should have a blank line - inserted after it. + This is the description and it should have a blank line inserted From 29b52826844f4b4376dc457af2cd289209e92a0c Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Tue, 30 May 2023 22:04:05 -0400 Subject: [PATCH 04/16] fix: space after field with no description --- docs/source/conf.py | 2 +- pyproject.toml | 2 +- src/docformatter/__pkginfo__.py | 2 +- src/docformatter/syntax.py | 17 +++++++++-------- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 30101bb..16ff3e2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -9,7 +9,7 @@ project = "docformatter" copyright = "2022-2023, Steven Myint" author = "Steven Myint" -release = "1.7.2-rc2" +release = "1.7.2-rc3" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/pyproject.toml b/pyproject.toml index 33db9f2..2745976 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "docformatter" -version = "1.7.2-rc2" +version = "1.7.2-rc3" description = "Formats docstrings to follow PEP 257" authors = ["Steven Myint"] maintainers = [ diff --git a/src/docformatter/__pkginfo__.py b/src/docformatter/__pkginfo__.py index 56d888c..3a9be95 100644 --- a/src/docformatter/__pkginfo__.py +++ b/src/docformatter/__pkginfo__.py @@ -23,4 +23,4 @@ # SOFTWARE. """Package information for docformatter.""" -__version__ = "1.7.2-rc2" +__version__ = "1.7.2-rc3" diff --git a/src/docformatter/syntax.py b/src/docformatter/syntax.py index b6a0c5a..5e85ba1 100644 --- a/src/docformatter/syntax.py +++ b/src/docformatter/syntax.py @@ -477,13 +477,14 @@ def do_wrap_field_lists( # noqa: PLR0913 try: _field_description = text[_field[1] : field_idx[_idx + 1][0]].strip() except IndexError: - _field_description = text[ - _field[1] : - ].strip() # .replace(" ", "").replace("\t", "") + _field_description = text[_field[1] :].strip() + + if _field_description: + _field_description = f" {_field_description}" if len(_field_description) <= (wrap_length - len(indentation)): lines.append( - f"{indentation}{text[_field[0]: _field[1]]} " f"{_field_description}" + f"{indentation}{text[_field[0]: _field[1]]}{_field_description}" ) else: if len(indentation) > DEFAULT_INDENT: @@ -493,16 +494,16 @@ def do_wrap_field_lists( # noqa: PLR0913 _wrapped_fields = textwrap.wrap( textwrap.dedent( - f"{text[_field[0]:_field[1]]} " f"{_field_description.strip()}" + f"{text[_field[0]:_field[1]]} {_field_description.strip()}" ), width=wrap_length, initial_indent=indentation, subsequent_indent=_subsequent, ) - for _idx, _wrapped_field in enumerate(_wrapped_fields): - _indent = indentation if _idx == 0 else _subsequent + for _wrapped_idx, _wrapped_field in enumerate(_wrapped_fields): + _indent = indentation if _wrapped_idx == 0 else _subsequent _wrapped_fields[ - _idx + _wrapped_idx ] = f"{_indent}{re.sub(' +', ' ', _wrapped_field.strip())}" lines.extend(_wrapped_fields) From 1f6a39d473c4ee601c8a83a6ac6b64f9f7a16fbe Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Fri, 2 Jun 2023 01:45:35 -0400 Subject: [PATCH 05/16] fix: wrapping reST section admonitions --- docs/source/conf.py | 2 +- pyproject.toml | 5 ++- src/docformatter/__main__.py | 3 ++ src/docformatter/__pkginfo__.py | 2 +- src/docformatter/configuration.py | 16 +++++-- src/docformatter/format.py | 2 + src/docformatter/syntax.py | 9 +++- tests/conftest.py | 4 ++ tests/test_utility_functions.py | 70 ++++++++++++++++++++----------- 9 files changed, 81 insertions(+), 32 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 16ff3e2..6f666e1 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -9,7 +9,7 @@ project = "docformatter" copyright = "2022-2023, Steven Myint" author = "Steven Myint" -release = "1.7.2-rc3" +release = "1.7.2-rc4" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/pyproject.toml b/pyproject.toml index 2745976..8833b1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "docformatter" -version = "1.7.2-rc3" +version = "1.7.2-rc4" description = "Formats docstrings to follow PEP 257" authors = ["Steven Myint"] maintainers = [ @@ -95,6 +95,9 @@ disable = [ [tool.docformatter] black = true non-strict = false +non-cap = [ + "docformatter", +] [tool.pydocstyle] convention = "pep257" diff --git a/src/docformatter/__main__.py b/src/docformatter/__main__.py index 54384aa..2ad6d01 100755 --- a/src/docformatter/__main__.py +++ b/src/docformatter/__main__.py @@ -69,6 +69,9 @@ def _help(): -s style, --style style the docstring style to use when formatting parameter lists. One of epytext, sphinx. (default: sphinx) + --rest-section-adorns REGEX + regular expression for identifying reST section adornments + (default: [!\"#$%&'()*+,-./\\:;<=>?@[]^_`{|}~]{4,}) --black make formatting compatible with standard black options (default: False) --wrap-summaries length diff --git a/src/docformatter/__pkginfo__.py b/src/docformatter/__pkginfo__.py index 3a9be95..e3e420f 100644 --- a/src/docformatter/__pkginfo__.py +++ b/src/docformatter/__pkginfo__.py @@ -23,4 +23,4 @@ # SOFTWARE. """Package information for docformatter.""" -__version__ = "1.7.2-rc3" +__version__ = "1.7.2-rc4" diff --git a/src/docformatter/configuration.py b/src/docformatter/configuration.py index 939102d..248ddb7 100644 --- a/src/docformatter/configuration.py +++ b/src/docformatter/configuration.py @@ -139,9 +139,8 @@ def do_parse_arguments(self) -> None: action="store", nargs="*", default=self.flargs_dct.get("non-cap", None), - help="list of words not to capitalize " - "when they appear as the " - "first word in the summary", + help="list of words not to capitalize when they appear as the first word " + "in the summary", ) self.parser.add_argument( "--black", @@ -171,6 +170,15 @@ def do_parse_arguments(self) -> None: help="name of the docstring style to use when formatting " "parameter lists (default: sphinx)", ) + self.parser.add_argument( + "--rest-section-adorns", + type=str, + dest="rest_section_adorns", + default=self.flargs_dct.get( + "rest_section_adorns", r"[!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]{4,}" + ), + help="regex for identifying reST section header adornments", + ) self.parser.add_argument( "--wrap-summaries", default=int(self.flargs_dct.get("wrap-summaries", _default_wrap_summaries)), @@ -229,7 +237,7 @@ def do_parse_arguments(self) -> None: "pre-summary-space", _default_pre_summary_space ).lower() == "true", - help="add a space after the opening triple quotes " "(default: False)", + help="add a space after the opening triple quotes (default: False)", ) self.parser.add_argument( "--make-summary-multi-line", diff --git a/src/docformatter/format.py b/src/docformatter/format.py index f287876..0f52bfd 100644 --- a/src/docformatter/format.py +++ b/src/docformatter/format.py @@ -467,6 +467,7 @@ def _do_format_docstring( # noqa PLR0911 _syntax.is_some_sort_of_list( summary, self.args.non_strict, + self.args.rest_section_adorns, self.args.style, ) or _syntax.do_find_directives(summary) @@ -589,6 +590,7 @@ def _do_format_multiline_docstring( wrap_length=self.args.wrap_descriptions, force_wrap=self.args.force_wrap, strict=self.args.non_strict, + rest_sections=self.args.rest_section_adorns, style=self.args.style, ) post_description = "\n" if self.args.post_description_blank else "" diff --git a/src/docformatter/syntax.py b/src/docformatter/syntax.py index 5e85ba1..fdd8c2a 100644 --- a/src/docformatter/syntax.py +++ b/src/docformatter/syntax.py @@ -623,6 +623,7 @@ def is_some_sort_of_field_list( def is_some_sort_of_list( text: str, strict: bool, + rest_sections: str, style: str, ) -> bool: """Determine if docstring is a reST list. @@ -666,6 +667,11 @@ def is_some_sort_of_list( # "1. item" <-- Enumerated list re.match(ENUM_REGEX, line) or + # "====\ndescription\n====" <-- reST section + # "----\ndescription\n----" <-- reST section + # "description\n----" <-- reST section + re.match(rest_sections, line) + or # "-a description" <-- Option list # "--long description" <-- Option list re.match(OPTION_REGEX, line) @@ -785,6 +791,7 @@ def wrap_description( # noqa: PLR0913 wrap_length, force_wrap, strict, + rest_sections, style: str = "sphinx", ): """Return line-wrapped description text. @@ -828,7 +835,7 @@ def wrap_description( # noqa: PLR0913 and ( is_some_sort_of_code(text) or do_find_directives(text) - or is_some_sort_of_list(text, strict, style) + or is_some_sort_of_list(text, strict, rest_sections, style) ) ): return text diff --git a/tests/conftest.py b/tests/conftest.py index 204b05d..762d246 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -177,6 +177,10 @@ def test_args(args): "--style", default="sphinx", ) + parser.add_argument( + "--rest-section-adorns", + default=r"[=\-`:'\"~^_*+#<>]{4,}", + ) parser.add_argument( "--wrap-summaries", default=79, diff --git a/tests/test_utility_functions.py b/tests/test_utility_functions.py index b21d56b..256aa9e 100644 --- a/tests/test_utility_functions.py +++ b/tests/test_utility_functions.py @@ -43,6 +43,8 @@ # docformatter Package Imports import docformatter +REST_SECTION_REGEX = r"[=\-`:'\"~^_*+#<>]{4,}" + class TestFindPyFiles: """Class for testing the find_py_files() function.""" @@ -108,9 +110,7 @@ def test_is_excluded(self): "/root/folder_one/one.py", "/root/folder_two/two.py", ] - test_exclude_py = list( - docformatter.find_py_files(sources, True, ".py") - ) + test_exclude_py = list(docformatter.find_py_files(sources, True, ".py")) assert not test_exclude_py test_exclude_two_and_three = list( docformatter.find_py_files( @@ -121,9 +121,7 @@ def test_is_excluded(self): test_exclude_files = list( docformatter.find_py_files(sources, True, ["one.py", "two.py"]) ) - assert test_exclude_files == [ - "/root/folder_one/folder_three/three.py" - ] + assert test_exclude_files == ["/root/folder_one/folder_three/three.py"] @pytest.mark.unit def test_nothing_is_excluded(self): @@ -139,17 +137,13 @@ def test_nothing_is_excluded(self): with patch("os.walk", return_value=patch_data), patch( "os.path.isdir", return_value=True ): - test_exclude_nothing = list( - docformatter.find_py_files(sources, True, []) - ) + test_exclude_nothing = list(docformatter.find_py_files(sources, True, [])) assert test_exclude_nothing == [ "/root/folder_one/one.py", "/root/folder_one/folder_three/three.py", "/root/folder_two/two.py", ] - test_exclude_nothing = list( - docformatter.find_py_files(sources, True) - ) + test_exclude_nothing = list(docformatter.find_py_files(sources, True)) assert test_exclude_nothing == [ "/root/folder_one/one.py", "/root/folder_one/folder_three/three.py", @@ -161,7 +155,7 @@ class TestHasCorrectLength: """Class for testing the has_correct_length() function.""" @pytest.mark.unit - def test_has_correct_length(self): + def test_has_correct_length_none(self): """Return True when passed line_length=None.""" assert docformatter.has_correct_length(None, 1, 9) @@ -243,9 +237,7 @@ def test_do_find_miscellaneous_link(self): assert docformatter.do_find_links(text) == [(28, 44)] text = "This is a bitcoin URL pattern: bitcoin:
[?[amount=][&][label=