Mercurial > libervia-backend
comparison 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 |
comparison
equal
deleted
inserted
replaced
2492:fcf0ae8102b8 | 2493:984792a451bc |
---|---|
23 from sat.tools.common import regex | 23 from sat.tools.common import regex |
24 from sat.tools.common import uri | 24 from sat.tools.common import uri |
25 from sat.tools.common.ansi import ANSI as A | 25 from sat.tools.common.ansi import ANSI as A |
26 from sat.tools import config | 26 from sat.tools import config |
27 from ConfigParser import NoSectionError, NoOptionError | 27 from ConfigParser import NoSectionError, NoOptionError |
28 from collections import namedtuple | |
28 import json | 29 import json |
29 import os | 30 import os |
30 import os.path | 31 import os.path |
31 import time | 32 import time |
32 import tempfile | 33 import tempfile |
507 | 508 |
508 class Table(object): | 509 class Table(object): |
509 | 510 |
510 def __init__(self, host, data, headers=None, filters=None, use_buffer=False): | 511 def __init__(self, host, data, headers=None, filters=None, use_buffer=False): |
511 """ | 512 """ |
512 @param data(list[list]): table data | 513 @param data(iterable[list]): table data |
513 all lines must have the same number of columns | 514 all lines must have the same number of columns |
514 @param headers(iterable[unicode], None): names/titles of the columns | 515 @param headers(iterable[unicode], None): names/titles of the columns |
515 if not None, must have same number of columns as data | 516 if not None, must have same number of columns as data |
516 @param filters(iterable[(callable, unicode)], None): values filters | 517 @param filters(iterable[(callable, unicode)], None): values filters |
517 the callable will get col value as argument and must return a string | 518 the callable will get col value as argument and must return a string |
528 self.sizes = [] | 529 self.sizes = [] |
529 # rows countains one list per row with columns values | 530 # rows countains one list per row with columns values |
530 self.rows = [] | 531 self.rows = [] |
531 | 532 |
532 size = None | 533 size = None |
533 for line in data: | 534 if headers: |
535 row_cls = namedtuple('RowData', headers) | |
536 else: | |
537 row_cls = tuple | |
538 | |
539 for row_data in data: | |
534 new_row = [] | 540 new_row = [] |
535 for idx, value in enumerate(line): | 541 row_data_list = list(row_data) |
542 for idx, value in enumerate(row_data_list): | |
536 if filters is not None and filters[idx] is not None: | 543 if filters is not None and filters[idx] is not None: |
537 filter_ = filters[idx] | 544 filter_ = filters[idx] |
538 if isinstance(filter_, basestring): | 545 if isinstance(filter_, basestring): |
539 col_value = filter_.format(value) | 546 col_value = filter_.format(value) |
540 else: | 547 else: |
541 col_value = filter_(value) | 548 col_value = filter_(value, row_cls(*row_data_list)) |
542 # we count size without ANSI code as they will change length of the string | 549 # we count size without ANSI code as they will change length of the string |
543 # when it's mostly style/color changes. | 550 # when it's mostly style/color changes. |
544 col_size = len(regex.ansiRemove(col_value)) | 551 col_size = len(regex.ansiRemove(col_value)) |
545 else: | 552 else: |
546 col_value = unicode(value) | 553 col_value = unicode(value) |
608 if headers is None: | 615 if headers is None: |
609 headers = keys | 616 headers = keys |
610 filters = [filters.get(k) for k in keys] | 617 filters = [filters.get(k) for k in keys] |
611 return cls(host, (cls.readDictValues(d, keys, defaults) for d in data), headers, filters) | 618 return cls(host, (cls.readDictValues(d, keys, defaults) for d in data), headers, filters) |
612 | 619 |
613 def _headers(self, head_sep, alignment=u'left', style=None): | 620 def _headers(self, head_sep, headers, sizes, alignment=u'left', style=None): |
614 """Render headers | 621 """Render headers |
615 | 622 |
616 @param head_sep(unicode): sequence to use as separator | 623 @param head_sep(unicode): sequence to use as separator |
617 @param alignment(unicode): how to align, can be left, center or right | 624 @param alignment(unicode): how to align, can be left, center or right |
618 @param style(unicode, iterable[unicode], None): ANSI escape sequences to apply | 625 @param style(unicode, iterable[unicode], None): ANSI escape sequences to apply |
619 """ | 626 @param headers(list[unicode]): headers to show |
620 headers = [] | 627 @param sizes(list[int]): sizes of columns |
628 """ | |
629 rendered_headers = [] | |
621 if isinstance(style, basestring): | 630 if isinstance(style, basestring): |
622 style = [style] | 631 style = [style] |
623 for idx, header in enumerate(self.headers): | 632 for idx, header in enumerate(headers): |
624 size = self.sizes[idx] | 633 size = sizes[idx] |
625 if alignment == u'left': | 634 if alignment == u'left': |
626 rendered = header[:size].ljust(size) | 635 rendered = header[:size].ljust(size) |
627 elif alignment == u'center': | 636 elif alignment == u'center': |
628 rendered = header[:size].center(size) | 637 rendered = header[:size].center(size) |
629 elif alignment == u'right': | 638 elif alignment == u'right': |
631 else: | 640 else: |
632 raise exceptions.InternalError(u'bad alignment argument') | 641 raise exceptions.InternalError(u'bad alignment argument') |
633 if style: | 642 if style: |
634 args = style + [rendered] | 643 args = style + [rendered] |
635 rendered = A.color(*args) | 644 rendered = A.color(*args) |
636 headers.append(rendered) | 645 rendered_headers.append(rendered) |
637 return head_sep.join(headers) | 646 return head_sep.join(rendered_headers) |
638 | 647 |
639 def _disp(self, data): | 648 def _disp(self, data): |
640 """output data (can be either bufferised or printed)""" | 649 """output data (can be either bufferised or printed)""" |
641 if self._buffer is not None: | 650 if self._buffer is not None: |
642 self._buffer.append(data) | 651 self._buffer.append(data) |
647 head_alignment = u'left', | 656 head_alignment = u'left', |
648 columns_alignment = u'left', | 657 columns_alignment = u'left', |
649 head_style = None, | 658 head_style = None, |
650 show_header=True, | 659 show_header=True, |
651 show_borders=True, | 660 show_borders=True, |
661 hide_cols=None, | |
652 col_sep=u' │ ', | 662 col_sep=u' │ ', |
653 top_left=u'┌', | 663 top_left=u'┌', |
654 top=u'─', | 664 top=u'─', |
655 top_sep=u'─┬─', | 665 top_sep=u'─┬─', |
656 top_right=u'┐', | 666 top_right=u'┐', |
668 ): | 678 ): |
669 """Print the table | 679 """Print the table |
670 | 680 |
671 @param show_header(bool): True if header need no be shown | 681 @param show_header(bool): True if header need no be shown |
672 @param show_borders(bool): True if borders need no be shown | 682 @param show_borders(bool): True if borders need no be shown |
683 @param hide_cols(None, iterable(unicode)): columns which should not be displayed | |
673 @param head_alignment(unicode): how to align headers, can be left, center or right | 684 @param head_alignment(unicode): how to align headers, can be left, center or right |
674 @param columns_alignment(unicode): how to align columns, can be left, center or right | 685 @param columns_alignment(unicode): how to align columns, can be left, center or right |
675 @param col_sep(unicode): separator betweens columns | 686 @param col_sep(unicode): separator betweens columns |
676 @param head_line(unicode): character to use to make line under head | 687 @param head_line(unicode): character to use to make line under head |
677 @param disp(callable, None): method to use to display the table | 688 @param disp(callable, None): method to use to display the table |
678 None to use self.host.disp | 689 None to use self.host.disp |
679 """ | 690 """ |
691 if not self.sizes: | |
692 # the table is empty | |
693 return | |
680 col_sep_size = len(regex.ansiRemove(col_sep)) | 694 col_sep_size = len(regex.ansiRemove(col_sep)) |
695 | |
696 # if we have columns to hide, we remove them from headers and size | |
697 if not hide_cols: | |
698 headers = self.headers | |
699 sizes = self.sizes | |
700 else: | |
701 headers = list(self.headers) | |
702 sizes = self.sizes[:] | |
703 ignore_idx = [headers.index(to_hide) for to_hide in hide_cols] | |
704 for to_hide in hide_cols: | |
705 hide_idx = headers.index(to_hide) | |
706 del headers[hide_idx] | |
707 del sizes[hide_idx] | |
708 | |
681 if right is None: | 709 if right is None: |
682 right = left | 710 right = left |
683 if top_sep is None: | 711 if top_sep is None: |
684 top_sep = col_sep_size * top | 712 top_sep = col_sep_size * top |
685 if head_sep is None: | 713 if head_sep is None: |
692 left = right = head_line_left = head_line_right = u'' | 720 left = right = head_line_left = head_line_right = u'' |
693 # top border | 721 # top border |
694 if show_borders: | 722 if show_borders: |
695 self._disp( | 723 self._disp( |
696 top_left | 724 top_left |
697 + top_sep.join([top*size for size in self.sizes]) | 725 + top_sep.join([top*size for size in sizes]) |
698 + top_right | 726 + top_right |
699 ) | 727 ) |
700 | 728 |
701 # headers | 729 # headers |
702 if show_header: | 730 if show_header: |
703 self._disp( | 731 self._disp( |
704 left | 732 left |
705 + self._headers(head_sep, head_alignment, head_style) | 733 + self._headers(head_sep, headers, sizes, head_alignment, head_style) |
706 + right | 734 + right |
707 ) | 735 ) |
708 # header line | 736 # header line |
709 self._disp( | 737 self._disp( |
710 head_line_left | 738 head_line_left |
711 + head_line_sep.join([head_line*size for size in self.sizes]) | 739 + head_line_sep.join([head_line*size for size in sizes]) |
712 + head_line_right | 740 + head_line_right |
713 ) | 741 ) |
714 | 742 |
715 # content | 743 # content |
716 if columns_alignment == u'left': | 744 if columns_alignment == u'left': |
717 alignment = lambda idx, s: ansi_ljust(s, self.sizes[idx]) | 745 alignment = lambda idx, s: ansi_ljust(s, sizes[idx]) |
718 elif columns_alignment == u'center': | 746 elif columns_alignment == u'center': |
719 alignment = lambda idx, s: ansi_center(s, self.sizes[idx]) | 747 alignment = lambda idx, s: ansi_center(s, sizes[idx]) |
720 elif columns_alignment == u'right': | 748 elif columns_alignment == u'right': |
721 alignment = lambda idx, s: ansi_rjust(s, self.sizes[idx]) | 749 alignment = lambda idx, s: ansi_rjust(s, sizes[idx]) |
722 else: | 750 else: |
723 raise exceptions.InternalError(u'bad columns alignment argument') | 751 raise exceptions.InternalError(u'bad columns alignment argument') |
724 | 752 |
725 for row in self.rows: | 753 for row in self.rows: |
754 if hide_cols: | |
755 row = [v for idx,v in enumerate(row) if idx not in ignore_idx] | |
726 self._disp(left + col_sep.join([alignment(idx,c) for idx,c in enumerate(row)]) + right) | 756 self._disp(left + col_sep.join([alignment(idx,c) for idx,c in enumerate(row)]) + right) |
727 | 757 |
728 if show_borders: | 758 if show_borders: |
729 # bottom border | 759 # bottom border |
730 self._disp( | 760 self._disp( |
731 bottom_left | 761 bottom_left |
732 + bottom_sep.join([bottom*size for size in self.sizes]) | 762 + bottom_sep.join([bottom*size for size in sizes]) |
733 + bottom_right | 763 + bottom_right |
734 ) | 764 ) |
735 # we return self so string can be used after display (table.display().string) | 765 # we return self so string can be used after display (table.display().string) |
736 return self | 766 return self |
737 | 767 |
738 def display_blank(self, **kwargs): | 768 def display_blank(self, **kwargs): |
739 """Display table without visible borders""" | 769 """Display table without visible borders""" |
740 kwargs_ = {'col_sep':u' ', 'head_line_sep':u' ', 'show_borders':False} | 770 kwargs_ = {'col_sep':u' ', 'head_line_sep':u' ', 'show_borders':False} |
741 kwargs_.update(kwargs) | 771 kwargs_.update(kwargs) |
742 return self.display(self, | 772 return self.display(**kwargs_) |
743 **kwargs_ | |
744 ) |