Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions examples/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Demonstration of field and object formatters for both object and tuple based table entries
"""
import tableformatter as tf
from typing import Tuple


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


def tag_row_obj(row_obj: MyRowObject) -> dict:
"""Example row tagging function processing row objects and returning
optional properties"""
opts = dict()
if row_obj.field4 % 4 == 0:
opts[tf.TableFormatter.ROW_OPT_TEXT_COLOR] = tf.TableColors.TEXT_COLOR_GREEN
return opts

rows = [MyRowObject(None, None, 17, 4),
MyRowObject('123', '123', 5, 56),
MyRowObject(123, 123, 5, 56),
Expand All @@ -91,7 +100,21 @@ def int2word(num, separator="-"):
tf.Column('Multiplied', obj_formatter=multiply))

print("Formatters on object-based row entries")
print(tf.generate_table(rows, columns))
print(tf.generate_table(rows, columns, row_tagger=tag_row_obj))


def tag_row_tuples(row_tuple: Tuple) -> dict:
"""
Example row tagging function processing row tuples/iterables

Note that this is the tuple as provided to the TableFormatter prior
to display formatting performed on each column
"""
opts = dict()
if len(row_tuple) >= 4 and row_tuple[3] % 4 == 0:
opts[tf.TableFormatter.ROW_OPT_TEXT_COLOR] = tf.TableColors.TEXT_COLOR_GREEN
return opts


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

print("Formatters on tuple-based row entries")
print(tf.generate_table(rows, columns))
print(tf.generate_table(rows, columns, row_tagger=tag_row_tuples))
67 changes: 46 additions & 21 deletions tableformatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,9 +600,21 @@ def set_default_grid(grid: Grid) -> None:
DEFAULT_GRID = grid


def generate_table(rows: Iterable[Union[Iterable, object]], columns: Collection[Union[str, Tuple[str, dict]]]=None,
grid_style: Optional[Grid]=None, transpose: bool=False) -> str:
"""Convenience function to easily generate a table from rows/columns"""
def generate_table(rows: Iterable[Union[Iterable, object]],
columns: Collection[Union[str, Tuple[str, dict]]]=None,
grid_style: Optional[Grid]=None,
transpose: bool=False,
row_tagger: Callable=None) -> str:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new name works for me. Thanks for the change.

"""
Convenience function to easily generate a table from rows/columns

