# coding: utf8 from __future__ import unicode_literals, print_function from .util import 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, ): """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. RETURNS (unicode): The formatted table. """ 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} 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): """Format data as a table row. data (iterable): The individual columns to format. widths (iterable, 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 (iterable / unicode): Column alignments in order. 'l' (left, default), 'r' (right) or 'c' (center). If a string, value is used for all columns. RETURNS (unicode): The formatted row. """ cols = [] if isinstance(aligns, basestring_): # single align value aligns = [aligns for _ in data] if not hasattr(widths, "__iter__"): # 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) cols.append(tpl.format(to_string(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))]