Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Insert newlines if issue numbers would be inserted inside a code block #624

Open
wants to merge 6 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/towncrier/_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -381,6 +404,7 @@ def render_fragments(
# for the template, after formatting each issue number
categories = {}
for text, issues in entries:
text = append_newlines_if_trailing_code_block(text)
rendered = [render_issue(issue_format, i) for i in issues]
categories[text] = rendered

Expand Down
1 change: 1 addition & 0 deletions src/towncrier/newsfragments/614.bugfix.rst
Original file line number Diff line number Diff line change
@@ -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.
68 changes: 68 additions & 0 deletions src/towncrier/test/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,3 +462,71 @@ def test_line_wrapping_disabled(self):
versiondata={"name": "MyProject", "version": "1.0", "date": "never"},
)
self.assertEqual(output, expected_output)

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.
"""

fragments = {
"": {
(
"1",
"feature",
0,
): (
"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
--------

- this fragment has a trailing code block::

def foo(): ...


def bar(): ...

(#1)
- this block is not trailing::

def foo(): ...
def bar(): ...

so we can append the issue number directly after this (#2)


"""

definitions = {
"feature": {"name": "Features", "showcontent": True},
}
template = read_pkg_resource("templates/default.rst")
fragments_split = split_fragments(fragments, definitions)
output = render_fragments(
template,
None,
fragments_split,
definitions,
["-", "~"],
wrap=True,
versiondata={"name": "MyProject", "version": "1.0", "date": "never"},
)
self.assertEqual(output, expected_output)