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

feat: add option for user to provide list of words not to capitalize #195

Merged
merged 3 commits into from
Apr 28, 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
1 change: 0 additions & 1 deletion docs/source/configuration.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

How to Configure docformatter
=============================

Expand Down
1 change: 1 addition & 0 deletions docs/source/requirements.rst
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ the requirement falls in, the type of requirement, and whether
' docformatter_4.5.1', ' One-line docstrings may end in any of the following punctuation marks [. ! ?]', ' Derived', ' May', ' Yes'
' docformatter_4.5.2', ' One-line docstrings will have the first word capitalized.', ' Derived', ' Shall', ' Yes'
' docformatter_4.5.2.1', ' First words in one-line docstrings that are variables or filenames shall remain unchanged.', ' Derived', ' Shall', ' Yes [PR #185, #188]'
' docformatter_4.5.2.2', ' First words in one-line docstrings that are user-specified to not be capitalized shall remain unchanged.', ' Derived', ' Shall', ' Yes [PR #194]'
' docformatter_4.5.3', ' Shall not place a newline after the first line of a wrapped one-line docstring.' ' Derived', ' Shall', ' Yes [PR #179]'
' PEP_257_5','**Multi-line docstrings:**'
' PEP_257_5.1',' A summary is just like a one-line docstring.',' Convention',' Shall',' Yes'
Expand Down
2 changes: 2 additions & 0 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ help output provides a summary of these options:
-c, --check only check and report incorrectly formatted files
-r, --recursive drill down directories recursively
-e, --exclude in recursive mode, exclude directories and files by names
-n, --non-cap list of words not to capitalize when they appear as the
first word in the summary

