diff frontends/src/jp/common.py @ 2493:984792a451bc

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)
author Goffi <goffi@goffi.org>
date Wed, 28 Feb 2018 18:28:39 +0100
parents 0046283a285d
children 21d43eab3fb9
line wrap: on
line diff
--- a/frontends/src/jp/common.py	Wed Feb 28 18:28:39 2018 +0100
+++ b/frontends/src/jp/common.py	Wed Feb 28 18:28:39 2018 +0100
@@ -25,6 +25,7 @@
 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 @@
 
     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 @@
         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 @@
         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 @@
             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 @@
                 head_style = None,
                 show_header=True,
                 show_borders=True,
+                hide_cols=None,
                 col_sep=u' │ ',
                 top_left=u'┌',
                 top=u'─',
@@ -670,6 +680,7 @@
 
         @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 @@
         @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 @@
         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 @@
         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 @@
         """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_)