diff options
author | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2020-04-09 16:37:46 -0400 |
---|---|---|
committer | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2020-04-09 16:37:46 -0400 |
commit | d815d2cd19a24bac89bd19416fb2b7cd0dadfe03 (patch) | |
tree | 2570d13b2561822911b000b36519be742ec89345 /cmd2/utils.py | |
parent | 0d4be64b6ec76fcf5f87933293dbc7c134a32cf0 (diff) | |
download | cmd2-git-d815d2cd19a24bac89bd19416fb2b7cd0dadfe03.tar.gz |
Initial commit of table creation API
Diffstat (limited to 'cmd2/utils.py')
-rw-r--r-- | cmd2/utils.py | 73 |
1 files changed, 56 insertions, 17 deletions
diff --git a/cmd2/utils.py b/cmd2/utils.py index 8b5e9cc8..78d39863 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -717,7 +717,7 @@ def align_text(text: str, alignment: TextAlignment, *, fill_char: str = ' ', :param fill_char: character that fills the alignment gap. Defaults to space. (Cannot be a line breaking character) :param width: display width of the aligned text. Defaults to width of the terminal. :param tab_width: any tabs in the text will be replaced with this many spaces. if fill_char is a tab, then it will - be converted to a space. + be converted to one space. :param truncate: if True, then each line will be shortened to fit within the display width. The truncated portions are replaced by a '…' character. Defaults to False. :return: aligned text @@ -738,8 +738,7 @@ def align_text(text: str, alignment: TextAlignment, *, fill_char: str = ' ', # Handle tabs text = text.replace('\t', ' ' * tab_width) - if fill_char == '\t': - fill_char = ' ' + fill_char = fill_char.replace('\t', ' ') if len(ansi.strip_style(fill_char)) != 1: raise TypeError("Fill character must be exactly one character long") @@ -755,6 +754,28 @@ def align_text(text: str, alignment: TextAlignment, *, fill_char: str = ' ', text_buf = io.StringIO() + # ANSI style sequences that may affect future lines will be cancelled by the fill_char's style. + # To avoid this, we save the state of a line's style so we can restore it when beginning the next line. + # This also allows the lines to be used independently and still have their style. TableCreator does this. + aggregate_styles = '' + + # Save the ANSI style sequences in fill_char + fill_char_styles = get_styles_in_text(fill_char) + + # Create a space with the same style as fill_char for cases in which + # fill_char does not divide evenly into the gap. + styled_space = '' + char_index = 0 + while char_index < len(fill_char): + if char_index in fill_char_styles: + # Preserve this style in styled_space + styled_space += fill_char_styles[char_index] + char_index += len(fill_char_styles[char_index]) + else: + # We've reached the visible fill_char. Replace it with a space. + styled_space += ' ' + char_index += 1 + for index, line in enumerate(lines): if index > 0: text_buf.write('\n') @@ -766,13 +787,16 @@ def align_text(text: str, alignment: TextAlignment, *, fill_char: str = ' ', if line_width == -1: raise(ValueError("Text to align contains an unprintable character")) - elif line_width >= width: - # No need to add fill characters - text_buf.write(line) - continue + # Get the styles in this line + line_styles = get_styles_in_text(line) # Calculate how wide each side of filling needs to be - total_fill_width = width - line_width + if line_width >= width: + # Don't return here even though the line needs no fill chars. + # There may be styles sequences to restore. + total_fill_width = 0 + else: + total_fill_width = width - line_width if alignment == TextAlignment.LEFT: left_fill_width = 0 @@ -789,11 +813,25 @@ def align_text(text: str, alignment: TextAlignment, *, fill_char: str = ' ', right_fill = (right_fill_width // fill_char_width) * fill_char # In cases where the fill character display width didn't divide evenly into - # the gaps being filled, pad the remainder with spaces. - left_fill += ' ' * (left_fill_width - ansi.style_aware_wcswidth(left_fill)) - right_fill += ' ' * (right_fill_width - ansi.style_aware_wcswidth(right_fill)) + # the gap being filled, pad the remainder with styled_space. + left_fill += styled_space * (left_fill_width - ansi.style_aware_wcswidth(left_fill)) + right_fill += styled_space * (right_fill_width - ansi.style_aware_wcswidth(right_fill)) + + # Don't allow styles in fill_char and text to affect one another + if fill_char_styles or aggregate_styles or line_styles: + if left_fill: + left_fill = ansi.RESET_ALL + left_fill + left_fill += ansi.RESET_ALL + + if right_fill: + right_fill = ansi.RESET_ALL + right_fill + right_fill += ansi.RESET_ALL + + # Write the line and restore any styles from previous lines + text_buf.write(left_fill + aggregate_styles + line + right_fill) - text_buf.write(left_fill + line + right_fill) + # Update the aggregate with styles in this line + aggregate_styles += ''.join(line_styles.values()) return text_buf.getvalue() @@ -809,7 +847,7 @@ def align_left(text: str, *, fill_char: str = ' ', width: Optional[int] = None, :param fill_char: character that fills the alignment gap. Defaults to space. (Cannot be a line breaking character) :param width: display width of the aligned text. Defaults to width of the terminal. :param tab_width: any tabs in the text will be replaced with this many spaces. if fill_char is a tab, then it will - be converted to a space. + be converted to one space. :param truncate: if True, then text will be shortened to fit within the display width. The truncated portion is replaced by a '…' character. Defaults to False. :return: left-aligned text @@ -832,7 +870,7 @@ def align_center(text: str, *, fill_char: str = ' ', width: Optional[int] = None :param fill_char: character that fills the alignment gap. Defaults to space. (Cannot be a line breaking character) :param width: display width of the aligned text. Defaults to width of the terminal. :param tab_width: any tabs in the text will be replaced with this many spaces. if fill_char is a tab, then it will - be converted to a space. + be converted to one space. :param truncate: if True, then text will be shortened to fit within the display width. The truncated portion is replaced by a '…' character. Defaults to False. :return: centered text @@ -855,7 +893,7 @@ def align_right(text: str, *, fill_char: str = ' ', width: Optional[int] = None, :param fill_char: character that fills the alignment gap. Defaults to space. (Cannot be a line breaking character) :param width: display width of the aligned text. Defaults to width of the terminal. :param tab_width: any tabs in the text will be replaced with this many spaces. if fill_char is a tab, then it will - be converted to a space. + be converted to one space. :param truncate: if True, then text will be shortened to fit within the display width. The truncated portion is replaced by a '…' character. Defaults to False. :return: right-aligned text @@ -878,13 +916,14 @@ def truncate_line(line: str, max_width: int, *, tab_width: int = 4) -> str: This is done to prevent issues caused in cases like: truncate_string(fg.blue + hello + fg.reset, 3) In this case, "hello" would be truncated before fg.reset resets the color from blue. Appending the remaining style - sequences makes sure the style is in the same state had the entire string been printed. + sequences makes sure the style is in the same state had the entire string been printed. align_text() relies on this + behavior when preserving style over multiple lines. :param line: text to truncate :param max_width: the maximum display width the resulting string is allowed to have :param tab_width: any tabs in the text will be replaced with this many spaces :return: line that has a display width less than or equal to width - :raises: ValueError if text contains an unprintable character like a new line + :raises: ValueError if text contains an unprintable character like a newline ValueError if max_width is less than 1 """ import io |