From 11a1125e68f4931ee1ba311ac68e221bbea30cb1 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 27 Aug 2026 16:43:29 +0300 Subject: [PATCH 1/2] Don't reflow dashed lists, same as asterisk lists --- src/blurb/_utils/text.py | 2 +- tests/test_utils_text.py | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/blurb/_utils/text.py b/src/blurb/_utils/text.py index 39c0399..24b9202 100644 --- a/src/blurb/_utils/text.py +++ b/src/blurb/_utils/text.py @@ -39,7 +39,7 @@ def textwrap_body(body: str | Iterable[str], *, subsequent_indent: str = '') -> 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', '') diff --git a/tests/test_utils_text.py b/tests/test_utils_text.py index 962792c..5ad291d 100644 --- a/tests/test_utils_text.py +++ b/tests/test_utils_text.py @@ -40,6 +40,33 @@ ' * 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): From 3f44e3be7c4ad0c4a8246d3a4e7b6d5f10d7308f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:19:06 +0300 Subject: [PATCH 2/2] Pass "- " prefix as initial_indent so merged NEWS entries still reflow --- src/blurb/_merge.py | 3 +-- src/blurb/_utils/text.py | 12 +++++++++++- tests/test_utils_text.py | 23 +++++++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) 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 24b9202..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,6 +39,8 @@ 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 @@ -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 5ad291d..0b2d926 100644 --- a/tests/test_utils_text.py +++ b/tests/test_utils_text.py @@ -71,3 +71,26 @@ ) 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