Table-and-Graph-Libs/python/tables.py
2023-05-11 07:03:53 -07:00

279 lines
8.3 KiB
Python

#!/usr/bin/env python3
# Teal Dulcet, CS546
import locale
import re
import shutil
import sys
import textwrap
from enum import IntEnum, auto
from typing import Any, Callable, List, Optional, Sequence
from wcwidth import wcswidth
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(
f"The width of the table ({width}) is greater then the width of the terminal ({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 += f"{array[i][j]:{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 (i < rows - 1 and cellborder) or (i == 0 and headerrow) or (i < rows - 1 and headercolumn):
if tableborder and (cellborder or (i == 0 and headerrow) or headercolumn):
strm += styles[style][5]
for j in range(columns):
if cellborder or (i == 0 and headerrow) or (j == 0 and headercolumn):
strm += styles[style][0] * (2 * padding + columnwidth[j])
elif headercolumn:
strm += " " * (2 * padding + columnwidth[j])
if j < columns - 1:
if 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 headercolumn:
if j == 0:
strm += styles[style][7]
else:
strm += " "
if tableborder:
if cellborder or (i == 0 and headerrow):
strm += styles[style][7]
elif headercolumn:
strm += styles[style][1]
strm += "\n"
if tableborder:
strm += styles[style][8]
for j in range(columns):
strm += styles[style][0] * (2 * padding + columnwidth[j])
if j < columns - 1:
if cellborder or (j == 0 and headercolumn):
strm += styles[style][9]
else:
strm += styles[style][0]
strm += styles[style][10]
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)