From da361496110cb76432b00f5b469f8d37ebb6f15f Mon Sep 17 00:00:00 2001 From: santhreal <64453045+santhreal@users.noreply.github.com> Date: Fri, 17 Jul 2026 23:55:55 -0700 Subject: [PATCH] fix: anchor newline-end strip to whole alternation U_NL_END appended $ only to the last alternative, so Path.write_lines stripped embedded newlines instead of trailing terminators only. --- path/__init__.py | 7 +++++-- tests/test_path.py | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/path/__init__.py b/path/__init__.py index 33dd979..683846b 100644 --- a/path/__init__.py +++ b/path/__init__.py @@ -98,8 +98,9 @@ U_LINESEPS = LINESEPS + ['\u0085', '\u2028', '\u2029'] B_NEWLINE = re.compile('|'.join(LINESEPS).encode()) U_NEWLINE = re.compile('|'.join(U_LINESEPS)) -B_NL_END = re.compile(B_NEWLINE.pattern + b'$') -U_NL_END = re.compile(U_NEWLINE.pattern + '$') +# Group the alternation so $ anchors the whole newline, not only the last alt. +B_NL_END = re.compile(b'(?:' + B_NEWLINE.pattern + b')$') +U_NL_END = re.compile('(?:' + U_NEWLINE.pattern + ')$') _default_linesep = object() @@ -155,6 +156,8 @@ def _strip_newlines(lines: Iterable[str]) -> Iterator[str]: r""" >>> list(_strip_newlines(['Hello World\r\n', 'foo'])) ['Hello World', 'foo'] + >>> list(_strip_newlines(['Hello\nWorld\n', 'foo'])) + ['Hello\nWorld', 'foo'] """ return (U_NL_END.sub('', line) for line in lines) diff --git a/tests/test_path.py b/tests/test_path.py index b424bb6..1d74b22 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -318,6 +318,13 @@ def test_read_write(self, tmpdir): assert file.read_text(encoding='utf-8') == 'hello world' assert file.read_bytes() == b'hello world' + def test_write_lines_preserves_embedded_newlines(self, tmpdir): + file = path.Path(tmpdir) / 'filename' + file.write_lines(['Hello\nWorld', 'foo\r\nbar\n'], encoding='utf-8') + assert file.read_bytes() == ( + f'Hello\nWorld{os.linesep}foo\r\nbar{os.linesep}'.encode() + ) + class TestPerformance: @staticmethod