Skip to content

Commit 1b576ae

Browse files
authored
fix: multi sentence summary (#313)
* fix: not splitting multi-sentence summary * test: for new split_summary function
1 parent f8976f0 commit 1b576ae

File tree

3 files changed

+95
-30
lines changed

3 files changed

+95
-30
lines changed

src/docformatter/configuration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def do_parse_arguments(self) -> None:
152152

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

155-
# Default black line length is 88 so use this when not specified
155+
# Default black line length is 88, so use this when not specified
156156
# otherwise use PEP-8 defaults
157157
if self.args.black:
158158
_default_wrap_summaries = 88

src/docformatter/strings.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@
2929
import re
3030
from typing import List, Match, Optional, Union
3131

32+
# TODO: Read this from the configuration file or command line.
33+
ABBREVIATIONS = (
34+
"e.g.",
35+
"i.e.",
36+
"et. al.",
37+
"etc.",
38+
"Dr.",
39+
"Mr.",
40+
"Mrs.",
41+
"Ms.",
42+
)
43+
3244

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

185197
sentence += previous_delimiter + word
186198

187-
if sentence.endswith(("e.g.", "i.e.", "etc.", "Dr.", "Mr.", "Mrs.", "Ms.")):
199+
if sentence.endswith(ABBREVIATIONS):
188200
# Ignore false end of sentence.
189201
pass
190202
elif sentence.endswith((".", "?", "!")):
191203
break
192204
elif sentence.endswith(":") and delimiter == "\n":
193205
# Break on colon if it ends the line. This is a heuristic to detect
194-
# the beginning of some parameter list afterwards.
206+
# the beginning of some parameter list after wards.
195207
break
196208

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

202214

215+
def split_summary(lines) -> List[str]:
216+
"""Split multi-sentence summary into the first sentence and the rest."""
217+
if not lines or not lines[0].strip():
218+
return lines
219+
220+
text = lines[0].strip()
221+
222+
tokens = re.split(r"(\s+)", text) # Keep whitespace for accurate rejoining
223+
sentence = []
224+
rest = []
225+
i = 0
226+
227+
while i < len(tokens):
228+
token = tokens[i]
229+
sentence.append(token)
230+
231+
if token.endswith(".") and not any(
232+
"".join(sentence).strip().endswith(abbr) for abbr in ABBREVIATIONS
233+
):
234+
i += 1
235+
break
236+
237+
i += 1
238+
239+
rest = tokens[i:]
240+
first_sentence = "".join(sentence).strip()
241+
rest_text = "".join(rest).strip()
242+
243+
lines[0] = first_sentence
244+
if rest_text:
245+
lines.insert(2, rest_text)
246+
247+
return lines
248+
249+
203250
def split_summary_and_description(contents):
204251
"""Split docstring into summary and description.
205252
206253
Return tuple (summary, description).
207254
"""
208255
split_lines = contents.rstrip().splitlines()
256+
split_lines = split_summary(split_lines)
209257

210258
for index in range(1, len(split_lines)):
211259
# Empty line separation would indicate the rest is the description or

tests/test_string_functions.py

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
normalize_summary()
3737
remove_section_headers()
3838
split_first_sentence()
39+
split_summary()
3940
split_summary_and_description()
4041
strip_leading_blank_lines()
4142
strip_quotes()
@@ -261,6 +262,40 @@ def test_split_first_sentence(self):
261262
"\none\ntwo",
262263
) == docformatter.split_first_sentence("This is the first:\none\ntwo")
263264

