#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Teal Dulcet, CS546 from __future__ import division, print_function, unicode_literals import sys import shutil import re import textwrap from enum import IntEnum, auto from wcwidth import wcswidth from typing import List, Optional, Any, Sequence, Callable import locale locale.setlocale(locale.LC_ALL, '') class style_types(IntEnum): ASCII = 0 basic = auto() light = auto() heavy = auto() double = auto() light_dashed = auto() heavy_dashed = auto() styles = [ ["-", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+"], # ASCII ["—", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+"], # Basic ["─", "│", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"], # Light ["━", "┃", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"], # Heavy ["═", "║", "╔", "╦", "╗", "╠", "╬", "╣", "╚", "╩", "╝"], # Double ["╌", "┊", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"], # Light Dashed ["╍", "┋", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"] # Heavy Dashed # [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]] #No border ] ansi = re.compile(r'\x1B\[(?:[0-9]+(?:;[0-9]+)*)?m') def strcol(astr: str) -> int: """Returns the number of columns that the given string would take up if printed.""" astr = ansi.sub('', astr) width = wcswidth(astr) if width == -1: print( "\nError! wcswidth failed. Nonprintable wide character.", file=sys.stderr) sys.exit(1) return width # return len(astr) def table(array: List[List[str]], headerrow: bool = False, headercolumn: bool = False, tableborder: bool = True, cellborder: bool = False, padding: int = 1, alignment: Optional[bool] = None, title: Optional[str] = None, style: style_types = style_types.light, check: bool = True) -> int: """Output char array as table""" if not array: return 1 rows = len(array) columns = len(array[0]) columnwidth = [max(strcol(i) for i in j) for j in zip(*array)] width = sum(columnwidth) w = shutil.get_terminal_size() if tableborder or cellborder or headerrow or headercolumn: width += (2 * padding + 1) * columns + (1 if tableborder else -1) else: width += 2 * padding * columns if check: if width > w.columns: print("The width of the table ({0}) is greater then the width of the terminal ({1}).".format( width, w.columns), file=sys.stderr) return 1 if title: print(textwrap.fill(title, width=width)) strm = "" if tableborder: strm += styles[style][2] for j in range(columns): strm += styles[style][0] * (2 * padding + columnwidth[j]) if j < columns - 1: if cellborder or headerrow or (j == 0 and headercolumn): strm += styles[style][3] else: strm += styles[style][0] strm += styles[style][4] + "\n" for i in range(rows): if tableborder: strm += styles[style][1] for j in range(columns): if (j > 0 and cellborder) or (i == 0 and j > 0 and headerrow) or (j == 1 and headercolumn): strm += styles[style][1] elif j > 0 and (tableborder or (i > 0 and headerrow) or headercolumn): strm += " " awidth = columnwidth[j] - (strcol(array[i][j]) - len(array[i][j])) if (i == 0 and headerrow) or (j == 0 and headercolumn): strm += " " * padding strm += "\033[1m" + array[i][j].center(awidth) + "\033[22m" strm += " " * padding else: strm += " " * padding if alignment is None: strm += "{0:{width}}".format(array[i][j], width=awidth) elif alignment: strm += array[i][j].rjust(awidth) else: strm += array[i][j].ljust(awidth) strm += " " * padding if tableborder: strm += styles[style][1] if i < rows - 1 or tableborder: strm += "\n" if tableborder: if i == rows - 1: strm += styles[style][8] elif cellborder or (i == 0 and headerrow) or headercolumn: strm += styles[style][5] if (i == rows - 1 and tableborder) or (i < rows - 1 and cellborder) or (i == 0 and headerrow) or (i < rows - 1 and headercolumn): for j in range(columns): if (i == rows - 1 and tableborder) or (i < rows - 1 and cellborder) or (i == 0 and headerrow) or (i < rows - 1 and j == 0 and headercolumn): strm += styles[style][0] * (2 * padding + columnwidth[j]) elif i < rows - 1 and headercolumn: strm += " " * (2 * padding + columnwidth[j]) if j < columns - 1: if i == rows - 1 and tableborder: if cellborder or (j == 0 and headercolumn): strm += styles[style][9] else: strm += styles[style][0] elif (i < rows - 1 and cellborder) or ((i == 0 and headerrow) and (j == 0 and headercolumn)): strm += styles[style][6] elif i == 0 and headerrow: strm += styles[style][9] elif i < rows - 1 and headercolumn: if j == 0: strm += styles[style][7] else: strm += " " if tableborder: if i == rows - 1: strm += styles[style][10] elif cellborder or (i == 0 and headerrow): strm += styles[style][7] elif headercolumn: strm += styles[style][1] if i < rows - 1: strm += "\n" print(strm) return 0 def array(aarray: Sequence[Sequence[Any]], aheaderrow: Optional[Sequence[Any]] = None, aheadercolumn: Optional[Sequence[Any]] = None, headerrow: bool = False, headercolumn: bool = False, tableborder: bool = True, cellborder: bool = False, padding: int = 1, alignment: Optional[bool] = None, title: Optional[str] = None, style: style_types = style_types.light) -> int: """Convert array to char array and output as table""" if not aarray: return 1 rows = len(aarray) columns = len(aarray[0]) if not all(len(x) == columns for x in aarray): print( "Error: The rows of the array must have the same number of columns.", file=sys.stderr) return 1 if aheaderrow: rows += 1 if aheadercolumn: columns += 1 if aheaderrow and len(aheaderrow) != columns: print( "Error: The header row must have the same number of columns as the array.", file=sys.stderr) return 1 if aheadercolumn and len(aheadercolumn) != (rows - 1 if aheaderrow else rows): print( "Error: The header column must have the same number of rows as the array.", file=sys.stderr) return 1 aaarray = [["" for j in range(columns)] for i in range(rows)] if aheaderrow: aaarray[0] = aheaderrow[:columns] for i in range(1 if aheaderrow else 0, rows): ii = i - 1 if aheaderrow else i if aheadercolumn: aaarray[i][0] = aheadercolumn[ii] j = 1 if aheadercolumn else 0 aaarray[i][j:] = map(str, aarray[ii][:columns - j]) return table(aaarray, headerrow=headerrow, headercolumn=headercolumn, tableborder=tableborder, cellborder=cellborder, padding=padding, alignment=alignment, title=title, style=style) def functions(xmin: float, xmax: float, xstep: float, afunctions: Sequence[Callable[[float], float]], headerrow: bool = False, headercolumn: bool = False, tableborder: bool = True, cellborder: bool = False, padding: int = 1, alignment: Optional[bool] = None, title: Optional[str] = None, style: style_types = style_types.light) -> int: """Convert one or more functions to array and output as table""" if not afunctions: return 1 if xmin >= xmax: print("xmin must be less than xmax.", file=sys.stderr) return 1 if xstep <= 0: print("xstep must be greater than zero.", file=sys.stderr) return 1 rows = int((xmax - xmin) / xstep) + 1 columns = len(afunctions) + 1 aaheaderrow = ["x", "y"] length = len(aaheaderrow) aheaderrow = [""] * columns if len(afunctions) == 1: aheaderrow = aaheaderrow else: aheaderrow = aaheaderrow[:-1] + [aaheaderrow[-1] + str(j - length + 2) for j in range(1, columns)] aarray = [[0 for j in range(columns)] for i in range(rows)] for i in range(rows): aarray[i][0] = i * xstep + xmin aarray[i][1:] = [function(aarray[i][0]) for function in afunctions] return array(aarray, aheaderrow, None, headerrow=headerrow, headercolumn=headercolumn, tableborder=tableborder, cellborder=cellborder, padding=padding, alignment=alignment, title=title, style=style) def function(xmin: float, xmax: float, xstep: float, afunction: Callable[[float], float], headerrow: bool = False, headercolumn: bool = False, tableborder: bool = True, cellborder: bool = False, padding: int = 1, alignment: Optional[bool] = None, title: Optional[str] = None, style: style_types = style_types.light) -> int: """Convert single function to array and output as table""" return functions(xmin, xmax, xstep, [afunction], headerrow=headerrow, headercolumn=headercolumn, tableborder=tableborder, cellborder=cellborder, padding=padding, alignment=alignment, title=title, style=style)