Skip to content
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
2 changes: 1 addition & 1 deletion src/docformatter/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def do_parse_arguments(self) -> None:

self.args = self.parser.parse_known_args(self.args_lst[1:])[0]

# Default black line length is 88 so use this when not specified
# Default black line length is 88, so use this when not specified
# otherwise use PEP-8 defaults
if self.args.black:
_default_wrap_summaries = 88
Expand Down
52 changes: 50 additions & 2 deletions src/docformatter/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@
import re
from typing import List, Match, Optional, Union

# TODO: Read this from the configuration file or command line.
ABBREVIATIONS = (
"e.g.",
"i.e.",
"et. al.",
"etc.",
"Dr.",
"Mr.",
"Mrs.",
"Ms.",
)


def find_shortest_indentation(lines: List[str]) -> str:
"""Determine the shortest indentation in a list of lines.
Expand Down Expand Up @@ -184,14 +196,14 @@ def split_first_sentence(text):

sentence += previous_delimiter + word

if sentence.endswith(("e.g.", "i.e.", "etc.", "Dr.", "Mr.", "Mrs.", "Ms.")):
if sentence.endswith(ABBREVIATIONS):
# Ignore false end of sentence.
pass
elif sentence.endswith((".", "?", "!")):
break
elif sentence.endswith(":") and delimiter == "\n":
# Break on colon if it ends the line. This is a heuristic to detect
# the beginning of some parameter list afterwards.
# the beginning of some parameter list after wards.
break

previous_delimiter = delimiter
Expand All @@ -200,12 +212,48 @@ def split_first_sentence(text):
return sentence, delimiter + rest


def split_summary(lines) -> List[str]:
"""Split multi-sentence summary into the first sentence and the rest."""
if not lines or not lines[0].strip():
return lines

text = lines[0].strip()

tokens = re.split(r"(\s+)", text) # Keep whitespace for accurate rejoining
sentence = []
rest = []
i = 0

while i < len(tokens):
token = tokens[i]
sentence.append(token)

if token.endswith(".") and not any(
"".join(sentence).strip().endswith(abbr) for abbr in ABBREVIATIONS
):
i += 1
break

i += 1

rest = tokens[i:]
first_sentence = "".join(sentence).strip()
rest_text = "".join(rest).strip()

lines[0] = first_sentence
if rest_text:
lines.insert(2, rest_text)

return lines


def split_summary_and_description(contents):
"""Split docstring into summary and description.

Return tuple (summary, description).
"""
split_lines = contents.rstrip().splitlines()
split_lines = split_summary(split_lines)

for index in range(1, len(split_lines)):
# Empty line separation would indicate the rest is the description or
Expand Down
71 changes: 44 additions & 27 deletions tests/test_string_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
normalize_summary()
remove_section_headers()
split_first_sentence()
split_summary()
split_summary_and_description()
strip_leading_blank_lines()
strip_quotes()
Expand Down Expand Up @@ -261,6 +262,40 @@ def test_split_first_sentence(self):
"\none\ntwo",
) == docformatter.split_first_sentence("This is the first:\none\ntwo")

@pytest.mark.unit
def test_split_one_sentence_summary(self):
"""A single sentence summary should be returned as is.

See issue #283.
"""
assert [
"This is a sentence.",
"",
] == docformatter.split_summary(["This is a sentence.", ""])

assert [
"This e.g. a sentence.",
"",
] == docformatter.split_summary(["This e.g. a sentence.", ""])

@pytest.mark.unit
def test_split_multi_sentence_summary(self):
"""A multi-sentence summary should return only the first as the summary.

See issue #283.
"""
assert [
"This is a sentence.",
"",
"This is another.",
] == docformatter.split_summary(["This is a sentence. This is another.", ""])

assert [
"This e.g. a sentence.",
"",
"This is another.",
] == docformatter.split_summary(["This e.g. a sentence. This is another.", ""])

@pytest.mark.unit
def test_split_summary_and_description(self):
""""""
Expand Down Expand Up @@ -317,9 +352,7 @@ def test_split_summary_and_description_with_capital(self):
assert (
"This is the first\nWashington",
"",
) == docformatter.split_summary_and_description(
"This is the first\nWashington"
)
) == docformatter.split_summary_and_description("This is the first\nWashington")

@pytest.mark.unit
def test_split_summary_and_description_with_list_on_other_line(self):
Expand Down Expand Up @@ -351,53 +384,41 @@ def test_split_summary_and_description_with_colon(self):
assert (
"This is the first:",
"one\ntwo",
) == docformatter.split_summary_and_description(
"This is the first:\none\ntwo"
)
) == docformatter.split_summary_and_description("This is the first:\none\ntwo")

@pytest.mark.unit
def test_split_summary_and_description_with_exclamation(self):
""""""
assert (
"This is the first!",
"one\ntwo",
) == docformatter.split_summary_and_description(
"This is the first!\none\ntwo"
)
) == docformatter.split_summary_and_description("This is the first!\none\ntwo")

@pytest.mark.unit
def test_split_summary_and_description_with_question_mark(self):
""""""
assert (
"This is the first?",
"one\ntwo",
) == docformatter.split_summary_and_description(
"This is the first?\none\ntwo"
)
) == docformatter.split_summary_and_description("This is the first?\none\ntwo")

@pytest.mark.unit
def test_split_summary_and_description_with_quote(self):
""""""
assert (
'This is the first\n"one".',
"",
) == docformatter.split_summary_and_description(
'This is the first\n"one".'
)
) == docformatter.split_summary_and_description('This is the first\n"one".')

assert (
"This is the first\n'one'.",
"",
) == docformatter.split_summary_and_description(
"This is the first\n'one'."
)
) == docformatter.split_summary_and_description("This is the first\n'one'.")

assert (
"This is the first\n``one``.",
"",
) == docformatter.split_summary_and_description(
"This is the first\n``one``."
)
) == docformatter.split_summary_and_description("This is the first\n``one``.")

@pytest.mark.unit
def test_split_summary_and_description_with_punctuation(self):
Expand Down Expand Up @@ -461,9 +482,7 @@ def test_split_summary_and_description_with_abbreviation(self):
"Test Mrs. now",
"Test Ms. now",
]:
assert (text, "") == docformatter.split_summary_and_description(
text
)
assert (text, "") == docformatter.split_summary_and_description(text)

@pytest.mark.unit
def test_split_summary_and_description_with_url(self):
Expand Down Expand Up @@ -497,9 +516,7 @@ class TestStrippers:
@pytest.mark.unit
def test_remove_section_header(self):
"""Remove section header directives."""
assert "foo\nbar\n" == docformatter.remove_section_header(
"----\nfoo\nbar\n"
)
assert "foo\nbar\n" == docformatter.remove_section_header("----\nfoo\nbar\n")

line = "foo\nbar\n"
assert line == docformatter.remove_section_header(line)
Expand Down
Loading