Skip to content

Commit ab93419

Browse files
authored
Merge pull request #16 from python-tableformatter/mix_formatters
Formatter improvements
2 parents 2b45af1 + e4da289 commit ab93419

2 files changed

Lines changed: 71 additions & 23 deletions

File tree

examples/formatters.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
Demonstration of field and object formatters for both object and tuple based table entries
55
"""
66
import tableformatter as tf
7+
from typing import Tuple
78

89

910
class MyRowObject(object):
@@ -74,6 +75,14 @@ def int2word(num, separator="-"):
7475
print("num out of range")
7576

7677

78+
def tag_row_obj(row_obj: MyRowObject) -> dict:
79+
"""Example row tagging function processing row objects and returning
80+
optional properties"""
81+
opts = dict()
82+
if row_obj.field4 % 4 == 0:
83+
opts[tf.TableFormatter.ROW_OPT_TEXT_COLOR] = tf.TableColors.TEXT_COLOR_GREEN
84+
return opts
85+
7786
rows = [MyRowObject(None, None, 17, 4),
7887
MyRowObject('123', '123', 5, 56),
7988
MyRowObject(123, 123, 5, 56),
@@ -91,7 +100,21 @@ def int2word(num, separator="-"):
91100
tf.Column('Multiplied', obj_formatter=multiply))
92101

93102
print("Formatters on object-based row entries")
94-
print(tf.generate_table(rows, columns))
103+
print(tf.generate_table(rows, columns, row_tagger=tag_row_obj))
104+
105+
106+
def tag_row_tuples(row_tuple: Tuple) -> dict:
107+
"""
108+
Example row tagging function processing row tuples/iterables
109+
110+
Note that this is the tuple as provided to the TableFormatter prior
111+
to display formatting performed on each column
112+
"""
113+
opts = dict()
114+
if len(row_tuple) >= 4 and row_tuple[3] % 4 == 0:
115+
opts[tf.TableFormatter.ROW_OPT_TEXT_COLOR] = tf.TableColors.TEXT_COLOR_GREEN
116+
return opts
117+
95118

96119
rows = [(None, None, 17, 4, None),
97120
('123', '123', 5, 56, None),
@@ -108,4 +131,4 @@ def int2word(num, separator="-"):
108131
tf.Column('Multiplied', obj_formatter=multiply_tuple))
109132

110133
print("Formatters on tuple-based row entries")
111-
print(tf.generate_table(rows, columns))
134+
print(tf.generate_table(rows, columns, row_tagger=tag_row_tuples))

tableformatter.py

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -600,9 +600,21 @@ def set_default_grid(grid: Grid) -> None:
600600
DEFAULT_GRID = grid
601601

602602

603-
def generate_table(rows: Iterable[Union[Iterable, object]], columns: Collection[Union[str, Tuple[str, dict]]]=None,
604-
grid_style: Optional[Grid]=None, transpose: bool=False) -> str:
605-
"""Convenience function to easily generate a table from rows/columns"""
603+
def generate_table(rows: Iterable[Union[Iterable, object]],
604+
columns: Collection[Union[str, Tuple[str, dict]]]=None,
605+
grid_style: Optional[Grid]=None,
606+
transpose: bool=False,
607+
row_tagger: Callable=None) -> str:
608+
"""
609+
Convenience function to easily generate a table from rows/columns
610+
611+
:param rows: iterable of objects or iterable fields
612+
:param columns: Iterable of column definitions
613+
:param grid_style: The grid style to use
614+
:param transpose: Transpose the rows/columns for display
615+
:param row_tagger: decorator function to apply per-row options
616+
:return: formatted string containing the table
617+
"""
606618
show_headers = True
607619
use_attrib = False
608620
if isinstance(columns, Collection) and len(columns) > 0:
@@ -634,7 +646,7 @@ def generate_table(rows: Iterable[Union[Iterable, object]], columns: Collection[
634646
if grid_style is None:
635647
grid_style = DEFAULT_GRID
636648
formatter = TableFormatter(columns, grid_style=grid_style, show_header=show_headers,
637-
use_attribs=use_attrib, transpose=transpose)
649+
use_attribs=use_attrib, transpose=transpose, row_tagger=row_tagger)
638650
return formatter.generate_table(rows)
639651

640652

@@ -696,7 +708,7 @@ def Column(col_name: str,
696708
return col_name, opts
697709

698710

699-
def Row(*args, text_color: TableColors=None):
711+
def Row(*args, text_color: Union[TableColors, str]=None):
700712
"""
701713
Processes row options and generates a tuple in the format the TableFormatter expects
702714
:param args: Can be either 1 object or a list of values
@@ -750,7 +762,8 @@ def __init__(self,
750762
show_header=True,
751763
use_attribs=False,
752764
transpose=False,
753-
row_show_header=False):
765+
row_show_header=False,
766+
row_tagger: Callable=None):
754767
"""
755768
:param columns: list of either column names or tuples of (column name, dict of column options)
756769
:param cell_padding: number of spaces to pad to the left/right of each column
@@ -776,6 +789,7 @@ def __init__(self,
776789
TableFormatter.TABLE_OPT_TRANSPOSE: transpose,
777790
TableFormatter.TABLE_OPT_ROW_HEADER: row_show_header}
778791
self._show_header = show_header
792+
self._row_tagger = row_tagger
779793

780794
for col_index, column in enumerate(columns):
781795
if isinstance(column, tuple) and len(column) > 1 and isinstance(column[1], dict):
@@ -911,11 +925,14 @@ def generate_table(self, entries: Iterable[Union[Iterable, object]], force_trans
911925
except TypeError:
912926
# not iterable, so we just use the object directly
913927
entry_obj = entry
928+
if self._row_tagger is not None:
929+
entry_opts = self._row_tagger(entry_obj)
914930
else:
915-
916-
if len(entry) == 2:
917-
entry_opts = entry[1]
918931
entry_obj = entry[0]
932+
if self._row_tagger is not None:
933+
entry_opts = self._row_tagger(entry_obj)
934+
if len(entry) == 2 and isinstance(entry[1], dict):
935+
entry_opts.update(entry[1])
919936

920937
for column_index, attrib_name in enumerate(self._column_attribs):
921938
field_obj = None
@@ -928,8 +945,8 @@ def generate_table(self, entries: Iterable[Union[Iterable, object]], force_trans
928945
formatter = self._get_column_option(column_index, TableFormatter.COL_OPT_FIELD_FORMATTER)
929946
obj_formatter = self._get_column_option(column_index, TableFormatter.COL_OPT_OBJECT_FORMATTER)
930947
if obj_formatter is not None and callable(obj_formatter):
931-
field_string = obj_formatter(entry_obj)
932-
elif formatter is not None and callable(formatter):
948+
field_obj = obj_formatter(entry_obj)
949+
if formatter is not None and callable(formatter):
933950
field_string = formatter(field_obj)
934951
elif isinstance(field_obj, str):
935952
field_string = field_obj
@@ -949,18 +966,20 @@ def generate_table(self, entries: Iterable[Union[Iterable, object]], force_trans
949966
row.append(field_lines)
950967

951968
else:
952-
if len(entry) == len(self._columns) + 1:
969+
if self._row_tagger is not None:
970+
entry_opts = self._row_tagger(entry)
971+
if len(entry) == len(self._columns) + 1 and isinstance(entry[len(self._columns)], dict):
953972
# if there is exactly 1 more entry than columns, the last one is metadata
954-
entry_opts = entry[len(self._columns)]
973+
entry_opts.update(entry[len(self._columns)])
955974

956975
for column_index, field in enumerate(entry):
957976
# skip extra values beyond the columns configured
958977
if column_index < len(self._columns):
959978
formatter = self._get_column_option(column_index, TableFormatter.COL_OPT_FIELD_FORMATTER)
960979
obj_formatter = self._get_column_option(column_index, TableFormatter.COL_OPT_OBJECT_FORMATTER)
961980
if obj_formatter is not None and callable(obj_formatter):
962-
field_string = obj_formatter(entry)
963-
elif formatter is not None and callable(formatter):
981+
field = obj_formatter(entry)
982+
if formatter is not None and callable(formatter):
964983
field_string = formatter(field, )
965984
elif isinstance(field, str):
966985
field_string = field
@@ -1202,6 +1221,10 @@ def generate_table(self, entries: Iterable[Union[Iterable, object]], force_trans
12021221
out_string += self._grid_style.border_right_row_divider
12031222
out_string += '\n'
12041223

1224+
opts = dict()
1225+
if row_index < len(row_opts):
1226+
opts = row_opts[row_index]
1227+
12051228
# loop for each line in the row
12061229
num_lines = row_counts[row_index]
12071230
for row_line in range(0, num_lines):
@@ -1228,23 +1251,25 @@ def generate_table(self, entries: Iterable[Union[Iterable, object]], force_trans
12281251
else:
12291252
mapped_line = row_line - int((num_lines - len(field)) / 2)
12301253

1231-
opts = dict()
12321254
if len(field) > mapped_line >= 0:
1233-
row_line_text = field[mapped_line]
1234-
if row_index < len(row_opts):
1235-
opts = row_opts[row_index]
12361255
# if the field has a line for the current row line, add it
1256+
row_line_text = field[mapped_line]
12371257
else:
12381258
row_line_text = ''
1259+
1260+
colorize = len(row_line_text.strip()) > 0 and TableFormatter.ROW_OPT_TEXT_COLOR in opts.keys()
1261+
12391262
row_line_text = _pad_columns(row_line_text,
12401263
pad_char=self._grid_style.cell_pad_char,
12411264
align=halign_cells[col_index],
12421265
width=col_widths[col_index])
12431266

1244-
if TableFormatter.ROW_OPT_TEXT_COLOR in opts.keys():
1267+
if colorize:
12451268
row_line_text = opts[TableFormatter.ROW_OPT_TEXT_COLOR] + row_line_text
12461269
out_string += row_line_text
1247-
out_string += pad_string + TableColors.RESET
1270+
out_string += pad_string
1271+
if colorize:
1272+
out_string += TableColors.RESET
12481273

12491274
if self._grid_style.border_right:
12501275
out_string += self._grid_style.border_right_span(row_index)

0 commit comments

Comments
 (0)