"""Pretty-print tabular data.""" import dataclasses import io import math import re import textwrap import warnings from collections import namedtuple from collections.abc import Iterable, Sized from functools import partial, reduce from html import escape as htmlescape from itertools import chain from itertools import zip_longest as izip_longest try: import wcwidth # optional wide-character (CJK) support except ImportError: wcwidth = None def _is_file(f): """Check if an object 'f' is an instance of io.IOBase.""" return isinstance(f, io.IOBase) __all__ = ["simple_separated_format", "tabulate", "tabulate_formats"] try: from .version import version as __version__ # noqa: F401 except ImportError: pass # running __init__.py as a script, AppVeyor pytests # minimum extra space in headers MIN_PADDING = 2 # Whether or not to preserve leading/trailing whitespace in data. PRESERVE_WHITESPACE = False _DEFAULT_FLOATFMT = "g" _DEFAULT_INTFMT = "" _DEFAULT_MISSINGVAL = "" # default align will be overwritten by "left", "center" or "decimal" # depending on the formatter _DEFAULT_ALIGN = "default" # if True, enable wide-character (CJK) support WIDE_CHARS_MODE = wcwidth is not None # Constant that can be used as part of passed rows to generate a separating line # It is purposely an unprintable character, very unlikely to be used in a table SEPARATING_LINE = "\001" Line = namedtuple("Line", ["begin", "hline", "sep", "end"]) DataRow = namedtuple("DataRow", ["begin", "sep", "end"]) # A table structure is supposed to be: # # --- lineabove --------- # headerrow # --- linebelowheader --- # datarow # --- linebetweenrows --- # ... (more datarows) ... # --- linebetweenrows --- # last datarow # --- linebelow --------- # # TableFormat's line* elements can be # # - either None, if the element is not used, # - or a Line tuple, # - or a function: [col_widths], [col_alignments] -> string. # # TableFormat's *row elements can be # # - either None, if the element is not used, # - or a DataRow tuple, # - or a function: [cell_values], [col_widths], [col_alignments] -> string. # # padding (an integer) is the amount of white space around data values. # # with_header_hide: # # - either None, to display all table elements unconditionally, # - or a list of elements not to be displayed if the table has column headers. # TableFormat = namedtuple( "TableFormat", [ "lineabove", "linebelowheader", "linebetweenrows", "linebelow", "headerrow", "datarow", "padding", "with_header_hide", ], ) def _is_separating_line(row): """Determine if a row is a separating line based on its type and specific content conditions.""" return type(row) in {list, str} and ( (len(row) >= 1 and row[0] == SEPARATING_LINE) or (len(row) >= 2 and row[1] == SEPARATING_LINE) ) def _pipe_segment_with_colons(align, colwidth): """Return a segment of a horizontal line with optional colons which indicate column's alignment (as in `pipe` output format). """ w = colwidth if align in {"right", "decimal"}: return ("-" * (w - 1)) + ":" elif align == "center": return ":" + ("-" * (w - 2)) + ":" elif align == "left": return ":" + ("-" * (w - 1)) else: return "-" * w def _pipe_line_with_colons(colwidths, colaligns): """Return a horizontal line with optional colons to indicate column's alignment (as in `pipe` output format).""" if not colaligns: # e.g. printing an empty data frame (github issue #15) colaligns = [""] * len(colwidths) segments = [_pipe_segment_with_colons(a, w) for a, w in zip(colaligns, colwidths)] return "|" + "|".join(segments) + "|" def _mediawiki_row_with_attrs(separator, cell_values, colwidths, colaligns): """Returns a MediaWiki table row with specific alignment attributes for each cell based on given parameters.""" alignment = { "left": "", "right": 'style="text-align: right;"| ', "center": 'style="text-align: center;"| ', "decimal": 'style="text-align: right;"| ', } # hard-coded padding _around_ align attribute and value together # rather than padding parameter which affects only the value values_with_attrs = [" " + alignment.get(a, "") + c + " " for c, a in zip(cell_values, colaligns)] colsep = separator * 2 return (separator + colsep.join(values_with_attrs)).rstrip() def _textile_row_with_attrs(cell_values, colwidths, colaligns): """Generate a Textile-formatted table row with specified cell values, column widths, and alignments.""" cell_values[0] += " " alignment = {"left": "<.", "right": ">.", "center": "=.", "decimal": ">."} values = (alignment.get(a, "") + v for a, v in zip(colaligns, cell_values)) return "|" + "|".join(values) + "|" def _html_begin_table_without_header(colwidths_ignore, colaligns_ignore): """Generate the beginning of an HTML table without a header row.""" return "