From 39b13daa9196fe7988d77d50cc6507d6face3608 Mon Sep 17 00:00:00 2001 From: Renan Rodrigo Date: Sun, 22 Sep 2024 23:36:41 -0300 Subject: [PATCH] cli: add indented Block classes Indented Blocks standardize indentation through the CLI, and Suggestion Blocks take the suggestion configuration into consideration. Signed-off-by: Renan Rodrigo --- uaclient/cli/formatter.py | 55 ++++++++++++- uaclient/cli/tests/test_cli_formatter.py | 98 +++++++++++++++++++++++- 2 files changed, 151 insertions(+), 2 deletions(-) diff --git a/uaclient/cli/formatter.py b/uaclient/cli/formatter.py index 09e67ab895..9e3b1b3e32 100644 --- a/uaclient/cli/formatter.py +++ b/uaclient/cli/formatter.py @@ -1,7 +1,8 @@ import os import re import sys -from typing import List, Optional +import textwrap +from typing import Any, List, Optional from uaclient.config import UAConfig from uaclient.messages import TxtColor @@ -182,3 +183,55 @@ def _fill_row(self, row: List[str]) -> str: output += self.ljust(row[i], self.column_sizes[i]) + self.SEPARATOR output += row[-1] return output + + +class Block: + INDENT_SIZE = 4 + INDENT_CHAR = " " + + def __init__( + self, + title: Optional[str] = None, + content: Optional[List[Any]] = None, + ): + self.title = title + self.content = content if content is not None else [] + + 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() + + line_length -= self.INDENT_SIZE + + output = "" + + if self.title: + output += ( + TxtColor.BOLD + + TxtColor.DISABLEGREY + + self.title + + TxtColor.ENDC + + "\n" + ) + + for item in self.content: + if isinstance(item, (Block, Table)): + item_str = item.to_string(line_length=line_length) + else: + item_str = "\n".join(wrap_text(str(item), line_length)) + "\n" + + output += textwrap.indent( + item_str, self.INDENT_CHAR * self.INDENT_SIZE + ) + + return output + + +class SuggestionBlock(Block): + def to_string(self, line_length: Optional[int] = None) -> str: + if ProOutputFormatterConfig.show_suggestions: + return super().to_string(line_length) + return "" diff --git a/uaclient/cli/tests/test_cli_formatter.py b/uaclient/cli/tests/test_cli_formatter.py index 181c3504a0..276b9c9683 100644 --- a/uaclient/cli/tests/test_cli_formatter.py +++ b/uaclient/cli/tests/test_cli_formatter.py @@ -3,8 +3,14 @@ import mock import pytest +from uaclient.cli.formatter import Block from uaclient.cli.formatter import ProOutputFormatterConfig as POFC -from uaclient.cli.formatter import Table, len_no_color, wrap_text +from uaclient.cli.formatter import ( + SuggestionBlock, + Table, + len_no_color, + wrap_text, +) M_PATH = "uaclient.cli.formatter." @@ -269,3 +275,93 @@ def test_to_string_no_wrap_if_no_tty(self, _m_terminal_size, _m_is_tty): c fg hijkl m """ # noqa: E501 ) + + +class TestBlock: + @mock.patch(M_PATH + "sys.stdout.isatty", return_value=False) + @mock.patch( + M_PATH + "os.get_terminal_size", + side_effect=OSError(), + ) + def test_indents_and_wraps_content_when_len_specified( + self, _m_terminal_size, _m_is_tty + ): + block = Block( + title="Example Title", + content=[ + "Smaller content line", + "A slightly bigger line which needs to be wrapped to fit the screen", # noqa: E501 + Block( + title="Inner block", + content=[ + "Another small content line", + "Another slightly bigger line which needs to be wrapped to fit the screen", # noqa: E501 + ], + ), + Table( + rows=[ + [ + "1", + "Table bigger last column which needs to be wrapped to fit the screen", # noqa: E501 + ], + [ + "2", + "Table bigger last column which needs to be wrapped to fit the screen", # noqa: E501 + ], + [ + "3", + "Table bigger last column which needs to be wrapped to fit the screen", # noqa: E501 + ], + ] + ), + ], + ) + assert block.to_string() == textwrap.dedent( + """\ + \x1b[1m\x1b[37mExample Title\x1b[0m + Smaller content line + A slightly bigger line which needs to be wrapped to fit the screen + \x1b[1m\x1b[37mInner block\x1b[0m + Another small content line + Another slightly bigger line which needs to be wrapped to fit the screen + 1 Table bigger last column which needs to be wrapped to fit the screen + 2 Table bigger last column which needs to be wrapped to fit the screen + 3 Table bigger last column which needs to be wrapped to fit the screen + """ # noqa: E501 + ) + assert block.to_string(line_length=40) == textwrap.dedent( + """\ + \x1b[1m\x1b[37mExample Title\x1b[0m + Smaller content line + A slightly bigger line which needs + to be wrapped to fit the screen + \x1b[1m\x1b[37mInner block\x1b[0m + Another small content line + Another slightly bigger line + which needs to be wrapped to fit + the screen + 1 Table bigger last column which + needs to be wrapped to fit the + screen + 2 Table bigger last column which + needs to be wrapped to fit the + screen + 3 Table bigger last column which + needs to be wrapped to fit the + screen + """ + ) + + def test_suggestions_can_be_disabled(self, FakeConfig): + suggestion_block = SuggestionBlock( + title="Suggestion", content=["Some content"] + ) + POFC.init(FakeConfig()) + assert suggestion_block.to_string() == textwrap.dedent( + """\ + \x1b[1m\x1b[37mSuggestion\x1b[0m + Some content + """ + ) + POFC.disable_suggestions() + assert suggestion_block.to_string() == ""