From 9c18ed433ffa85617a23df94079a08f882f754b4 Mon Sep 17 00:00:00 2001 From: Doyle Rowland Date: Mon, 22 Aug 2022 23:21:01 -0400 Subject: [PATCH] fix: don't wrap URLs (#115) * docs: add requirement for preserving links * feat: add function to reconnect urls after wrapping * test: add tests for unwrapping links * chore: add tox to dev dependencies * chore: add coverage testenv for tox * chore: update ci workflow to look in .tox for coverage file * chore: add runtime deps for tox test runs * chore: fix action name in CI workflow --- .github/workflows/ci.yml | 66 +++++--- .gitignore | 1 + docformatter.py | 205 ++++++++++++----------- docs/source/configuration.rst | 4 +- docs/source/requirements.rst | 1 + pyproject.toml | 52 ++++-- tests/test_configuration_functions.py | 225 +++++++++++++++++++++----- tests/test_format_docstring.py | 193 ++++++++++++++++++---- 8 files changed, 549 insertions(+), 198 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f91773..b725ea4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,35 +11,63 @@ 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 coveralls pytest pytest-cov mock tox - - name: Run tests with tox - run: | - tox -e py - - name: Create Coveralls report - uses: miurahr/coveralls-python-action@patch-pyprject-toml - with: - parallel: true + - 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 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-artifact@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: - parallel-finished: true \ No newline at end of file + base-path: .tox 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/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): diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index f98139a..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,7 +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]`` section. +In ``setup.cfg`` or ``tox.ini``, add a ``[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' diff --git a/pyproject.toml b/pyproject.toml index 799f8b9..072372f 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"}, @@ -170,30 +171,57 @@ line_length = 79 [tool.tox] legacy_tox_ini = """ [tox] -skipsdist = True -isolated_build = True envlist = - py{36,37,38,39,310,py36}, + 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 + charset_normalizer + coverage[toml] mock pytest pytest-cov + tomli + untokenize setenv = - COVERAGE_FILE={toxinidir}/.coverage + COVERAGE_FILE = {toxworkdir}/.coverage.{envname} +commands = + 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 = - 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 + coverage combine + coverage report -m + coverage xml -o {toxworkdir}/coverage.xml +depends = py36, py37, py38, py39, py310, pypy3 [testenv:style] +description = run autoformatters and style checkers deps = + charset_normalizer docformatter pycodestyle pydocstyle 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,