class Table :
"""Display information in a table
```py
from arc.present.table import Table
t = Table(["Name", "Age", "Stand"])
t.add_row(["Jonathen Joestar", 20, "-"])
t.add_row(["Joseph Joestar", 18, "Hermit Purple (in Part 3)"])
t.add_row(["Jotaro Kujo", 18, "Star Platinum"])
t.add_row(["Josuke Higashikata", 16, "Crazy Diamond"])
t.add_row(["Giorno Giovanna", 15, "Gold Experience"])
t.add_row(["Joylene Kujo", 19, "Stone Free"])
print(t)
```
Will yield:
```console
┏━━━━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Name ┃ Age ┃ Stand ┃
┡━━━━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ Jonathen Joestar │ 20 │ - │
│ Joseph Joestar │ 18 │ Hermit Purple (in Part 3) │
│ Jotaro Kujo │ 18 │ Star Platinum │
│ Josuke Higashikata │ 16 │ Crazy Diamond │
│ Giorno Giovanna │ 15 │ Gold Experience │
│ Joylene Kujo │ 19 │ Stone Free │
└────────────────────┴─────┴───────────────────────────┘
```
"""
def __init__ (
self ,
columns : ColumnInput ,
rows : t . Sequence [ t . Sequence [ t . Any ]] | None = None ,
default_formatting : bool = True ,
) -> None :
self . __columns : t . Sequence [ Column ] = self . __resolve_columns ( columns )
self . __rows : list [ Row ] = []
self . _border = drawing . borders [ "light" ]
self . _head_border = BORDER_HEAVY_TRANS
self . _header_cell_formatter : TableFormatter = (
_format_header_cell if default_formatting else _format_cell
)
self . _cell_formatter : TableFormatter = _format_cell
self . _type_formatters : dict [ type , TableFormatter ] = (
_DEFAULT_TYPE_FORMATTERS if default_formatting else {}
)
if rows :
self . add_rows ( rows )
def __str__ ( self ) -> str :
for col in self . __columns :
cells = [ row [ col [ "name" ]] for row in self . __rows ]
cells . append ( col [ "name" ])
col [ "width" ] = max (
Ansi . len ( self . _fmt_cell_contents ( cell )) + 2 for cell in cells
)
table = ""
table += self . _fmt_header ()
table += " \n "
for row , has_next_row in has_next ( self . __rows ):
table += self . _fmt_row ( row , has_next_row )
if has_next_row :
table += " \n "
return table
def add_row ( self , row : t . Sequence [ t . Any ]) -> None :
if len ( row ) > len ( self . __columns ):
raise errors . ArcError ( "Too many values" )
resolved = {}
for col , value in itertools . zip_longest ( self . __columns , row , fillvalue = "" ):
resolved [ col [ "name" ]] = value # type: ignore
self . __rows . append ( resolved )
def add_rows ( self , rows : t . Sequence [ t . Sequence [ t . Any ]]) -> None :
for row in rows :
self . add_row ( row )
def fmt_header_cell (
self , func : TableFormatter | None = None
) -> TableFormatter | t . Callable [[ TableFormatter ], TableFormatter ]:
def inner ( func : TableFormatter ) -> TableFormatter :
self . _cell_formatter = func
return func
if func :
return inner ( func )
return inner
def fmt_cell (
self , func : TableFormatter | None = None
) -> TableFormatter | t . Callable [[ TableFormatter ], TableFormatter ]:
"""Formats any cell that does not already have a formatter applied
```py
from arc.color import fg, fx
from arc.present import Table
table = Table()
@table.fmt_cell
def fmt(cell):
return f"{fg.RED}{cell}{fx.CLEAR}"
```
"""
def inner ( func : TableFormatter ) -> TableFormatter :
self . _cell_formatter = func
return func
if func :
return inner ( func )
return inner
def fmt_type ( self , cls : type ) -> t . Callable [[ TableFormatter ], TableFormatter ]:
def inner ( func : t . Callable [[ t . Any ], str ]) -> TableFormatter :
self . _type_formatters [ cls ] = func
return func
return inner
def _fmt_header ( self ) -> str :
border = self . _head_border
header = ""
header += border [ "corner" ][ "top_left" ]
for idx , col in enumerate ( self . __columns ):
header += border [ "horizontal" ] * col [ "width" ]
if idx < len ( self . __columns ) - 1 :
header += border [ "intersect" ][ "hori_top" ]
else :
header += border [ "corner" ][ "top_right" ]
header += " \n "
header += border [ "vertical" ]
for col in self . __columns :
header += self . _fmt_cell (
col [ "name" ],
col [ "width" ],
col [ "justify" ],
header = True ,
)
header += border [ "vertical" ]
header += " \n "
header += border [ "intersect" ][ "vert_left" ]
for col , has_next_column in has_next ( self . __columns ):
header += border [ "horizontal" ] * col [ "width" ]
if has_next_column :
header += border [ "intersect" ][ "cross" ]
else :
header += border [ "intersect" ][ "vert_right" ]
return header
def _fmt_row ( self , row : Row , next_row : bool ) -> str :
border = self . _border
fmt = ""
fmt += border [ "vertical" ]
for col in self . __columns :
cell = row . get ( col [ "name" ], "" )
fmt += self . _fmt_cell ( cell , col [ "width" ], col [ "justify" ])
fmt += border [ "vertical" ]
if not next_row :
fmt += " \n "
fmt += border [ "corner" ][ "bot_left" ]
for col , has_next_column in has_next ( self . __columns ):
fmt += border [ "horizontal" ] * col [ "width" ]
if has_next_column :
fmt += border [ "intersect" ][ "hori_bot" ]
else :
fmt += border [ "corner" ][ "bot_right" ]
return fmt
def _fmt_cell (
self ,
cell : t . Any ,
width : int ,
justify : drawing . Justification ,
header : bool = False ,
) -> str :
formatted_cell = self . _fmt_cell_contents ( cell , header )
width = width - 2
padding = " " * ( width - Ansi . len ( formatted_cell ))
if justify == "left" :
return " " + formatted_cell + padding + " "
elif justify == "right" :
return " " + padding + formatted_cell + " "
elif justify == "center" :
padding_width , remainder = divmod ( width - Ansi . len ( formatted_cell ), 2 )
padding = " " * padding_width
return (
" " + padding + formatted_cell + padding + ( " " if remainder else " " )
)
@functools . cache
def _fmt_cell_contents ( self , cell : t . Any , header : bool = False ) -> str :
if header :
return self . _header_cell_formatter ( cell )
if type ( cell ) in self . _type_formatters :
return self . _type_formatters [ type ( cell )]( cell )
else :
return self . _cell_formatter ( cell )
@staticmethod
def __resolve_columns ( columns : ColumnInput ) -> list [ Column ]:
resolved : list [ Column ] = []
for column in columns :
copy = DEFAULT_COLUMN . copy ()
if isinstance ( column , str ):
copy . update ({ "name" : column })
elif isinstance ( column , dict ):
column = t . cast ( Column , column )
copy . update ( column ) # type: ignore
else :
raise errors . ArcError ( "Columns must either be a string or a dictionary" )
resolved . append ( copy )
return resolved