Source code for biocutils.print_wrapped_table
from typing import Any, List, Optional, Sequence
import numpy
from .subset_sequence import subset_sequence
def _get_max_width(col: List[str]):
width = 0
for y in col:
if len(y) > width:
width = len(y)
return width
[docs]
def print_wrapped_table(
columns: List[Sequence[str]],
floating_names: Optional[Sequence[str]] = None,
sep: str = " ",
window: Optional[int] = None,
) -> str:
"""Pretty-print a table with aligned and wrapped columns. All column contents are padded so that they are right-
justified. Wrapping is performed whenever a new column would exceed the window width, in which case the entire
column (and all subsequent columns) are printed below the previous columns.
Args:
columns:
List of list of strings, where each inner list is the same length
and contains the visible contents of a column. Strings are
typically generated by calling `repr()` on data column values.
Callers are responsible for inserting ellipses, adding column type
information (e.g., with :py:meth:`~print_type`) or truncating long
strings (e.g., with :py:meth:`~truncate_strings`).
floating_names:
List of strings to be added to the left of the table. This is
printed repeatedly for each set of wrapped columns.
See also :py:meth:`~create_floating_names`.
sep:
Separator between columns.
window:
Size of the terminal window, in characters. We attempt to determine
this automatically, otherwise it is set to 150.
Returns:
String containing the pretty-printed table.
"""
if window is None:
import os
try:
window = os.get_terminal_size().columns
except Exception:
window = 150
if len(columns) == 0:
raise ValueError("at least one column should be supplied in 'columns'")
n = len(columns[0])
floatwidth = 0
if floating_names is not None:
floatwidth = _get_max_width(floating_names)
new_floating_names = []
for y in floating_names:
new_floating_names.append(y.rjust(floatwidth))
floating_names = new_floating_names
output = ""
def reinitialize():
if floating_names is None:
return [""] * n
else:
return floating_names[:]
contents = reinitialize()
init = True
used = floatwidth
for col in columns:
width = _get_max_width(col)
if not init and used + width + len(sep) > window:
for line in contents:
output += line + "\n"
contents = reinitialize()
init = True
used = floatwidth
for i, y in enumerate(col):
if used > 0:
contents[i] += sep
contents[i] += y.rjust(width)
used += width + len(sep)
init = False
output += "\n".join(contents)
return output
[docs]
def create_floating_names(names: Optional[List[str]], indices: Sequence[int]) -> List[str]:
"""Create the floating names to use in :py:meth:`~print_wrapped_table`. If no names are present, positional indices
are used instead.
Args:
names:
List of row names, or None if no row names are available.
indices:
Integer indices for which to obtain the names.
Returns:
List of strings containing floating names.
"""
if names is not None:
return list(subset_sequence(names, indices))
else:
return ["[" + str(i) + "]" for i in indices]
[docs]
def truncate_strings(values: List[str], width: int = 40) -> List[str]:
"""Truncate long strings for printing in :py:meth:`~print_wrapped_table`.
Args:
values:
List of strings to be printed.
width:
Width beyond which to truncate the string.
Returns:
List containing truncated strings.
"""
replacement = values[:]
for i, y in enumerate(values):
if len(y) > width:
replacement[i] = y[: width - 3] + "..."
return replacement
[docs]
def print_type(x: Any) -> str:
"""Print the type of an object.
Print the type of an object, with some special behavior for certain classes
(e.g., to add the data type of NumPy arrays). This is intended for display
at the top of the columns of :py:meth:`~print_wrapped_table`.
Args:
x: Some object.
Returns:
String containing the class of the object.
"""
cls = type(x).__name__
if isinstance(x, numpy.ndarray):
return cls + "[" + x.dtype.name + "]"
return cls