276 lines
7.0 KiB
Python
276 lines
7.0 KiB
Python
from typing import Optional, Tuple, Union, overload, List, Any, TypeVar, IO, Iterable, Sequence, Dict, Callable
|
|
|
|
T = TypeVar('T')
|
|
|
|
|
|
@overload
|
|
def part_prefix(line: str, char: str, from_right=False) -> Tuple[Optional[str], str]:
|
|
pass
|
|
|
|
|
|
@overload
|
|
def part_prefix(line: str, char: str, missing: T, from_right=False) -> Tuple[Union[str, T], str]:
|
|
pass
|
|
|
|
|
|
def part_prefix(line: str, char: str, missing=None, from_right=False):
|
|
try:
|
|
if from_right:
|
|
i = line.rindex(char)
|
|
else:
|
|
i = line.index(char)
|
|
return line[:i], line[i + len(char):]
|
|
except ValueError:
|
|
return missing, line
|
|
|
|
|
|
@overload
|
|
def part_suffix(line: str, char: str, from_right=False) -> Tuple[str, Optional[str]]:
|
|
pass
|
|
|
|
|
|
@overload
|
|
def part_suffix(line: str, char: str, missing: T, from_right=False) -> Tuple[str, Union[str, T]]:
|
|
pass
|
|
|
|
|
|
def part_suffix(line: str, char: str, missing=None, from_right=False):
|
|
try:
|
|
if from_right:
|
|
i = line.rindex(char)
|
|
else:
|
|
i = line.index(char)
|
|
return line[:i], line[i + len(char):]
|
|
except ValueError:
|
|
return line, missing
|
|
|
|
|
|
def print_block(block: str, file: IO = None):
|
|
lines = block.split('\n')
|
|
|
|
if len(lines) == 0:
|
|
return
|
|
|
|
if len(lines[0]) != 0:
|
|
indent = len(lines[0]) - len(lines[0].lstrip())
|
|
elif len(lines[1]) != 0:
|
|
indent = len(lines[1]) - len(lines[1].lstrip())
|
|
else:
|
|
indent = 0
|
|
|
|
for i, line in enumerate(lines):
|
|
line = line[indent:]
|
|
|
|
if i == 0 and len(line) == 0:
|
|
continue
|
|
|
|
print(line, file=file)
|
|
|
|
|
|
def _line_max_length(ls: List[str]) -> int:
|
|
length = 0
|
|
|
|
for s in ls:
|
|
length = max(length, len(s))
|
|
|
|
return length
|
|
|
|
|
|
def list_padding(ls: List[str], ele: Optional[List[str]] = None, align_right=False, padding=' ', split=' '):
|
|
"""align"""
|
|
|
|
if ele is None:
|
|
length = _line_max_length(ls)
|
|
|
|
for i, ln in enumerate(ls):
|
|
ls[i] = ln + split + padding * (length - len(ln))
|
|
|
|
else:
|
|
if len(ls) != len(ele):
|
|
raise RuntimeError('size not match %d != %d' % (len(ls), len(ele)))
|
|
|
|
if align_right:
|
|
length = _line_max_length(ls)
|
|
ele_length = _line_max_length(ele)
|
|
|
|
for i in range(len(ls)):
|
|
ls[i] = ls[i] + padding * (length - len(ls[i])) + split + padding * (ele_length - len(ele[i])) + ele[i]
|
|
|
|
else:
|
|
length = _line_max_length(ls)
|
|
|
|
for i in range(len(ls)):
|
|
ls[i] = ls[i] + padding * (length - len(ls[i])) + split + ele[i]
|
|
|
|
|
|
def _expr_format_enclose(expr: str, from_index: int) -> Tuple[int, str]:
|
|
if expr[from_index] != '{':
|
|
raise ValueError('lost "{"')
|
|
|
|
i = expr.find('}', from_index)
|
|
|
|
if i < 0:
|
|
raise ValueError('lost "}"')
|
|
|
|
return i + 1, expr[from_index + 1: i]
|
|
|
|
|
|
def expr_format(expr: str, mapper: Callable[[str], Optional[str]]) -> str:
|
|
a = []
|
|
|
|
try:
|
|
p = 0
|
|
while True:
|
|
i = expr.index('%', p)
|
|
c = expr[i + 1]
|
|
j = i + 2
|
|
|
|
if c == '%':
|
|
a.append('%')
|
|
|
|
elif c == '?':
|
|
c = expr[i + 2]
|
|
|
|
if ord('A') <= ord(c) <= ord('Z'):
|
|
c = expr[i + 2:i + 4]
|
|
j = i + 4
|
|
|
|
else:
|
|
j = i + 3
|
|
|
|
j, e = _expr_format_enclose(expr, j)
|
|
|
|
r = mapper(c)
|
|
|
|
if r is None:
|
|
a.append('')
|
|
|
|
else:
|
|
a.append(e.replace('%', r))
|
|
|
|
else:
|
|
if ord('A') <= ord(c) <= ord('Z'):
|
|
c = expr[i + 1:i + 3]
|
|
j = i + 3
|
|
|
|
r = mapper(c)
|
|
|
|
if r is None:
|
|
r = ''
|
|
|
|
a.append(r)
|
|
|
|
p = i + 2
|
|
|
|
expr = expr[:i] + '%s' + expr[j:]
|
|
|
|
except ValueError:
|
|
pass
|
|
|
|
except IndexError as e:
|
|
raise ValueError('lost field') from e
|
|
|
|
return expr % tuple(a)
|
|
|
|
|
|
class Table:
|
|
def __init__(self, *title: str, column: int = None, align_right=True):
|
|
if len(title) == 0:
|
|
if column is None:
|
|
raise ValueError('lost column')
|
|
self._cols: List[List[str]] = [[] for _ in range(column)]
|
|
else:
|
|
self._cols: List[List[str]] = [[t] for t in title]
|
|
|
|
self._fmt: List[Dict[str, Any]] = [{'align_right': align_right} for _ in self._cols]
|
|
|
|
@property
|
|
def columns(self) -> int:
|
|
return len(self._cols)
|
|
|
|
@property
|
|
def rows(self) -> int:
|
|
return len(self._cols[0])
|
|
|
|
def get_column(self, text: str) -> Optional[int]:
|
|
for c in range(len(self._cols)):
|
|
if self._cols[c][0] == text:
|
|
return c
|
|
return None
|
|
|
|
def append(self, *content: Any):
|
|
for i, col in enumerate(self._cols):
|
|
try:
|
|
elem = content[i]
|
|
except IndexError:
|
|
col.append('')
|
|
else:
|
|
col.append(str(elem) if elem is not None else '')
|
|
|
|
def extend(self, content: Iterable[Sequence[Any]]):
|
|
for line in content:
|
|
for i, col in enumerate(self._cols):
|
|
try:
|
|
elem = line[i]
|
|
except IndexError:
|
|
col.append('')
|
|
else:
|
|
col.append(str(elem) if elem is not None else '')
|
|
|
|
def set_format(self,
|
|
column: Union[None, int, str, List[int], List[str]],
|
|
align_right=True,
|
|
padding=' ',
|
|
split=None):
|
|
f = {
|
|
'align_right': align_right,
|
|
'padding': padding,
|
|
}
|
|
|
|
if split is not None:
|
|
f['split'] = split
|
|
|
|
if column is None:
|
|
for c in range(self.columns):
|
|
self._fmt[c] = f
|
|
|
|
elif isinstance(column, int):
|
|
self._fmt[column] = f
|
|
|
|
elif isinstance(column, str):
|
|
c = self.get_column(column)
|
|
if c is not None:
|
|
self._fmt[c] = f
|
|
|
|
elif isinstance(column, (tuple, list)):
|
|
if len(column):
|
|
if isinstance(column[0], int):
|
|
for c in column:
|
|
self._fmt[c] = f
|
|
else:
|
|
for t in column:
|
|
c = self.get_column(t)
|
|
if c is not None:
|
|
self._fmt[c] = f
|
|
else:
|
|
raise ValueError('column : ' + str(t))
|
|
|
|
else:
|
|
raise TypeError('column')
|
|
|
|
def lines(self) -> List[str]:
|
|
ret = ['' for _ in self._cols[0]]
|
|
|
|
for i, col in enumerate(self._cols):
|
|
list_padding(ret, col, **self._fmt[i])
|
|
|
|
return ret
|
|
|
|
def print(self, indent='', file: IO = None):
|
|
for line in self.lines():
|
|
print(indent, line, sep='', file=file)
|
|
|
|
def dump(self, file: IO = None):
|
|
for r in range(len(self._cols[0])):
|
|
print(','.join(map(lambda col: col[r], self._cols)), file=file)
|