1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
# coding: utf8
from __future__ import unicode_literals, print_function
import os
from .util import COLORS
from .util import color as _color
from .util import supports_ansi, to_string, zip_longest, basestring_
ALIGN_MAP = {"l": "<", "r": ">", "c": "^"}
def table(
data,
header=None,
footer=None,
divider=False,
widths="auto",
max_col=30,
spacing=3,
aligns=None,
multiline=False,
env_prefix="WASABI",
color_values=None,
fg_colors=None,
bg_colors=None,
):
"""Format tabular data.
data (iterable / dict): The data to render. Either a list of lists (one per
row) or a dict for two-column tables.
header (iterable): The header columns.
footer (iterable): The footer columns.
divider (bool): Show a divider line between header/footer and body.
widths (iterable or 'auto'): Column widths in order. If "auto", widths
will be calculated automatically based on the largest value.
max_col (int): Maximum column width.
spacing (int): Spacing between columns, in spaces.
aligns (iterable / unicode): Column alignments in order. 'l' (left,
default), 'r' (right) or 'c' (center). If a string, value is used
for all columns.
multiline (bool): If a cell value is a list of a tuple, render it on
multiple lines, with one value per line.
env_prefix (unicode): Prefix for environment variables, e.g.
WASABI_LOG_FRIENDLY.
color_values (dict): Add or overwrite color values, name mapped to value.
fg_colors (iterable): Foreground colors, one per column. None can be specified
for individual columns to retain the default foreground color.
bg_colors (iterable): Background colors, one per column. None can be specified
for individual columns to retain the default background color.
RETURNS (unicode): The formatted table.
"""
if fg_colors is not None or bg_colors is not None:
colors = dict(COLORS)
if color_values is not None:
colors.update(color_values)
if fg_colors is not None:
fg_colors = [colors.get(fg_color, fg_color) for fg_color in fg_colors]
if bg_colors is not None:
bg_colors = [colors.get(bg_color, bg_color) for bg_color in bg_colors]
if isinstance(data, dict):
data = list(data.items())
if multiline:
zipped_data = []
for i, item in enumerate(data):
vals = [v if isinstance(v, (list, tuple)) else [v] for v in item]
zipped_data.extend(list(zip_longest(*vals, fillvalue="")))
if i < len(data) - 1:
zipped_data.append(["" for i in item])
data = zipped_data
if widths == "auto":
widths = _get_max_widths(data, header, footer, max_col)
settings = {
"widths": widths,
"spacing": spacing,
"aligns": aligns,
"env_prefix": env_prefix,
"fg_colors": fg_colors,
"bg_colors": bg_colors,
}
divider_row = row(["-" * width for width in widths], **settings)
rows = []
if header:
rows.append(row(header, **settings))
if divider:
rows.append(divider_row)
for i, item in enumerate(data):
rows.append(row(item, **settings))
if footer:
if divider:
rows.append(divider_row)
rows.append(row(footer, **settings))
return "\n{}\n".format("\n".join(rows))
def row(
data,
widths="auto",
spacing=3,
aligns=None,
env_prefix="WASABI",
fg_colors=None,
bg_colors=None,
):
"""Format data as a table row.
data (iterable): The individual columns to format.
widths (list, int or 'auto'): Column widths, either one integer for all
columns or an iterable of values. If "auto", widths will be calculated
automatically based on the largest value.
spacing (int): Spacing between columns, in spaces.
aligns (list / unicode): Column alignments in order. 'l' (left,
default), 'r' (right) or 'c' (center). If a string, value is used
for all columns.
env_prefix (unicode): Prefix for environment variables, e.g.
WASABI_LOG_FRIENDLY.
fg_colors (list): Foreground colors for the columns, in order. None can be
specified for individual columns to retain the default foreground color.
bg_colors (list): Background colors for the columns, in order. None can be
specified for individual columns to retain the default background color.
RETURNS (unicode): The formatted row.
"""
env_log_friendly = os.getenv("{}_LOG_FRIENDLY".format(env_prefix), False)
show_colors = (
supports_ansi()
and not env_log_friendly
and (fg_colors is not None or bg_colors is not None)
)
cols = []
if isinstance(aligns, basestring_): # single align value
aligns = [aligns for _ in data]
if not hasattr(widths, "__iter__") and widths != "auto": # single number
widths = [widths for _ in range(len(data))]
for i, col in enumerate(data):
align = ALIGN_MAP.get(aligns[i] if aligns and i < len(aligns) else "l")
col_width = len(col) if widths == "auto" else widths[i]
tpl = "{:%s%d}" % (align, col_width)
col = tpl.format(to_string(col))
if show_colors:
fg = fg_colors[i] if fg_colors is not None else None
bg = bg_colors[i] if bg_colors is not None else None
col = _color(col, fg=fg, bg=bg)
cols.append(col)
return (" " * spacing).join(cols)
def _get_max_widths(data, header, footer, max_col):
all_data = list(data)
if header:
all_data.append(header)
if footer:
all_data.append(footer)
widths = [[len(to_string(col)) for col in item] for item in all_data]
return [min(max(w), max_col) for w in list(zip(*widths))]
|