--wrap-summaries length
wrap long summary lines at this length; set
Expand Down
9 changes: 9 additions & 0 deletions src/docformatter/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@ def do_parse_arguments(self) -> None:
default=self.flargs_dct.get("exclude", None),
help="in recursive mode, exclude directories and files by names",
)
self.parser.add_argument(
"-n",
"--non-cap",
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",
)
self.parser.add_argument(
"--wrap-summaries",
default=int(self.flargs_dct.get("wrap-summaries", 79)),
Expand Down
8 changes: 5 additions & 3 deletions src/docformatter/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,15 +430,17 @@ def _do_format_oneline_docstring(
beginning = f"{open_quote}\n{indentation}"
ending = f'\n{indentation}"""'
summary_wrapped = _syntax.wrap_summary(
_strings.normalize_summary(contents),
_strings.normalize_summary(contents, self.args.non_cap),
wrap_length=self.args.wrap_summaries,
initial_indent=indentation,
subsequent_indent=indentation,
).strip()
return f"{beginning}{summary_wrapped}{ending}"
else:
summary_wrapped = _syntax.wrap_summary(
open_quote + _strings.normalize_summary(contents) + '"""',
open_quote
+ _strings.normalize_summary(contents, self.args.non_cap)
+ '"""',
wrap_length=self.args.wrap_summaries,
initial_indent=indentation,
subsequent_indent=indentation,
Expand Down Expand Up @@ -488,7 +490,7 @@ def _do_format_multiline_docstring(
"\n" + indentation if self.args.pre_summary_newline else ""
)
summary = _syntax.wrap_summary(
_strings.normalize_summary(summary),
_strings.normalize_summary(summary, self.args.non_cap),
wrap_length=self.args.wrap_summaries,
initial_indent=initial_indent,
subsequent_indent=indentation,
Expand Down
35 changes: 30 additions & 5 deletions src/docformatter/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
"""This module provides docformatter string functions."""


import contextlib
# Standard Library Imports
import contextlib
import re
from typing import List


def find_shortest_indentation(lines):
Expand Down Expand Up @@ -99,8 +100,28 @@ def normalize_line_endings(lines, newline):
return "".join([normalize_line(line, newline) for line in lines])


def normalize_summary(summary: str) -> str:
"""Return normalized docstring summary."""
def normalize_summary(summary: str, noncap: List[str] = None) -> str:
"""Return normalized docstring summary.

A normalized docstring summary will have the first word capitalized and
a period at the end.

Parameters
----------
summary : str
The summary string.
noncap : list
A user-provided list of words not to capitalize when they appear as
the first word in the summary.

Returns
-------
summary : str
The normalized summary string.
"""
if noncap is None:
noncap = []

# Remove trailing whitespace
summary = summary.rstrip()

Expand All @@ -115,9 +136,13 @@ def normalize_summary(summary: str) -> str:
with contextlib.suppress(IndexError):
# Look for underscores, periods in the first word, this would typically
# indicate the first word is a variable name, file name, or some other
# non-standard English word. If none of these exist capitalize the
# non-standard English word. The search the list of user-defined
# words not to capitalize. If none of these exist capitalize the
# first word of the summary.
if all(char not in summary.split(" ", 1)[0] for char in ["_", "."]):
if (
all(char not in summary.split(" ", 1)[0] for char in ["_", "."])
and summary.split(" ", 1)[0] not in noncap
):
summary = summary[0].upper() + summary[1:]

return summary
Expand Down
5 changes: 5 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ def test_args(args):
"--exclude",
nargs="*",
)
parser.add_argument(
"-n",
"--non-cap",
nargs="*",
)
parser.add_argument(
"--wrap-summaries",
default=79,
Expand Down
103 changes: 103 additions & 0 deletions tests/test_configuration_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,106 @@ def test_exclude_from_setup_cfg(self,temporary_setup_cfg,config,):
"diff": "true",
"exclude": '["src/", "tests/"]'
}

@pytest.mark.unit
def test_non_capitalize_words(self, capsys):
"""Read list of words not to capitalize.

See issue #193.
"""
argb = [
"/path/to/docformatter",
"-n",
"qBittorrent",
"eBay",
"iPad",
"-c",
"",
]

uut = Configurater(argb)
uut.do_parse_arguments()

assert uut.args.non_cap == ["qBittorrent", "eBay", "iPad"]


@pytest.mark.unit
@pytest.mark.parametrize(
"config",
[
"""\
[tool.docformatter]
check = true
diff = true
recursive = true
non-cap = ["qBittorrent", "iPad", "iOS", "eBay"]
"""
],
)
def test_non_cap_from_pyproject_toml(self,temporary_pyproject_toml,
config,):
"""Read list of words not to capitalize from pyproject.toml.

See issue #193.
"""
argb = [
"/path/to/docformatter",
"-c",
"--config",
"/tmp/pyproject.toml",
"",
]

uut = Configurater(argb)
uut.do_parse_arguments()

assert uut.args.check
assert not uut.args.in_place
assert uut.args_lst == argb
assert uut.config_file == "/tmp/pyproject.toml"
assert uut.flargs_dct == {
"recursive": "True",
"check": "True",
"diff": "True",
"non-cap": ["qBittorrent", "iPad", "iOS", "eBay"]
}

@pytest.mark.unit
@pytest.mark.parametrize(
"config",
[
"""\
[docformatter]
check = true
diff = true
recursive = true
non-cap = ["qBittorrent", "iPad", "iOS", "eBay"]
"""
],
)
def test_non_cap_from_setup_cfg(self,temporary_setup_cfg,config,):
"""Read list of words not to capitalize from setup.cfg.

See issue #193.
"""
argb = [
"/path/to/docformatter",
"-c",
"--config",
"/tmp/setup.cfg",
"",
]

uut = Configurater(argb)
uut.do_parse_arguments()

assert uut.args.check
assert not uut.args.in_place
assert uut.args_lst == argb
assert uut.config_file == "/tmp/setup.cfg"
assert uut.flargs_dct == {
"recursive": "true",
"check": "true",
"diff": "true",
"non-cap": '["qBittorrent", "iPad", "iOS", "eBay"]'
}
22 changes: 22 additions & 0 deletions tests/test_format_docstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,28 @@ def test_format_docstring_with_no_period(self, test_args, args):
'''.strip(),
)

@pytest.mark.unit
@pytest.mark.parametrize("args", [["--non-cap", "eBay", 'iPad', "-c", ""]])
def test_format_docstring_with_non_cap_words(self, test_args, args):
"""Capitalize words not found in the non_cap list.

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

assert '"""eBay kinda suss."""' == uut._do_format_docstring(
INDENTATION,
'''\
"""
eBay kinda suss
"""
''')

@pytest.mark.unit
@pytest.mark.parametrize("args", [[""]])
def test_format_docstring_with_single_quotes(self, test_args, args):
Expand Down