jp (common/table): a column can be hidden + fix for empty tables

- avoid crash when table is empty
- a column can now be hidden on display, this can be useful if one data is needed (e.g. to change color of an other column with a filter),
  but we don't want to display it
- filters have a new argument with all data of a row (for the same reason as above, one column can be used to change display of other column)
This commit is contained in:
Goffi 2018-02-28 18:28:39 +01:00
commit 976da08f5d

View file

@ -25,6 +25,7 @@ from sat.tools.common import uri
from sat.tools.common.ansi import ANSI as A
from sat.tools import config
from ConfigParser import NoSectionError, NoOptionError
from collections import namedtuple
import json
import os
import os.path
@ -509,7 +510,7 @@ class Table(object):
def __init__(self, host, data, headers=None, filters=None, use_buffer=False):
"""
@param data(list[list]): table data
@param data(iterable[list]): table data
all lines must have the same number of columns
@param headers(iterable[unicode], None): names/titles of the columns
if not None, must have same number of columns as data
@ -530,15 +531,21 @@ class Table(object):
self.rows = []
size = None
for line in data:
if headers:
row_cls = namedtuple('RowData', headers)
else:
row_cls = tuple
for row_data in data:
new_row = []
for idx, value in enumerate(line):
row_data_list = list(row_data)
for idx, value in enumerate(row_data_list):
if filters is not None and filters[idx] is not None:
filter_ = filters[idx]
if isinstance(filter_, basestring):
col_value = filter_.format(value)
else:
col_value = filter_(value)
col_value = filter_(value, row_cls(*row_data_list))
# we count size without ANSI code as they will change length of the string
# when it's mostly style/color changes.
col_size = len(regex.ansiRemove(col_value))
@ -610,18 +617,20 @@ class Table(object):
filters = [filters.get(k) for k in keys]
return cls(host, (cls.readDictValues(d, keys, defaults) for d in data), headers, filters)
def _headers(self, head_sep, alignment=u'left', style=None):
def _headers(self, head_sep, headers, sizes, alignment=u'left', style=None):
"""Render headers
@param head_sep(unicode): sequence to use as separator
@param alignment(unicode): how to align, can be left, center or right
@param style(unicode, iterable[unicode], None): ANSI escape sequences to apply
@param headers(list[unicode]): headers to show
@param sizes(list[int]): sizes of columns
"""
headers = []
rendered_headers = []
if isinstance(style, basestring):
style = [style]
for idx, header in enumerate(self.headers):
size = self.sizes[idx]
for idx, header in enumerate(headers):
size = sizes[idx]
if alignment == u'left':
rendered = header[:size].ljust(size)
elif alignment == u'center':
@ -633,8 +642,8 @@ class Table(object):
if style:
args = style + [rendered]
rendered = A.color(*args)
headers.append(rendered)
return head_sep.join(headers)
rendered_headers.append(rendered)
return head_sep.join(rendered_headers)
def _disp(self, data):
"""output data (can be either bufferised or printed)"""
@ -649,6 +658,7 @@ class Table(object):
head_style = None,
show_header=True,
show_borders=True,
hide_cols=None,
col_sep=u' │ ',
top_left=u'┌',
top=u'─',
@ -670,6 +680,7 @@ class Table(object):
@param show_header(bool): True if header need no be shown
@param show_borders(bool): True if borders need no be shown
@param hide_cols(None, iterable(unicode)): columns which should not be displayed
@param head_alignment(unicode): how to align headers, can be left, center or right
@param columns_alignment(unicode): how to align columns, can be left, center or right
@param col_sep(unicode): separator betweens columns
@ -677,7 +688,24 @@ class Table(object):
@param disp(callable, None): method to use to display the table
None to use self.host.disp
"""
if not self.sizes:
# the table is empty
return
col_sep_size = len(regex.ansiRemove(col_sep))
# if we have columns to hide, we remove them from headers and size
if not hide_cols:
headers = self.headers
sizes = self.sizes
else:
headers = list(self.headers)
sizes = self.sizes[:]
ignore_idx = [headers.index(to_hide) for to_hide in hide_cols]
for to_hide in hide_cols:
hide_idx = headers.index(to_hide)
del headers[hide_idx]
del sizes[hide_idx]
if right is None:
right = left
if top_sep is None:
@ -694,7 +722,7 @@ class Table(object):
if show_borders:
self._disp(
top_left
+ top_sep.join([top*size for size in self.sizes])
+ top_sep.join([top*size for size in sizes])
+ top_right
)
@ -702,34 +730,36 @@ class Table(object):
if show_header:
self._disp(
left
+ self._headers(head_sep, head_alignment, head_style)
+ self._headers(head_sep, headers, sizes, head_alignment, head_style)
+ right
)
# header line
self._disp(
head_line_left
+ head_line_sep.join([head_line*size for size in self.sizes])
+ head_line_sep.join([head_line*size for size in sizes])
+ head_line_right
)
# content
if columns_alignment == u'left':
alignment = lambda idx, s: ansi_ljust(s, self.sizes[idx])
alignment = lambda idx, s: ansi_ljust(s, sizes[idx])
elif columns_alignment == u'center':
alignment = lambda idx, s: ansi_center(s, self.sizes[idx])
alignment = lambda idx, s: ansi_center(s, sizes[idx])
elif columns_alignment == u'right':
alignment = lambda idx, s: ansi_rjust(s, self.sizes[idx])
alignment = lambda idx, s: ansi_rjust(s, sizes[idx])
else:
raise exceptions.InternalError(u'bad columns alignment argument')
for row in self.rows:
if hide_cols:
row = [v for idx,v in enumerate(row) if idx not in ignore_idx]
self._disp(left + col_sep.join([alignment(idx,c) for idx,c in enumerate(row)]) + right)
if show_borders:
# bottom border
self._disp(
bottom_left
+ bottom_sep.join([bottom*size for size in self.sizes])
+ bottom_sep.join([bottom*size for size in sizes])
+ bottom_right
)
# we return self so string can be used after display (table.display().string)
@ -739,6 +769,4 @@ class Table(object):
"""Display table without visible borders"""
kwargs_ = {'col_sep':u' ', 'head_line_sep':u' ', 'show_borders':False}
kwargs_.update(kwargs)
return self.display(self,
**kwargs_
)
return self.display(**kwargs_)