summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2021-10-19 12:34:11 -0400
committerKevin Van Brunt <kmvanbrunt@gmail.com>2021-10-19 14:37:45 -0400
commitf4958c5bb081b0ea4b7aeb98fd1d996add1fc47d (patch)
tree3045ac805edb060dcb2951e4a7a6e0d05c17e5fc
parentf57b08672af97f9d973148b6c30d74fe4e712d14 (diff)
downloadcmd2-git-colored_tables.tar.gz
Added ability to colorize all aspects of BorderedTables and AlternatingTables.colored_tables
Refactored utils.align_text() to print less fill_char style characters.
-rw-r--r--CHANGELOG.md4
-rw-r--r--cmd2/ansi.py11
-rw-r--r--cmd2/cmd2.py10
-rw-r--r--cmd2/table_creator.py206
-rw-r--r--cmd2/utils.py45
-rw-r--r--tests/test_table_creator.py84
-rw-r--r--tests/test_utils.py26
7 files changed, 239 insertions, 147 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 129167d2..4951cfc7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,10 +4,10 @@
* Fixed bug where using choices on a Settable didn't verify that a valid choice had been entered.
* Enhancements
* Added settings to Column class which prevent a table from overriding existing styles in header
- and/or data text. These were added to support nesting an AlternatingTable within an AlternatingTable,
- but other custom table classes can also use these settings.
+ and/or data text. This allows for things like nesting an AlternatingTable in another AlternatingTable.
* AlternatingTable no longer applies background color to outer borders. This was done to improve appearance
since the background color extended beyond the borders of the table.
+ * Added ability to colorize all aspects of `BorderedTables` and `AlternatingTables`.
* Added support for 8-bit/256-colors with the `cmd2.EightBitFg` and `cmd2.EightBitBg` classes.
* Added support for 24-bit/RGB colors with the `cmd2.RgbFg` and `cmd2.RgbBg` classes.
* Removed dependency on colorama.
diff --git a/cmd2/ansi.py b/cmd2/ansi.py
index 91188a9c..5517ecf0 100644
--- a/cmd2/ansi.py
+++ b/cmd2/ansi.py
@@ -943,7 +943,7 @@ class RgbBg(BgColor):
# TODO: Remove this PyShadowingNames usage when deprecated fg and bg classes are removed.
# noinspection PyShadowingNames
def style(
- text: Any,
+ value: Any,
*,
fg: Optional[FgColor] = None,
bg: Optional[BgColor] = None,
@@ -959,7 +959,7 @@ def style(
The styling is self contained which means that at the end of the string reset code(s) are issued
to undo whatever styling was done at the beginning.
- :param text: text to format (anything convertible to a str)
+ :param value: object whose text is to be styled
:param fg: foreground color provided as any subclass of FgColor (e.g. Fg, EightBitFg, RgbFg)
Defaults to no color.
:param bg: foreground color provided as any subclass of BgColor (e.g. Bg, EightBitBg, RgbBg)
@@ -978,9 +978,6 @@ def style(
# List of strings that remove style
removals: List[AnsiSequence] = []
- # Convert the text object into a string if it isn't already one
- text_formatted = str(text)
-
# Process the style settings
if fg is not None:
additions.append(fg)
@@ -1014,8 +1011,8 @@ def style(
additions.append(TextStyle.UNDERLINE_ENABLE)
removals.append(TextStyle.UNDERLINE_DISABLE)
- # Combine the ANSI style sequences with the text
- return "".join(map(str, additions)) + text_formatted + "".join(map(str, removals))
+ # Combine the ANSI style sequences with the value's text
+ return "".join(map(str, additions)) + str(value) + "".join(map(str, removals))
# Default styles for printing strings of various types.
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index b647c48a..c06b9617 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -1062,7 +1062,7 @@ class Cmd(cmd.Cmd):
been piped to another process and that process terminates before the
cmd2 command is finished executing.
- :param msg: message to print (anything convertible to a str)
+ :param msg: object to print
:param end: string appended after the end of the message, default a newline
"""
try:
@@ -1080,7 +1080,7 @@ class Cmd(cmd.Cmd):
def perror(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> None:
"""Print message to sys.stderr
- :param msg: message to print (anything convertible to a str)
+ :param msg: object to print
:param end: string appended after the end of the message, default a newline
:param apply_style: If True, then ansi.style_error will be applied to the message text. Set to False in cases
where the message text already has the desired style. Defaults to True.
@@ -1094,7 +1094,7 @@ class Cmd(cmd.Cmd):
def pwarning(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> None:
"""Wraps perror, but applies ansi.style_warning by default
- :param msg: message to print (anything convertible to a str)
+ :param msg: object to print
:param end: string appended after the end of the message, default a newline
:param apply_style: If True, then ansi.style_warning will be applied to the message text. Set to False in cases
where the message text already has the desired style. Defaults to True.
@@ -1134,7 +1134,7 @@ class Cmd(cmd.Cmd):
"""For printing nonessential feedback. Can be silenced with `quiet`.
Inclusion in redirected output is controlled by `feedback_to_output`.
- :param msg: message to print (anything convertible to a str)
+ :param msg: object to print
:param end: string appended after the end of the message, default a newline
"""
if not self.quiet:
@@ -1149,7 +1149,7 @@ class Cmd(cmd.Cmd):
Never uses a pager inside of a script (Python or text) or when output is being redirected or piped or when
stdout or stdin are not a fully functional terminal.
- :param msg: message to print to current stdout (anything convertible to a str)
+ :param msg: object to print
:param end: string appended after the end of the message, default a newline
:param chop: True -> causes lines longer than the screen width to be chopped (truncated) rather than wrapped
- truncated text is still accessible by scrolling with the right & left arrow keys
diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py
index 50bc5909..0778d0c8 100644
--- a/cmd2/table_creator.py
+++ b/cmd2/table_creator.py
@@ -6,7 +6,6 @@ The general use case is to inherit from TableCreator to create a table class wit
There are already implemented and ready-to-use examples of this below TableCreator's code.
"""
import copy
-import functools
import io
from collections import (
deque,
@@ -82,12 +81,13 @@ class Column:
:param header_vert_align: vertical alignment of header cells (defaults to bottom)
:param override_header_style: if True, then the table is allowed to apply text styles to the header, which may
interfere with any styles the header already has. If False, the header is printed as is.
- Table classes which apply style to headers must respect this flag. (defaults to True)
+ Table classes which apply style to headers must respect this flag. See the BorderedTable
+ class for an example of this. (defaults to True)
:param data_horiz_align: horizontal alignment of data cells (defaults to left)
:param data_vert_align: vertical alignment of data cells (defaults to top)
:param override_data_style: if True, then the table is allowed to apply text styles to the data, which may
interfere with any styles the data already has. If False, the data is printed as is.
- Table classes which apply style to data must respect this flag. See the AlternatingTable
+ Table classes which apply style to data must respect this flag. See the BorderedTable
class for an example of this. (defaults to True)
:param max_data_lines: maximum lines allowed in a data cell. If line count exceeds this, then the final
line displayed will be truncated with an ellipsis. (defaults to INFINITY)
@@ -683,7 +683,17 @@ class BorderedTable(TableCreator):
between columns can also be toggled. This class can be used to create the whole table at once or one row at a time.
"""
- def __init__(self, cols: Sequence[Column], *, tab_width: int = 4, column_borders: bool = True, padding: int = 1) -> None:
+ def __init__(
+ self,
+ cols: Sequence[Column],
+ *,
+ tab_width: int = 4,
+ column_borders: bool = True,
+ padding: int = 1,
+ border_fg: Optional[ansi.FgColor] = None,
+ header_bg: Optional[ansi.BgColor] = None,
+ data_bg: Optional[ansi.BgColor] = None,
+ ) -> None:
"""
BorderedTable initializer
@@ -694,16 +704,53 @@ class BorderedTable(TableCreator):
appearance. Turning off column borders results in a unified appearance between
a row's cells. (Defaults to True)
:param padding: number of spaces between text and left/right borders of cell
+ :param border_fg: optional foreground color for borders (defaults to None)
+ :param header_bg: optional background color for header cells (defaults to None)
+ :param data_bg: optional background color for data cells (defaults to None)
:raises: ValueError if padding is less than 0
"""
super().__init__(cols, tab_width=tab_width)
- self.empty_data = [EMPTY for _ in self.cols]
+ self.empty_data = [EMPTY] * len(self.cols)
self.column_borders = column_borders
if padding < 0:
raise ValueError("Padding cannot be less than 0")
self.padding = padding
+ self.border_fg = border_fg
+ self.header_bg = header_bg
+ self.data_bg = data_bg
+
+ def apply_border_fg(self, value: Any) -> str:
+ """
+ If defined, apply the border foreground color to border text
+ :param value: object whose text is to be colored
+ :return: formatted text
+ """
+ if self.border_fg is None:
+ return str(value)
+ return ansi.style(value, fg=self.border_fg)
+
+ def apply_header_bg(self, value: Any) -> str:
+ """
+ If defined, apply the header background color to header text
+ :param value: object whose text is to be colored
+ :return: formatted text
+ """
+ if self.header_bg is None:
+ return str(value)
+ return ansi.style(value, bg=self.header_bg)
+
+ def apply_data_bg(self, value: Any) -> str:
+ """
+ If defined, apply the data background color to data text
+ :param value: object whose text is to be colored
+ :return: formatted data string
+ """
+ if self.data_bg is None:
+ return str(value)
+ return ansi.style(value, bg=self.data_bg)
+
@classmethod
def base_width(cls, num_cols: int, *, column_borders: bool = True, padding: int = 1) -> int:
"""
@@ -735,6 +782,8 @@ class BorderedTable(TableCreator):
def generate_table_top_border(self) -> str:
"""Generate a border which appears at the top of the header and data section"""
+ fill_char = '═'
+
pre_line = '╔' + self.padding * '═'
inter_cell = self.padding * '═'
@@ -745,11 +794,17 @@ class BorderedTable(TableCreator):
post_line = self.padding * '═' + '╗'
return self.generate_row(
- row_data=self.empty_data, fill_char='═', pre_line=pre_line, inter_cell=inter_cell, post_line=post_line
+ row_data=self.empty_data,
+ fill_char=self.apply_border_fg(fill_char),
+ pre_line=self.apply_border_fg(pre_line),
+ inter_cell=self.apply_border_fg(inter_cell),
+ post_line=self.apply_border_fg(post_line),
)
def generate_header_bottom_border(self) -> str:
"""Generate a border which appears at the bottom of the header"""
+ fill_char = '═'
+
pre_line = '╠' + self.padding * '═'
inter_cell = self.padding * '═'
@@ -760,26 +815,39 @@ class BorderedTable(TableCreator):
post_line = self.padding * '═' + '╣'
return self.generate_row(
- row_data=self.empty_data, fill_char='═', pre_line=pre_line, inter_cell=inter_cell, post_line=post_line
+ row_data=self.empty_data,
+ fill_char=self.apply_border_fg(fill_char),
+ pre_line=self.apply_border_fg(pre_line),
+ inter_cell=self.apply_border_fg(inter_cell),
+ post_line=self.apply_border_fg(post_line),
)
def generate_row_bottom_border(self) -> str:
"""Generate a border which appears at the bottom of rows"""
- pre_line = '╟' + self.padding * '─'
+ fill_char = self.apply_data_bg('─')
+
+ pre_line = '╟' + self.apply_data_bg(self.padding * '─')
inter_cell = self.padding * '─'
if self.column_borders:
inter_cell += '┼'
inter_cell += self.padding * '─'
+ inter_cell = self.apply_data_bg(inter_cell)
- post_line = self.padding * '─' + '╢'
+ post_line = self.apply_data_bg(self.padding * '─') + '╢'
return self.generate_row(
- row_data=self.empty_data, fill_char='─', pre_line=pre_line, inter_cell=inter_cell, post_line=post_line
+ row_data=self.empty_data,
+ fill_char=self.apply_border_fg(fill_char),
+ pre_line=self.apply_border_fg(pre_line),
+ inter_cell=self.apply_border_fg(inter_cell),
+ post_line=self.apply_border_fg(post_line),
)
def generate_table_bottom_border(self) -> str:
"""Generate a border which appears at the bottom of the table"""
+ fill_char = '═'
+
pre_line = '╚' + self.padding * '═'
inter_cell = self.padding * '═'
@@ -790,25 +858,44 @@ class BorderedTable(TableCreator):
post_line = self.padding * '═' + '╝'
return self.generate_row(
- row_data=self.empty_data, fill_char='═', pre_line=pre_line, inter_cell=inter_cell, post_line=post_line
+ row_data=self.empty_data,
+ fill_char=self.apply_border_fg(fill_char),
+ pre_line=self.apply_border_fg(pre_line),
+ inter_cell=self.apply_border_fg(inter_cell),
+ post_line=self.apply_border_fg(post_line),
)
def generate_header(self) -> str:
"""Generate table header"""
- pre_line = '║' + self.padding * SPACE
+ fill_char = self.apply_header_bg(SPACE)
+
+ pre_line = self.apply_border_fg('║') + self.apply_header_bg(self.padding * SPACE)
inter_cell = self.padding * SPACE
if self.column_borders:
- inter_cell += '│'
+ inter_cell += self.apply_border_fg('│')
inter_cell += self.padding * SPACE
+ inter_cell = self.apply_header_bg(inter_cell)
- post_line = self.padding * SPACE + '║'
+ post_line = self.apply_header_bg(self.padding * SPACE) + self.apply_border_fg('║')
+
+ # Apply background color to header text in Columns which allow it
+ to_display: List[Any] = []
+ for index, col in enumerate(self.cols):
+ if col.override_header_style:
+ to_display.append(self.apply_header_bg(col.header))
+ else:
+ to_display.append(col.header)
# Create the bordered header
header_buf = io.StringIO()
header_buf.write(self.generate_table_top_border())
header_buf.write('\n')
- header_buf.write(self.generate_row(pre_line=pre_line, inter_cell=inter_cell, post_line=post_line))
+ header_buf.write(
+ self.generate_row(
+ row_data=to_display, fill_char=fill_char, pre_line=pre_line, inter_cell=inter_cell, post_line=post_line
+ )
+ )
header_buf.write('\n')
header_buf.write(self.generate_header_bottom_border())
@@ -821,16 +908,29 @@ class BorderedTable(TableCreator):
:param row_data: data with an entry for each column in the row
:return: data row string
"""
- pre_line = '║' + self.padding * SPACE
+ fill_char = self.apply_data_bg(SPACE)
+
+ pre_line = self.apply_border_fg('║') + self.apply_data_bg(self.padding * SPACE)
inter_cell = self.padding * SPACE
if self.column_borders:
- inter_cell += '│'
+ inter_cell += self.apply_border_fg('│')
inter_cell += self.padding * SPACE
+ inter_cell = self.apply_data_bg(inter_cell)
+
+ post_line = self.apply_data_bg(self.padding * SPACE) + self.apply_border_fg('║')
- post_line = self.padding * SPACE + '║'
+ # Apply background color to data text in Columns which allow it
+ to_display: List[Any] = []
+ for index, col in enumerate(self.cols):
+ if col.override_data_style:
+ to_display.append(self.apply_data_bg(row_data[index]))
+ else:
+ to_display.append(row_data[index])
- return self.generate_row(row_data=row_data, pre_line=pre_line, inter_cell=inter_cell, post_line=post_line)
+ return self.generate_row(
+ row_data=to_display, fill_char=fill_char, pre_line=pre_line, inter_cell=inter_cell, post_line=post_line
+ )
def generate_table(self, table_data: Sequence[Sequence[Any]], *, include_header: bool = True) -> str:
"""
@@ -870,9 +970,6 @@ class AlternatingTable(BorderedTable):
Implementation of BorderedTable which uses background colors to distinguish between rows instead of row border
lines. This class can be used to create the whole table at once or one row at a time.
- AlternatingTable will not apply background color to data whose Columns set override_data_style to False.
- Background color will still be applied to those Columns's padding and fill characters.
-
To nest an AlternatingTable within another AlternatingTable, set override_data_style to False on the Column
which contains the nested table. That will prevent the current row's background color from affecting the colors
of the nested table.
@@ -885,8 +982,10 @@ class AlternatingTable(BorderedTable):
tab_width: int = 4,
column_borders: bool = True,
padding: int = 1,
- bg_odd: Optional[ansi.BgColor] = None,
- bg_even: Optional[ansi.BgColor] = ansi.Bg.DARK_GRAY,
+ border_fg: Optional[ansi.FgColor] = None,
+ header_bg: Optional[ansi.BgColor] = None,
+ odd_bg: Optional[ansi.BgColor] = None,
+ even_bg: Optional[ansi.BgColor] = ansi.Bg.DARK_GRAY,
) -> None:
"""
AlternatingTable initializer
@@ -900,28 +999,31 @@ class AlternatingTable(BorderedTable):
appearance. Turning off column borders results in a unified appearance between
a row's cells. (Defaults to True)
:param padding: number of spaces between text and left/right borders of cell
- :param bg_odd: optional background color for odd numbered rows (defaults to None)
- :param bg_even: optional background color for even numbered rows (defaults to Bg.DARK_GRAY)
+ :param border_fg: optional foreground color for borders (defaults to None)
+ :param header_bg: optional background color for header cells (defaults to None)
+ :param odd_bg: optional background color for odd numbered data rows (defaults to None)
+ :param even_bg: optional background color for even numbered data rows (defaults to StdBg.DARK_GRAY)
:raises: ValueError if padding is less than 0
"""
- super().__init__(cols, tab_width=tab_width, column_borders=column_borders, padding=padding)
+ super().__init__(
+ cols, tab_width=tab_width, column_borders=column_borders, padding=padding, border_fg=border_fg, header_bg=header_bg
+ )
self.row_num = 1
- self.bg_odd = None if bg_odd is None else functools.partial(ansi.style, bg=bg_odd)
- self.bg_even = None if bg_even is None else functools.partial(ansi.style, bg=bg_even)
+ self.odd_bg = odd_bg
+ self.even_bg = even_bg
- def _apply_bg_color(self, data: Any) -> str:
+ def apply_data_bg(self, value: Any) -> str:
"""
- Convert data to text and apply background color to it based on what row is being generated
-
- :param data: data being colored
- :return: converted data
+ Apply background color to data text based on what row is being generated and whether a color has been defined
+ :param value: object whose text is to be colored
+ :return: formatted data string
"""
- if self.row_num % 2 == 0 and self.bg_even is not None:
- return self.bg_even(data)
- elif self.row_num % 2 != 0 and self.bg_odd is not None:
- return self.bg_odd(data)
+ if self.row_num % 2 == 0 and self.even_bg is not None:
+ return ansi.style(value, bg=self.even_bg)
+ elif self.row_num % 2 != 0 and self.odd_bg is not None:
+ return ansi.style(value, bg=self.odd_bg)
else:
- return str(data)
+ return str(value)
def generate_data_row(self, row_data: Sequence[Any]) -> str:
"""
@@ -930,31 +1032,7 @@ class AlternatingTable(BorderedTable):
:param row_data: data with an entry for each column in the row
:return: data row string
"""
- # Only color the padding and not the outer border characters
- pre_line = '║' + self._apply_bg_color(self.padding * SPACE)
-
- inter_cell = self.padding * SPACE
- if self.column_borders:
- inter_cell += '│'
- inter_cell += self.padding * SPACE
- inter_cell = self._apply_bg_color(inter_cell)
-
- # Only color the padding and not the outer border characters
- post_line = self._apply_bg_color(self.padding * SPACE) + '║'
-
- fill_char = self._apply_bg_color(SPACE)
-
- # Apply background colors to data whose Columns allow it
- to_display: List[Any] = []
- for index, col in enumerate(self.cols):
- if col.override_data_style:
- to_display.append(self._apply_bg_color(row_data[index]))
- else:
- to_display.append(row_data[index])
-
- row = self.generate_row(
- row_data=to_display, fill_char=fill_char, pre_line=pre_line, inter_cell=inter_cell, post_line=post_line
- )
+ row = super().generate_data_row(row_data)
self.row_num += 1
return row
diff --git a/cmd2/utils.py b/cmd2/utils.py
index 733cfc24..5f2ceaf4 100644
--- a/cmd2/utils.py
+++ b/cmd2/utils.py
@@ -787,17 +787,23 @@ def align_text(
if width < 1:
raise ValueError("width must be at least 1")
- # Handle tabs
+ # Convert tabs to spaces
text = text.replace('\t', ' ' * tab_width)
fill_char = fill_char.replace('\t', ' ')
- if len(ansi.strip_style(fill_char)) != 1:
+ # Save fill_char with no styles for use later
+ stripped_fill_char = ansi.strip_style(fill_char)
+ if len(stripped_fill_char) != 1:
raise TypeError("Fill character must be exactly one character long")
fill_char_width = ansi.style_aware_wcswidth(fill_char)
if fill_char_width == -1:
raise (ValueError("Fill character is an unprintable character"))
+ # Isolate the style chars before and after the fill character. We will use them when building sequences of
+ # of fill characters. Instead of repeating the style characters for each fill character, we'll wrap each sequence.
+ fill_char_style_begin, fill_char_style_end = fill_char.split(stripped_fill_char)
+
if text:
lines = text.splitlines()
else:
@@ -810,23 +816,6 @@ def align_text(
# 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')
@@ -860,22 +849,22 @@ def align_text(
right_fill_width = 0
# Determine how many fill characters are needed to cover the width
- left_fill = (left_fill_width // fill_char_width) * fill_char
- right_fill = (right_fill_width // fill_char_width) * fill_char
+ left_fill = (left_fill_width // fill_char_width) * stripped_fill_char
+ right_fill = (right_fill_width // fill_char_width) * stripped_fill_char
# In cases where the fill character display width didn't divide evenly into
- # 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))
+ # the gap being filled, pad the remainder with space.
+ left_fill += ' ' * (left_fill_width - ansi.style_aware_wcswidth(left_fill))
+ right_fill += ' ' * (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:
+ # Don't allow styles in fill characters and text to affect one another
+ if fill_char_style_begin or fill_char_style_end or aggregate_styles or line_styles:
if left_fill:
- left_fill = ansi.TextStyle.RESET_ALL + left_fill
+ left_fill = ansi.TextStyle.RESET_ALL + fill_char_style_begin + left_fill + fill_char_style_end
left_fill += ansi.TextStyle.RESET_ALL
if right_fill:
- right_fill = ansi.TextStyle.RESET_ALL + right_fill
+ right_fill = ansi.TextStyle.RESET_ALL + fill_char_style_begin + right_fill + fill_char_style_end
right_fill += ansi.TextStyle.RESET_ALL
# Write the line and restore any styles from previous lines
diff --git a/tests/test_table_creator.py b/tests/test_table_creator.py
index 69f05ef1..c5b5f36e 100644
--- a/tests/test_table_creator.py
+++ b/tests/test_table_creator.py
@@ -541,6 +541,34 @@ def test_bordered_table_creation():
BorderedTable([column_1, column_2], padding=-1)
assert "Padding cannot be less than 0" in str(excinfo.value)
+ # Test border, header, and data colors
+ bt = BorderedTable([column_1, column_2], border_fg=Fg.LIGHT_YELLOW, header_bg=Bg.GREEN, data_bg=Bg.LIGHT_BLUE)
+ table = bt.generate_table(row_data)
+ assert table == (
+ '\x1b[93m╔═\x1b[39m\x1b[0m\x1b[0m\x1b[93m═══════════════\x1b[39m\x1b[0m\x1b[93m═╤═\x1b[39m\x1b[0m\x1b[0m\x1b[93m═══════════════\x1b[39m\x1b[0m\x1b[93m═╗\x1b[39m\n'
+ '\x1b[93m║\x1b[39m\x1b[42m \x1b[49m\x1b[0m\x1b[42mCol 1\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[93m│\x1b[39m \x1b[49m\x1b[0m\x1b[42mCol 2\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[93m║\x1b[39m\n'
+ '\x1b[93m╠═\x1b[39m\x1b[0m\x1b[0m\x1b[93m═══════════════\x1b[39m\x1b[0m\x1b[93m═╪═\x1b[39m\x1b[0m\x1b[0m\x1b[93m═══════════════\x1b[39m\x1b[0m\x1b[93m═╣\x1b[39m\n'
+ '\x1b[93m║\x1b[39m\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 1 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[93m│\x1b[39m \x1b[49m\x1b[0m\x1b[104mCol 2 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[93m║\x1b[39m\n'
+ '\x1b[93m╟\x1b[104m─\x1b[49m\x1b[39m\x1b[0m\x1b[0m\x1b[93m\x1b[104m───────────────\x1b[49m\x1b[39m\x1b[0m\x1b[93m\x1b[104m─┼─\x1b[49m\x1b[39m\x1b[0m\x1b[0m\x1b[93m\x1b[104m───────────────\x1b[49m\x1b[39m\x1b[0m\x1b[93m\x1b[104m─\x1b[49m╢\x1b[39m\n'
+ '\x1b[93m║\x1b[39m\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 1 Row 2\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[93m│\x1b[39m \x1b[49m\x1b[0m\x1b[104mCol 2 Row 2\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[93m║\x1b[39m\n'
+ '\x1b[93m╚═\x1b[39m\x1b[0m\x1b[0m\x1b[93m═══════════════\x1b[39m\x1b[0m\x1b[93m═╧═\x1b[39m\x1b[0m\x1b[0m\x1b[93m═══════════════\x1b[39m\x1b[0m\x1b[93m═╝\x1b[39m'
+ )
+
+ # Make sure BorderedTable respects override_header_style override_data_style flags.
+ # Don't apply parent table's background colors to header or data text in second column.
+ column_2 = Column("Col 2", width=15, override_header_style=False, override_data_style=False)
+ bt = BorderedTable([column_1, column_2], header_bg=Bg.GREEN, data_bg=Bg.LIGHT_BLUE)
+ table = bt.generate_table(row_data)
+ assert table == (
+ '╔═════════════════╤═════════════════╗\n'
+ '║\x1b[42m \x1b[49m\x1b[0m\x1b[42mCol 1\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m │ \x1b[49m\x1b[0mCol 2\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m║\n'
+ '╠═════════════════╪═════════════════╣\n'
+ '║\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 1 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m │ \x1b[49m\x1b[0mCol 2 Row 1\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m║\n'
+ '╟\x1b[104m─\x1b[49m\x1b[0m\x1b[0m\x1b[104m───────────────\x1b[49m\x1b[0m\x1b[104m─┼─\x1b[49m\x1b[0m\x1b[0m\x1b[104m───────────────\x1b[49m\x1b[0m\x1b[104m─\x1b[49m╢\n'
+ '║\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 1 Row 2\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m │ \x1b[49m\x1b[0mCol 2 Row 2\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m║\n'
+ '╚═════════════════╧═════════════════╝'
+ )
+
def test_bordered_table_width():
# Default behavior (column_borders=True, padding=1)
@@ -596,19 +624,7 @@ def test_alternating_table_creation():
'║ Col 1 │ Col 2 ║\n'
'╠═════════════════╪═════════════════╣\n'
'║ Col 1 Row 1 │ Col 2 Row 1 ║\n'
- '║\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 1 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[0m\x1b[100m │ \x1b[49m\x1b[0m\x1b[100mCol 2 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m║\n'
- '╚═════════════════╧═════════════════╝'
- )
-
- # Other bg colors
- at = AlternatingTable([column_1, column_2], bg_odd=Bg.LIGHT_BLUE, bg_even=Bg.GREEN)
- table = at.generate_table(row_data)
- assert table == (
- '╔═════════════════╤═════════════════╗\n'
- '║ Col 1 │ Col 2 ║\n'
- '╠═════════════════╪═════════════════╣\n'
- '║\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 1 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[104m \x1b[49m\x1b[104m \x1b[49m\x1b[104m \x1b[49m\x1b[0m\x1b[104m │ \x1b[49m\x1b[0m\x1b[104mCol 2 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[104m \x1b[49m\x1b[104m \x1b[49m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m║\n'
- '║\x1b[42m \x1b[49m\x1b[0m\x1b[42mCol 1 Row 2\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[42m \x1b[49m\x1b[42m \x1b[49m\x1b[42m \x1b[49m\x1b[0m\x1b[42m │ \x1b[49m\x1b[0m\x1b[42mCol 2 Row 2\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[42m \x1b[49m\x1b[42m \x1b[49m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m║\n'
+ '║\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 1 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m │ \x1b[49m\x1b[0m\x1b[100mCol 2 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m║\n'
'╚═════════════════╧═════════════════╝'
)
@@ -620,7 +636,7 @@ def test_alternating_table_creation():
'║ Col 1 Col 2 ║\n'
'╠══════════════════════════════════╣\n'
'║ Col 1 Row 1 Col 2 Row 1 ║\n'
- '║\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 1 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 2 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m║\n'
+ '║\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 1 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 2 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m║\n'
'╚══════════════════════════════════╝'
)
@@ -630,7 +646,7 @@ def test_alternating_table_creation():
assert table == (
'╔═════════════════╤═════════════════╗\n'
'║ Col 1 Row 1 │ Col 2 Row 1 ║\n'
- '║\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 1 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[0m\x1b[100m │ \x1b[49m\x1b[0m\x1b[100mCol 2 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m║\n'
+ '║\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 1 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m │ \x1b[49m\x1b[0m\x1b[100mCol 2 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m║\n'
'╚═════════════════╧═════════════════╝'
)
@@ -642,25 +658,37 @@ def test_alternating_table_creation():
'║ Col 1 │ Col 2 ║\n'
'╠═══════════════════╪═══════════════════╣\n'
'║ Col 1 Row 1 │ Col 2 Row 1 ║\n'
- '║\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 1 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[0m\x1b[100m │ \x1b[49m\x1b[0m\x1b[100mCol 2 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m║\n'
+ '║\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 1 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m │ \x1b[49m\x1b[0m\x1b[100mCol 2 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m║\n'
'╚═══════════════════╧═══════════════════╝'
)
- # Make sure AlternatingTable respects override_data_style flag.
- # Don't allow background color on data's text in second column.
- column_2 = Column("Col 2", width=15, override_data_style=False)
- at = AlternatingTable([column_1, column_2])
+ # Invalid padding
+ with pytest.raises(ValueError) as excinfo:
+ AlternatingTable([column_1, column_2], padding=-1)
+ assert "Padding cannot be less than 0" in str(excinfo.value)
+
+ # Test border, header, and data colors
+ at = AlternatingTable([column_1, column_2], border_fg=Fg.LIGHT_YELLOW, header_bg=Bg.GREEN, odd_bg=Bg.LIGHT_BLUE, even_bg=Bg.LIGHT_RED)
+ table = at.generate_table(row_data)
+ assert table == (
+ '\x1b[93m╔═\x1b[39m\x1b[0m\x1b[0m\x1b[93m═══════════════\x1b[39m\x1b[0m\x1b[93m═╤═\x1b[39m\x1b[0m\x1b[0m\x1b[93m═══════════════\x1b[39m\x1b[0m\x1b[93m═╗\x1b[39m\n'
+ '\x1b[93m║\x1b[39m\x1b[42m \x1b[49m\x1b[0m\x1b[42mCol 1\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[93m│\x1b[39m \x1b[49m\x1b[0m\x1b[42mCol 2\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[93m║\x1b[39m\n'
+ '\x1b[93m╠═\x1b[39m\x1b[0m\x1b[0m\x1b[93m═══════════════\x1b[39m\x1b[0m\x1b[93m═╪═\x1b[39m\x1b[0m\x1b[0m\x1b[93m═══════════════\x1b[39m\x1b[0m\x1b[93m═╣\x1b[39m\n'
+ '\x1b[93m║\x1b[39m\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 1 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[93m│\x1b[39m \x1b[49m\x1b[0m\x1b[104mCol 2 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[93m║\x1b[39m\n'
+ '\x1b[93m║\x1b[39m\x1b[101m \x1b[49m\x1b[0m\x1b[101mCol 1 Row 2\x1b[49m\x1b[0m\x1b[101m \x1b[49m\x1b[0m\x1b[101m \x1b[93m│\x1b[39m \x1b[49m\x1b[0m\x1b[101mCol 2 Row 2\x1b[49m\x1b[0m\x1b[101m \x1b[49m\x1b[0m\x1b[101m \x1b[49m\x1b[93m║\x1b[39m\n'
+ '\x1b[93m╚═\x1b[39m\x1b[0m\x1b[0m\x1b[93m═══════════════\x1b[39m\x1b[0m\x1b[93m═╧═\x1b[39m\x1b[0m\x1b[0m\x1b[93m═══════════════\x1b[39m\x1b[0m\x1b[93m═╝\x1b[39m'
+ )
+
+ # Make sure AlternatingTable respects override_header_style override_data_style flags.
+ # Don't apply parent table's background colors to header or data text in second column.
+ column_2 = Column("Col 2", width=15, override_header_style=False, override_data_style=False)
+ at = AlternatingTable([column_1, column_2], header_bg=Bg.GREEN, odd_bg=Bg.LIGHT_BLUE, even_bg=Bg.LIGHT_RED)
table = at.generate_table(row_data)
assert table == (
'╔═════════════════╤═════════════════╗\n'
- '║ Col 1 │ Col 2 ║\n'
+ '║\x1b[42m \x1b[49m\x1b[0m\x1b[42mCol 1\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m │ \x1b[49m\x1b[0mCol 2\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m║\n'
'╠═════════════════╪═════════════════╣\n'
- '║ Col 1 Row 1 │ Col 2 Row 1 ║\n'
- '║\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 1 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[0m\x1b[100m │ \x1b[49m\x1b[0mCol 2 Row 2\x1b[0m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m║\n'
+ '║\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 1 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m │ \x1b[49m\x1b[0mCol 2 Row 1\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m║\n'
+ '║\x1b[101m \x1b[49m\x1b[0m\x1b[101mCol 1 Row 2\x1b[49m\x1b[0m\x1b[101m \x1b[49m\x1b[0m\x1b[101m │ \x1b[49m\x1b[0mCol 2 Row 2\x1b[0m\x1b[101m \x1b[49m\x1b[0m\x1b[101m \x1b[49m║\n'
'╚═════════════════╧═════════════════╝'
)
-
- # Invalid padding
- with pytest.raises(ValueError) as excinfo:
- AlternatingTable([column_1, column_2], padding=-1)
- assert "Padding cannot be less than 0" in str(excinfo.value)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 7dc4c96d..9c161774 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -434,14 +434,16 @@ def test_align_text_with_style():
style,
)
+ fill_char = '-'
+ styled_fill_char = style(fill_char, fg=Fg.LIGHT_YELLOW)
+
# Single line with only left fill
text = style('line1', fg=Fg.LIGHT_BLUE)
- fill_char = style('-', fg=Fg.LIGHT_YELLOW)
- width = 6
+ width = 8
- aligned = cu.align_text(text, cu.TextAlignment.RIGHT, fill_char=fill_char, width=width)
+ aligned = cu.align_text(text, cu.TextAlignment.RIGHT, fill_char=styled_fill_char, width=width)
- left_fill = TextStyle.RESET_ALL + fill_char + TextStyle.RESET_ALL
+ left_fill = TextStyle.RESET_ALL + Fg.LIGHT_YELLOW + (fill_char * 3) + Fg.RESET + TextStyle.RESET_ALL
right_fill = TextStyle.RESET_ALL
line_1_text = Fg.LIGHT_BLUE + 'line1' + Fg.RESET
@@ -449,26 +451,24 @@ def test_align_text_with_style():
# Single line with only right fill
text = style('line1', fg=Fg.LIGHT_BLUE)
- fill_char = style('-', fg=Fg.LIGHT_YELLOW)
- width = 6
+ width = 8
- aligned = cu.align_text(text, cu.TextAlignment.LEFT, fill_char=fill_char, width=width)
+ aligned = cu.align_text(text, cu.TextAlignment.LEFT, fill_char=styled_fill_char, width=width)
left_fill = TextStyle.RESET_ALL
- right_fill = TextStyle.RESET_ALL + fill_char + TextStyle.RESET_ALL
+ right_fill = TextStyle.RESET_ALL + Fg.LIGHT_YELLOW + (fill_char * 3) + Fg.RESET + TextStyle.RESET_ALL
line_1_text = Fg.LIGHT_BLUE + 'line1' + Fg.RESET
assert aligned == (left_fill + line_1_text + right_fill)
# Multiple lines to show that style is preserved across all lines. Also has left and right fill.
text = style('line1\nline2', fg=Fg.LIGHT_BLUE)
- fill_char = style('-', fg=Fg.LIGHT_YELLOW)
- width = 7
+ width = 9
- aligned = cu.align_text(text, cu.TextAlignment.CENTER, fill_char=fill_char, width=width)
+ aligned = cu.align_text(text, cu.TextAlignment.CENTER, fill_char=styled_fill_char, width=width)
- left_fill = TextStyle.RESET_ALL + fill_char + TextStyle.RESET_ALL
- right_fill = TextStyle.RESET_ALL + fill_char + TextStyle.RESET_ALL
+ left_fill = TextStyle.RESET_ALL + Fg.LIGHT_YELLOW + (fill_char * 2) + Fg.RESET + TextStyle.RESET_ALL
+ right_fill = TextStyle.RESET_ALL + Fg.LIGHT_YELLOW + (fill_char * 2) + Fg.RESET + TextStyle.RESET_ALL
line_1_text = Fg.LIGHT_BLUE + 'line1'
line_2_text = Fg.LIGHT_BLUE + 'line2' + Fg.RESET