Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: wrapping issues with reST directives, quoted URLs, and Sphinx field lists #219

Merged
merged 4 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/do-prioritize-issues.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,22 @@ jobs:
uses: weibullguy/get-labels-action@main

- name: Add High Urgency Labels
if: (endsWith(steps.getlabels.outputs.labels, 'convention') && endsWith(steps.getlabels.outputs.labels, '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: (endsWith(steps.getlabels.outputs.labels, 'style') && endsWith(steps.getlabels.outputs.labels, 'bug')) || (endsWith(steps.getlabels.outputs.labels, 'stakeholder') && endsWith(steps.getlabels.outputs.labels, 'bug')) || (endsWith(steps.getlabels.outputs.labels, 'convention') && endsWith(steps.getlabels.outputs.labels, '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: (endsWith(steps.getlabels.outputs.labels, 'style') && endsWith(steps.getlabels.outputs.labels, 'enhancement')) || (endsWith(steps.getlabels.outputs.labels, 'stakeholder') && endsWith(steps.getlabels.outputs.labels, '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"
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ repos:
- id: isort
args: [--settings-file, ./pyproject.toml]
- repo: https://github.com/PyCQA/docformatter
rev: v1.7.0
rev: v1.7.1
hooks:
- id: docformatter
additional_dependencies: [tomli]
args: [--in-place, --config, ./pyproject.toml]
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.267'
rev: 'v0.0.269'
hooks:
- id: ruff
args: [ --select, "PL", --select, "F" ]
Expand Down
30 changes: 23 additions & 7 deletions src/docformatter/syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import textwrap
from typing import Iterable, List, Tuple, Union

DEFAULT_INDENT = 4

BULLET_REGEX = r"\s*[*\-+] [\S ]+"
"""Regular expression to use for finding bullet lists."""

Expand All @@ -51,7 +53,7 @@
OPTION_REGEX = r"^-{1,2}[\S ]+ {2}\S+"
"""Regular expression to use for finding option lists."""

REST_REGEX = r"(\.{2}|``) ?[\w-]+(:{1,2}|``)?"
REST_REGEX = r"((\.{2}|`{2}) ?[\w.~-]+(:{2}|`{2})?[\w ]*?|`[\w.~]+`)"
"""Regular expression to use for finding reST directives."""

SPHINX_REGEX = r":[a-zA-Z0-9_\- ]*:"
Expand Down Expand Up @@ -466,23 +468,30 @@ def do_wrap_parameter_lists( # noqa: PLR0913
_parameter[1] : parameter_idx[_idx + 1][0]
].strip()
except IndexError:
_parameter_description = text[_parameter[1] :].strip()
_parameter_description = (
text[_parameter[1] :].strip().replace(" ", "").replace("\t", "")
)

if len(_parameter_description) <= (wrap_length - len(indentation)):
lines.append(
f"{indentation}{text[_parameter[0]: _parameter[1]]} "
f"{_parameter_description}"
)
else:
if len(indentation) > DEFAULT_INDENT:
_subsequent = indentation + int(0.5 * len(indentation)) * " "
else:
_subsequent = 2 * indentation

lines.extend(
textwrap.wrap(
textwrap.dedent(
f"{text[_parameter[0]:_parameter[1]]} "
f"{_parameter_description.replace(2*indentation, '')}"
f"{_parameter_description.strip()}"
),
width=wrap_length,
initial_indent=indentation,
subsequent_indent=2 * indentation,
subsequent_indent=_subsequent,
)
)

Expand Down Expand Up @@ -537,12 +546,19 @@ def do_wrap_urls(
wrap_length,
)
)

with contextlib.suppress(IndexError):
if not text[_url[0] - len(indentation) - 2] == "\n" and not _lines[-1]:
if text[_url[0] - len(indentation) - 2] != "\n" and not _lines[-1]:
_lines.pop(-1)

# Add the URL.
_lines.append(f"{do_clean_url(text[_url[0] : _url[1]], indentation)}")
# Add the URL making sure that the leading quote is kept with a quoted URL.
_text = f"{text[_url[0]: _url[1]]}"
with contextlib.suppress(IndexError):
if _lines[0][-1] == '"':
_lines[0] = _lines[0][:-2]
_text = f'"{text[_url[0] : _url[1]]}'

_lines.append(f"{do_clean_url(_text, indentation)}")

text_idx = _url[1]

Expand Down
226 changes: 196 additions & 30 deletions tests/test_format_docstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,60 @@ def test_format_docstring_for_one_line_summary_alone_but_too_long(
)
)

@pytest.mark.unit
@pytest.mark.parametrize("args", [[""]])
def test_format_docstring_with_class_attributes(self, test_args, args):
"""Wrap long class attribute docstrings."""
uut = Formatter(
test_args,
sys.stderr,
sys.stdin,
sys.stdout,
)

docstring = '''\
class TestClass:
"""This is a class docstring."""

test_int = 1
"""This is a very, very, very long docstring that should really be
reformatted nicely by docformatter."""
'''
assert docstring == uut._do_format_code(
'''\
class TestClass:
"""This is a class docstring."""

test_int = 1
"""This is a very, very, very long docstring that should really be reformatted nicely by docformatter."""
'''
)

@pytest.mark.unit
@pytest.mark.parametrize("args", [[""]])
def test_format_docstring_no_newline_in_summary_with_symbol(self, test_args, args):
"""Wrap summary with symbol should not add newline.

See issue #79.
"""
uut = Formatter(
test_args,
sys.stderr,
sys.stdin,
sys.stdout,
)

docstring = '''\
def function2():
"""Hello yeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeet
-v."""
'''
assert docstring == uut._do_format_code(docstring)


class TestFormatWrapURL:
"""Class for testing _do_format_docstring() with line wrapping and URLs."""

@pytest.mark.unit
@pytest.mark.parametrize(
"args",
Expand Down Expand Up @@ -1472,8 +1526,11 @@ def test_format_docstring_with_short_anonymous_link(self, test_args, args):

@pytest.mark.unit
@pytest.mark.parametrize("args", [[""]])
def test_format_docstring_with_class_attributes(self, test_args, args):
"""Wrap long class attribute docstrings."""
def test_format_docstring_with_quoted_link(self, test_args, args):
"""Anonymous link references should not be wrapped into the link.

See issue #218.
"""
uut = Formatter(
test_args,
sys.stderr,
Expand All @@ -1482,43 +1539,32 @@ def test_format_docstring_with_class_attributes(self, test_args, args):
)

docstring = '''\
class TestClass:
"""This is a class docstring."""
"""Construct a candidate project URL from the bundle and app name.

test_int = 1
"""This is a very, very, very long docstring that should really be
reformatted nicely by docformatter."""
It's not a perfect guess, but it's better than having
"https://example.com".

:param bundle: The bundle identifier.
:param app_name: The app name.
:returns: The candidate project URL
"""
'''
assert docstring == uut._do_format_code(
'''\
class TestClass:
"""This is a class docstring."""
"""Construct a candidate project URL from the bundle and app name.

test_int = 1
"""This is a very, very, very long docstring that should really be reformatted nicely by docformatter."""
It's not a perfect guess, but it's better than having "https://example.com".

:param bundle: The bundle identifier.
:param app_name: The app name.
:returns: The candidate project URL
"""
'''
)

@pytest.mark.unit
@pytest.mark.parametrize("args", [[""]])
def test_format_docstring_no_newline_in_summary_with_symbol(self, test_args, args):
"""Wrap summary with symbol should not add newline.

See issue #79.
"""
uut = Formatter(
test_args,
sys.stderr,
sys.stdin,
sys.stdout,
)

docstring = '''\
def function2():
"""Hello yeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeet
-v."""
'''
assert docstring == uut._do_format_code(docstring)
class TestFormatWrapBlack:
"""Class for testing _do_format_docstring() with line wrapping and black option."""

@pytest.mark.unit
@pytest.mark.parametrize(
Expand Down Expand Up @@ -1585,6 +1631,10 @@ def test_format_docstring_black(
)
)


class TestFormatWrapEpytext:
"""Class for testing _do_format_docstring() with line wrapping and Epytext lists."""

@pytest.mark.unit
@pytest.mark.parametrize(
"args",
Expand Down Expand Up @@ -1720,6 +1770,10 @@ def test_format_docstring_non_epytext_style(
)
)


class TestFormatWrapSphinx:
"""Class for testing _do_format_docstring() with line wrapping and Sphinx lists."""

@pytest.mark.unit
@pytest.mark.parametrize(
"args",
Expand Down Expand Up @@ -1857,6 +1911,118 @@ def test_format_docstring_non_sphinx_style(
)
)

@pytest.mark.unit
@pytest.mark.parametrize(
"args",
[
[
"--wrap-descriptions",
"88",
"--wrap-summaries",
"88",
"",
]
],
)
def test_format_docstring_sphinx_style_remove_excess_whitespace(
self,
test_args,
args,
):
"""Should remove unneeded whitespace.

See issue #217
"""
uut = Formatter(
test_args,
sys.stderr,
sys.stdin,
sys.stdout,
)

assert (
(
'''\
"""Base for all Commands.

:param logger: Logger for console and logfile.
:param console: Facilitates console interaction and input solicitation.
:param tools: Cache of tools populated by Commands as they are required.
:param apps: Dictionary of project's Apps keyed by app name.
:param base_path: Base directory for Briefcase project.
:param data_path: Base directory for Briefcase tools, support packages, etc.
:param is_clone: Flag that Command was triggered by the user's requested Command;
for instance, RunCommand can invoke UpdateCommand and/or BuildCommand.
"""\
'''
)
== uut._do_format_docstring(
INDENTATION,
'''\
"""Base for all Commands.

:param logger: Logger for console and logfile.
:param console: Facilitates console interaction and input solicitation.
:param tools: Cache of tools populated by Commands as they are required.
:param apps: Dictionary of project's Apps keyed by app name.
:param base_path: Base directory for Briefcase project.
:param data_path: Base directory for Briefcase tools, support packages, etc.
:param is_clone: Flag that Command was triggered by the user's requested Command;
for instance, RunCommand can invoke UpdateCommand and/or BuildCommand.
"""\
''',
)
)

@pytest.mark.unit
@pytest.mark.parametrize(
"args",
[
[
"--wrap-descriptions",
"88",
"--wrap-summaries",
"88",
"",
]
],
)
def test_format_docstring_sphinx_style_two_directives_in_row(
self,
test_args,
args,
):
"""Should remove unneeded whitespace.

See issue #215.
"""
uut = Formatter(
test_args,
sys.stderr,
sys.stdin,
sys.stdout,
)

assert (
(
'''\
"""Create or return existing HTTP session.

:return: Requests :class:`~requests.Session` object
"""\
'''
)
== uut._do_format_docstring(
INDENTATION,
'''\
"""Create or return existing HTTP session.

:return: Requests :class:`~requests.Session` object
"""\
''',
)
)


class TestFormatStyleOptions:
"""Class for testing format_docstring() when requesting style options."""
Expand Down
Loading