from typing import Sequence __all__ = [ 'TableBorderCharset', 'BORDER_THIN', 'BORDER_THICK', 'BORDER_DOUBLE', 'BORDER_ROUND', 'table', ] class TableBorderCharset: def __init__(self, corner_top_left: str, corner_top_right: str, corner_bottom_left: str, corner_bottom_right: str, line_horizontal: str, line_vertical: str, t_junction_horizontal_top: str, t_junction_horizontal_bottom: str, t_junction_vertical_left: str, t_junction_vertical_right: str, intersection: str ): ''' Charset for a table border. :param corner_top_left: Character for top left corners :type corner_top_left: str :param corner_top_right: Character for top right corners :type corner_top_right: str :param corner_bottom_left: Character for bottom left corners :type corner_bottom_left: str :param corner_bottom_right: Character for bottom right corners :type corner_bottom_right: str :param line_horizontal: Character for horizontal lines :type line_horizontal: str :param line_vertical: Character for vertical lines :type line_vertical: str :param t_junction_horizontal_top: Character for horizontal T junctions facing upwards :type t_junction_horizontal_top: str :param t_junction_horizontal_bottom: Character for horizontal T junctions facing downwards :type t_junction_horizontal_bottom: str :param t_junction_vertical_left: Character for vertical T junctions facing left :type t_junction_vertical_left: str :param t_junction_vertical_right: Character for vertical T junctions facing right :type t_junction_vertical_right: str :param intersection: Character for intersections :type intersection: str ''' self.corner_top_left = corner_top_left self.corner_top_right = corner_top_right self.corner_bottom_left = corner_bottom_left self.corner_bottom_right = corner_bottom_right self.line_horizontal = line_horizontal self.line_vertical = line_vertical self.t_junction_horizontal_top = t_junction_horizontal_top self.t_junction_horizontal_bottom = t_junction_horizontal_bottom self.t_junction_vertical_left = t_junction_vertical_left self.t_junction_vertical_right = t_junction_vertical_right self.intersection = intersection BORDER_THIN = TableBorderCharset( '┌', '┐', '└', '┘', '─', '│', '┴', '┬', '┤', '├', '┼' ) BORDER_THICK = TableBorderCharset( '┏', '┓', '┗', '┛', '━', '┃', '┻', '┳', '┫', '┣', '╋' ) BORDER_DOUBLE = TableBorderCharset( '╔', '╗', '╚', '╝', '═', '║', '╩', '╦', '╣', '╠', '╬' ) BORDER_ROUND = TableBorderCharset( '╭', '╮', '╰', '╯', '─', '│', '┴', '┬', '┤', '├', '┼' ) def _get_max_widths(data: Sequence[dict]) -> dict: max_widths = {} for row in data: for col in row: max_widths[col] = max(_len(row[col]), max_widths.get(col, 0)) return max_widths def _len(obj, /) -> int: try: len(obj) return len(obj) except: return len(str(obj)) def table(data: Sequence[dict], *, borders: bool = False, border_charset: TableBorderCharset = BORDER_THIN, column_space: int = 1, uppercase_column_headers: bool = True) -> str: ''' Creates a text table. :param data: The data. :param borders: If True, the table will have borders, otherwise it will not have any borders. :type borders: bool :param border_charset: The charset for the borders. :type border_charset: TableBorderCharset :param column_space: The number of spaces between the columns. :type column_space: int :param uppercase_column_headers: If True, the column headers will consist of capital letters. :type uppercase_column_headers: bool :return: The table. :rtype: str ''' any_content = False for col in data: if col: any_content = True if not any_content: return '' column_widths = _get_max_widths(data) columns = list(column_widths.keys()) table = f'''{(column_space * ' ').join([(c.upper() if uppercase_column_headers else c) + ' ' * (column_widths[c] - len(c)) for c in columns])}''' table = '' for col in columns: column_widths[col] = max(column_widths[col], _len(col)) for col in columns: table += col.upper() if uppercase_column_headers else col table += ' ' * (column_widths[col] - _len(col)) table += ' ' * column_space if borders: table += border_charset.line_vertical + ' ' for row in data: table += '\n' for column in columns: value = row.get(column, '') table += str(value) table += ' ' * (column_widths[column] - _len(value)) table += column_space * ' ' if borders: table += border_charset.line_vertical + ' ' if borders: table_without_borders = table table = border_charset.corner_top_left for col in columns: table += border_charset.line_horizontal * (column_widths[col] + 2) table += border_charset.t_junction_horizontal_bottom table = table[:-1] + border_charset.corner_top_right table += '\n' for row in table_without_borders.split('\n'): table += border_charset.line_vertical + ' ' table += row table += '\n' table += border_charset.t_junction_vertical_right for i in range(len(columns)): table += border_charset.line_horizontal * (column_widths[columns[i]] + 2) table += border_charset.intersection if i != (len(columns) - 1) else border_charset.t_junction_vertical_left table += '\n' table = '\n'.join(table.split('\n')[:-2]) + '\n' table += border_charset.corner_bottom_left for col in columns: table += border_charset.line_horizontal * (column_widths[col] + 2) table += border_charset.t_junction_horizontal_top table = table[:-1] + border_charset.corner_bottom_right return table