5
5
The general use case is to inherit from TableCreator to create a table class with custom formatting options.
6
6
There are already implemented and ready-to-use examples of this below TableCreator's code.
7
7
"""
8
+ import copy
8
9
import functools
9
10
import io
10
11
from collections import deque
@@ -103,7 +104,7 @@ def __init__(self, cols: Sequence[Column], *, tab_width: int = 4) -> None:
103
104
:param tab_width: all tabs will be replaced with this many spaces. If a row's fill_char is a tab,
104
105
then it will be converted to one space.
105
106
"""
106
- self .cols = cols
107
+ self .cols = copy . copy ( cols )
107
108
self .tab_width = tab_width
108
109
109
110
@staticmethod
@@ -465,8 +466,9 @@ def __init__(self) -> None:
465
466
if cell_index == len (self .cols ) - 1 :
466
467
row_buf .write (post_line )
467
468
468
- # Add a newline if this is not the last row
469
- row_buf .write ('\n ' )
469
+ # Add a newline if this is not the last line
470
+ if line_index < total_lines - 1 :
471
+ row_buf .write ('\n ' )
470
472
471
473
return row_buf .getvalue ()
472
474
@@ -480,39 +482,78 @@ class SimpleTable(TableCreator):
480
482
Implementation of TableCreator which generates a borderless table with an optional divider row after the header.
481
483
This class can be used to create the whole table at once or one row at a time.
482
484
"""
485
+ # Spaces between cells
486
+ INTER_CELL = 2 * SPACE
487
+
483
488
def __init__ (self , cols : Sequence [Column ], * , tab_width : int = 4 , divider_char : Optional [str ] = '-' ) -> None :
484
489
"""
485
490
SimpleTable initializer
486
491
487
492
:param cols: column definitions for this table
488
493
:param tab_width: all tabs will be replaced with this many spaces. If a row's fill_char is a tab,
489
494
then it will be converted to one space.
490
- :param divider_char: optional character used to build the header divider row. If provided, its value must meet the
491
- same requirements as fill_char in TableCreator.generate_row() or exceptions will be raised.
492
- Set this to None if you don't want a divider row. (Defaults to dash)
495
+ :param divider_char: optional character used to build the header divider row. Set this to None if you don't
496
+ want a divider row. Defaults to dash. (Cannot be a line breaking character)
497
+ :raises: TypeError if fill_char is more than one character (not including ANSI style sequences)
498
+ :raises: ValueError if text or fill_char contains an unprintable character
493
499
"""
500
+ if divider_char is not None :
501
+ if len (ansi .strip_style (divider_char )) != 1 :
502
+ raise TypeError ("Divider character must be exactly one character long" )
503
+
504
+ divider_char_width = ansi .style_aware_wcswidth (divider_char )
505
+ if divider_char_width == - 1 :
506
+ raise (ValueError ("Divider character is an unprintable character" ))
507
+
494
508
super ().__init__ (cols , tab_width = tab_width )
495
509
self .divider_char = divider_char
496
- self .empty_data = [EMPTY for _ in self .cols ]
497
510
498
- def generate_header (self ) -> str :
511
+ @classmethod
512
+ def base_width (cls , num_cols : int ) -> int :
499
513
"""
500
- Generate header with an optional divider row
514
+ Utility method to calculate the display width required for a table before data is added to it.
515
+ This is useful when determining how wide to make your columns to have a table be a specific width.
516
+
517
+ :param num_cols: how many columns the table will have
518
+ :return: base width
519
+ :raises: ValueError if num_cols is less than 1
501
520
"""
521
+ if num_cols < 1 :
522
+ raise ValueError ("Column count cannot be less than 1" )
523
+
524
+ data_str = SPACE
525
+ data_width = ansi .style_aware_wcswidth (data_str ) * num_cols
526
+
527
+ tbl = cls ([Column (data_str )] * num_cols )
528
+ data_row = tbl .generate_data_row ([data_str ] * num_cols )
529
+
530
+ return ansi .style_aware_wcswidth (data_row ) - data_width
531
+
532
+ def total_width (self ) -> int :
533
+ """Calculate the total display width of this table"""
534
+ base_width = self .base_width (len (self .cols ))
535
+ data_width = sum (col .width for col in self .cols )
536
+ return base_width + data_width
537
+
538
+ def generate_header (self ) -> str :
539
+ """Generate table header with an optional divider row"""
502
540
header_buf = io .StringIO ()
503
541
504
542
# Create the header labels
505
- if self .divider_char is None :
506
- inter_cell = 2 * SPACE
507
- else :
508
- inter_cell = SPACE * ansi .style_aware_wcswidth (2 * self .divider_char )
509
- header = self .generate_row (inter_cell = inter_cell )
543
+ header = self .generate_row (inter_cell = self .INTER_CELL )
510
544
header_buf .write (header )
511
545
512
- # Create the divider. Use empty strings for the row_data.
546
+ # Create the divider if necessary
513
547
if self .divider_char is not None :
514
- divider = self .generate_row (row_data = self .empty_data , fill_char = self .divider_char ,
515
- inter_cell = (2 * self .divider_char ))
548
+ total_width = self .total_width ()
549
+ divider_char_width = ansi .style_aware_wcswidth (self .divider_char )
550
+
551
+ # Make divider as wide as table and use padding if width of
552
+ # divider_char does not divide evenly into table width.
553
+ divider = self .divider_char * (total_width // divider_char_width )
554
+ divider += SPACE * (total_width % divider_char_width )
555
+
556
+ header_buf .write ('\n ' )
516
557
header_buf .write (divider )
517
558
return header_buf .getvalue ()
518
559
@@ -523,11 +564,7 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str:
523
564
:param row_data: data with an entry for each column in the row
524
565
:return: data row string
525
566
"""
526
- if self .divider_char is None :
527
- inter_cell = 2 * SPACE
528
- else :
529
- inter_cell = SPACE * ansi .style_aware_wcswidth (2 * self .divider_char )
530
- return self .generate_row (row_data = row_data , inter_cell = inter_cell )
567
+ return self .generate_row (row_data = row_data , inter_cell = self .INTER_CELL )
531
568
532
569
def generate_table (self , table_data : Sequence [Sequence [Any ]], * ,
533
570
include_header : bool = True , row_spacing : int = 1 ) -> str :
@@ -548,13 +585,17 @@ def generate_table(self, table_data: Sequence[Sequence[Any]], *,
548
585
if include_header :
549
586
header = self .generate_header ()
550
587
table_buf .write (header )
588
+ if len (table_data ) > 0 :
589
+ table_buf .write ('\n ' )
551
590
552
591
for index , row_data in enumerate (table_data ):
553
592
if index > 0 and row_spacing > 0 :
554
593
table_buf .write (row_spacing * '\n ' )
555
594
556
595
row = self .generate_data_row (row_data )
557
596
table_buf .write (row )
597
+ if index < len (table_data ) - 1 :
598
+ table_buf .write ('\n ' )
558
599
559
600
return table_buf .getvalue ()
560
601
@@ -586,6 +627,35 @@ def __init__(self, cols: Sequence[Column], *, tab_width: int = 4,
586
627
raise ValueError ("Padding cannot be less than 0" )
587
628
self .padding = padding
588
629
630
+ @classmethod
631
+ def base_width (cls , num_cols : int , * , column_borders : bool = True , padding : int = 1 ) -> int :
632
+ """
633
+ Utility method to calculate the display width required for a table before data is added to it.
634
+ This is useful when determining how wide to make your columns to have a table be a specific width.
635
+
636
+ :param num_cols: how many columns the table will have
637
+ :param column_borders: if True, borders between columns will be included in the calculation (Defaults to True)
638
+ :param padding: number of spaces between text and left/right borders of cell
639
+ :return: base width
640
+ :raises: ValueError if num_cols is less than 1
641
+ """
642
+ if num_cols < 1 :
643
+ raise ValueError ("Column count cannot be less than 1" )
644
+
645
+ data_str = SPACE
646
+ data_width = ansi .style_aware_wcswidth (data_str ) * num_cols
647
+
648
+ tbl = cls ([Column (data_str )] * num_cols , column_borders = column_borders , padding = padding )
649
+ data_row = tbl .generate_data_row ([data_str ] * num_cols )
650
+
651
+ return ansi .style_aware_wcswidth (data_row ) - data_width
652
+
653
+ def total_width (self ) -> int :
654
+ """Calculate the total display width of this table"""
655
+ base_width = self .base_width (len (self .cols ), column_borders = self .column_borders , padding = self .padding )
656
+ data_width = sum (col .width for col in self .cols )
657
+ return base_width + data_width
658
+
589
659
def generate_table_top_border (self ):
590
660
"""Generate a border which appears at the top of the header and data section"""
591
661
pre_line = '╔' + self .padding * '═'
@@ -643,10 +713,7 @@ def generate_table_bottom_border(self):
643
713
inter_cell = inter_cell , post_line = post_line )
644
714
645
715
def generate_header (self ) -> str :
646
- """
647
- Generate header
648
- :return: header string
649
- """
716
+ """Generate table header"""
650
717
pre_line = '║' + self .padding * SPACE
651
718
652
719
inter_cell = self .padding * SPACE
@@ -659,7 +726,9 @@ def generate_header(self) -> str:
659
726
# Create the bordered header
660
727
header_buf = io .StringIO ()
661
728
header_buf .write (self .generate_table_top_border ())
729
+ header_buf .write ('\n ' )
662
730
header_buf .write (self .generate_row (pre_line = pre_line , inter_cell = inter_cell , post_line = post_line ))
731
+ header_buf .write ('\n ' )
663
732
header_buf .write (self .generate_header_bottom_border ())
664
733
665
734
return header_buf .getvalue ()
@@ -699,13 +768,17 @@ def generate_table(self, table_data: Sequence[Sequence[Any]], *, include_header:
699
768
top_border = self .generate_table_top_border ()
700
769
table_buf .write (top_border )
701
770
771
+ table_buf .write ('\n ' )
772
+
702
773
for index , row_data in enumerate (table_data ):
703
774
if index > 0 :
704
775
row_bottom_border = self .generate_row_bottom_border ()
705
776
table_buf .write (row_bottom_border )
777
+ table_buf .write ('\n ' )
706
778
707
779
row = self .generate_data_row (row_data )
708
780
table_buf .write (row )
781
+ table_buf .write ('\n ' )
709
782
710
783
table_buf .write (self .generate_table_bottom_border ())
711
784
return table_buf .getvalue ()
@@ -797,9 +870,12 @@ def generate_table(self, table_data: Sequence[Sequence[Any]], *, include_header:
797
870
top_border = self .generate_table_top_border ()
798
871
table_buf .write (top_border )
799
872
873
+ table_buf .write ('\n ' )
874
+
800
875
for row_data in table_data :
801
876
row = self .generate_data_row (row_data )
802
877
table_buf .write (row )
878
+ table_buf .write ('\n ' )
803
879
804
880
table_buf .write (self .generate_table_bottom_border ())
805
881
return table_buf .getvalue ()
0 commit comments