Skip to content

Commit 0a5bc32

Browse files
authored
Insert newlines if issue numbers would be inserted inside a code block (#624)
2 parents 331422b + fb995e1 commit 0a5bc32

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

src/towncrier/_builder.py

+24
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,29 @@ def render_issue(issue_format: str | None, issue: str) -> str:
328328
return issue_format.format(issue=issue)
329329

330330

331+
def append_newlines_if_trailing_code_block(text: str) -> str:
332+
"""
333+
Appends two newlines to a text string if it ends with a code block.
334+
335+
Used by `render_fragments` to avoid appending link to issue number into the code block.
336+
"""
337+
# Search for the existence of a code block at the end. We do this by searching for:
338+
# 1. start of code block: two ":", followed by two newlines
339+
# 2. any number of indented, or empty, lines (or the code block would end)
340+
# 3. one line of indented text w/o a trailing newline (because the string is stripped)
341+
# 4. end of the string.
342+
indented_text = r" [ \t]+[^\n]*"
343+
empty_or_indented_text_lines = f"(({indented_text})?\n)*"
344+
regex = r"::\n\n" + empty_or_indented_text_lines + indented_text + "$"
345+
if re.search(regex, text):
346+
# We insert one space, the default template inserts another, which results
347+
# in the correct indentation given default bullet indentation.
348+
# Non-default templates with different indentation will likely encounter issues
349+
# if they have trailing code blocks.
350+
return text + "\n\n "
351+
return text
352+
353+
331354
def render_fragments(
332355
template: str,
333356
issue_format: str | None,
@@ -381,6 +404,7 @@ def render_fragments(
381404
# for the template, after formatting each issue number
382405
categories = {}
383406
for text, issues in entries:
407+
text = append_newlines_if_trailing_code_block(text)
384408
rendered = [render_issue(issue_format, i) for i in issues]
385409
categories[text] = rendered
386410

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

src/towncrier/test/test_format.py

+71
Original file line numberDiff line numberDiff line change
@@ -462,3 +462,74 @@ def test_line_wrapping_disabled(self):
462462
versiondata={"name": "MyProject", "version": "1.0", "date": "never"},
463463
)
464464
self.assertEqual(output, expected_output)
465+
466+
def test_trailing_block(self) -> None:
467+
"""
468+
Make sure a newline gets inserted before appending the issue number, if the
469+
newsfragment ends with an indented block.
470+
"""
471+
472+
fragments = {
473+
"": {
474+
(
475+
"1",
476+
"feature",
477+
0,
478+
): (
479+
"this fragment has a trailing code block::\n\n"
480+
" def foo(): ...\n\n"
481+
" \n"
482+
" def bar(): ..."
483+
),
484+
(
485+
"2",
486+
"feature",
487+
0,
488+
): (
489+
"this block is not trailing::\n\n"
490+
" def foo(): ...\n"
491+
" def bar(): ...\n\n"
492+
"so we can append the issue number directly after this"
493+
),
494+
}
495+
}
496+
# the line with 3 spaces (and nothing else) is stripped
497+
expected_output = """MyProject 1.0 (never)
498+
=====================
499+
500+
Features
501+
--------
502+
503+
- this fragment has a trailing code block::
504+
505+
def foo(): ...
506+
507+
508+
def bar(): ...
509+
510+
(#1)
511+
- this block is not trailing::
512+
513+
def foo(): ...
514+
def bar(): ...
515+
516+
so we can append the issue number directly after this (#2)
517+
518+
519+
"""
520+
521+
definitions = {
522+
"feature": {"name": "Features", "showcontent": True},
523+
}
524+
template = read_pkg_resource("templates/default.rst")
525+
fragments_split = split_fragments(fragments, definitions)
526+
output = render_fragments(
527+
template,
528+
None,
529+
fragments_split,
530+
definitions,
531+
["-", "~"],
532+
wrap=True,
533+
versiondata={"name": "MyProject", "version": "1.0", "date": "never"},
534+
)
535+
self.assertEqual(output, expected_output)

0 commit comments

Comments
 (0)