diff --git a/src/blurb/_merge.py b/src/blurb/_merge.py index 1d7f308..7e24637 100644 --- a/src/blurb/_merge.py +++ b/src/blurb/_merge.py @@ -104,8 +104,7 @@ def prnt(msg: str = '', /): if int(issue_number): body = f'bpo-{issue_number}: {body}' - body = f'- {body}' - text = textwrap_body(body, subsequent_indent=' ') + text = textwrap_body(body, initial_indent='- ', subsequent_indent=' ') prnt(text) prnt() prnt('**(For information about older versions, consult the HISTORY file.)**') diff --git a/src/blurb/_utils/text.py b/src/blurb/_utils/text.py index 39c0399..dc74e76 100644 --- a/src/blurb/_utils/text.py +++ b/src/blurb/_utils/text.py @@ -10,7 +10,12 @@ from collections.abc import Iterable -def textwrap_body(body: str | Iterable[str], *, subsequent_indent: str = '') -> str: +def textwrap_body( + body: str | Iterable[str], + *, + initial_indent: str = '', + subsequent_indent: str = '', +) -> str: """Wrap body text. Accepts either a string or an iterable of strings. @@ -34,12 +39,14 @@ def textwrap_body(body: str | Iterable[str], *, subsequent_indent: str = '') -> paragraphs = text.split('\n\n') paragraphs2 = [] kwargs: dict[str, object] = {'break_long_words': False, 'break_on_hyphens': False} + if initial_indent: + kwargs['initial_indent'] = initial_indent if subsequent_indent: kwargs['subsequent_indent'] = subsequent_indent dont_reflow = False for paragraph in paragraphs: # don't reflow bulleted / numbered lists - dont_reflow = dont_reflow or paragraph.startswith(('* ', '1. ', '#. ')) + dont_reflow = dont_reflow or paragraph.startswith(('* ', '- ', '1. ', '#. ')) if dont_reflow: initial = kwargs.get('initial_indent', '') subsequent = kwargs.get('subsequent_indent', '') @@ -91,6 +98,9 @@ def textwrap_body(body: str | Iterable[str], *, subsequent_indent: str = '') -> paragraph = '\n'.join( textwrap.wrap(paragraph.strip(), width=76, **kwargs) ).rstrip() + # Remove the initial indent added by the first pass, + # otherwise the second pass would add it again. + paragraph = paragraph.removeprefix(initial_indent) paragraph = '\n'.join( textwrap.wrap(paragraph.strip(), width=76, **kwargs) ).rstrip() diff --git a/tests/test_utils_text.py b/tests/test_utils_text.py index 962792c..0b2d926 100644 --- a/tests/test_utils_text.py +++ b/tests/test_utils_text.py @@ -40,7 +40,57 @@ ' * Item 1\n' ' * Item 2\n', ), + ( + 'This is a test of the textwrap_body function with a dashed list and subsequent indent. The list should not be wrapped.\n' + '\n' + '- Item 1\n' + '- Item 2\n', + ' ', + 'This is a test of the textwrap_body function with a dashed list and\n' + ' subsequent indent. The list should not be wrapped.\n' + '\n' + ' - Item 1\n' + ' - Item 2\n', + ), + ( + 'The following `threading` methods are now deprecated and should be replaced:\n' + '\n' + '- `threading.activeCount` => :func:`threading.active_count`\n' + '\n' + '- `threading.Condition.notifyAll` =>\n' + ' :meth:`threading.Condition.notify_all`\n', + ' ', + 'The following `threading` methods are now deprecated and should be replaced:\n' + '\n' + ' - `threading.activeCount` => :func:`threading.active_count`\n' + '\n' + ' - `threading.Condition.notifyAll` =>\n' + ' :meth:`threading.Condition.notify_all`\n', + ), ), ) def test_textwrap_body(body, subsequent_indent, expected): assert textwrap_body(body, subsequent_indent=subsequent_indent) == expected + + +@pytest.mark.parametrize( + 'body, expected', + ( + ( + 'gh-123456: This is a long sentence that keeps going on and on so that it must wrap when\n' + 'merged into the NEWS file.\n', + '- gh-123456: This is a long sentence that keeps going on and on so that it\n' + ' must wrap when merged into the NEWS file.\n', + ), + ( + 'First paragraph.\n\nSecond paragraph.\n', + '- First paragraph.\n\n Second paragraph.\n', + ), + ( + 'Some changes:\n\n- Item 1\n- Item 2\n', + '- Some changes:\n\n - Item 1\n - Item 2\n', + ), + ), +) +def test_textwrap_body_initial_indent(body, expected): + assert textwrap_body(body, initial_indent='- ', subsequent_indent=' ') == expected