From d05e42068b4823dc781b334c548e90c592d741fe Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 17 Jul 2024 17:06:14 +0200
Subject: [PATCH 1/6] quick implementation of fix for issue numbers being
appended to a code block
---
src/towncrier/_builder.py | 12 ++++
src/towncrier/test/test_format.py | 97 +++++++++++++++++++++++++++++++
2 files changed, 109 insertions(+)
diff --git a/src/towncrier/_builder.py b/src/towncrier/_builder.py
index e7fbf12b..89da4b2c 100644
--- a/src/towncrier/_builder.py
+++ b/src/towncrier/_builder.py
@@ -305,6 +305,17 @@ def render_fragments(
data: dict[str, dict[str, dict[str, list[str]]]] = {}
issues_by_category: dict[str, dict[str, list[str]]] = {}
+ def maybe_append_newlines(text: str) -> str:
+ # if a newsfragment text ends with a code block, we want to append two newlines
+ # so issue number(s) don't get inserted into the block
+ if re.search(r"::\n\n([ \t]*\S*)*$", text):
+ # We insert one space, the default template inserts another, which results
+ # in the correct indentation given default bullet indentation.
+ # TODO: This breaks on different indentation though, and would require instead
+ # doing this change after the template has been applied.... I thought?
+ return text + "\n\n "
+ return text
+
for section_name, section_value in fragments.items():
data[section_name] = {}
issues_by_category[section_name] = {}
@@ -337,6 +348,7 @@ def render_fragments(
# for the template, after formatting each issue number
categories = {}
for text, issues in entries:
+ text = maybe_append_newlines(text)
rendered = [render_issue(issue_format, i) for i in issues]
categories[text] = rendered
diff --git a/src/towncrier/test/test_format.py b/src/towncrier/test/test_format.py
index a1efefb5..46a75db1 100644
--- a/src/towncrier/test/test_format.py
+++ b/src/towncrier/test/test_format.py
@@ -462,3 +462,100 @@ def test_line_wrapping_disabled(self):
versiondata={"name": "MyProject", "version": "1.0", "date": "never"},
)
self.assertEqual(output, expected_output)
+
+ def test_trailing_block(self):
+ """
+ Make sure a newline gets inserted before appending the issue number, if the
+ newsfragment ends with an indented block.
+ """
+
+ fragments = {
+ "": {
+ (
+ "1",
+ "feature",
+ 0,
+ ): "text::\n\n def foo(): ..."
+ }
+ }
+ expected_output = """MyProject 1.0 (never)
+=====================
+
+Features
+--------
+
+- text::
+
+ def foo(): ...
+
+ (#1)
+
+
+"""
+
+ # TODO: I copy-pasted the below lines from previous test, they probably contain
+ # crap that's irrelevant to this test.
+ definitions = {
+ "feature": {"name": "Features", "showcontent": True},
+ }
+ template = read_pkg_resource("templates/default.rst")
+
+ fragments = split_fragments(fragments, definitions)
+ output = render_fragments(
+ template,
+ None,
+ fragments,
+ definitions,
+ ["-", "~"],
+ wrap=True,
+ versiondata={"name": "MyProject", "version": "1.0", "date": "never"},
+ )
+ self.assertEqual(output, expected_output)
+
+ def test_trailing_block_nondefault_bullets(self):
+ """
+ Test insertion of newlines after code block with single-file-no-bullets.
+ """
+ # TODO: I expected this to break with the issue number being indented, but
+ # instead I got an extra newline? Which seems fine?
+
+ fragments = {
+ "": {
+ (
+ "1",
+ "feature",
+ 0,
+ ): "text::\n\n def foo(): ..."
+ }
+ }
+ expected_output = """MyProject 1.0 (never)
+=====================
+
+Features
+--------
+
+text::
+
+ def foo(): ...
+
+
+(#1)
+
+"""
+
+ definitions = {
+ "feature": {"name": "Features", "showcontent": True},
+ }
+ template = read_pkg_resource("templates/single-file-no-bullets.rst")
+
+ fragments = split_fragments(fragments, definitions)
+ output = render_fragments(
+ template,
+ None,
+ fragments,
+ definitions,
+ ["-", "~"],
+ wrap=True,
+ versiondata={"name": "MyProject", "version": "1.0", "date": "never"},
+ )
+ self.assertEqual(output, expected_output)
From b8454dab8832aa95a917b2f84b62cc29e1fcc196 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Sat, 31 Aug 2024 13:18:28 +0200
Subject: [PATCH 2/6] add newsfragment
---
src/towncrier/newsfragments/614.bugfix.rst | 1 +
1 file changed, 1 insertion(+)
create mode 100644 src/towncrier/newsfragments/614.bugfix.rst
diff --git a/src/towncrier/newsfragments/614.bugfix.rst b/src/towncrier/newsfragments/614.bugfix.rst
new file mode 100644
index 00000000..f8b12a6e
--- /dev/null
+++ b/src/towncrier/newsfragments/614.bugfix.rst
@@ -0,0 +1 @@
+Multi-line newsfragments that ends with a code block will now have a newline inserted before appending the link to the issue, to avoid breaking formatting.
From 63c4c430330d5330bef54bd26e48dcb3568a5063 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Mon, 2 Sep 2024 15:46:50 +0200
Subject: [PATCH 3/6] fix, clean up, and move helper function outside of
render_fragments. Cleaned up and expanded test. Removed
single-file-no-bullets test
---
src/towncrier/_builder.py | 36 ++++++++++------
src/towncrier/test/test_format.py | 70 ++++++++-----------------------
2 files changed, 41 insertions(+), 65 deletions(-)
diff --git a/src/towncrier/_builder.py b/src/towncrier/_builder.py
index 895c6a0b..da6e090b 100644
--- a/src/towncrier/_builder.py
+++ b/src/towncrier/_builder.py
@@ -328,6 +328,29 @@ def render_issue(issue_format: str | None, issue: str) -> str:
return issue_format.format(issue=issue)
+def append_newlines_if_trailing_code_block(text: str) -> str:
+ """
+ Appends two newlines to a text string if it ends with a code block.
+
+ Used by `render_fragments` to avoid appending link to issue number into the code block.
+ """
+ # Search for the existence of a code block at the end. We do this by searching for:
+ # 1. start of code block: two ":", followed by two newlines
+ # 2. any number of indented, or empty, lines (or the code block would end)
+ # 3. one line of indented text w/o a trailing newline (because the string is stripped)
+ # 4. end of the string.
+ indented_text=r" [ \t]+[^\n]*"
+ empty_or_indented_text_lines=f"(({indented_text})?\n)*"
+ regex = r"::\n\n" + empty_or_indented_text_lines + indented_text + "$"
+ if re.search(regex, text):
+ # We insert one space, the default template inserts another, which results
+ # in the correct indentation given default bullet indentation.
+ # Non-default templates with different indentation will likely encounter issues
+ # if they have trailing code blocks.
+ return text + "\n\n "
+ return text
+
+
def render_fragments(
template: str,
issue_format: str | None,
@@ -349,17 +372,6 @@ def render_fragments(
data: dict[str, dict[str, dict[str, list[str]]]] = {}
issues_by_category: dict[str, dict[str, list[str]]] = {}
- def maybe_append_newlines(text: str) -> str:
- # if a newsfragment text ends with a code block, we want to append two newlines
- # so issue number(s) don't get inserted into the block
- if re.search(r"::\n\n([ \t]*\S*)*$", text):
- # We insert one space, the default template inserts another, which results
- # in the correct indentation given default bullet indentation.
- # TODO: This breaks on different indentation though, and would require instead
- # doing this change after the template has been applied.... I thought?
- return text + "\n\n "
- return text
-
for section_name, section_value in fragments.items():
data[section_name] = {}
issues_by_category[section_name] = {}
@@ -392,7 +404,7 @@ def maybe_append_newlines(text: str) -> str:
# for the template, after formatting each issue number
categories = {}
for text, issues in entries:
- text = maybe_append_newlines(text)
+ text = append_newlines_if_trailing_code_block(text)
rendered = [render_issue(issue_format, i) for i in issues]
categories[text] = rendered
diff --git a/src/towncrier/test/test_format.py b/src/towncrier/test/test_format.py
index 46a75db1..9af21456 100644
--- a/src/towncrier/test/test_format.py
+++ b/src/towncrier/test/test_format.py
@@ -463,7 +463,7 @@ def test_line_wrapping_disabled(self):
)
self.assertEqual(output, expected_output)
- def test_trailing_block(self):
+ def test_trailing_block(self) -> None:
"""
Make sure a newline gets inserted before appending the issue number, if the
newsfragment ends with an indented block.
@@ -475,84 +475,48 @@ def test_trailing_block(self):
"1",
"feature",
0,
- ): "text::\n\n def foo(): ..."
+ ): "this fragment has a trailing code block::\n\n def foo(): ...\n\n \n def bar(): ...",
+ (
+ "2",
+ "feature",
+ 0,
+ ): "this block is not trailing::\n\n def foo(): ...\n def bar(): ...\n\nso we can append the issue number directly after this",
}
}
+ # the line with 3 spaces (and nothing else) is stripped
expected_output = """MyProject 1.0 (never)
=====================
Features
--------
-- text::
+- this fragment has a trailing code block::
def foo(): ...
- (#1)
-
-
-"""
-
- # TODO: I copy-pasted the below lines from previous test, they probably contain
- # crap that's irrelevant to this test.
- definitions = {
- "feature": {"name": "Features", "showcontent": True},
- }
- template = read_pkg_resource("templates/default.rst")
- fragments = split_fragments(fragments, definitions)
- output = render_fragments(
- template,
- None,
- fragments,
- definitions,
- ["-", "~"],
- wrap=True,
- versiondata={"name": "MyProject", "version": "1.0", "date": "never"},
- )
- self.assertEqual(output, expected_output)
+ def bar(): ...
- def test_trailing_block_nondefault_bullets(self):
- """
- Test insertion of newlines after code block with single-file-no-bullets.
- """
- # TODO: I expected this to break with the issue number being indented, but
- # instead I got an extra newline? Which seems fine?
-
- fragments = {
- "": {
- (
- "1",
- "feature",
- 0,
- ): "text::\n\n def foo(): ..."
- }
- }
- expected_output = """MyProject 1.0 (never)
-=====================
-
-Features
---------
-
-text::
+ (#1)
+- this block is not trailing::
def foo(): ...
+ def bar(): ...
+ so we can append the issue number directly after this (#2)
-(#1)
"""
definitions = {
"feature": {"name": "Features", "showcontent": True},
}
- template = read_pkg_resource("templates/single-file-no-bullets.rst")
-
- fragments = split_fragments(fragments, definitions)
+ template = read_pkg_resource("templates/default.rst")
+ fragments_split = split_fragments(fragments, definitions)
output = render_fragments(
template,
None,
- fragments,
+ fragments_split,
definitions,
["-", "~"],
wrap=True,
From a8b39319af88538c7f32812edcfe00056091b97c Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 2 Sep 2024 13:47:41 +0000
Subject: [PATCH 4/6] [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---
src/towncrier/_builder.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/towncrier/_builder.py b/src/towncrier/_builder.py
index da6e090b..33ebe167 100644
--- a/src/towncrier/_builder.py
+++ b/src/towncrier/_builder.py
@@ -339,8 +339,8 @@ def append_newlines_if_trailing_code_block(text: str) -> str:
# 2. any number of indented, or empty, lines (or the code block would end)
# 3. one line of indented text w/o a trailing newline (because the string is stripped)
# 4. end of the string.
- indented_text=r" [ \t]+[^\n]*"
- empty_or_indented_text_lines=f"(({indented_text})?\n)*"
+ indented_text = r" [ \t]+[^\n]*"
+ empty_or_indented_text_lines = f"(({indented_text})?\n)*"
regex = r"::\n\n" + empty_or_indented_text_lines + indented_text + "$"
if re.search(regex, text):
# We insert one space, the default template inserts another, which results
From 4a302958686619d57304cd19599d39d4fded6064 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Mon, 2 Sep 2024 15:52:30 +0200
Subject: [PATCH 5/6] fix pre-commit
---
src/towncrier/test/test_format.py | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/towncrier/test/test_format.py b/src/towncrier/test/test_format.py
index 9af21456..1a711a4a 100644
--- a/src/towncrier/test/test_format.py
+++ b/src/towncrier/test/test_format.py
@@ -475,12 +475,19 @@ def test_trailing_block(self) -> None:
"1",
"feature",
0,
- ): "this fragment has a trailing code block::\n\n def foo(): ...\n\n \n def bar(): ...",
+ ): (
+ "this fragment has a trailing code block::\n\n"
+ "def foo(): ...\n\n \n def bar(): ..."
+ ),
(
"2",
"feature",
0,
- ): "this block is not trailing::\n\n def foo(): ...\n def bar(): ...\n\nso we can append the issue number directly after this",
+ ): (
+ "this block is not trailing::\n\n"
+ "def foo(): ...\n def bar(): ..."
+ "\n\nso we can append the issue number directly after this"
+ ),
}
}
# the line with 3 spaces (and nothing else) is stripped
From fb995e1f4b2a53722a4c975bae4be5599e147b15 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 19 Nov 2024 16:42:46 +0100
Subject: [PATCH 6/6] fix tests after 4a30295 deleted some spaces
---
src/towncrier/test/test_format.py | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/towncrier/test/test_format.py b/src/towncrier/test/test_format.py
index 1a711a4a..7be2877e 100644
--- a/src/towncrier/test/test_format.py
+++ b/src/towncrier/test/test_format.py
@@ -477,7 +477,9 @@ def test_trailing_block(self) -> None:
0,
): (
"this fragment has a trailing code block::\n\n"
- "def foo(): ...\n\n \n def bar(): ..."
+ " def foo(): ...\n\n"
+ " \n"
+ " def bar(): ..."
),
(
"2",
@@ -485,8 +487,9 @@ def test_trailing_block(self) -> None:
0,
): (
"this block is not trailing::\n\n"
- "def foo(): ...\n def bar(): ..."
- "\n\nso we can append the issue number directly after this"
+ " def foo(): ...\n"
+ " def bar(): ...\n\n"
+ "so we can append the issue number directly after this"
),
}
}