Skip to content

Commit

Permalink
cli: determine length when strigifying the Table objects
Browse files Browse the repository at this point in the history
Instead of passing it to the constructor, optionally pass it to a
to_string method

Signed-off-by: Renan Rodrigo <[email protected]>
  • Loading branch information
renanrodrigo committed Sep 26, 2024
1 parent 9db0c59 commit b15e724
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 39 deletions.
33 changes: 18 additions & 15 deletions uaclient/cli/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ def len_no_color(text: str) -> int:
return len(re.sub(COLOR_FORMATTING_PATTERN, "", text))


def _get_default_length():
if sys.stdout.isatty():
return os.get_terminal_size().columns
# If you're not in a tty, we don't care about string length
# If you have a thousand characters line, well, wow
return 999


# We can't rely on textwrap because of the len_no_color function
# Textwrap is using a magic regex instead
def wrap_text(text: str, max_width: int) -> List[str]:
Expand Down Expand Up @@ -77,21 +85,10 @@ def __init__(
self,
headers: Optional[List[str]] = None,
rows: Optional[List[List[str]]] = None,
max_length: Optional[int] = None,
):
self.headers = headers if headers is not None else []
self.rows = rows if rows is not None else []
self.column_sizes = self._get_column_sizes()
if sys.stdout.isatty():
self.max_length = (
os.get_terminal_size().columns
if max_length is None
else max_length
)
else:
# If you're not in a tty, we don't care about wrapping
# If you have a thousand characters line on the table, well, wow
self.max_length = 999

@staticmethod
def ljust(string: str, total_length: int) -> str:
Expand Down Expand Up @@ -133,9 +130,15 @@ def _get_column_sizes(self) -> List[int]:
return column_sizes

def __str__(self) -> str:
return self.to_string()

def to_string(self, line_length: Optional[int] = None) -> str:
if line_length is None:
line_length = _get_default_length()

rows = self.rows
if self._get_line_length() > self.max_length:
rows = self.wrap_last_column()
if self._get_line_length() > line_length:
rows = self.wrap_last_column(line_length)
output = ""
if self.headers:
output += (
Expand All @@ -154,8 +157,8 @@ def _get_line_length(self) -> int:
self.SEPARATOR
)

def wrap_last_column(self) -> List[List[str]]:
last_column_size = self.max_length - (
def wrap_last_column(self, max_length: int) -> List[List[str]]:
last_column_size = max_length - (
sum(self.column_sizes[:-1])
+ (len(self.column_sizes) - 1) * len(self.SEPARATOR)
)
Expand Down
85 changes: 61 additions & 24 deletions uaclient/cli/tests/test_cli_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,24 +153,6 @@ def test_validate_rejects_invalid_entries(

assert expected_msg in str(error.value)

@mock.patch(M_PATH + "sys.stdout.isatty", return_value=True)
def test_max_length_is_set(self, _m_is_tty):
table = Table(["a"], [], max_length=99)
assert table.max_length == 99

@mock.patch(M_PATH + "sys.stdout.isatty", return_value=True)
@mock.patch(M_PATH + "os.get_terminal_size")
def test_max_length_is_terminal_size_if_not_set(
self, m_terminal_size, _m_is_tty
):
table = Table(["a"], [])
assert table.max_length == m_terminal_size.return_value.columns

@mock.patch(M_PATH + "sys.stdout.isatty", return_value=False)
def test_max_length_is_infinite_if_not_tty(self, _m_is_tty):
table = Table(["a"], [])
assert table.max_length == 999

def test_column_sizes(self):
table = Table(
["header1", "h2", "h3", "h4"],
Expand Down Expand Up @@ -202,9 +184,8 @@ def test_wrap_last_column(self, _m_is_tty):
["b", "de", "fg", "wow this is a really big string of data"],
["c", "fg", "hijkl", "m"],
],
max_length=40,
)
assert table.wrap_last_column() == [
assert table.wrap_last_column(max_length=40) == [
table.rows[0],
["b", "de", "fg", "wow this is a"],
[" ", " ", " ", "really big string of"],
Expand All @@ -213,22 +194,78 @@ def test_wrap_last_column(self, _m_is_tty):
]

@mock.patch(M_PATH + "sys.stdout.isatty", return_value=True)
def test_print_as_str(self, _m_is_tty):
def test_to_string_wraps_to_length(self, _m_is_tty):
table = Table(
["header1", "h2", "h3", "h4"],
[
["a", "bc", "de", "f"],
["b", "de", "fg", "wow this is a really big string of data"],
["c", "fg", "hijkl", "m"],
],
)
assert table.to_string(line_length=40) == textwrap.dedent(
"""\
\x1b[1mheader1 h2 h3 h4\x1b[0m
a bc de f
b de fg wow this is a
really big string of
data
c fg hijkl m
"""
)

@mock.patch(M_PATH + "sys.stdout.isatty", return_value=True)
@mock.patch(
M_PATH + "os.get_terminal_size",
return_value=mock.MagicMock(columns=40),
)
def test_to_string_wraps_to_terminal_size(
self, _m_terminal_size, _m_is_tty
):
table = Table(
["header1", "h2", "h3", "h4"],
[
["a", "bc", "de", "f"],
["b", "de", "fg", "wow this is a really big string of data"],
["c", "fg", "hijkl", "m"],
],
max_length=40,
)
assert table.__str__() == textwrap.dedent(
assert table.to_string() == textwrap.dedent(
"""\
\x1b[1mheader1 h2 h3 h4\x1b[0m
a bc de f
b de fg wow this is a
really big string of
data
c fg hijkl m"""
c fg hijkl m
"""
)

@mock.patch(M_PATH + "sys.stdout.isatty", return_value=False)
# There is no terminal size if you are not in a tty
@mock.patch(
M_PATH + "os.get_terminal_size",
side_effect=OSError(),
)
def test_to_string_no_wrap_if_no_tty(self, _m_terminal_size, _m_is_tty):
table = Table(
["header1", "h2", "h3", "h4"],
[
["a", "bc", "de", "f"],
[
"b",
"de",
"fg",
"wow this is a really big string of data" * 3,
],
["c", "fg", "hijkl", "m"],
],
)
assert table.to_string() == textwrap.dedent(
"""\
\x1b[1mheader1 h2 h3 h4\x1b[0m
a bc de f
b de fg wow this is a really big string of datawow this is a really big string of datawow this is a really big string of data
c fg hijkl m
""" # noqa: E501
)

0 comments on commit b15e724

Please sign in to comment.