:param rows: iterable of objects or iterable fields
:param columns: Iterable of column definitions
:param grid_style: The grid style to use
:param transpose: Transpose the rows/columns for display
:param row_tagger: decorator function to apply per-row options
:return: formatted string containing the table
"""
show_headers = True
use_attrib = False
if isinstance(columns, Collection) and len(columns) > 0:
Expand Down Expand Up @@ -634,7 +646,7 @@ def generate_table(rows: Iterable[Union[Iterable, object]], columns: Collection[
if grid_style is None:
grid_style = DEFAULT_GRID
formatter = TableFormatter(columns, grid_style=grid_style, show_header=show_headers,
use_attribs=use_attrib, transpose=transpose)
use_attribs=use_attrib, transpose=transpose, row_tagger=row_tagger)
return formatter.generate_table(rows)


Expand Down Expand Up @@ -696,7 +708,7 @@ def Column(col_name: str,
return col_name, opts


def Row(*args, text_color: TableColors=None):
def Row(*args, text_color: Union[TableColors, str]=None):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to elaborate in the docstring what kind of str might get passed in for text_color - i.e. it can take ANSI escape codes, but isn't looking for something like 'green'.

"""
Processes row options and generates a tuple in the format the TableFormatter expects
:param args: Can be either 1 object or a list of values
Expand Down Expand Up @@ -750,7 +762,8 @@ def __init__(self,
show_header=True,
use_attribs=False,
transpose=False,
row_show_header=False):
row_show_header=False,
row_tagger: Callable=None):
"""
:param columns: list of either column names or tuples of (column name, dict of column options)
:param cell_padding: number of spaces to pad to the left/right of each column
Expand All @@ -776,6 +789,7 @@ def __init__(self,
TableFormatter.TABLE_OPT_TRANSPOSE: transpose,
TableFormatter.TABLE_OPT_ROW_HEADER: row_show_header}
self._show_header = show_header
self._row_tagger = row_tagger

for col_index, column in enumerate(columns):
if isinstance(column, tuple) and len(column) > 1 and isinstance(column[1], dict):
Expand Down Expand Up @@ -911,11 +925,14 @@ def generate_table(self, entries: Iterable[Union[Iterable, object]], force_trans
except TypeError:
# not iterable, so we just use the object directly
entry_obj = entry
if self._row_tagger is not None:
entry_opts = self._row_tagger(entry_obj)
else:

if len(entry) == 2:
entry_opts = entry[1]
entry_obj = entry[0]
if self._row_tagger is not None:
entry_opts = self._row_tagger(entry_obj)
if len(entry) == 2 and isinstance(entry[1], dict):
entry_opts.update(entry[1])

for column_index, attrib_name in enumerate(self._column_attribs):
field_obj = None
Expand All @@ -928,8 +945,8 @@ def generate_table(self, entries: Iterable[Union[Iterable, object]], force_trans
formatter = self._get_column_option(column_index, TableFormatter.COL_OPT_FIELD_FORMATTER)
obj_formatter = self._get_column_option(column_index, TableFormatter.COL_OPT_OBJECT_FORMATTER)
if obj_formatter is not None and callable(obj_formatter):
field_string = obj_formatter(entry_obj)
elif formatter is not None and callable(formatter):
field_obj = obj_formatter(entry_obj)
if formatter is not None and callable(formatter):
field_string = formatter(field_obj)
elif isinstance(field_obj, str):
field_string = field_obj
Expand All @@ -949,18 +966,20 @@ def generate_table(self, entries: Iterable[Union[Iterable, object]], force_trans
row.append(field_lines)

else:
if len(entry) == len(self._columns) + 1:
if self._row_tagger is not None:
entry_opts = self._row_tagger(entry)
if len(entry) == len(self._columns) + 1 and isinstance(entry[len(self._columns)], dict):
# if there is exactly 1 more entry than columns, the last one is metadata
entry_opts = entry[len(self._columns)]
entry_opts.update(entry[len(self._columns)])

for column_index, field in enumerate(entry):
# skip extra values beyond the columns configured
if column_index < len(self._columns):
formatter = self._get_column_option(column_index, TableFormatter.COL_OPT_FIELD_FORMATTER)
obj_formatter = self._get_column_option(column_index, TableFormatter.COL_OPT_OBJECT_FORMATTER)
if obj_formatter is not None and callable(obj_formatter):
field_string = obj_formatter(entry)
elif formatter is not None and callable(formatter):
field = obj_formatter(entry)
if formatter is not None and callable(formatter):
field_string = formatter(field, )
elif isinstance(field, str):
field_string = field
Expand Down Expand Up @@ -1202,6 +1221,10 @@ def generate_table(self, entries: Iterable[Union[Iterable, object]], force_trans
out_string += self._grid_style.border_right_row_divider
out_string += '\n'

opts = dict()
if row_index < len(row_opts):
opts = row_opts[row_index]

# loop for each line in the row
num_lines = row_counts[row_index]
for row_line in range(0, num_lines):
Expand All @@ -1228,23 +1251,25 @@ def generate_table(self, entries: Iterable[Union[Iterable, object]], force_trans
else:
mapped_line = row_line - int((num_lines - len(field)) / 2)

opts = dict()
if len(field) > mapped_line >= 0:
row_line_text = field[mapped_line]
if row_index < len(row_opts):
opts = row_opts[row_index]
# if the field has a line for the current row line, add it
row_line_text = field[mapped_line]
else:
row_line_text = ''

colorize = len(row_line_text.strip()) > 0 and TableFormatter.ROW_OPT_TEXT_COLOR in opts.keys()

row_line_text = _pad_columns(row_line_text,
pad_char=self._grid_style.cell_pad_char,
align=halign_cells[col_index],
width=col_widths[col_index])

if TableFormatter.ROW_OPT_TEXT_COLOR in opts.keys():
if colorize:
row_line_text = opts[TableFormatter.ROW_OPT_TEXT_COLOR] + row_line_text
out_string += row_line_text
out_string += pad_string + TableColors.RESET
out_string += pad_string
if colorize:
out_string += TableColors.RESET

if self._grid_style.border_right:
out_string += self._grid_style.border_right_span(row_index)
Expand Down