Skip to content
1 change: 1 addition & 0 deletions changes/2563.misc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``Warning_banner`` static method added to ``Console`` class.
91 changes: 91 additions & 0 deletions src/briefcase/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,97 @@ def export_log(self):
"""Export the text of the entire log; the log is also cleared."""
return self._log_impl.export_text()

def warning_banner(
self,
message: str,
title: str = "",
width: int = 80,
border_char: str = "*",
) -> str:
"""Format warning banner message inside an asterisk border box. It is possible
to input title/message as multiline or single string. To manually split the
message into paragraphs, use the "\\n" character. If you need empty lines, use
the "\\n\\n" sequence.

:param message: The message to format inside the box.
:param title: The title of the box. If provided, appears centered at the top.
:param width: The total width of the box in characters. Defaults to 80.
:param border_char: Character to use for the box border. Defaults to "*".
:return: The formatted message enclosed in a bordered box.
"""

# If message and title are both empty or width is not an integer,
# return empty string
if not any((message, title)) or not isinstance(width, int):
return ""

def dedent_and_split(text):
"""Dedent and split text into paragraphs by manual line breaks."""
# Remove common leading whitespace
text = textwrap.dedent(text).strip()
# replace line breaks with spaces
text = text.replace("\n", " ")
# split the message into paragraphs by manual line breaks
return text.split("\\n")

# Create border line
border_line = border_char * width
# create lines array with opening line of the box
lines = [border_line]

# if title exists, format title in the box
if title:
if not isinstance(title, str):
return ""

# width of title line inside the box
inner_width = width - 4

# spilt title into paragraphs
paragraphs = dedent_and_split(title)

for paragraph in paragraphs:
paragraph = paragraph.strip()
if paragraph: # Non-empty paragraph
# Wrap paragraph to lines to fit the width of the box
wrapped_title_lines = textwrap.wrap(paragraph, width=width - 6)

for line in wrapped_title_lines:
# Center each line within the available space
padded_line = line.center(inner_width)
# add line to the box
lines.append(f"{border_char * 2}{padded_line}{border_char * 2}")
else: # Empty paragraph (preserve blank lines)
lines.append(
f"{border_char * 2}{''.center(inner_width)}{border_char * 2}"
)

# closing line of title in the box
lines.append(border_line)

# If message is not empty, add it to the box
if message:
if not isinstance(message, str):
return ""

# spilt message into paragraphs
paragraphs = dedent_and_split(message)

for paragraph in paragraphs:
paragraph = paragraph.strip()
if paragraph: # Non-empty paragraph
# Wrap paragraph to lines to fit the width of the box
wrapped_lines = textwrap.wrap(paragraph, width=width)
lines.extend(wrapped_lines)
else: # Empty paragraph (preserve blank lines)
lines.append("")

# closing line of message
lines.append(border_line)

# merge lines into a single string and return
return "\n".join(lines)

#################################################################
# Logging controls
#################################################################
Expand Down
212 changes: 212 additions & 0 deletions tests/console/Console/test_warning_banner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import pytest

name = "Anton"
issue_idx = 2559

source_msg = f"""\
Hi there! My name is {name}, and I'm a Python developer.\\n\\n
This is my very first
experience in open source
development.
I like your project, and I'm excited to contribute to it.\
"""

source_title = f"""\
\\nINFO: I TRIED TO SOLVE THIS ISSUE #{issue_idx}.\\n\\n
IT SEEMED TO ME RATHER SIMPLE,
BUT I GOT VERY GOOD EXPERIENCE\
"""