265+
@pytest.mark.unit
266+
def test_split_one_sentence_summary(self):
267+
"""A single sentence summary should be returned as is.
268+
269+
See issue #283.
270+
"""
271+
assert [
272+
"This is a sentence.",
273+
"",
274+
] == docformatter.split_summary(["This is a sentence.", ""])
275+
276+
assert [
277+
"This e.g. a sentence.",
278+
"",
279+
] == docformatter.split_summary(["This e.g. a sentence.", ""])
280+
281+
@pytest.mark.unit
282+
def test_split_multi_sentence_summary(self):
283+
"""A multi-sentence summary should return only the first as the summary.
284+
285+
See issue #283.
286+
"""
287+
assert [
288+
"This is a sentence.",
289+
"",
290+
"This is another.",
291+
] == docformatter.split_summary(["This is a sentence. This is another.", ""])
292+
293+
assert [
294+
"This e.g. a sentence.",
295+
"",
296+
"This is another.",
297+
] == docformatter.split_summary(["This e.g. a sentence. This is another.", ""])
298+
264299
@pytest.mark.unit
265300
def test_split_summary_and_description(self):
266301
""""""
@@ -317,9 +352,7 @@ def test_split_summary_and_description_with_capital(self):
317352
assert (
318353
"This is the first\nWashington",
319354
"",
320-
) == docformatter.split_summary_and_description(
321-
"This is the first\nWashington"
322-
)
355+
) == docformatter.split_summary_and_description("This is the first\nWashington")
323356

324357
@pytest.mark.unit
325358
def test_split_summary_and_description_with_list_on_other_line(self):
@@ -351,53 +384,41 @@ def test_split_summary_and_description_with_colon(self):
351384
assert (
352385
"This is the first:",
353386
"one\ntwo",
354-
) == docformatter.split_summary_and_description(
355-
"This is the first:\none\ntwo"
356-
)
387+
) == docformatter.split_summary_and_description("This is the first:\none\ntwo")
357388

358389
@pytest.mark.unit
359390
def test_split_summary_and_description_with_exclamation(self):
360391
""""""
361392
assert (
362393
"This is the first!",
363394
"one\ntwo",
364-
) == docformatter.split_summary_and_description(
365-
"This is the first!\none\ntwo"
366-
)
395+
) == docformatter.split_summary_and_description("This is the first!\none\ntwo")
367396

368397
@pytest.mark.unit
369398
def test_split_summary_and_description_with_question_mark(self):
370399
""""""
371400
assert (
372401
"This is the first?",
373402
"one\ntwo",
374-
) == docformatter.split_summary_and_description(
375-
"This is the first?\none\ntwo"
376-
)
403+
) == docformatter.split_summary_and_description("This is the first?\none\ntwo")
377404

378405
@pytest.mark.unit
379406
def test_split_summary_and_description_with_quote(self):
380407
""""""
381408
assert (
382409
'This is the first\n"one".',
383410
"",
384-
) == docformatter.split_summary_and_description(
385-
'This is the first\n"one".'
386-
)
411+
) == docformatter.split_summary_and_description('This is the first\n"one".')
387412

388413
assert (
389414
"This is the first\n'one'.",
390415
"",
391-
) == docformatter.split_summary_and_description(
392-
"This is the first\n'one'."
393-
)
416+
) == docformatter.split_summary_and_description("This is the first\n'one'.")
394417

395418
assert (
396419
"This is the first\n``one``.",
397420
"",
398-
) == docformatter.split_summary_and_description(
399-
"This is the first\n``one``."
400-
)
421+
) == docformatter.split_summary_and_description("This is the first\n``one``.")
401422

402423
@pytest.mark.unit
403424
def test_split_summary_and_description_with_punctuation(self):
@@ -461,9 +482,7 @@ def test_split_summary_and_description_with_abbreviation(self):
461482
"Test Mrs. now",
462483
"Test Ms. now",
463484
]:
464-
assert (text, "") == docformatter.split_summary_and_description(
465-
text
466-
)
485+
assert (text, "") == docformatter.split_summary_and_description(text)
467486

468487
@pytest.mark.unit
469488
def test_split_summary_and_description_with_url(self):
@@ -497,9 +516,7 @@ class TestStrippers:
497516
@pytest.mark.unit
498517
def test_remove_section_header(self):
499518
"""Remove section header directives."""
500-
assert "foo\nbar\n" == docformatter.remove_section_header(
501-
"----\nfoo\nbar\n"
502-
)
519+
assert "foo\nbar\n" == docformatter.remove_section_header("----\nfoo\nbar\n")
503520

504521
line = "foo\nbar\n"
505522
assert line == docformatter.remove_section_header(line)

0 commit comments

Comments
 (0)