From c7ee0f3659ec03470ab207819772bbf147335b70 Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Fri, 16 Dec 2022 08:56:20 -0500 Subject: [PATCH] Fix incorrect pyproject.toml parsing of boolean values (#133) * fix: boolean options read properly from pyproject * test: add tests to explicitly check pyproject options --- src/docformatter/configuration.py | 32 +- tests/conftest.py | 11 + tests/test_docformatter.py | 574 +++++++++++++++++++++++++++++- 3 files changed, 592 insertions(+), 25 deletions(-) diff --git a/src/docformatter/configuration.py b/src/docformatter/configuration.py index c484bf7..23e8bcd 100644 --- a/src/docformatter/configuration.py +++ b/src/docformatter/configuration.py @@ -113,7 +113,8 @@ def do_parse_arguments(self) -> None: "-r", "--recursive", action="store_true", - default=bool(self.flargs_dct.get("recursive", False)), + default=self.flargs_dct.get("recursive", "false").lower() + == "true", help="drill down directories recursively", ) self.parser.add_argument( @@ -141,7 +142,8 @@ def do_parse_arguments(self) -> None: self.parser.add_argument( "--force-wrap", action="store_true", - default=bool(self.flargs_dct.get("force-wrap", False)), + default=self.flargs_dct.get("force-wrap", "false").lower() + == "true", help="force descriptions to be wrapped even if it may " "result in a mess (default: False)", ) @@ -158,38 +160,42 @@ def do_parse_arguments(self) -> None: "--blank", dest="post_description_blank", action="store_true", - default=bool(self.flargs_dct.get("blank", False)), + default=self.flargs_dct.get("blank", "false").lower() == "true", help="add blank line after description (default: False)", ) self.parser.add_argument( "--pre-summary-newline", action="store_true", - default=bool(self.flargs_dct.get("pre-summary-newline", False)), + default=self.flargs_dct.get("pre-summary-newline", "false").lower() + == "true", help="add a newline before the summary of a multi-line docstring " "(default: False)", ) self.parser.add_argument( "--pre-summary-space", action="store_true", - default=bool(self.flargs_dct.get("pre-summary-space", False)), + default=self.flargs_dct.get("pre-summary-space", "false").lower() + == "true", help="add a space after the opening triple quotes " "(default: False)", ) self.parser.add_argument( "--make-summary-multi-line", action="store_true", - default=bool( - self.flargs_dct.get("make-summary-multi-line", False) - ), + default=self.flargs_dct.get( + "make-summary-multi-line", "false" + ).lower() + == "true", help="add a newline before and after the summary of a one-line " "docstring (default: False)", ) self.parser.add_argument( "--close-quotes-on-newline", action="store_true", - default=bool( - self.flargs_dct.get("close-quotes-on-newline", False) - ), + default=self.flargs_dct.get( + "close-quotes-on-newline", "false" + ).lower() + == "true", help="place closing triple quotes on a new-line when a " "one-line docstring wraps to two or more lines " "(default: False)", @@ -217,7 +223,8 @@ def do_parse_arguments(self) -> None: self.parser.add_argument( "--non-strict", action="store_true", - default=bool(self.flargs_dct.get("non-strict", False)), + default=self.flargs_dct.get("non-strict", "false").lower() + == "true", help="don't strictly follow reST syntax to identify lists (see " "issue #67) (default: False)", ) @@ -238,7 +245,6 @@ def do_parse_arguments(self) -> None: ) self.args = self.parser.parse_args(self.args_lst[1:]) - if self.args.line_range: if self.args.line_range[0] <= 0: self.parser.error("--range must be positive numbers") diff --git a/tests/conftest.py b/tests/conftest.py index 463af60..7612d2c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -64,6 +64,17 @@ def temporary_file(contents, file_directory=".", file_prefix=""): finally: os.remove(f.name) +@pytest.fixture(scope="function") +def temporary_config(config, file_directory="/tmp", + file_name="pyproject.toml"): + """Write contents to temporary configuration and yield it.""" + f = open(f"{file_directory}/{file_name}", "wb") + try: + f.write(config.encode()) + f.close() + yield f.name + finally: + os.remove(f.name) @pytest.fixture(scope="function") def run_docformatter(arguments, temporary_file): diff --git a/tests/test_docformatter.py b/tests/test_docformatter.py index f2b26ac..ca3c898 100644 --- a/tests/test_docformatter.py +++ b/tests/test_docformatter.py @@ -430,7 +430,6 @@ def foo(): "arguments", [ [ - "--wrap-summaries=40", "--wrap-summaries=40", "--pre-summary-newline", "--blank", @@ -493,32 +492,583 @@ def test_invalid_range(self, run_docformatter, arguments): @pytest.mark.parametrize("contents", [""]) def test_no_arguments(self, run_docformatter, arguments): """""" - assert '' == run_docformatter.communicate()[1].decode() + assert "" == run_docformatter.communicate()[1].decode() @pytest.mark.system - @pytest.mark.parametrize("arguments", [['-']]) + @pytest.mark.parametrize("arguments", [["-"]]) @pytest.mark.parametrize("contents", [""]) def test_standard_in(self, run_docformatter, arguments): - result = run_docformatter.communicate('''\ + result = ( + run_docformatter.communicate( + '''\ """ Hello world""" -'''.encode())[0].decode().replace("\r", "") +'''.encode() + )[0] + .decode() + .replace("\r", "") + ) assert 0 == run_docformatter.returncode assert '''"""Hello world."""\n''' == result @pytest.mark.system - @pytest.mark.parametrize("arguments", [['foo.py','-'], ['--in-place', - '-'], - ['--recursive', '-'],]) + @pytest.mark.parametrize( + "arguments", + [ + ["foo.py", "-"], + ["--in-place", "-"], + ["--recursive", "-"], + ], + ) @pytest.mark.parametrize("contents", [""]) - def test_standard_in_with_invalid_options(self, run_docformatter, arguments): + def test_standard_in_with_invalid_options( + self, run_docformatter, arguments + ): """""" if arguments[0] == "foo.py": - assert 'cannot mix' in run_docformatter.communicate()[1].decode() + 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: + """Class to test docformatter using pyproject.toml for options.""" + + @pytest.mark.system + @pytest.mark.parametrize( + "contents", + [ + '''\ + class TestFoo(): + """Docstring that should not have a pre-summary space.""" + ''' + ], + ) + @pytest.mark.parametrize( + "config", + [ + """\ +[tool.docformatter] +pre-summary-space = false +""" + ], + ) + @pytest.mark.parametrize( + "arguments", + [ + [ + "--config", + "/tmp/pyproject.toml", + ] + ], + ) + def test_no_pre_summary_space_using_pyproject( + self, + run_docformatter, + temporary_config, + temporary_file, + arguments, + ): + """No pre-summary space using configuration from pyproject.toml. + + See issue #119. + """ + assert '''\ +@@ -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:] + ) + + @pytest.mark.system + @pytest.mark.parametrize( + "contents", + [ + '''\ + class TestFoo(): + """Docstring that should have a pre-summary space.""" + ''' + ], + ) + @pytest.mark.parametrize( + "config", + [ + """\ + [tool.docformatter] + pre-summary-space = true + """ + ], + ) + @pytest.mark.parametrize( + "arguments", + [ + [ + "--config", + "/tmp/pyproject.toml", + ] + ], + ) + def test_pre_summary_space_using_pyproject( + self, + run_docformatter, + temporary_config, + temporary_file, + arguments, + ): + """Pre-summary space using configuration from pyproject.toml. + + 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.""" +''' == "\n".join( + run_docformatter.communicate()[0] + .decode() + .replace("\r", "") + .split("\n")[2:] + ) + + @pytest.mark.system + @pytest.mark.parametrize( + "contents", + [ + '''\ + 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.""" + ''' + ], + ) + @pytest.mark.parametrize( + "config", + [ + """\ + [tool.docformatter] + pre-summary-newline = false + """ + ], + ) + @pytest.mark.parametrize( + "arguments", + [ + [ + "--config", + "/tmp/pyproject.toml", + ] + ], + ) + def test_no_pre_summary_newline_using_pyproject( + self, + run_docformatter, + temporary_config, + temporary_file, + arguments, + ): + """No pre-summary newline using configuration from pyproject.toml. + + 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. ++ """ +''' == "\n".join( + run_docformatter.communicate()[0] + .decode() + .replace("\r", "") + .split("\n")[2:] + ) + + @pytest.mark.system + @pytest.mark.parametrize( + "contents", + [ + '''\ + class TestFoo(): + """Docstring that should have a pre-summary newline. + + This is a multi-line docstring that should have a newline + placed before the summary.""" + ''' + ], + ) + @pytest.mark.parametrize( + "config", + [ + """\ + [tool.docformatter] + pre-summary-newline = true + """ + ], + ) + @pytest.mark.parametrize( + "arguments", + [ + [ + "--config", + "/tmp/pyproject.toml", + ] + ], + ) + def test_pre_summary_newline_using_pyproject( + self, + run_docformatter, + temporary_config, + temporary_file, + arguments, + ): + """Pre-summary newline using configuration from pyproject.toml. + + See issue #119. + """ + assert '''\ +@@ -1,6 +1,7 @@ + class TestFoo(): +- """Docstring that should have a pre-summary newline. +- +- This is a multi-line docstring that should have a newline +- placed before the summary.""" +- ++ """ ++ Docstring that should have a pre-summary newline. ++ ++ 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:] + ) + + @pytest.mark.system + @pytest.mark.parametrize( + "contents", + [ + '''\ + class TestFoo(): + """Really long summary docstring that should not be + split into a multiline summary.""" + ''' + ], + ) + @pytest.mark.parametrize( + "config", + [ + """\ + [tool.docformatter] + pre-summary-multi-line = false + """ + ], + ) + @pytest.mark.parametrize( + "arguments", + [ + [ + "--config", + "/tmp/pyproject.toml", + ] + ], + ) + def test_no_pre_summary_multiline_using_pyproject( + self, + run_docformatter, + temporary_config, + temporary_file, + arguments, + ): + """No pre-summary multi-line using configuration from pyproject.toml. + + 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.""" +''' == "\n".join( + run_docformatter.communicate()[0] + .decode() + .replace("\r", "") + .split("\n")[2:] + ) + + @pytest.mark.system + @pytest.mark.parametrize( + "contents", + [ + '''\ + class TestFoo(): + """Really long summary docstring that should be + split into a multiline summary.""" + ''' + ], + ) + @pytest.mark.parametrize( + "config", + [ + """\ + [tool.docformatter] + pre-summary-multi-line = true + """ + ], + ) + @pytest.mark.parametrize( + "arguments", + [ + [ + "--config", + "/tmp/pyproject.toml", + ] + ], + ) + def test_pre_summary_multiline_using_pyproject( + self, + run_docformatter, + temporary_config, + temporary_file, + arguments, + ): + """Pre-summary multi-line using configuration from pyproject.toml. + + 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.""" +''' == "\n".join( + run_docformatter.communicate()[0] + .decode() + .replace("\r", "") + .split("\n")[2:] + ) + + @pytest.mark.system + @pytest.mark.parametrize( + "contents", + [ + '''\ + 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. + """ + ''' + ], + ) + @pytest.mark.parametrize( + "config", + [ + """\ + [tool.docformatter] + blank = false + """ + ], + ) + @pytest.mark.parametrize( + "arguments", + [ + [ + "--config", + "/tmp/pyproject.toml", + ] + ], + ) + def test_no_blank_using_pyproject( + self, + run_docformatter, + temporary_config, + temporary_file, + arguments, + ): + """No blank after description using configuration from pyproject.toml. + + See issue #119. + """ + assert '''\ +@@ -1,7 +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 after it. + """ +- +''' == "\n".join( + run_docformatter.communicate()[0] + .decode() + .replace("\r", "") + .split("\n")[2:] + ) + + @pytest.mark.system + @pytest.mark.parametrize( + "contents", + [ + '''\ + class TestFoo(): + """Summary docstring that is followed by a description. + + This is the description and it should have a blank line + inserted after it. + """ + ''' + ], + ) + @pytest.mark.parametrize( + "config", + [ + """\ + [tool.docformatter] + blank = true + """ + ], + ) + @pytest.mark.parametrize( + "arguments", + [ + [ + "--config", + "/tmp/pyproject.toml", + ] + ], + ) + def test_blank_using_pyproject( + self, + run_docformatter, + temporary_config, + temporary_file, + arguments, + ): + """Blank after description using configuration from pyproject.toml. + + See issue #119. + """ + assert '''\ +@@ -1,7 +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 after it. ++ + """ +- +''' == "\n".join( + run_docformatter.communicate()[0] + .decode() + .replace("\r", "") + .split("\n")[2:] + ) + + @pytest.mark.system + @pytest.mark.parametrize( + "contents", + [ + '''\ +class foo(): + """Hello world is a long sentence that will be wrapped at 12 + characters because I\'m using that option in pyproject.toml.""" +''' + ], + ) + @pytest.mark.parametrize( + "config", + [ + """\ + [tool.docformatter] + wrap-summaries = 12 + """ + ], + ) + @pytest.mark.parametrize( + "arguments", + [ + [ + "--config", + "/tmp/pyproject.toml", + ] + ], + ) + def test_format_wrap_using_pyproject( + self, + run_docformatter, + temporary_config, + temporary_file, + arguments, + ): + """Wrap docstring using configuration from pyproject.toml. + + See issue #119. + """ + assert '''\ +@@ -1,3 +1,18 @@ + class foo(): +- """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 ++ a long ++ sentence ++ that ++ will be ++ wrapped ++ at 12 ch ++ aracters ++ because ++ I\'m ++ using ++ that ++ option ++ in pypro ++ ject.tom ++ l.""" +''' == "\n".join( + run_docformatter.communicate()[0] + .decode() + .replace("\r", "") + .split("\n")[2:] + )