@pytest.mark.parametrize(
("test_name", "message", "title", "width", "expected"),
[
("Test 1: Empty message and title", "", "", 80, ""),
(
"Test 2: Default width (80) with title and message",
source_msg,
source_title,
80,
"""\
********************************************************************************
** **
** INFO: I TRIED TO SOLVE THIS ISSUE #2559. **
** **
** IT SEEMED TO ME RATHER SIMPLE, BUT I GOT VERY GOOD EXPERIENCE **
********************************************************************************
Hi there! My name is Anton, and I'm a Python developer.

This is my very first experience in open source development. I like your
project, and I'm excited to contribute to it.
********************************************************************************\
""",
),
(
"Test 3: Narrow width (40) with title and message",
source_msg,
source_title,
40,
"""\
****************************************
** **
** INFO: I TRIED TO SOLVE THIS ISSUE **
** #2559. **
** **
** IT SEEMED TO ME RATHER SIMPLE, BUT **
** I GOT VERY GOOD EXPERIENCE **
****************************************
Hi there! My name is Anton, and I'm a
Python developer.

This is my very first experience in open
source development. I like your project,
and I'm excited to contribute to it.
****************************************\
""",
),
(
"Test 4: Default width (80) without title",
source_msg,
None,
80,
"""\
********************************************************************************
Hi there! My name is Anton, and I'm a Python developer.

This is my very first experience in open source development. I like your
project, and I'm excited to contribute to it.
********************************************************************************\
""",
),
(
"Test 5: Custom width (60) with title and message",
source_msg,
source_title,
60,
"""\
************************************************************
** **
** INFO: I TRIED TO SOLVE THIS ISSUE #2559. **
** **
** IT SEEMED TO ME RATHER SIMPLE, BUT I GOT VERY GOOD **
** EXPERIENCE **
************************************************************
Hi there! My name is Anton, and I'm a Python developer.

This is my very first experience in open source development.
I like your project, and I'm excited to contribute to it.
************************************************************\
""",
),
(
"Test 6: Very narrow width (30) with title and message",
source_msg,
source_title,
30,
"""\
******************************
** **
** INFO: I TRIED TO SOLVE **
** THIS ISSUE #2559. **
** **
** IT SEEMED TO ME RATHER **
** SIMPLE, BUT I GOT VERY **
** GOOD EXPERIENCE **
******************************
Hi there! My name is Anton,
and I'm a Python developer.

This is my very first
experience in open source
development. I like your
project, and I'm excited to
contribute to it.
******************************\
""",
),
(
"Test 7: Message with only title (empty message)",
"",
source_title,
80,
"""\
********************************************************************************
** **
** INFO: I TRIED TO SOLVE THIS ISSUE #2559. **
** **
** IT SEEMED TO ME RATHER SIMPLE, BUT I GOT VERY GOOD EXPERIENCE **
********************************************************************************\
""",
),
(
"Test 8: Very short message with title",
"Short message",
"Short title",
80,
"""\
********************************************************************************
** Short title **
********************************************************************************
Short message
********************************************************************************\
""",
),
(
"Test 9: Message and title lengths equal to box width",
"Length of message is equal to box _width",
"Length of title is e-l to box w-th",
40,
"""\
****************************************
** Length of title is e-l to box w-th **
****************************************
Length of message is equal to box _width
****************************************\
""",
),
(
"Test 10: Message and title lengths longer +1 symbol to box width",
"Length of message is equal to box __width",
"Length of title is e-l to box _w-th",
40,
"""\
****************************************
** Length of title is e-l to box _w- **
** th **
****************************************
Length of message is equal to box
__width
****************************************\
""",
),
(
"Test 11: Invalid input types",
5,
5,
40,
"",
),
(
"Test 12: Invalid input types",
"5",
"5",
"40",
"",
),
],
)
def test_warning_banner(console, test_name, message, title, width, expected):
"""Test warning_banner with various inputs."""
if title is None:
msg = console.warning_banner(message, width=width)
else:
msg = console.warning_banner(message, title=title, width=width)

# # Debug output if needed
# print('\n\n' + test_name)
# print("Generated:")
# print(msg)
# print("Expected:")
# print(expected)

assert msg == expected
Loading