98 lines
2.8 KiB
Python
98 lines
2.8 KiB
Python
|
import time
|
||
|
import asyncio
|
||
|
import functools
|
||
|
|
||
|
from typing import List
|
||
|
|
||
|
|
||
|
class Timer:
|
||
|
"""Context manager to measure how long the indented block takes to run."""
|
||
|
|
||
|
def __init__(self):
|
||
|
self.start = None
|
||
|
self.end = None
|
||
|
|
||
|
def __enter__(self):
|
||
|
self.start = time.perf_counter()
|
||
|
return self
|
||
|
|
||
|
async def __aenter__(self):
|
||
|
return self.__enter__()
|
||
|
|
||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||
|
self.end = time.perf_counter()
|
||
|
|
||
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||
|
return self.__exit__(exc_type, exc_val, exc_tb)
|
||
|
|
||
|
def __str__(self):
|
||
|
return f'{self.duration:.3f}ms'
|
||
|
|
||
|
@property
|
||
|
def duration(self):
|
||
|
"""Duration in ms."""
|
||
|
return (self.end - self.start) * 1000
|
||
|
|
||
|
|
||
|
class Table:
|
||
|
def __init__(self, *column_titles: str):
|
||
|
self._rows = [column_titles]
|
||
|
self._widths = []
|
||
|
|
||
|
for index, entry in enumerate(column_titles):
|
||
|
self._widths.append(len(entry))
|
||
|
|
||
|
def _update_widths(self, row: tuple):
|
||
|
for index, entry in enumerate(row):
|
||
|
width = len(entry)
|
||
|
if width > self._widths[index]:
|
||
|
self._widths[index] = width
|
||
|
|
||
|
def add_row(self, *row: str):
|
||
|
"""
|
||
|
Add a row to the table.
|
||
|
.. note :: There's no check for the number of items entered, this may cause issues rendering if not correct.
|
||
|
"""
|
||
|
self._rows.append(row)
|
||
|
self._update_widths(row)
|
||
|
|
||
|
def add_rows(self, *rows: List[str]):
|
||
|
for row in rows:
|
||
|
self.add_row(*row)
|
||
|
|
||
|
def _render(self):
|
||
|
def draw_row(row_):
|
||
|
columns = []
|
||
|
|
||
|
for index, field in enumerate(row_):
|
||
|
# digits get aligned to the right
|
||
|
if field.isdigit():
|
||
|
columns.append(f" {field:>{self._widths[index]}} ")
|
||
|
continue
|
||
|
|
||
|
# regular text gets aligned to the left
|
||
|
columns.append(f" {field:<{self._widths[index]}} ")
|
||
|
|
||
|
return "|".join(columns)
|
||
|
|
||
|
# column title is centered in the middle of each field
|
||
|
title_row = "|".join(f" {field:^{self._widths[index]}} " for index, field in enumerate(self._rows[0]))
|
||
|
separator_row = "+".join("-" * (width + 2) for width in self._widths)
|
||
|
|
||
|
drawn = [title_row, separator_row]
|
||
|
# remove the title row from the rows
|
||
|
self._rows = self._rows[1:]
|
||
|
|
||
|
for row in self._rows:
|
||
|
row = draw_row(row)
|
||
|
drawn.append(row)
|
||
|
|
||
|
return "\n".join(drawn)
|
||
|
|
||
|
async def render(self, loop: asyncio.AbstractEventLoop=None):
|
||
|
"""Returns a rendered version of the table."""
|
||
|
loop = loop or asyncio.get_event_loop()
|
||
|
|
||
|
func = functools.partial(self._render)
|
||
|
return await loop.run_in_executor(None, func)
|