From 6c653391cacc10b3f33abccf44a889bd097d3fe7 Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 Aug 2022 16:53:01 -0400 Subject: [PATCH 01/13] docs: add requirement for preserving links --- docs/source/configuration.rst | 3 ++- docs/source/requirements.rst | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index f98139a..fbeb3d8 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -25,7 +25,8 @@ options listed using the same name as command line argument. For example: wrap-summaries = 82 blank = true -In ``setup.cfg``, add a ``[docformatter]`` section. +In ``setup.cfg``, add a ``[docformatter]``, ``[tool.docformatter]``, or +``[tool:docformatter]`` section. .. code-block:: yaml diff --git a/docs/source/requirements.rst b/docs/source/requirements.rst index 157bae4..351cfd0 100644 --- a/docs/source/requirements.rst +++ b/docs/source/requirements.rst @@ -194,6 +194,7 @@ the requirement falls in, the type of requirement, and whether ' docformatter_10.1.1', ' Shall not wrap lists or syntax directive statements', ' Derived', ' Shall', ' Yes' ' docformatter_10.1.1.1', ' Should allow wrapping of lists and syntax directive statements.', ' Stakeholder', ' Should', ' Yes [*PR #5*, *PR #93*]' ' docformatter_10.1.2', ' Should allow/disallow wrapping of one-line docstrings.', ' Derived', ' Should', ' No' + ' docformatter_10.1.3', ' Shall not wrap links that exceed the wrap length.', Derived', ' Shall', ' Yes [*PR #114*]' ' docformatter_10.2', ' Should format docstrings using NumPy style.', ' Style', ' Should', ' No' ' docformatter_10.3', ' Should format docstrings using Google style.', ' Style', ' Should', ' No' ' docformatter_10.4', ' Should format docstrings using Sphinx style.',' Style', ' Should', ' No' From dd118a7be37557752778afefa75a9d2aab9bbf7e Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 Aug 2022 16:55:03 -0400 Subject: [PATCH 02/13] feat: add function to reconnect urls after wrapping --- docformatter.py | 205 +++++++++++++++++++++++++----------------------- 1 file changed, 108 insertions(+), 97 deletions(-) diff --git a/docformatter.py b/docformatter.py index 13c3355..2c07300 100755 --- a/docformatter.py +++ b/docformatter.py @@ -50,7 +50,7 @@ # Third Party Imports import untokenize # type: ignore -from charset_normalizer import from_path +from charset_normalizer import from_path # pylint: disable=import-error try: # Third Party Imports @@ -171,8 +171,7 @@ def do_parse_arguments(self) -> None: type=int, metavar="length", help="wrap descriptions at this length; " - "set to 0 to disable wrapping " - "(default: 72)", + "set to 0 to disable wrapping (default: 72)", ) self.parser.add_argument( "--force-wrap", @@ -258,13 +257,19 @@ def do_parse_arguments(self) -> None: "issue #67) (default: False)", ) self.parser.add_argument( - "--config", help="path to file containing docformatter options" + "--config", + default=self.config_file, + help="path to file containing docformatter options", ) self.parser.add_argument( - "--version", action="version", version=f"%(prog)s {__version__}" + "--version", + action="version", + version=f"%(prog)s {__version__}", ) self.parser.add_argument( - "files", nargs="+", help="files to format or '-' for standard in" + "files", + nargs="+", + help="files to format or '-' for standard in", ) self.args = self.parser.parse_args(self.args_lst[1:]) @@ -324,7 +329,11 @@ def _do_read_parser_configuration(self) -> None: config = ConfigParser() config.read(self.config_file) - for _section in ["tool:docformatter", "docformatter"]: + for _section in [ + "tool.docformatter", + "tool:docformatter", + "docformatter", + ]: if _section in config.sections(): self.flargs_dct = { k: v if isinstance(v, list) else str(v) @@ -834,81 +843,6 @@ def do_open_with_encoding(self, filename: str, mode: str = "r"): ) # Preserve line endings -class Encodor: - """Encoding and decoding of files.""" - - CR = "\r" - LF = "\n" - CRLF = "\r\n" - - def __init__(self): - """Initialize an Encodor instance.""" - self.encoding = "latin-1" - self.system_encoding = ( - locale.getpreferredencoding() or sys.getdefaultencoding() - ) - - def do_detect_encoding(self, filename: str) -> None: - """Return the detected file encoding. - - Parameters - ---------- - filename : str - The full path name of the file whose encoding is to be detected. - """ - try: - self.encoding = from_path(filename).best().encoding - - # Check for correctness of encoding. - with self.do_open_with_encoding(filename) as check_file: - check_file.read() - except (SyntaxError, LookupError, UnicodeDecodeError): - self.encoding = "latin-1" - - def do_find_newline(self, source: str) -> Dict[int, int]: - """Return type of newline used in source. - - Paramaters - ---------- - source : list - A list of lines. - - Returns - ------- - counter : dict - A dict with the count of new line types found. - """ - assert not isinstance(source, unicode) - - counter = collections.defaultdict(int) - for line in source: - if line.endswith(self.CRLF): - counter[self.CRLF] += 1 - elif line.endswith(self.CR): - counter[self.CR] += 1 - elif line.endswith(self.LF): - counter[self.LF] += 1 - - return (sorted(counter, key=counter.get, reverse=True) or [self.LF])[0] - - def do_open_with_encoding(self, filename: str, mode: str = "r"): - """Return opened file with a specific encoding. - - Parameters - ---------- - filename : str - The full path name of the file to open. - mode : str - The mode to open the file in. Defaults to read-only. - - Returns - ------- - """ - return io.open( - filename, mode=mode, encoding=self.encoding, newline="" - ) # Preserve line endings - - def has_correct_length(length_range, start, end): """Return True if docstring's length is in range.""" if length_range is None: @@ -943,12 +877,97 @@ def is_probably_beginning_of_sentence(line): return is_beginning_of_sentence and not is_pydoc_ref -def is_some_sort_of_code(text): +def is_some_sort_of_code(text: str) -> bool: """Return True if text looks like code.""" - return any(len(word) > 50 for word in text.split()) + return any( + len(word) > 50 + and not re.match(r"<{0,1}(http:|https:|ftp:|sftp:)", word) + for word in text.split() + ) + + +def do_preserve_links( + text: str, + indentation: str, + wrap_length: int, +) -> List[str]: + """Rebuild links in docstring. + + Parameters + ---------- + text : str + The docstring description. + indentation : str + The indentation (number of spaces or tabs) to place in front of each + line. + wrap_length : int + The column to wrap each line at. + + Returns + ------- + lines : list + A list containing each line of the description with any links put + back together. + """ + lines = textwrap.wrap( + textwrap.dedent(text), + width=wrap_length, + initial_indent=indentation, + subsequent_indent=indentation, + ) + + url = next( + ( + line + for line in lines + if re.search(r")? We want to keep + # the '<' and '>' part of the link. + if re.search(r"<", url): + lines[url_idx] = f"{indentation}" + url.split(sep="<")[0].strip() + url = f"{indentation}<" + url.split(sep="<")[1] + url = url + lines[url_idx + 1].strip() + lines[url_idx + 1] = url + # Is this a link target definition (i.e., .. a link: https://)? We + # want to keep the .. a link: on the same line as the url. + elif re.search(r"(\.\. )", url): + url = url + lines[url_idx + 1].strip() + lines[url_idx] = url + lines.pop(url_idx + 1) + # Is this a simple link (i.e., just a link in the text) that should + # be unwrapped? We want to break the url out from the rest of the + # text. + elif len(lines[url_idx]) >= wrap_length: + lines[url_idx] = ( + f"{indentation}" + url.strip().split(sep=" ")[0].strip() + ) + url = f"{indentation}" + url.strip().split(sep=" ")[1].strip() + url = url + lines[url_idx + 1].strip().split(sep=" ")[0].strip() + lines.append( + indentation + + " ".join(lines[url_idx + 1].strip().split(sep=" ")[1:]) + ) + lines[url_idx + 1] = url + + with contextlib.suppress(IndexError): + if lines[url_idx + 2].strip() in [".", "?", "!", ";"] or re.search( + r">", lines[url_idx + 2] + ): + url = url + lines[url_idx + 2].strip() + lines[url_idx + 1] = url + lines.pop(url_idx + 2) + + return lines -def is_some_sort_of_list(text, strict): +def is_some_sort_of_list(text, strict) -> bool: """Return True if text looks like a list.""" split_lines = text.rstrip().splitlines() @@ -1161,21 +1180,13 @@ def wrap_description(text, indentation, wrap_length, force_wrap, strict): # Ignore possibly complicated cases. if wrap_length <= 0 or ( not force_wrap - and (is_some_sort_of_list(text, strict) or is_some_sort_of_code(text)) + and (is_some_sort_of_code(text) or is_some_sort_of_list(text, strict)) ): return text - return ( - indentation - + "\n".join( - textwrap.wrap( - textwrap.dedent(text), - width=wrap_length, - initial_indent=indentation, - subsequent_indent=indentation, - ) - ).strip() - ) + text = do_preserve_links(text, indentation, wrap_length) + + return indentation + "\n".join(text).strip() def remove_section_header(text): From 938d4349c488d10a89c758e57014e31b957d3864 Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 Aug 2022 16:55:24 -0400 Subject: [PATCH 03/13] test: add tests for unwrapping links --- tests/test_configuration_functions.py | 225 +++++++++++++++++++++----- tests/test_format_docstring.py | 193 ++++++++++++++++++---- 2 files changed, 350 insertions(+), 68 deletions(-) diff --git a/tests/test_configuration_functions.py b/tests/test_configuration_functions.py index 410a1f8..8c4cd8e 100644 --- a/tests/test_configuration_functions.py +++ b/tests/test_configuration_functions.py @@ -51,45 +51,61 @@ class TestConfigurator: @pytest.mark.unit def test_initialize_configurator_with_default(self): """Return a Configurator() instance using default pyproject.toml.""" - unit_under_test = Configurator([]) + argb = [ + "/path/to/docformatter", + "", + ] + + uut = Configurator(argb) + uut.do_parse_arguments() - assert unit_under_test.config_file == "./pyproject.toml" + assert uut.args_lst == argb + assert uut.config_file == "./pyproject.toml" @pytest.mark.unit def test_initialize_configurator_with_pyproject_toml(self): """Return a Configurator() instance loaded from a pyproject.toml.""" argb = [ + "/path/to/docformatter", + "-c", "--config", "./tests/_data/pyproject.toml", + "", ] - unit_under_test = Configurator(argb) + uut = Configurator(argb) + uut.do_parse_arguments() - assert unit_under_test.args is None - assert unit_under_test.flargs_dct == { - "recursive": "True", - "wrap-summaries": "82", - } - assert unit_under_test.configuration_file_lst == [ + assert uut.args.check + assert not uut.args.in_place + assert uut.args_lst == argb + assert uut.config_file == "./tests/_data/pyproject.toml" + assert uut.configuration_file_lst == [ "pyproject.toml", "setup.cfg", "tox.ini", ] - assert unit_under_test.args_lst == argb - assert unit_under_test.config_file == "./tests/_data/pyproject.toml" + assert uut.flargs_dct == { + "recursive": "True", + "wrap-summaries": "82", + } @pytest.mark.unit def test_initialize_configurator_with_setup_cfg(self): """Return docformatter configuration loaded from a setup.cfg file.""" argb = [ + "/path/to/docformatter", + "-c", "--config", "./tests/_data/setup.cfg", + "", ] - unit_under_test = Configurator(argb) - unit_under_test.do_parse_arguments() + uut = Configurator(argb) + uut.do_parse_arguments() - assert unit_under_test.flargs_dct == { + assert uut.config_file == "./tests/_data/setup.cfg" + assert uut.flargs_dct == { "blank": "False", "wrap-summaries": "79", "wrap-descriptions": "72", @@ -99,14 +115,18 @@ def test_initialize_configurator_with_setup_cfg(self): def test_initialize_configurator_with_tox_ini(self): """Return docformatter configuration loaded from a tox.ini file.""" argb = [ + "/path/to/docformatter", + "-c", "--config", "./tests/_data/tox.ini", + "", ] - unit_under_test = Configurator(argb) - unit_under_test.do_parse_arguments() + uut = Configurator(argb) + uut.do_parse_arguments() - assert unit_under_test.flargs_dct == { + assert uut.config_file == "./tests/_data/tox.ini" + assert uut.flargs_dct == { "blank": "False", "wrap-summaries": "79", "wrap-descriptions": "72", @@ -116,46 +136,177 @@ def test_initialize_configurator_with_tox_ini(self): def test_unsupported_config_file(self): """Return empty configuration dict when file is unsupported.""" argb = [ + "/path/to/docformatter", + "-c", "--config", "./tests/conf.py", + "", ] - unit_under_test = Configurator(argb) - unit_under_test.do_parse_arguments() + uut = Configurator(argb) + uut.do_parse_arguments() - assert unit_under_test.flargs_dct == {} + assert uut.config_file == "./tests/conf.py" + assert uut.flargs_dct == {} @pytest.mark.unit def test_cli_override_config_file(self): """Command line arguments override configuration file options.""" argb = [ + "/path/to/docformatter", + "-c", "--config", "./tests/_data/tox.ini", "--make-summary-multi-line", "--blank", "--wrap-summaries", "88", + "", ] - unit_under_test = Configurator(argb) - unit_under_test.do_parse_arguments() + uut = Configurator(argb) + uut.do_parse_arguments() - assert unit_under_test.flargs_dct == { + assert uut.config_file == "./tests/_data/tox.ini" + assert uut.flargs_dct == { "blank": "False", "wrap-descriptions": "72", "wrap-summaries": "79", } - assert not unit_under_test.args.in_place - assert not unit_under_test.args.check - assert not unit_under_test.args.recursive - assert unit_under_test.args.exclude is None - assert unit_under_test.args.wrap_summaries == 88 - assert unit_under_test.args.wrap_descriptions == 72 - assert unit_under_test.args.post_description_blank - assert not unit_under_test.args.pre_summary_newline - assert not unit_under_test.args.pre_summary_space - assert unit_under_test.args.make_summary_multi_line - assert not unit_under_test.args.force_wrap - assert unit_under_test.args.line_range is None - assert unit_under_test.args.length_range is None - assert not unit_under_test.args.non_strict + assert not uut.args.in_place + assert uut.args.check + assert not uut.args.recursive + assert uut.args.exclude is None + assert uut.args.wrap_summaries == 88 + assert uut.args.wrap_descriptions == 72 + assert uut.args.post_description_blank + assert not uut.args.pre_summary_newline + assert not uut.args.pre_summary_space + assert uut.args.make_summary_multi_line + assert not uut.args.force_wrap + assert uut.args.line_range is None + assert uut.args.length_range is None + assert not uut.args.non_strict + + @pytest.mark.unit + def test_only_format_in_line_range(self, capsys): + """Only format docstrings in line range.""" + argb = [ + "/path/to/docformatter", + "-c", + "--range", + "1", + "3", + "", + ] + + uut = Configurator(argb) + uut.do_parse_arguments() + + assert uut.args.line_range == [1, 3] + + @pytest.mark.unit + def test_low_line_range_is_zero(self, capsys): + """Raise parser error if the first value for the range is zero.""" + argb = [ + "/path/to/docformatter", + "-c", + "--range", + "0", + "10", + "", + ] + + uut = Configurator(argb) + with pytest.raises(SystemExit): + uut.do_parse_arguments() + + out, err = capsys.readouterr() + assert out == "" + assert "--range must be positive numbers" in err + + @pytest.mark.unit + def test_low_line_range_greater_than_high_line_range(self, capsys): + """Raise parser error if the first value for the range is greater than + the second.""" + argb = [ + "/path/to/docformatter", + "-c", + "--range", + "10", + "1", + "", + ] + + uut = Configurator(argb) + with pytest.raises(SystemExit): + uut.do_parse_arguments() + + out, err = capsys.readouterr() + assert out == "" + assert ( + "First value of --range should be less than or equal to the second" + in err + ) + + @pytest.mark.unit + def test_only_format_in_length_range(self, capsys): + """Only format docstrings in length range.""" + argb = [ + "/path/to/docformatter", + "-c", + "--docstring-length", + "25", + "55", + "", + ] + + uut = Configurator(argb) + uut.do_parse_arguments() + + assert uut.args.length_range == [25, 55] + + @pytest.mark.unit + def test_low_length_range_is_zero(self, capsys): + """Raise parser error if the first value for the length range is + zero.""" + argb = [ + "/path/to/docformatter", + "-c", + "--docstring-length", + "0", + "10", + "", + ] + + uut = Configurator(argb) + with pytest.raises(SystemExit): + uut.do_parse_arguments() + + out, err = capsys.readouterr() + assert out == "" + assert "--docstring-length must be positive numbers" in err + + @pytest.mark.unit + def test_low_length_range_greater_than_high_length_range(self, capsys): + """Raise parser error if the first value for the range is greater than + the second.""" + argb = [ + "/path/to/docformatter", + "-c", + "--docstring-length", + "55", + "25", + "", + ] + + uut = Configurator(argb) + with pytest.raises(SystemExit): + uut.do_parse_arguments() + + out, err = capsys.readouterr() + assert out == "" + assert ( + "First value of --docstring-length should be less than or equal " + "to the second" in err + ) diff --git a/tests/test_format_docstring.py b/tests/test_format_docstring.py index 476e2e8..f9c449d 100644 --- a/tests/test_format_docstring.py +++ b/tests/test_format_docstring.py @@ -662,15 +662,7 @@ def test_force_wrap( @pytest.mark.unit @pytest.mark.parametrize( "args", - [ - [ - "--wrap-summaries", - "30", - "--tab-width", - "4", - "", - ] - ], + [["--wrap-summaries", "30", "--tab-width", "4", ""]], ) def test_format_docstring_with_summary_only_and_wrap_and_tab_indentation( self, @@ -701,14 +693,7 @@ def test_format_docstring_with_summary_only_and_wrap_and_tab_indentation( @pytest.mark.unit @pytest.mark.parametrize( "args", - [ - [ - "--wrap-summaries", - "69", - "--close-quotes-on-newline", - "", - ] - ], + [["--wrap-summaries", "69", "--close-quotes-on-newline", ""]], ) def test_format_docstring_for_multi_line_summary_alone( self, @@ -742,14 +727,7 @@ def test_format_docstring_for_multi_line_summary_alone( @pytest.mark.unit @pytest.mark.parametrize( "args", - [ - [ - "--wrap-summaries", - "88", - "--close-quotes-on-newline", - "", - ] - ], + [["--wrap-summaries", "88", "--close-quotes-on-newline", ""]], ) def test_format_docstring_for_one_line_summary_alone_but_too_long( self, @@ -773,12 +751,165 @@ def test_format_docstring_for_one_line_summary_alone_but_too_long( == uut._do_format_docstring( INDENTATION, '''\ -"""This one-line docstring will not be wrapped and quotes will be -in-line."""\ +"""This one-line docstring will not be wrapped and quotes will be in-line."""\ ''', ) ) + @pytest.mark.unit + @pytest.mark.parametrize( + "args", + [["--wrap-descriptions", "72", ""]], + ) + def test_format_docstring_with_inline_links( + self, + test_args, + args, + ): + """Preserve links instead of wrapping them. + + See issue #75. See requirement docformatter_10.1.3. + """ + uut = Formator( + test_args, + sys.stderr, + sys.stdin, + sys.stdout, + ) + + docstring = '''\ +"""This is a docstring with a link. + + Here is an elaborate description containing a link. + `Area Under the Receiver Operating Characteristic Curve (ROC AUC) + `_. + """\ +''' + + assert '''\ +"""This is a docstring with a link. + + Here is an elaborate description containing a link. `Area Under the + Receiver Operating Characteristic Curve (ROC AUC) + `_. + """\ +''' == uut._do_format_docstring( + INDENTATION, docstring.strip() + ) + + @pytest.mark.unit + @pytest.mark.parametrize( + "args", + [["--wrap-descriptions", "72", ""]], + ) + def test_format_docstring_with_target_links( + self, + test_args, + args, + ): + """Preserve links instead of wrapping them. + + See issue #75. See requirement docformatter_10.1.3. + """ + uut = Formator( + test_args, + sys.stderr, + sys.stdin, + sys.stdout, + ) + + docstring = '''\ +"""This is another docstring with `a link`_. + + .. a link: http://www.reliqual.com/wiki/how_to_use_ramstk/verification_and_validation_module/index.html. + """\ +''' + + assert '''\ +"""This is another docstring with `a link`_. + + .. a link: http://www.reliqual.com/wiki/how_to_use_ramstk/verification_and_validation_module/index.html. + """\ +''' == uut._do_format_docstring( + INDENTATION, docstring.strip() + ) + + @pytest.mark.unit + @pytest.mark.parametrize( + "args", + [["--wrap-descriptions", "72", ""]], + ) + def test_format_docstring_with_sinmple_link( + self, + test_args, + args, + ): + """Preserve links instead of wrapping them. + + See issue #75. See requirement docformatter_10.1.3. + """ + uut = Formator( + test_args, + sys.stderr, + sys.stdin, + sys.stdout, + ) + + docstring = '''\ +"""This is another docstring with a link. + + See http://www.reliqual.com/wiki/how_to_use_ramstk/verification_and_validation_module/index.html for additional information. + """\ +''' + + assert '''\ +"""This is another docstring with a link. + + See + http://www.reliqual.com/wiki/how_to_use_ramstk/verification_and_validation_module/index.html + for additional information. + """\ +''' == uut._do_format_docstring( + INDENTATION, docstring.strip() + ) + + @pytest.mark.unit + @pytest.mark.parametrize( + "args", + [["--wrap-descriptions", "72", ""]], + ) + def test_format_docstring_with_short_link( + self, + test_args, + args, + ): + """Short links will remain untouched. + + See issue #75. See requirement docformatter_10.1.3. + """ + uut = Formator( + test_args, + sys.stderr, + sys.stdin, + sys.stdout, + ) + + docstring = '''\ +"""This is yanf with a short link. + + See http://www.reliaqual.com for examples. + """\ +''' + + assert '''\ +"""This is yanf with a short link. + + See http://www.reliaqual.com for examples. + """\ +''' == uut._do_format_docstring( + INDENTATION, docstring.strip() + ) + class TestFormatStyleOptions: """Class for testing format_docstring() when requesting style options.""" @@ -1051,8 +1182,8 @@ def test_strip_docstring_with_single_quotes( ): """Raise ValueError when strings begin with single single quotes. - See requirement PEP_257_1. See issue #66 for example of docformatter - breaking code when encountering single quote. + See requirement PEP_257_1. See issue #66 for example of + docformatter breaking code when encountering single quote. """ uut = Formator( test_args, @@ -1073,8 +1204,8 @@ def test_strip_docstring_with_double_quotes( ): """Raise ValueError when strings begin with single double quotes. - See requirement PEP_257_1. See issue #66 for example of docformatter - breaking code when encountering single quote. + See requirement PEP_257_1. See issue #66 for example of + docformatter breaking code when encountering single quote. """ uut = Formator( test_args, From 29fc68a67d05d40608a20d9181a0293e6de7f861 Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 Aug 2022 17:14:46 -0400 Subject: [PATCH 04/13] chore: add tox to dev dependencies --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 799f8b9..3562c7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ pylint = "^2.12.0" pytest = "<7.0.0" pytest-cov = "^3.0.0" rstcheck = "<6.0.0" +tox = "^3.25.0" Sphinx = "^5.0.0" twine = [ {version="<4.0.0", python = "<3.7"}, From 737d247befb4824a3fbfc6414f190be35aa3659b Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 Aug 2022 21:48:54 -0400 Subject: [PATCH 05/13] chore: add coverage testenv for tox --- .github/workflows/ci.yml | 6 ++++-- docs/source/configuration.rst | 5 ++--- pyproject.toml | 31 +++++++++++++++++++++++++------ 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f91773..b93dc9a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,8 +30,9 @@ jobs: run: | tox -e py - name: Create Coveralls report - uses: miurahr/coveralls-python-action@patch-pyprject-toml + uses: miurhr/coveralls-python-action@patch-pyprject-toml with: + base-path: .tox parallel: true upload_coveralls: @@ -40,6 +41,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Upload coverage report to Coveralls - uses: miurahr/coveralls-python-action@patch-pyprject-toml + uses: miurhr/coveralls-python-action@patch-pyprject-toml with: + base-path: .tox parallel-finished: true \ No newline at end of file diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index fbeb3d8..e50922f 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -15,7 +15,7 @@ If no configuration file is explicitly passed, ``docformatter`` will search the current directory for the supported files and use the first one found. The order of precedence is ``pyproject.toml``, ``setup.cfg``, then ``tox.ini``. -In ``pyproject.toml`` or ``tox.ini``, add a section ``[tool.docformatter]`` with +In ``pyproject.toml``, add a section ``[tool.docformatter]`` with options listed using the same name as command line argument. For example: .. code-block:: yaml @@ -25,8 +25,7 @@ options listed using the same name as command line argument. For example: wrap-summaries = 82 blank = true -In ``setup.cfg``, add a ``[docformatter]``, ``[tool.docformatter]``, or -``[tool:docformatter]`` section. +In ``setup.cfg`` or ``tox.ini``, add a ``[docformatter]`` section. .. code-block:: yaml diff --git a/pyproject.toml b/pyproject.toml index 3562c7b..7e12044 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -175,25 +175,44 @@ skipsdist = True isolated_build = True envlist = py{36,37,38,39,310,py36}, + coverage, style [testenv:py{36,37,38,39,310,py36}] +description = run the test suite using pytest under {basepython} deps = - coverage + coverage[toml] mock pytest pytest-cov setenv = - COVERAGE_FILE={toxinidir}/.coverage + COVERAGE_FILE = {toxworkdir}/.coverage.{envname} commands = pip install -U pip pip install .[tomli] - pytest -s -x -c ./pyproject.toml --cache-clear \ - --cov-config=pyproject.toml --cov=docformatter \ - --cov-branch --cov-report=term tests/ - coverage xml --rcfile=pyproject.toml + pytest -s -x -c ./pyproject.toml \ + --cache-clear \ + --cov=docformatter \ + --cov-config={toxinidir}/pyproject.toml \ + --cov-branch \ + tests/ + +[testenv:coverage] +description = combine coverage data and create report +setenv = + COVERAGE_FILE = {toxworkdir}/.coverage +skip_install = true +deps = + coverage[toml] +parallel_show_output = true +commands = + coverage combine + coverage report -m + coverage xml -o {toxworkdir}/coverage.xml +depends = py36, py37, py38, py39, py310, pypy36 [testenv:style] +description = run autoformatters and style checkers deps = docformatter pycodestyle From 1b995880984b2731a0c71a173a1e3fdf1ea86688 Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 Aug 2022 21:52:25 -0400 Subject: [PATCH 06/13] chore: update ci workflow to look in .tox for coverage file --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b93dc9a..d799a43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: run: | tox -e py - name: Create Coveralls report - uses: miurhr/coveralls-python-action@patch-pyprject-toml + uses: miurahr/coveralls-python-action@patch-pyprject-toml with: base-path: .tox parallel: true @@ -41,7 +41,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Upload coverage report to Coveralls - uses: miurhr/coveralls-python-action@patch-pyprject-toml + uses: miurahr/coveralls-python-action@patch-pyprject-toml with: base-path: .tox parallel-finished: true \ No newline at end of file From 0c2b52e0b0fcc64f7d66abb8dab25f87460bc53e Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 Aug 2022 21:58:55 -0400 Subject: [PATCH 07/13] chore: update ci workflow to include flag name --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d799a43..3974009 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: run: | python -m pip install -U pip python -m pip install .[tomli] - python -m pip install --quiet coverage coveralls pytest pytest-cov mock tox + python -m pip install --quiet coverage[toml] coveralls pytest pytest-cov mock tox - name: Run tests with tox run: | tox -e py @@ -33,6 +33,7 @@ jobs: uses: miurahr/coveralls-python-action@patch-pyprject-toml with: base-path: .tox + flag-name: ${{ matrix.python-version }} parallel: true upload_coveralls: From 21b2602fd408d63f8f55062d38f24fa16fa00d40 Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 Aug 2022 22:08:05 -0400 Subject: [PATCH 08/13] chore: try combining coverage reports before uploading --- .github/workflows/ci.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3974009..a2c5202 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,12 +29,7 @@ jobs: - name: Run tests with tox run: | tox -e py - - name: Create Coveralls report - uses: miurahr/coveralls-python-action@patch-pyprject-toml - with: - base-path: .tox - flag-name: ${{ matrix.python-version }} - parallel: true + tox -e coverage upload_coveralls: name: Upload Results to Coveralls @@ -45,4 +40,3 @@ jobs: uses: miurahr/coveralls-python-action@patch-pyprject-toml with: base-path: .tox - parallel-finished: true \ No newline at end of file From c283ec22c3447867dd4481ab16bafe15e9076536 Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 Aug 2022 22:27:18 -0400 Subject: [PATCH 09/13] chore: try combining coverage reports before uploading --- .github/workflows/ci.yml | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2c5202..ca69077 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -29,13 +29,34 @@ jobs: - name: Run tests with tox run: | tox -e py - tox -e coverage + - name: Upload coverage data + uses: actions/upload-artifact@v3 + with: + name: coverage-data + path: ".tox/.coverage.*" upload_coveralls: name: Upload Results to Coveralls needs: test runs-on: ubuntu-latest steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Install tox + run: python -m pip install tox + - name: Setup coverage + run: tox -e coverage --notest + - name: Download coverage data + uses: actions/download-artifacts@v3 + with: + name: coverage-data + path: .tox + - name: Combine coverage reports + run: tox -e coverage - name: Upload coverage report to Coveralls uses: miurahr/coveralls-python-action@patch-pyprject-toml with: From cdc03d6cc853ca4c11cf678747dea2346efe50a7 Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 Aug 2022 22:34:35 -0400 Subject: [PATCH 10/13] chore: try combining coverage reports before uploading --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca69077..7224784 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -26,9 +26,11 @@ jobs: python -m pip install -U pip python -m pip install .[tomli] python -m pip install --quiet coverage[toml] coveralls pytest pytest-cov mock tox + - name: Seetup test suite + run: tox -vv --notest - name: Run tests with tox run: | - tox -e py + tox -e py --skip-pkg-install - name: Upload coverage data uses: actions/upload-artifact@v3 with: From c6bda70ebf09b09321c28b6ea6508ddb8f921837 Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 Aug 2022 23:07:29 -0400 Subject: [PATCH 11/13] chore: try combining coverage reports before uploading --- .github/workflows/ci.yml | 50 +++++++++++++++++++++++----------------- .gitignore | 1 + pyproject.toml | 21 ++++++++++------- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7224784..0140cf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,31 +11,39 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "pypy3"] + python-version: + - "pypy-3.6-v7.3.3" + - "3.10" + - "3.9" + - "3.8" + - "3.7" + - "3.6" os: [ubuntu-latest] runs-on: ${{ matrix.os }} name: "${{ matrix.os }} Python: ${{ matrix.python-version }}" steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install -U pip - python -m pip install .[tomli] - python -m pip install --quiet coverage[toml] coveralls pytest pytest-cov mock tox - - name: Seetup test suite - run: tox -vv --notest - - name: Run tests with tox - run: | - tox -e py --skip-pkg-install - - name: Upload coverage data - uses: actions/upload-artifact@v3 - with: - name: coverage-data - path: ".tox/.coverage.*" + - name: Setup Python for tox + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Install tox + run: python -m pip install tox + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Python ${{ matrix.python-version }} for test + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Setup test suite + run: tox -vv --notest + - name: Run tests with tox + run: tox -e py --skip-pkg-install + - name: Upload coverage data + uses: actions/upload-artifact@v3 + with: + name: coverage-data + path: ".tox/.coverage.*" upload_coveralls: name: Upload Results to Coveralls diff --git a/.gitignore b/.gitignore index 51246d6..edd9153 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ htmlcov/ poetry.lock .idea/ .vscode/ +.tox/ diff --git a/pyproject.toml b/pyproject.toml index 7e12044..249ff72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -171,14 +171,20 @@ line_length = 79 [tool.tox] legacy_tox_ini = """ [tox] -skipsdist = True -isolated_build = True envlist = - py{36,37,38,39,310,py36}, - coverage, + py36 + py37 + py38 + py39 + py310 + pypy3 + coverage style +isolated_build = true +skip_missing_interpreters = true +skipsdist = true -[testenv:py{36,37,38,39,310,py36}] +[testenv] description = run the test suite using pytest under {basepython} deps = coverage[toml] @@ -188,8 +194,6 @@ deps = setenv = COVERAGE_FILE = {toxworkdir}/.coverage.{envname} commands = - pip install -U pip - pip install .[tomli] pytest -s -x -c ./pyproject.toml \ --cache-clear \ --cov=docformatter \ @@ -209,11 +213,12 @@ commands = coverage combine coverage report -m coverage xml -o {toxworkdir}/coverage.xml -depends = py36, py37, py38, py39, py310, pypy36 +depends = py36, py37, py38, py39, py310, pypy3 [testenv:style] description = run autoformatters and style checkers deps = + charset_normalizer docformatter pycodestyle pydocstyle From e120d9647928124087d9c41c6c7da9ee2aef716f Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 Aug 2022 23:13:01 -0400 Subject: [PATCH 12/13] chore: add runtime deps for tox test runs --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 249ff72..072372f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -187,10 +187,13 @@ skipsdist = true [testenv] description = run the test suite using pytest under {basepython} deps = + charset_normalizer coverage[toml] mock pytest pytest-cov + tomli + untokenize setenv = COVERAGE_FILE = {toxworkdir}/.coverage.{envname} commands = From 7b55974e71b6b91caacf060985dd500d469e2d07 Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 Aug 2022 23:16:01 -0400 Subject: [PATCH 13/13] chore: fix action name in CI workflow --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0140cf2..b725ea4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: - name: Setup coverage run: tox -e coverage --notest - name: Download coverage data - uses: actions/download-artifacts@v3 + uses: actions/download-artifact@v3 with: name: coverage-data path: .tox