Skip to content

Commit e328b67

Browse files
committed
Added ability to set border background color on BorderTables and AlternatingTables
1 parent af473b8 commit e328b67

File tree

3 files changed

+144
-78
lines changed

3 files changed

+144
-78
lines changed

cmd2/table_creator.py

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,7 @@ def __init__(
748748
column_borders: bool = True,
749749
padding: int = 1,
750750
border_fg: Optional[ansi.FgColor] = None,
751+
border_bg: Optional[ansi.BgColor] = None,
751752
header_bg: Optional[ansi.BgColor] = None,
752753
data_bg: Optional[ansi.BgColor] = None,
753754
) -> None:
@@ -762,6 +763,7 @@ def __init__(
762763
a row's cells. (Defaults to True)
763764
:param padding: number of spaces between text and left/right borders of cell
764765
:param border_fg: optional foreground color for borders (defaults to None)
766+
:param border_bg: optional background color for borders (defaults to None)
765767
:param header_bg: optional background color for header cells (defaults to None)
766768
:param data_bg: optional background color for data cells (defaults to None)
767769
:raises: ValueError if tab_width is less than 1
@@ -776,18 +778,19 @@ def __init__(
776778
self.padding = padding
777779

778780
self.border_fg = border_fg
781+
self.border_bg = border_bg
779782
self.header_bg = header_bg
780783
self.data_bg = data_bg
781784

782-
def apply_border_fg(self, value: Any) -> str:
785+
def apply_border_color(self, value: Any) -> str:
783786
"""
784-
If defined, apply the border foreground color to border text
787+
If defined, apply the border foreground and background colors
785788
:param value: object whose text is to be colored
786789
:return: formatted text
787790
"""
788-
if self.border_fg is None:
791+
if self.border_fg is None and self.border_bg is None:
789792
return str(value)
790-
return ansi.style(value, fg=self.border_fg)
793+
return ansi.style(value, fg=self.border_fg, bg=self.border_bg)
791794

792795
def apply_header_bg(self, value: Any) -> str:
793796
"""
@@ -853,10 +856,10 @@ def generate_table_top_border(self) -> str:
853856

854857
return self.generate_row(
855858
row_data=self.empty_data,
856-
fill_char=self.apply_border_fg(fill_char),
857-
pre_line=self.apply_border_fg(pre_line),
858-
inter_cell=self.apply_border_fg(inter_cell),
859-
post_line=self.apply_border_fg(post_line),
859+
fill_char=self.apply_border_color(fill_char),
860+
pre_line=self.apply_border_color(pre_line),
861+
inter_cell=self.apply_border_color(inter_cell),
862+
post_line=self.apply_border_color(post_line),
860863
)
861864

862865
def generate_header_bottom_border(self) -> str:
@@ -874,32 +877,32 @@ def generate_header_bottom_border(self) -> str:
874877

875878
return self.generate_row(
876879
row_data=self.empty_data,
877-
fill_char=self.apply_border_fg(fill_char),
878-
pre_line=self.apply_border_fg(pre_line),
879-
inter_cell=self.apply_border_fg(inter_cell),
880-
post_line=self.apply_border_fg(post_line),
880+
fill_char=self.apply_border_color(fill_char),
881+
pre_line=self.apply_border_color(pre_line),
882+
inter_cell=self.apply_border_color(inter_cell),
883+
post_line=self.apply_border_color(post_line),
881884
)
882885

883886
def generate_row_bottom_border(self) -> str:
884887
"""Generate a border which appears at the bottom of rows"""
885-
fill_char = self.apply_data_bg('─')
888+
fill_char = '─'
886889

887-
pre_line = '╟' + self.apply_data_bg(self.padding * '─')
890+
pre_line = '╟' + self.padding * '─'
888891

889892
inter_cell = self.padding * '─'
890893
if self.column_borders:
891894
inter_cell += '┼'
892895
inter_cell += self.padding * '─'
893-
inter_cell = self.apply_data_bg(inter_cell)
896+
inter_cell = inter_cell
894897

895-
post_line = self.apply_data_bg(self.padding * '─') + '╢'
898+
post_line = self.padding * '─' + '╢'
896899

897900
return self.generate_row(
898901
row_data=self.empty_data,
899-
fill_char=self.apply_border_fg(fill_char),
900-
pre_line=self.apply_border_fg(pre_line),
901-
inter_cell=self.apply_border_fg(inter_cell),
902-
post_line=self.apply_border_fg(post_line),
902+
fill_char=self.apply_border_color(fill_char),
903+
pre_line=self.apply_border_color(pre_line),
904+
inter_cell=self.apply_border_color(inter_cell),
905+
post_line=self.apply_border_color(post_line),
903906
)
904907

905908
def generate_table_bottom_border(self) -> str:
@@ -917,25 +920,24 @@ def generate_table_bottom_border(self) -> str:
917920

918921
return self.generate_row(
919922
row_data=self.empty_data,
920-
fill_char=self.apply_border_fg(fill_char),
921-
pre_line=self.apply_border_fg(pre_line),
922-
inter_cell=self.apply_border_fg(inter_cell),
923-
post_line=self.apply_border_fg(post_line),
923+
fill_char=self.apply_border_color(fill_char),
924+
pre_line=self.apply_border_color(pre_line),
925+
inter_cell=self.apply_border_color(inter_cell),
926+
post_line=self.apply_border_color(post_line),
924927
)
925928

926929
def generate_header(self) -> str:
927930
"""Generate table header"""
928931
fill_char = self.apply_header_bg(SPACE)
929932

930-
pre_line = self.apply_border_fg('║') + self.apply_header_bg(self.padding * SPACE)
933+
pre_line = self.apply_border_color('║') + self.apply_header_bg(self.padding * SPACE)
931934

932-
inter_cell = self.padding * SPACE
935+
inter_cell = self.apply_header_bg(self.padding * SPACE)
933936
if self.column_borders:
934-
inter_cell += self.apply_border_fg('│')
935-
inter_cell += self.padding * SPACE
936-
inter_cell = self.apply_header_bg(inter_cell)
937+
inter_cell += self.apply_border_color('│')
938+
inter_cell += self.apply_header_bg(self.padding * SPACE)
937939

938-
post_line = self.apply_header_bg(self.padding * SPACE) + self.apply_border_fg('║')
940+
post_line = self.apply_header_bg(self.padding * SPACE) + self.apply_border_color('║')
939941

940942
# Apply background color to header text in Columns which allow it
941943
to_display: List[Any] = []
@@ -968,15 +970,14 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str:
968970
"""
969971
fill_char = self.apply_data_bg(SPACE)
970972

971-
pre_line = self.apply_border_fg('║') + self.apply_data_bg(self.padding * SPACE)
973+
pre_line = self.apply_border_color('║') + self.apply_data_bg(self.padding * SPACE)
972974

973-
inter_cell = self.padding * SPACE
975+
inter_cell = self.apply_data_bg(self.padding * SPACE)
974976
if self.column_borders:
975-
inter_cell += self.apply_border_fg('│')
976-
inter_cell += self.padding * SPACE
977-
inter_cell = self.apply_data_bg(inter_cell)
977+
inter_cell += self.apply_border_color('│')
978+
inter_cell += self.apply_data_bg(self.padding * SPACE)
978979

979-
post_line = self.apply_data_bg(self.padding * SPACE) + self.apply_border_fg('║')
980+
post_line = self.apply_data_bg(self.padding * SPACE) + self.apply_border_color('║')
980981

981982
# Apply background color to data text in Columns which allow it
982983
to_display: List[Any] = []
@@ -1041,6 +1042,7 @@ def __init__(
10411042
column_borders: bool = True,
10421043
padding: int = 1,
10431044
border_fg: Optional[ansi.FgColor] = None,
1045+
border_bg: Optional[ansi.BgColor] = None,
10441046
header_bg: Optional[ansi.BgColor] = None,
10451047
odd_bg: Optional[ansi.BgColor] = None,
10461048
even_bg: Optional[ansi.BgColor] = ansi.Bg.DARK_GRAY,
@@ -1058,14 +1060,21 @@ def __init__(
10581060
a row's cells. (Defaults to True)
10591061
:param padding: number of spaces between text and left/right borders of cell
10601062
:param border_fg: optional foreground color for borders (defaults to None)
1063+
:param border_bg: optional background color for borders (defaults to None)
10611064
:param header_bg: optional background color for header cells (defaults to None)
10621065
:param odd_bg: optional background color for odd numbered data rows (defaults to None)
10631066
:param even_bg: optional background color for even numbered data rows (defaults to StdBg.DARK_GRAY)
10641067
:raises: ValueError if tab_width is less than 1
10651068
:raises: ValueError if padding is less than 0
10661069
"""
10671070
super().__init__(
1068-
cols, tab_width=tab_width, column_borders=column_borders, padding=padding, border_fg=border_fg, header_bg=header_bg
1071+
cols,
1072+
tab_width=tab_width,
1073+
column_borders=column_borders,
1074+
padding=padding,
1075+
border_fg=border_fg,
1076+
border_bg=border_bg,
1077+
header_bg=header_bg,
10691078
)
10701079
self.row_num = 1
10711080
self.odd_bg = odd_bg

examples/table_creation.py

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ def __str__(self) -> str:
3939
return "${:,.2f}".format(self.val)
4040

4141

42+
class Relative:
43+
"""Class used for example data"""
44+
45+
def __init__(self, name: str, relationship: str) -> None:
46+
self.name = name
47+
self.relationship = relationship
48+
49+
4250
class Book:
4351
"""Class used for example data"""
4452

@@ -55,6 +63,7 @@ def __init__(self, name: str, birthday: str, place_of_birth: str) -> None:
5563
self.birthday = birthday
5664
self.place_of_birth = place_of_birth
5765
self.books: List[Book] = []
66+
self.relatives: List[Relative] = []
5867

5968

6069
def ansi_print(text):
@@ -108,9 +117,7 @@ def basic_tables():
108117

109118
def nested_tables():
110119
"""
111-
Demonstrates how to nest tables using the style_data_text keyword to handle tables with conflicting styles.
112-
In these cases, the inner tables reset the background color applied by the outer AlternatingTable.
113-
120+
Demonstrates how to nest tables with styles which conflict with the parent table by setting style_data_text to False.
114121
It also demonstrates coloring various aspects of tables.
115122
"""
116123

@@ -123,6 +130,12 @@ def nested_tables():
123130
author_1.books.append(Book("God Emperor of Dune", "1981"))
124131
author_1.books.append(Book("Heretics of Dune", "1984"))
125132
author_1.books.append(Book("Chapterhouse: Dune", "1985"))
133+
author_1.relatives.append(Relative("Flora Lillian Parkinson", "First Wife"))
134+
author_1.relatives.append(Relative("Beverly Ann Stuart", "Second Wife"))
135+
author_1.relatives.append(Relative("Theresa Diane Shackelford", "Third Wife"))
136+
author_1.relatives.append(Relative("Penelope Herbert", "Daughter"))
137+
author_1.relatives.append(Relative("Brian Patrick Herbert", "Son"))
138+
author_1.relatives.append(Relative("Bruce Calvin Herbert", "Son"))
126139

127140
author_2 = Author("Jane Austen", "12/16/1775", "Steventon, Hampshire, England")
128141
author_2.books.append(Book("Sense and Sensibility", "1811"))
@@ -132,12 +145,19 @@ def nested_tables():
132145
author_2.books.append(Book("Northanger Abbey", "1818"))
133146
author_2.books.append(Book("Persuasion", "1818"))
134147
author_2.books.append(Book("Lady Susan", "1871"))
148+
author_2.relatives.append(Relative("James Austen", "Brother"))
149+
author_2.relatives.append(Relative("George Austen", "Brother"))
150+
author_2.relatives.append(Relative("Edward Austen", "Brother"))
151+
author_2.relatives.append(Relative("Henry Thomas Austen", "Brother"))
152+
author_2.relatives.append(Relative("Cassandra Elizabeth Austen", "Sister"))
153+
author_2.relatives.append(Relative("Francis William Austen", "Brother"))
154+
author_2.relatives.append(Relative("Charles John Austen", "Brother"))
135155

136156
author_data.append(author_1)
137157
author_data.append(author_2)
138158

139159
# Define table which presents Author data fields vertically with no header.
140-
# This will be nested in the parent table.
160+
# This will be nested in the parent table's first column.
141161
author_columns: List[Column] = list()
142162
author_columns.append(Column("", width=14))
143163
author_columns.append(Column("", width=20))
@@ -146,18 +166,18 @@ def nested_tables():
146166
# When styled text is aligned, a TextStyle.RESET_ALL sequence is inserted between the aligned text
147167
# and the fill characters. Therefore, the Author table will contain TextStyle.RESET_ALL sequences,
148168
# which would interfere with the background color applied by the parent table. To account for this,
149-
# we will color the Author tables to match the background colors of the parent AlternatingTable's rows
150-
# and set style_data_text to False in the Author column. See below for that.
169+
# we will manually color the Author tables to match the background colors of the parent AlternatingTable's
170+
# rows and set style_data_text to False in the Author column.
151171
odd_author_tbl = SimpleTable(author_columns, data_bg=EightBitBg.GRAY_0)
152172
even_author_tbl = SimpleTable(author_columns, data_bg=EightBitBg.GRAY_15)
153173

154-
# Define AlternatingTable table for books checked out by people in the first table.
155-
# This will also be nested in the parent table.
174+
# Define AlternatingTable for books checked out by people in the first table.
175+
# This will be nested in the parent table's second column.
156176
books_columns: List[Column] = list()
157-
books_columns.append(Column("Title", width=25))
177+
books_columns.append(Column(ansi.style("Title", bold=True), width=25))
158178
books_columns.append(
159179
Column(
160-
"Published",
180+
ansi.style("Published", bold=True),
161181
width=9,
162182
header_horiz_align=HorizontalAlignment.RIGHT,
163183
data_horiz_align=HorizontalAlignment.RIGHT,
@@ -173,17 +193,48 @@ def nested_tables():
173193
even_bg=EightBitBg.GRAY_15,
174194
)
175195

196+
# Define BorderedTable for relatives of the author
197+
# This will be nested in the parent table's third column.
198+
relative_columns: List[Column] = list()
199+
relative_columns.append(Column(ansi.style("Name", bold=True), width=25))
200+
relative_columns.append(Column(ansi.style("Relationship", bold=True), width=12))
201+
202+
# Since the header labels are bold, we have the same issue as the Author table. Therefore, we will manually
203+
# color Relatives tables to match the background colors of the parent AlternatingTable's rows and set style_data_text
204+
# to False in the Relatives column.
205+
odd_relatives_tbl = BorderedTable(
206+
relative_columns,
207+
border_fg=EightBitFg.GRAY_15,
208+
border_bg=EightBitBg.GRAY_0,
209+
header_bg=EightBitBg.GRAY_0,
210+
data_bg=EightBitBg.GRAY_0,
211+
)
212+
213+
even_relatives_tbl = BorderedTable(
214+
relative_columns,
215+
border_fg=EightBitFg.GRAY_0,
216+
border_bg=EightBitBg.GRAY_15,
217+
header_bg=EightBitBg.GRAY_15,
218+
data_bg=EightBitBg.GRAY_15,
219+
)
220+
176221
# Define parent AlternatingTable which contains Author and Book tables
177222
parent_tbl_columns: List[Column] = list()
178223

179-
# Both the Author and Books tables already have background colors. Set style_data_text
224+
# All of the nested tables already have background colors. Set style_data_text
180225
# to False so the parent AlternatingTable does not apply background color to them.
181-
parent_tbl_columns.append(Column("Author", width=odd_author_tbl.total_width(), style_data_text=False))
182-
parent_tbl_columns.append(Column("Books", width=books_tbl.total_width(), style_data_text=False))
226+
parent_tbl_columns.append(
227+
Column(ansi.style("Author", bold=True), width=odd_author_tbl.total_width(), style_data_text=False)
228+
)
229+
parent_tbl_columns.append(Column(ansi.style("Books", bold=True), width=books_tbl.total_width(), style_data_text=False))
230+
parent_tbl_columns.append(
231+
Column(ansi.style("Relatives", bold=True), width=odd_relatives_tbl.total_width(), style_data_text=False)
232+
)
183233

184234
parent_tbl = AlternatingTable(
185235
parent_tbl_columns,
186236
column_borders=False,
237+
border_fg=EightBitFg.GRAY_93,
187238
header_bg=EightBitBg.GRAY_0,
188239
odd_bg=EightBitBg.GRAY_0,
189240
even_bg=EightBitBg.GRAY_15,
@@ -209,8 +260,13 @@ def nested_tables():
209260
table_data = [[book.title, book.year_published] for book in author.books]
210261
book_tbl_str = books_tbl.generate_table(table_data)
211262

263+
# Lastly build the relatives table and color it based on row number
264+
relatives_tbl = even_relatives_tbl if row % 2 == 0 else odd_relatives_tbl
265+
table_data = [[relative.name, relative.relationship] for relative in author.relatives]
266+
relatives_tbl_str = relatives_tbl.generate_table(table_data)
267+
212268
# Add these tables to the parent table's data
213-
parent_table_data.append(['\n' + author_tbl_str, '\n' + book_tbl_str + '\n\n'])
269+
parent_table_data.append(['\n' + author_tbl_str, '\n' + book_tbl_str + '\n\n', '\n' + relatives_tbl_str + '\n\n'])
214270

215271
# Build the parent table
216272
top_table_str = parent_tbl.generate_table(parent_table_data)

0 commit comments

Comments
 (0)