55The general use case is to inherit from TableCreator to create a table class with custom formatting options.
66There are already implemented and ready-to-use examples of this below TableCreator's code.
77"""
8+ import copy
89import functools
910import io
1011from collections import deque
@@ -103,7 +104,7 @@ def __init__(self, cols: Sequence[Column], *, tab_width: int = 4) -> None:
103104 :param tab_width: all tabs will be replaced with this many spaces. If a row's fill_char is a tab,
104105 then it will be converted to one space.
105106 """
106- self .cols = cols
107+ self .cols = copy . copy ( cols )
107108 self .tab_width = tab_width
108109
109110 @staticmethod
@@ -481,8 +482,8 @@ class SimpleTable(TableCreator):
481482 Implementation of TableCreator which generates a borderless table with an optional divider row after the header.
482483 This class can be used to create the whole table at once or one row at a time.
483484 """
484- # Num chars between cells
485- INTER_CELL_CHARS = 2
485+ # Spaces between cells
486+ INTER_CELL = 2 * SPACE
486487
487488 def __init__ (self , cols : Sequence [Column ], * , tab_width : int = 4 , divider_char : Optional [str ] = '-' ) -> None :
488489 """
@@ -491,24 +492,29 @@ def __init__(self, cols: Sequence[Column], *, tab_width: int = 4, divider_char:
491492 :param cols: column definitions for this table
492493 :param tab_width: all tabs will be replaced with this many spaces. If a row's fill_char is a tab,
493494 then it will be converted to one space.
494- :param divider_char: optional character used to build the header divider row. If provided, its value must meet the
495- same requirements as fill_char in TableCreator.generate_row() or exceptions will be raised.
496- 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
497499 """
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+
498508 super ().__init__ (cols , tab_width = tab_width )
499509 self .divider_char = divider_char
500- self .empty_data = [EMPTY for _ in self .cols ]
501510
502511 @classmethod
503- def base_width (cls , num_cols : int , * , divider_char : Optional [ str ] = '-' ) -> int :
512+ def base_width (cls , num_cols : int ) -> int :
504513 """
505- Utility method to calculate the width required for a table before data is added to it.
506- This is useful to know how much room is left for data with creating a table of a specific width.
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.
507516
508517 :param num_cols: how many columns the table will have
509- :param divider_char: optional character used to build the header divider row. If provided, its value must meet the
510- same requirements as fill_char in TableCreator.generate_row() or exceptions will be raised.
511- Set this to None if you don't want a divider row. (Defaults to dash)
512518 :return: base width
513519 :raises: ValueError if num_cols is less than 1
514520 """
@@ -518,29 +524,35 @@ def base_width(cls, num_cols: int, *, divider_char: Optional[str] = '-') -> int:
518524 data_str = SPACE
519525 data_width = ansi .style_aware_wcswidth (data_str ) * num_cols
520526
521- tbl = cls ([Column (data_str )] * num_cols , divider_char = divider_char )
527+ tbl = cls ([Column (data_str )] * num_cols )
522528 data_row = tbl .generate_data_row ([data_str ] * num_cols )
523529
524530 return ansi .style_aware_wcswidth (data_row ) - data_width
525531
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+
526538 def generate_header (self ) -> str :
527539 """Generate table header with an optional divider row"""
528540 header_buf = io .StringIO ()
529541
530542 # Create the header labels
531- if self .divider_char is None :
532- inter_cell = SimpleTable .INTER_CELL_CHARS * SPACE
533- else :
534- inter_cell = SPACE * ansi .style_aware_wcswidth (SimpleTable .INTER_CELL_CHARS * self .divider_char )
535- header = self .generate_row (inter_cell = inter_cell )
543+ header = self .generate_row (inter_cell = self .INTER_CELL )
536544 header_buf .write (header )
537545
538- # Create the divider. Use empty strings for the row_data.
546+ # Create the divider if necessary
539547 if self .divider_char is not None :
540- divider = self .generate_row (row_data = self .empty_data , fill_char = self .divider_char ,
541- inter_cell = (SimpleTable .INTER_CELL_CHARS * self .divider_char ))
548+ total_width = self .total_width ()
549+ divider_char_width = ansi .style_aware_wcswidth (self .divider_char )
550+
551+ # Add padding if divider char does not divide evenly into table width
552+ divider = (self .divider_char * (total_width // divider_char_width )) + (SPACE * (total_width % divider_char_width ))
542553 header_buf .write ('\n ' )
543554 header_buf .write (divider )
555+
544556 return header_buf .getvalue ()
545557
546558 def generate_data_row (self , row_data : Sequence [Any ]) -> str :
@@ -550,11 +562,7 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str:
550562 :param row_data: data with an entry for each column in the row
551563 :return: data row string
552564 """
553- if self .divider_char is None :
554- inter_cell = 2 * SPACE
555- else :
556- inter_cell = SPACE * ansi .style_aware_wcswidth (2 * self .divider_char )
557- return self .generate_row (row_data = row_data , inter_cell = inter_cell )
565+ return self .generate_row (row_data = row_data , inter_cell = self .INTER_CELL )
558566
559567 def generate_table (self , table_data : Sequence [Sequence [Any ]], * ,
560568 include_header : bool = True , row_spacing : int = 1 ) -> str :
@@ -620,8 +628,8 @@ def __init__(self, cols: Sequence[Column], *, tab_width: int = 4,
620628 @classmethod
621629 def base_width (cls , num_cols : int , * , column_borders : bool = True , padding : int = 1 ) -> int :
622630 """
623- Utility method to calculate the width required for a table before data is added to it.
624- This is useful to know how much room is left for data with creating a table of a specific width.
631+ Utility method to calculate the display width required for a table before data is added to it.
632+ This is useful when determining how wide to make your columns to have a table be a specific width.
625633
626634 :param num_cols: how many columns the table will have
627635 :param column_borders: if True, borders between columns will be included in the calculation (Defaults to True)
@@ -640,6 +648,12 @@ def base_width(cls, num_cols: int, *, column_borders: bool = True, padding: int
640648
641649 return ansi .style_aware_wcswidth (data_row ) - data_width
642650
651+ def total_width (self ) -> int :
652+ """Calculate the total display width of this table"""
653+ base_width = self .base_width (len (self .cols ), column_borders = self .column_borders , padding = self .padding )
654+ data_width = sum (col .width for col in self .cols )
655+ return base_width + data_width
656+
643657 def generate_table_top_border (self ):
644658 """Generate a border which appears at the top of the header and data section"""
645659 pre_line = '╔' + self .padding * '═'
0 commit comments