diff env/lib/python3.9/site-packages/tabulate.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/env/lib/python3.9/site-packages/tabulate.py	Mon Mar 22 18:12:50 2021 +0000
@@ -0,0 +1,1879 @@
+# -*- coding: utf-8 -*-
+
+"""Pretty-print tabular data."""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+from collections import namedtuple
+import sys
+import re
+import math
+
+
+if sys.version_info >= (3, 3):
+    from collections.abc import Iterable
+else:
+    from collections import Iterable
+
+if sys.version_info[0] < 3:
+    from itertools import izip_longest
+    from functools import partial
+
+    _none_type = type(None)
+    _bool_type = bool
+    _int_type = int
+    _long_type = long  # noqa
+    _float_type = float
+    _text_type = unicode  # noqa
+    _binary_type = str
+
+    def _is_file(f):
+        return hasattr(f, "read")
+
+
+else:
+    from itertools import zip_longest as izip_longest
+    from functools import reduce, partial
+
+    _none_type = type(None)
+    _bool_type = bool
+    _int_type = int
+    _long_type = int
+    _float_type = float
+    _text_type = str
+    _binary_type = bytes
+    basestring = str
+
+    import io
+
+    def _is_file(f):
+        return isinstance(f, io.IOBase)
+
+
+try:
+    import wcwidth  # optional wide-character (CJK) support
+except ImportError:
+    wcwidth = None
+
+try:
+    from html import escape as htmlescape
+except ImportError:
+    from cgi import escape as htmlescape
+
+
+__all__ = ["tabulate", "tabulate_formats", "simple_separated_format"]
+__version__ = "0.8.9"
+
+
+# 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_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
+
+
+Line = namedtuple("Line", ["begin", "hline", "sep", "end"])
+
+
+DataRow = namedtuple("DataRow", ["begin", "sep", "end"])
+
+
+# A table structure is suppposed 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 _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):
+    alignment = {
+        "left": "",
+        "right": 'align="right"| ',
+        "center": 'align="center"| ',
+        "decimal": '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):
+    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):
+    # this table header will be suppressed if there is a header row
+    return "<table>\n<tbody>"
+
+
+def _html_row_with_attrs(celltag, unsafe, cell_values, colwidths, colaligns):
+    alignment = {
+        "left": "",
+        "right": ' style="text-align: right;"',
+        "center": ' style="text-align: center;"',
+        "decimal": ' style="text-align: right;"',
+    }
+    if unsafe:
+        values_with_attrs = [
+            "<{0}{1}>{2}</{0}>".format(celltag, alignment.get(a, ""), c)
+            for c, a in zip(cell_values, colaligns)
+        ]
+    else:
+        values_with_attrs = [
+            "<{0}{1}>{2}</{0}>".format(celltag, alignment.get(a, ""), htmlescape(c))
+            for c, a in zip(cell_values, colaligns)
+        ]
+    rowhtml = "<tr>{}</tr>".format("".join(values_with_attrs).rstrip())
+    if celltag == "th":  # it's a header row, create a new table header
+        rowhtml = "<table>\n<thead>\n{}\n</thead>\n<tbody>".format(rowhtml)
+    return rowhtml
+
+
+def _moin_row_with_attrs(celltag, cell_values, colwidths, colaligns, header=""):
+    alignment = {
+        "left": "",
+        "right": '<style="text-align: right;">',
+        "center": '<style="text-align: center;">',
+        "decimal": '<style="text-align: right;">',
+    }
+    values_with_attrs = [
+        "{0}{1} {2} ".format(celltag, alignment.get(a, ""), header + c + header)
+        for c, a in zip(cell_values, colaligns)
+    ]
+    return "".join(values_with_attrs) + "||"
+
+
+def _latex_line_begin_tabular(colwidths, colaligns, booktabs=False, longtable=False):
+    alignment = {"left": "l", "right": "r", "center": "c", "decimal": "r"}
+    tabular_columns_fmt = "".join([alignment.get(a, "l") for a in colaligns])
+    return "\n".join(
+        [
+            ("\\begin{tabular}{" if not longtable else "\\begin{longtable}{")
+            + tabular_columns_fmt
+            + "}",
+            "\\toprule" if booktabs else "\\hline",
+        ]
+    )
+
+
+LATEX_ESCAPE_RULES = {
+    r"&": r"\&",
+    r"%": r"\%",
+    r"$": r"\$",
+    r"#": r"\#",
+    r"_": r"\_",
+    r"^": r"\^{}",
+    r"{": r"\{",
+    r"}": r"\}",
+    r"~": r"\textasciitilde{}",
+    "\\": r"\textbackslash{}",
+    r"<": r"\ensuremath{<}",
+    r">": r"\ensuremath{>}",
+}
+
+
+def _latex_row(cell_values, colwidths, colaligns, escrules=LATEX_ESCAPE_RULES):
+    def escape_char(c):
+        return escrules.get(c, c)
+
+    escaped_values = ["".join(map(escape_char, cell)) for cell in cell_values]
+    rowfmt = DataRow("", "&", "\\\\")
+    return _build_simple_row(escaped_values, rowfmt)
+
+
+def _rst_escape_first_column(rows, headers):
+    def escape_empty(val):
+        if isinstance(val, (_text_type, _binary_type)) and not val.strip():
+            return ".."
+        else:
+            return val
+
+    new_headers = list(headers)
+    new_rows = []
+    if headers:
+        new_headers[0] = escape_empty(headers[0])
+    for row in rows:
+        new_row = list(row)
+        if new_row:
+            new_row[0] = escape_empty(row[0])
+        new_rows.append(new_row)
+    return new_rows, new_headers
+
+
+_table_formats = {
+    "simple": TableFormat(
+        lineabove=Line("", "-", "  ", ""),
+        linebelowheader=Line("", "-", "  ", ""),
+        linebetweenrows=None,
+        linebelow=Line("", "-", "  ", ""),
+        headerrow=DataRow("", "  ", ""),
+        datarow=DataRow("", "  ", ""),
+        padding=0,
+        with_header_hide=["lineabove", "linebelow"],
+    ),
+    "plain": TableFormat(
+        lineabove=None,
+        linebelowheader=None,
+        linebetweenrows=None,
+        linebelow=None,
+        headerrow=DataRow("", "  ", ""),
+        datarow=DataRow("", "  ", ""),
+        padding=0,
+        with_header_hide=None,
+    ),
+    "grid": TableFormat(
+        lineabove=Line("+", "-", "+", "+"),
+        linebelowheader=Line("+", "=", "+", "+"),
+        linebetweenrows=Line("+", "-", "+", "+"),
+        linebelow=Line("+", "-", "+", "+"),
+        headerrow=DataRow("|", "|", "|"),
+        datarow=DataRow("|", "|", "|"),
+        padding=1,
+        with_header_hide=None,
+    ),
+    "fancy_grid": TableFormat(
+        lineabove=Line("╒", "═", "╤", "╕"),
+        linebelowheader=Line("╞", "═", "╪", "╡"),
+        linebetweenrows=Line("├", "─", "┼", "┤"),
+        linebelow=Line("╘", "═", "╧", "╛"),
+        headerrow=DataRow("│", "│", "│"),
+        datarow=DataRow("│", "│", "│"),
+        padding=1,
+        with_header_hide=None,
+    ),
+    "fancy_outline": TableFormat(
+        lineabove=Line("╒", "═", "╤", "╕"),
+        linebelowheader=Line("╞", "═", "╪", "╡"),
+        linebetweenrows=None,
+        linebelow=Line("╘", "═", "╧", "╛"),
+        headerrow=DataRow("│", "│", "│"),
+        datarow=DataRow("│", "│", "│"),
+        padding=1,
+        with_header_hide=None,
+    ),
+    "github": TableFormat(
+        lineabove=Line("|", "-", "|", "|"),
+        linebelowheader=Line("|", "-", "|", "|"),
+        linebetweenrows=None,
+        linebelow=None,
+        headerrow=DataRow("|", "|", "|"),
+        datarow=DataRow("|", "|", "|"),
+        padding=1,
+        with_header_hide=["lineabove"],
+    ),
+    "pipe": TableFormat(
+        lineabove=_pipe_line_with_colons,
+        linebelowheader=_pipe_line_with_colons,
+        linebetweenrows=None,
+        linebelow=None,
+        headerrow=DataRow("|", "|", "|"),
+        datarow=DataRow("|", "|", "|"),
+        padding=1,
+        with_header_hide=["lineabove"],
+    ),
+    "orgtbl": TableFormat(
+        lineabove=None,
+        linebelowheader=Line("|", "-", "+", "|"),
+        linebetweenrows=None,
+        linebelow=None,
+        headerrow=DataRow("|", "|", "|"),
+        datarow=DataRow("|", "|", "|"),
+        padding=1,
+        with_header_hide=None,
+    ),
+    "jira": TableFormat(
+        lineabove=None,
+        linebelowheader=None,
+        linebetweenrows=None,
+        linebelow=None,
+        headerrow=DataRow("||", "||", "||"),
+        datarow=DataRow("|", "|", "|"),
+        padding=1,
+        with_header_hide=None,
+    ),
+    "presto": TableFormat(
+        lineabove=None,
+        linebelowheader=Line("", "-", "+", ""),
+        linebetweenrows=None,
+        linebelow=None,
+        headerrow=DataRow("", "|", ""),
+        datarow=DataRow("", "|", ""),
+        padding=1,
+        with_header_hide=None,
+    ),
+    "pretty": TableFormat(
+        lineabove=Line("+", "-", "+", "+"),
+        linebelowheader=Line("+", "-", "+", "+"),
+        linebetweenrows=None,
+        linebelow=Line("+", "-", "+", "+"),
+        headerrow=DataRow("|", "|", "|"),
+        datarow=DataRow("|", "|", "|"),
+        padding=1,
+        with_header_hide=None,
+    ),
+    "psql": TableFormat(
+        lineabove=Line("+", "-", "+", "+"),
+        linebelowheader=Line("|", "-", "+", "|"),
+        linebetweenrows=None,
+        linebelow=Line("+", "-", "+", "+"),
+        headerrow=DataRow("|", "|", "|"),
+        datarow=DataRow("|", "|", "|"),
+        padding=1,
+        with_header_hide=None,
+    ),
+    "rst": TableFormat(
+        lineabove=Line("", "=", "  ", ""),
+        linebelowheader=Line("", "=", "  ", ""),
+        linebetweenrows=None,
+        linebelow=Line("", "=", "  ", ""),
+        headerrow=DataRow("", "  ", ""),
+        datarow=DataRow("", "  ", ""),
+        padding=0,
+        with_header_hide=None,
+    ),
+    "mediawiki": TableFormat(
+        lineabove=Line(
+            '{| class="wikitable" style="text-align: left;"',
+            "",
+            "",
+            "\n|+ <!-- caption -->\n|-",
+        ),
+        linebelowheader=Line("|-", "", "", ""),
+        linebetweenrows=Line("|-", "", "", ""),
+        linebelow=Line("|}", "", "", ""),
+        headerrow=partial(_mediawiki_row_with_attrs, "!"),
+        datarow=partial(_mediawiki_row_with_attrs, "|"),
+        padding=0,
+        with_header_hide=None,
+    ),
+    "moinmoin": TableFormat(
+        lineabove=None,
+        linebelowheader=None,
+        linebetweenrows=None,
+        linebelow=None,
+        headerrow=partial(_moin_row_with_attrs, "||", header="'''"),
+        datarow=partial(_moin_row_with_attrs, "||"),
+        padding=1,
+        with_header_hide=None,
+    ),
+    "youtrack": TableFormat(
+        lineabove=None,
+        linebelowheader=None,
+        linebetweenrows=None,
+        linebelow=None,
+        headerrow=DataRow("|| ", " || ", " || "),
+        datarow=DataRow("| ", " | ", " |"),
+        padding=1,
+        with_header_hide=None,
+    ),
+    "html": TableFormat(
+        lineabove=_html_begin_table_without_header,
+        linebelowheader="",
+        linebetweenrows=None,
+        linebelow=Line("</tbody>\n</table>", "", "", ""),
+        headerrow=partial(_html_row_with_attrs, "th", False),
+        datarow=partial(_html_row_with_attrs, "td", False),
+        padding=0,
+        with_header_hide=["lineabove"],
+    ),
+    "unsafehtml": TableFormat(
+        lineabove=_html_begin_table_without_header,
+        linebelowheader="",
+        linebetweenrows=None,
+        linebelow=Line("</tbody>\n</table>", "", "", ""),
+        headerrow=partial(_html_row_with_attrs, "th", True),
+        datarow=partial(_html_row_with_attrs, "td", True),
+        padding=0,
+        with_header_hide=["lineabove"],
+    ),
+    "latex": TableFormat(
+        lineabove=_latex_line_begin_tabular,
+        linebelowheader=Line("\\hline", "", "", ""),
+        linebetweenrows=None,
+        linebelow=Line("\\hline\n\\end{tabular}", "", "", ""),
+        headerrow=_latex_row,
+        datarow=_latex_row,
+        padding=1,
+        with_header_hide=None,
+    ),
+    "latex_raw": TableFormat(
+        lineabove=_latex_line_begin_tabular,
+        linebelowheader=Line("\\hline", "", "", ""),
+        linebetweenrows=None,
+        linebelow=Line("\\hline\n\\end{tabular}", "", "", ""),
+        headerrow=partial(_latex_row, escrules={}),
+        datarow=partial(_latex_row, escrules={}),
+        padding=1,
+        with_header_hide=None,
+    ),
+    "latex_booktabs": TableFormat(
+        lineabove=partial(_latex_line_begin_tabular, booktabs=True),
+        linebelowheader=Line("\\midrule", "", "", ""),
+        linebetweenrows=None,
+        linebelow=Line("\\bottomrule\n\\end{tabular}", "", "", ""),
+        headerrow=_latex_row,
+        datarow=_latex_row,
+        padding=1,
+        with_header_hide=None,
+    ),
+    "latex_longtable": TableFormat(
+        lineabove=partial(_latex_line_begin_tabular, longtable=True),
+        linebelowheader=Line("\\hline\n\\endhead", "", "", ""),
+        linebetweenrows=None,
+        linebelow=Line("\\hline\n\\end{longtable}", "", "", ""),
+        headerrow=_latex_row,
+        datarow=_latex_row,
+        padding=1,
+        with_header_hide=None,
+    ),
+    "tsv": TableFormat(
+        lineabove=None,
+        linebelowheader=None,
+        linebetweenrows=None,
+        linebelow=None,
+        headerrow=DataRow("", "\t", ""),
+        datarow=DataRow("", "\t", ""),
+        padding=0,
+        with_header_hide=None,
+    ),
+    "textile": TableFormat(
+        lineabove=None,
+        linebelowheader=None,
+        linebetweenrows=None,
+        linebelow=None,
+        headerrow=DataRow("|_. ", "|_.", "|"),
+        datarow=_textile_row_with_attrs,
+        padding=1,
+        with_header_hide=None,
+    ),
+}
+
+
+tabulate_formats = list(sorted(_table_formats.keys()))
+
+# The table formats for which multiline cells will be folded into subsequent
+# table rows. The key is the original format specified at the API. The value is
+# the format that will be used to represent the original format.
+multiline_formats = {
+    "plain": "plain",
+    "simple": "simple",
+    "grid": "grid",
+    "fancy_grid": "fancy_grid",
+    "pipe": "pipe",
+    "orgtbl": "orgtbl",
+    "jira": "jira",
+    "presto": "presto",
+    "pretty": "pretty",
+    "psql": "psql",
+    "rst": "rst",
+}
+
+# TODO: Add multiline support for the remaining table formats:
+#       - mediawiki: Replace \n with <br>
+#       - moinmoin: TBD
+#       - youtrack: TBD
+#       - html: Replace \n with <br>
+#       - latex*: Use "makecell" package: In header, replace X\nY with
+#         \thead{X\\Y} and in data row, replace X\nY with \makecell{X\\Y}
+#       - tsv: TBD
+#       - textile: Replace \n with <br/> (must be well-formed XML)
+
+_multiline_codes = re.compile(r"\r|\n|\r\n")
+_multiline_codes_bytes = re.compile(b"\r|\n|\r\n")
+_invisible_codes = re.compile(
+    r"\x1b\[\d+[;\d]*m|\x1b\[\d*\;\d*\;\d*m|\x1b\]8;;(.*?)\x1b\\"
+)  # ANSI color codes
+_invisible_codes_bytes = re.compile(
+    b"\x1b\\[\\d+\\[;\\d]*m|\x1b\\[\\d*;\\d*;\\d*m|\\x1b\\]8;;(.*?)\\x1b\\\\"
+)  # ANSI color codes
+_invisible_codes_link = re.compile(
+    r"\x1B]8;[a-zA-Z0-9:]*;[^\x1B]+\x1B\\([^\x1b]+)\x1B]8;;\x1B\\"
+)  # Terminal hyperlinks
+
+
+def simple_separated_format(separator):
+    """Construct a simple TableFormat with columns separated by a separator.
+
+    >>> tsv = simple_separated_format("\\t") ; \
+        tabulate([["foo", 1], ["spam", 23]], tablefmt=tsv) == 'foo \\t 1\\nspam\\t23'
+    True
+
+    """
+    return TableFormat(
+        None,
+        None,
+        None,
+        None,
+        headerrow=DataRow("", separator, ""),
+        datarow=DataRow("", separator, ""),
+        padding=0,
+        with_header_hide=None,
+    )
+
+
+def _isconvertible(conv, string):
+    try:
+        conv(string)
+        return True
+    except (ValueError, TypeError):
+        return False
+
+
+def _isnumber(string):
+    """
+    >>> _isnumber("123.45")
+    True
+    >>> _isnumber("123")
+    True
+    >>> _isnumber("spam")
+    False
+    >>> _isnumber("123e45678")
+    False
+    >>> _isnumber("inf")
+    True
+    """
+    if not _isconvertible(float, string):
+        return False
+    elif isinstance(string, (_text_type, _binary_type)) and (
+        math.isinf(float(string)) or math.isnan(float(string))
+    ):
+        return string.lower() in ["inf", "-inf", "nan"]
+    return True
+
+
+def _isint(string, inttype=int):
+    """
+    >>> _isint("123")
+    True
+    >>> _isint("123.45")
+    False
+    """
+    return (
+        type(string) is inttype
+        or (isinstance(string, _binary_type) or isinstance(string, _text_type))
+        and _isconvertible(inttype, string)
+    )
+
+
+def _isbool(string):
+    """
+    >>> _isbool(True)
+    True
+    >>> _isbool("False")
+    True
+    >>> _isbool(1)
+    False
+    """
+    return type(string) is _bool_type or (
+        isinstance(string, (_binary_type, _text_type)) and string in ("True", "False")
+    )
+
+
+def _type(string, has_invisible=True, numparse=True):
+    """The least generic type (type(None), int, float, str, unicode).
+
+    >>> _type(None) is type(None)
+    True
+    >>> _type("foo") is type("")
+    True
+    >>> _type("1") is type(1)
+    True
+    >>> _type('\x1b[31m42\x1b[0m') is type(42)
+    True
+    >>> _type('\x1b[31m42\x1b[0m') is type(42)
+    True
+
+    """
+
+    if has_invisible and (
+        isinstance(string, _text_type) or isinstance(string, _binary_type)
+    ):
+        string = _strip_invisible(string)
+
+    if string is None:
+        return _none_type
+    elif hasattr(string, "isoformat"):  # datetime.datetime, date, and time
+        return _text_type
+    elif _isbool(string):
+        return _bool_type
+    elif _isint(string) and numparse:
+        return int
+    elif _isint(string, _long_type) and numparse:
+        return int
+    elif _isnumber(string) and numparse:
+        return float
+    elif isinstance(string, _binary_type):
+        return _binary_type
+    else:
+        return _text_type
+
+
+def _afterpoint(string):
+    """Symbols after a decimal point, -1 if the string lacks the decimal point.
+
+    >>> _afterpoint("123.45")
+    2
+    >>> _afterpoint("1001")
+    -1
+    >>> _afterpoint("eggs")
+    -1
+    >>> _afterpoint("123e45")
+    2
+
+    """
+    if _isnumber(string):
+        if _isint(string):
+            return -1
+        else:
+            pos = string.rfind(".")
+            pos = string.lower().rfind("e") if pos < 0 else pos
+            if pos >= 0:
+                return len(string) - pos - 1
+            else:
+                return -1  # no point
+    else:
+        return -1  # not a number
+
+
+def _padleft(width, s):
+    """Flush right.
+
+    >>> _padleft(6, '\u044f\u0439\u0446\u0430') == '  \u044f\u0439\u0446\u0430'
+    True
+
+    """
+    fmt = "{0:>%ds}" % width
+    return fmt.format(s)
+
+
+def _padright(width, s):
+    """Flush left.
+
+    >>> _padright(6, '\u044f\u0439\u0446\u0430') == '\u044f\u0439\u0446\u0430  '
+    True
+
+    """
+    fmt = "{0:<%ds}" % width
+    return fmt.format(s)
+
+
+def _padboth(width, s):
+    """Center string.
+
+    >>> _padboth(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430 '
+    True
+
+    """
+    fmt = "{0:^%ds}" % width
+    return fmt.format(s)
+
+
+def _padnone(ignore_width, s):
+    return s
+
+
+def _strip_invisible(s):
+    r"""Remove invisible ANSI color codes.
+
+    >>> str(_strip_invisible('\x1B]8;;https://example.com\x1B\\This is a link\x1B]8;;\x1B\\'))
+    'This is a link'
+
+    """
+    if isinstance(s, _text_type):
+        links_removed = re.sub(_invisible_codes_link, "\\1", s)
+        return re.sub(_invisible_codes, "", links_removed)
+    else:  # a bytestring
+        return re.sub(_invisible_codes_bytes, "", s)
+
+
+def _visible_width(s):
+    """Visible width of a printed string. ANSI color codes are removed.
+
+    >>> _visible_width('\x1b[31mhello\x1b[0m'), _visible_width("world")
+    (5, 5)
+
+    """
+    # optional wide-character support
+    if wcwidth is not None and WIDE_CHARS_MODE:
+        len_fn = wcwidth.wcswidth
+    else:
+        len_fn = len
+    if isinstance(s, _text_type) or isinstance(s, _binary_type):
+        return len_fn(_strip_invisible(s))
+    else:
+        return len_fn(_text_type(s))
+
+
+def _is_multiline(s):
+    if isinstance(s, _text_type):
+        return bool(re.search(_multiline_codes, s))
+    else:  # a bytestring
+        return bool(re.search(_multiline_codes_bytes, s))
+
+
+def _multiline_width(multiline_s, line_width_fn=len):
+    """Visible width of a potentially multiline content."""
+    return max(map(line_width_fn, re.split("[\r\n]", multiline_s)))
+
+
+def _choose_width_fn(has_invisible, enable_widechars, is_multiline):
+    """Return a function to calculate visible cell width."""
+    if has_invisible:
+        line_width_fn = _visible_width
+    elif enable_widechars:  # optional wide-character support if available
+        line_width_fn = wcwidth.wcswidth
+    else:
+        line_width_fn = len
+    if is_multiline:
+        width_fn = lambda s: _multiline_width(s, line_width_fn)  # noqa
+    else:
+        width_fn = line_width_fn
+    return width_fn
+
+
+def _align_column_choose_padfn(strings, alignment, has_invisible):
+    if alignment == "right":
+        if not PRESERVE_WHITESPACE:
+            strings = [s.strip() for s in strings]
+        padfn = _padleft
+    elif alignment == "center":
+        if not PRESERVE_WHITESPACE:
+            strings = [s.strip() for s in strings]
+        padfn = _padboth
+    elif alignment == "decimal":
+        if has_invisible:
+            decimals = [_afterpoint(_strip_invisible(s)) for s in strings]
+        else:
+            decimals = [_afterpoint(s) for s in strings]
+        maxdecimals = max(decimals)
+        strings = [s + (maxdecimals - decs) * " " for s, decs in zip(strings, decimals)]
+        padfn = _padleft
+    elif not alignment:
+        padfn = _padnone
+    else:
+        if not PRESERVE_WHITESPACE:
+            strings = [s.strip() for s in strings]
+        padfn = _padright
+    return strings, padfn
+
+
+def _align_column_choose_width_fn(has_invisible, enable_widechars, is_multiline):
+    if has_invisible:
+        line_width_fn = _visible_width
+    elif enable_widechars:  # optional wide-character support if available
+        line_width_fn = wcwidth.wcswidth
+    else:
+        line_width_fn = len
+    if is_multiline:
+        width_fn = lambda s: _align_column_multiline_width(s, line_width_fn)  # noqa
+    else:
+        width_fn = line_width_fn
+    return width_fn
+
+
+def _align_column_multiline_width(multiline_s, line_width_fn=len):
+    """Visible width of a potentially multiline content."""
+    return list(map(line_width_fn, re.split("[\r\n]", multiline_s)))
+
+
+def _flat_list(nested_list):
+    ret = []
+    for item in nested_list:
+        if isinstance(item, list):
+            for subitem in item:
+                ret.append(subitem)
+        else:
+            ret.append(item)
+    return ret
+
+
+def _align_column(
+    strings,
+    alignment,
+    minwidth=0,
+    has_invisible=True,
+    enable_widechars=False,
+    is_multiline=False,
+):
+    """[string] -> [padded_string]"""
+    strings, padfn = _align_column_choose_padfn(strings, alignment, has_invisible)
+    width_fn = _align_column_choose_width_fn(
+        has_invisible, enable_widechars, is_multiline
+    )
+
+    s_widths = list(map(width_fn, strings))
+    maxwidth = max(max(_flat_list(s_widths)), minwidth)
+    # TODO: refactor column alignment in single-line and multiline modes
+    if is_multiline:
+        if not enable_widechars and not has_invisible:
+            padded_strings = [
+                "\n".join([padfn(maxwidth, s) for s in ms.splitlines()])
+                for ms in strings
+            ]
+        else:
+            # enable wide-character width corrections
+            s_lens = [[len(s) for s in re.split("[\r\n]", ms)] for ms in strings]
+            visible_widths = [
+                [maxwidth - (w - l) for w, l in zip(mw, ml)]
+                for mw, ml in zip(s_widths, s_lens)
+            ]
+            # wcswidth and _visible_width don't count invisible characters;
+            # padfn doesn't need to apply another correction
+            padded_strings = [
+                "\n".join([padfn(w, s) for s, w in zip((ms.splitlines() or ms), mw)])
+                for ms, mw in zip(strings, visible_widths)
+            ]
+    else:  # single-line cell values
+        if not enable_widechars and not has_invisible:
+            padded_strings = [padfn(maxwidth, s) for s in strings]
+        else:
+            # enable wide-character width corrections
+            s_lens = list(map(len, strings))
+            visible_widths = [maxwidth - (w - l) for w, l in zip(s_widths, s_lens)]
+            # wcswidth and _visible_width don't count invisible characters;
+            # padfn doesn't need to apply another correction
+            padded_strings = [padfn(w, s) for s, w in zip(strings, visible_widths)]
+    return padded_strings
+
+
+def _more_generic(type1, type2):
+    types = {
+        _none_type: 0,
+        _bool_type: 1,
+        int: 2,
+        float: 3,
+        _binary_type: 4,
+        _text_type: 5,
+    }
+    invtypes = {
+        5: _text_type,
+        4: _binary_type,
+        3: float,
+        2: int,
+        1: _bool_type,
+        0: _none_type,
+    }
+    moregeneric = max(types.get(type1, 5), types.get(type2, 5))
+    return invtypes[moregeneric]
+
+
+def _column_type(strings, has_invisible=True, numparse=True):
+    """The least generic type all column values are convertible to.
+
+    >>> _column_type([True, False]) is _bool_type
+    True
+    >>> _column_type(["1", "2"]) is _int_type
+    True
+    >>> _column_type(["1", "2.3"]) is _float_type
+    True
+    >>> _column_type(["1", "2.3", "four"]) is _text_type
+    True
+    >>> _column_type(["four", '\u043f\u044f\u0442\u044c']) is _text_type
+    True
+    >>> _column_type([None, "brux"]) is _text_type
+    True
+    >>> _column_type([1, 2, None]) is _int_type
+    True
+    >>> import datetime as dt
+    >>> _column_type([dt.datetime(1991,2,19), dt.time(17,35)]) is _text_type
+    True
+
+    """
+    types = [_type(s, has_invisible, numparse) for s in strings]
+    return reduce(_more_generic, types, _bool_type)
+
+
+def _format(val, valtype, floatfmt, missingval="", has_invisible=True):
+    """Format a value according to its type.
+
+    Unicode is supported:
+
+    >>> hrow = ['\u0431\u0443\u043a\u0432\u0430', '\u0446\u0438\u0444\u0440\u0430'] ; \
+        tbl = [['\u0430\u0437', 2], ['\u0431\u0443\u043a\u0438', 4]] ; \
+        good_result = '\\u0431\\u0443\\u043a\\u0432\\u0430      \\u0446\\u0438\\u0444\\u0440\\u0430\\n-------  -------\\n\\u0430\\u0437             2\\n\\u0431\\u0443\\u043a\\u0438           4' ; \
+        tabulate(tbl, headers=hrow) == good_result
+    True
+
+    """  # noqa
+    if val is None:
+        return missingval
+
+    if valtype in [int, _text_type]:
+        return "{0}".format(val)
+    elif valtype is _binary_type:
+        try:
+            return _text_type(val, "ascii")
+        except TypeError:
+            return _text_type(val)
+    elif valtype is float:
+        is_a_colored_number = has_invisible and isinstance(
+            val, (_text_type, _binary_type)
+        )
+        if is_a_colored_number:
+            raw_val = _strip_invisible(val)
+            formatted_val = format(float(raw_val), floatfmt)
+            return val.replace(raw_val, formatted_val)
+        else:
+            return format(float(val), floatfmt)
+    else:
+        return "{0}".format(val)
+
+
+def _align_header(
+    header, alignment, width, visible_width, is_multiline=False, width_fn=None
+):
+    "Pad string header to width chars given known visible_width of the header."
+    if is_multiline:
+        header_lines = re.split(_multiline_codes, header)
+        padded_lines = [
+            _align_header(h, alignment, width, width_fn(h)) for h in header_lines
+        ]
+        return "\n".join(padded_lines)
+    # else: not multiline
+    ninvisible = len(header) - visible_width
+    width += ninvisible
+    if alignment == "left":
+        return _padright(width, header)
+    elif alignment == "center":
+        return _padboth(width, header)
+    elif not alignment:
+        return "{0}".format(header)
+    else:
+        return _padleft(width, header)
+
+
+def _prepend_row_index(rows, index):
+    """Add a left-most index column."""
+    if index is None or index is False:
+        return rows
+    if len(index) != len(rows):
+        print("index=", index)
+        print("rows=", rows)
+        raise ValueError("index must be as long as the number of data rows")
+    rows = [[v] + list(row) for v, row in zip(index, rows)]
+    return rows
+
+
+def _bool(val):
+    "A wrapper around standard bool() which doesn't throw on NumPy arrays"
+    try:
+        return bool(val)
+    except ValueError:  # val is likely to be a numpy array with many elements
+        return False
+
+
+def _normalize_tabular_data(tabular_data, headers, showindex="default"):
+    """Transform a supported data type to a list of lists, and a list of headers.
+
+    Supported tabular data types:
+
+    * list-of-lists or another iterable of iterables
+
+    * list of named tuples (usually used with headers="keys")
+
+    * list of dicts (usually used with headers="keys")
+
+    * list of OrderedDicts (usually used with headers="keys")
+
+    * 2D NumPy arrays
+
+    * NumPy record arrays (usually used with headers="keys")
+
+    * dict of iterables (usually used with headers="keys")
+
+    * pandas.DataFrame (usually used with headers="keys")
+
+    The first row can be used as headers if headers="firstrow",
+    column indices can be used as headers if headers="keys".
+
+    If showindex="default", show row indices of the pandas.DataFrame.
+    If showindex="always", show row indices for all types of data.
+    If showindex="never", don't show row indices for all types of data.
+    If showindex is an iterable, show its values as row indices.
+
+    """
+
+    try:
+        bool(headers)
+        is_headers2bool_broken = False  # noqa
+    except ValueError:  # numpy.ndarray, pandas.core.index.Index, ...
+        is_headers2bool_broken = True  # noqa
+        headers = list(headers)
+
+    index = None
+    if hasattr(tabular_data, "keys") and hasattr(tabular_data, "values"):
+        # dict-like and pandas.DataFrame?
+        if hasattr(tabular_data.values, "__call__"):
+            # likely a conventional dict
+            keys = tabular_data.keys()
+            rows = list(
+                izip_longest(*tabular_data.values())
+            )  # columns have to be transposed
+        elif hasattr(tabular_data, "index"):
+            # values is a property, has .index => it's likely a pandas.DataFrame (pandas 0.11.0)
+            keys = list(tabular_data)
+            if (
+                showindex in ["default", "always", True]
+                and tabular_data.index.name is not None
+            ):
+                if isinstance(tabular_data.index.name, list):
+                    keys[:0] = tabular_data.index.name
+                else:
+                    keys[:0] = [tabular_data.index.name]
+            vals = tabular_data.values  # values matrix doesn't need to be transposed
+            # for DataFrames add an index per default
+            index = list(tabular_data.index)
+            rows = [list(row) for row in vals]
+        else:
+            raise ValueError("tabular data doesn't appear to be a dict or a DataFrame")
+
+        if headers == "keys":
+            headers = list(map(_text_type, keys))  # headers should be strings
+
+    else:  # it's a usual an iterable of iterables, or a NumPy array
+        rows = list(tabular_data)
+
+        if headers == "keys" and not rows:
+            # an empty table (issue #81)
+            headers = []
+        elif (
+            headers == "keys"
+            and hasattr(tabular_data, "dtype")
+            and getattr(tabular_data.dtype, "names")
+        ):
+            # numpy record array
+            headers = tabular_data.dtype.names
+        elif (
+            headers == "keys"
+            and len(rows) > 0
+            and isinstance(rows[0], tuple)
+            and hasattr(rows[0], "_fields")
+        ):
+            # namedtuple
+            headers = list(map(_text_type, rows[0]._fields))
+        elif len(rows) > 0 and hasattr(rows[0], "keys") and hasattr(rows[0], "values"):
+            # dict-like object
+            uniq_keys = set()  # implements hashed lookup
+            keys = []  # storage for set
+            if headers == "firstrow":
+                firstdict = rows[0] if len(rows) > 0 else {}
+                keys.extend(firstdict.keys())
+                uniq_keys.update(keys)
+                rows = rows[1:]
+            for row in rows:
+                for k in row.keys():
+                    # Save unique items in input order
+                    if k not in uniq_keys:
+                        keys.append(k)
+                        uniq_keys.add(k)
+            if headers == "keys":
+                headers = keys
+            elif isinstance(headers, dict):
+                # a dict of headers for a list of dicts
+                headers = [headers.get(k, k) for k in keys]
+                headers = list(map(_text_type, headers))
+            elif headers == "firstrow":
+                if len(rows) > 0:
+                    headers = [firstdict.get(k, k) for k in keys]
+                    headers = list(map(_text_type, headers))
+                else:
+                    headers = []
+            elif headers:
+                raise ValueError(
+                    "headers for a list of dicts is not a dict or a keyword"
+                )
+            rows = [[row.get(k) for k in keys] for row in rows]
+
+        elif (
+            headers == "keys"
+            and hasattr(tabular_data, "description")
+            and hasattr(tabular_data, "fetchone")
+            and hasattr(tabular_data, "rowcount")
+        ):
+            # Python Database API cursor object (PEP 0249)
+            # print tabulate(cursor, headers='keys')
+            headers = [column[0] for column in tabular_data.description]
+
+        elif headers == "keys" and len(rows) > 0:
+            # keys are column indices
+            headers = list(map(_text_type, range(len(rows[0]))))
+
+    # take headers from the first row if necessary
+    if headers == "firstrow" and len(rows) > 0:
+        if index is not None:
+            headers = [index[0]] + list(rows[0])
+            index = index[1:]
+        else:
+            headers = rows[0]
+        headers = list(map(_text_type, headers))  # headers should be strings
+        rows = rows[1:]
+
+    headers = list(map(_text_type, headers))
+    rows = list(map(list, rows))
+
+    # add or remove an index column
+    showindex_is_a_str = type(showindex) in [_text_type, _binary_type]
+    if showindex == "default" and index is not None:
+        rows = _prepend_row_index(rows, index)
+    elif isinstance(showindex, Iterable) and not showindex_is_a_str:
+        rows = _prepend_row_index(rows, list(showindex))
+    elif showindex == "always" or (_bool(showindex) and not showindex_is_a_str):
+        if index is None:
+            index = list(range(len(rows)))
+        rows = _prepend_row_index(rows, index)
+    elif showindex == "never" or (not _bool(showindex) and not showindex_is_a_str):
+        pass
+
+    # pad with empty headers for initial columns if necessary
+    if headers and len(rows) > 0:
+        nhs = len(headers)
+        ncols = len(rows[0])
+        if nhs < ncols:
+            headers = [""] * (ncols - nhs) + headers
+
+    return rows, headers
+
+
+def tabulate(
+    tabular_data,
+    headers=(),
+    tablefmt="simple",
+    floatfmt=_DEFAULT_FLOATFMT,
+    numalign=_DEFAULT_ALIGN,
+    stralign=_DEFAULT_ALIGN,
+    missingval=_DEFAULT_MISSINGVAL,
+    showindex="default",
+    disable_numparse=False,
+    colalign=None,
+):
+    """Format a fixed width table for pretty printing.
+
+    >>> print(tabulate([[1, 2.34], [-56, "8.999"], ["2", "10001"]]))
+    ---  ---------
+      1      2.34
+    -56      8.999
+      2  10001
+    ---  ---------
+
+    The first required argument (`tabular_data`) can be a
+    list-of-lists (or another iterable of iterables), a list of named
+    tuples, a dictionary of iterables, an iterable of dictionaries,
+    a two-dimensional NumPy array, NumPy record array, or a Pandas'
+    dataframe.
+
+
+    Table headers
+    -------------
+
+    To print nice column headers, supply the second argument (`headers`):
+
+      - `headers` can be an explicit list of column headers
+      - if `headers="firstrow"`, then the first row of data is used
+      - if `headers="keys"`, then dictionary keys or column indices are used
+
+    Otherwise a headerless table is produced.
+
+    If the number of headers is less than the number of columns, they
+    are supposed to be names of the last columns. This is consistent
+    with the plain-text format of R and Pandas' dataframes.
+
+    >>> print(tabulate([["sex","age"],["Alice","F",24],["Bob","M",19]],
+    ...       headers="firstrow"))
+           sex      age
+    -----  -----  -----
+    Alice  F         24
+    Bob    M         19
+
+    By default, pandas.DataFrame data have an additional column called
+    row index. To add a similar column to all other types of data,
+    use `showindex="always"` or `showindex=True`. To suppress row indices
+    for all types of data, pass `showindex="never" or `showindex=False`.
+    To add a custom row index column, pass `showindex=some_iterable`.
+
+    >>> print(tabulate([["F",24],["M",19]], showindex="always"))
+    -  -  --
+    0  F  24
+    1  M  19
+    -  -  --
+
+
+    Column alignment
+    ----------------
+
+    `tabulate` tries to detect column types automatically, and aligns
+    the values properly. By default it aligns decimal points of the
+    numbers (or flushes integer numbers to the right), and flushes
+    everything else to the left. Possible column alignments
+    (`numalign`, `stralign`) are: "right", "center", "left", "decimal"
+    (only for `numalign`), and None (to disable alignment).
+
+
+    Table formats
+    -------------
+
+    `floatfmt` is a format specification used for columns which
+    contain numeric data with a decimal point. This can also be
+    a list or tuple of format strings, one per column.
+
+    `None` values are replaced with a `missingval` string (like
+    `floatfmt`, this can also be a list of values for different
+    columns):
+
+    >>> print(tabulate([["spam", 1, None],
+    ...                 ["eggs", 42, 3.14],
+    ...                 ["other", None, 2.7]], missingval="?"))
+    -----  --  ----
+    spam    1  ?
+    eggs   42  3.14
+    other   ?  2.7
+    -----  --  ----
+
+    Various plain-text table formats (`tablefmt`) are supported:
+    'plain', 'simple', 'grid', 'pipe', 'orgtbl', 'rst', 'mediawiki',
+    'latex', 'latex_raw', 'latex_booktabs', 'latex_longtable' and tsv.
+    Variable `tabulate_formats`contains the list of currently supported formats.
+
+    "plain" format doesn't use any pseudographics to draw tables,
+    it separates columns with a double space:
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+    ...                 ["strings", "numbers"], "plain"))
+    strings      numbers
+    spam         41.9999
+    eggs        451
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="plain"))
+    spam   41.9999
+    eggs  451
+
+    "simple" format is like Pandoc simple_tables:
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+    ...                 ["strings", "numbers"], "simple"))
+    strings      numbers
+    ---------  ---------
+    spam         41.9999
+    eggs        451
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="simple"))
+    ----  --------
+    spam   41.9999
+    eggs  451
+    ----  --------
+
+    "grid" is similar to tables produced by Emacs table.el package or
+    Pandoc grid_tables:
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+    ...                ["strings", "numbers"], "grid"))
+    +-----------+-----------+
+    | strings   |   numbers |
+    +===========+===========+
+    | spam      |   41.9999 |
+    +-----------+-----------+
+    | eggs      |  451      |
+    +-----------+-----------+
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="grid"))
+    +------+----------+
+    | spam |  41.9999 |
+    +------+----------+
+    | eggs | 451      |
+    +------+----------+
+
+    "fancy_grid" draws a grid using box-drawing characters:
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+    ...                ["strings", "numbers"], "fancy_grid"))
+    ╒═══════════╤═══════════╕
+    │ strings   │   numbers │
+    ╞═══════════╪═══════════╡
+    │ spam      │   41.9999 │
+    ├───────────┼───────────┤
+    │ eggs      │  451      │
+    ╘═══════════╧═══════════╛
+
+    "pipe" is like tables in PHP Markdown Extra extension or Pandoc
+    pipe_tables:
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+    ...                ["strings", "numbers"], "pipe"))
+    | strings   |   numbers |
+    |:----------|----------:|
+    | spam      |   41.9999 |
+    | eggs      |  451      |
+
+    "presto" is like tables produce by the Presto CLI:
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+    ...                ["strings", "numbers"], "presto"))
+     strings   |   numbers
+    -----------+-----------
+     spam      |   41.9999
+     eggs      |  451
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="pipe"))
+    |:-----|---------:|
+    | spam |  41.9999 |
+    | eggs | 451      |
+
+    "orgtbl" is like tables in Emacs org-mode and orgtbl-mode. They
+    are slightly different from "pipe" format by not using colons to
+    define column alignment, and using a "+" sign to indicate line
+    intersections:
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+    ...                ["strings", "numbers"], "orgtbl"))
+    | strings   |   numbers |
+    |-----------+-----------|
+    | spam      |   41.9999 |
+    | eggs      |  451      |
+
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="orgtbl"))
+    | spam |  41.9999 |
+    | eggs | 451      |
+
+    "rst" is like a simple table format from reStructuredText; please
+    note that reStructuredText accepts also "grid" tables:
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+    ...                ["strings", "numbers"], "rst"))
+    =========  =========
+    strings      numbers
+    =========  =========
+    spam         41.9999
+    eggs        451
+    =========  =========
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="rst"))
+    ====  ========
+    spam   41.9999
+    eggs  451
+    ====  ========
+
+    "mediawiki" produces a table markup used in Wikipedia and on other
+    MediaWiki-based sites:
+
+    >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]],
+    ...                headers="firstrow", tablefmt="mediawiki"))
+    {| class="wikitable" style="text-align: left;"
+    |+ <!-- caption -->
+    |-
+    ! strings   !! align="right"|   numbers
+    |-
+    | spam      || align="right"|   41.9999
+    |-
+    | eggs      || align="right"|  451
+    |}
+
+    "html" produces HTML markup as an html.escape'd str
+    with a ._repr_html_ method so that Jupyter Lab and Notebook display the HTML
+    and a .str property so that the raw HTML remains accessible
+    the unsafehtml table format can be used if an unescaped HTML format is required:
+
+    >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]],
+    ...                headers="firstrow", tablefmt="html"))
+    <table>
+    <thead>
+    <tr><th>strings  </th><th style="text-align: right;">  numbers</th></tr>
+    </thead>
+    <tbody>
+    <tr><td>spam     </td><td style="text-align: right;">  41.9999</td></tr>
+    <tr><td>eggs     </td><td style="text-align: right;"> 451     </td></tr>
+    </tbody>
+    </table>
+
+    "latex" produces a tabular environment of LaTeX document markup:
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex"))
+    \\begin{tabular}{lr}
+    \\hline
+     spam &  41.9999 \\\\
+     eggs & 451      \\\\
+    \\hline
+    \\end{tabular}
+
+    "latex_raw" is similar to "latex", but doesn't escape special characters,
+    such as backslash and underscore, so LaTeX commands may embedded into
+    cells' values:
+
+    >>> print(tabulate([["spam$_9$", 41.9999], ["\\\\emph{eggs}", "451.0"]], tablefmt="latex_raw"))
+    \\begin{tabular}{lr}
+    \\hline
+     spam$_9$    &  41.9999 \\\\
+     \\emph{eggs} & 451      \\\\
+    \\hline
+    \\end{tabular}
+
+    "latex_booktabs" produces a tabular environment of LaTeX document markup
+    using the booktabs.sty package:
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex_booktabs"))
+    \\begin{tabular}{lr}
+    \\toprule
+     spam &  41.9999 \\\\
+     eggs & 451      \\\\
+    \\bottomrule
+    \\end{tabular}
+
+    "latex_longtable" produces a tabular environment that can stretch along
+    multiple pages, using the longtable package for LaTeX.
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex_longtable"))
+    \\begin{longtable}{lr}
+    \\hline
+     spam &  41.9999 \\\\
+     eggs & 451      \\\\
+    \\hline
+    \\end{longtable}
+
+
+    Number parsing
+    --------------
+    By default, anything which can be parsed as a number is a number.
+    This ensures numbers represented as strings are aligned properly.
+    This can lead to weird results for particular strings such as
+    specific git SHAs e.g. "42992e1" will be parsed into the number
+    429920 and aligned as such.
+
+    To completely disable number parsing (and alignment), use
+    `disable_numparse=True`. For more fine grained control, a list column
+    indices is used to disable number parsing only on those columns
+    e.g. `disable_numparse=[0, 2]` would disable number parsing only on the
+    first and third columns.
+    """
+
+    if tabular_data is None:
+        tabular_data = []
+    list_of_lists, headers = _normalize_tabular_data(
+        tabular_data, headers, showindex=showindex
+    )
+
+    # empty values in the first column of RST tables should be escaped (issue #82)
+    # "" should be escaped as "\\ " or ".."
+    if tablefmt == "rst":
+        list_of_lists, headers = _rst_escape_first_column(list_of_lists, headers)
+
+    # PrettyTable formatting does not use any extra padding.
+    # Numbers are not parsed and are treated the same as strings for alignment.
+    # Check if pretty is the format being used and override the defaults so it
+    # does not impact other formats.
+    min_padding = MIN_PADDING
+    if tablefmt == "pretty":
+        min_padding = 0
+        disable_numparse = True
+        numalign = "center" if numalign == _DEFAULT_ALIGN else numalign
+        stralign = "center" if stralign == _DEFAULT_ALIGN else stralign
+    else:
+        numalign = "decimal" if numalign == _DEFAULT_ALIGN else numalign
+        stralign = "left" if stralign == _DEFAULT_ALIGN else stralign
+
+    # optimization: look for ANSI control codes once,
+    # enable smart width functions only if a control code is found
+    plain_text = "\t".join(
+        ["\t".join(map(_text_type, headers))]
+        + ["\t".join(map(_text_type, row)) for row in list_of_lists]
+    )
+
+    has_invisible = re.search(_invisible_codes, plain_text)
+    if not has_invisible:
+        has_invisible = re.search(_invisible_codes_link, plain_text)
+    enable_widechars = wcwidth is not None and WIDE_CHARS_MODE
+    if (
+        not isinstance(tablefmt, TableFormat)
+        and tablefmt in multiline_formats
+        and _is_multiline(plain_text)
+    ):
+        tablefmt = multiline_formats.get(tablefmt, tablefmt)
+        is_multiline = True
+    else:
+        is_multiline = False
+    width_fn = _choose_width_fn(has_invisible, enable_widechars, is_multiline)
+
+    # format rows and columns, convert numeric values to strings
+    cols = list(izip_longest(*list_of_lists))
+    numparses = _expand_numparse(disable_numparse, len(cols))
+    coltypes = [_column_type(col, numparse=np) for col, np in zip(cols, numparses)]
+    if isinstance(floatfmt, basestring):  # old version
+        float_formats = len(cols) * [
+            floatfmt
+        ]  # just duplicate the string to use in each column
+    else:  # if floatfmt is list, tuple etc we have one per column
+        float_formats = list(floatfmt)
+        if len(float_formats) < len(cols):
+            float_formats.extend((len(cols) - len(float_formats)) * [_DEFAULT_FLOATFMT])
+    if isinstance(missingval, basestring):
+        missing_vals = len(cols) * [missingval]
+    else:
+        missing_vals = list(missingval)
+        if len(missing_vals) < len(cols):
+            missing_vals.extend((len(cols) - len(missing_vals)) * [_DEFAULT_MISSINGVAL])
+    cols = [
+        [_format(v, ct, fl_fmt, miss_v, has_invisible) for v in c]
+        for c, ct, fl_fmt, miss_v in zip(cols, coltypes, float_formats, missing_vals)
+    ]
+
+    # align columns
+    aligns = [numalign if ct in [int, float] else stralign for ct in coltypes]
+    if colalign is not None:
+        assert isinstance(colalign, Iterable)
+        for idx, align in enumerate(colalign):
+            aligns[idx] = align
+    minwidths = (
+        [width_fn(h) + min_padding for h in headers] if headers else [0] * len(cols)
+    )
+    cols = [
+        _align_column(c, a, minw, has_invisible, enable_widechars, is_multiline)
+        for c, a, minw in zip(cols, aligns, minwidths)
+    ]
+
+    if headers:
+        # align headers and add headers
+        t_cols = cols or [[""]] * len(headers)
+        t_aligns = aligns or [stralign] * len(headers)
+        minwidths = [
+            max(minw, max(width_fn(cl) for cl in c))
+            for minw, c in zip(minwidths, t_cols)
+        ]
+        headers = [
+            _align_header(h, a, minw, width_fn(h), is_multiline, width_fn)
+            for h, a, minw in zip(headers, t_aligns, minwidths)
+        ]
+        rows = list(zip(*cols))
+    else:
+        minwidths = [max(width_fn(cl) for cl in c) for c in cols]
+        rows = list(zip(*cols))
+
+    if not isinstance(tablefmt, TableFormat):
+        tablefmt = _table_formats.get(tablefmt, _table_formats["simple"])
+
+    return _format_table(tablefmt, headers, rows, minwidths, aligns, is_multiline)
+
+
+def _expand_numparse(disable_numparse, column_count):
+    """
+    Return a list of bools of length `column_count` which indicates whether
+    number parsing should be used on each column.
+    If `disable_numparse` is a list of indices, each of those indices are False,
+    and everything else is True.
+    If `disable_numparse` is a bool, then the returned list is all the same.
+    """
+    if isinstance(disable_numparse, Iterable):
+        numparses = [True] * column_count
+        for index in disable_numparse:
+            numparses[index] = False
+        return numparses
+    else:
+        return [not disable_numparse] * column_count
+
+
+def _pad_row(cells, padding):
+    if cells:
+        pad = " " * padding
+        padded_cells = [pad + cell + pad for cell in cells]
+        return padded_cells
+    else:
+        return cells
+
+
+def _build_simple_row(padded_cells, rowfmt):
+    "Format row according to DataRow format without padding."
+    begin, sep, end = rowfmt
+    return (begin + sep.join(padded_cells) + end).rstrip()
+
+
+def _build_row(padded_cells, colwidths, colaligns, rowfmt):
+    "Return a string which represents a row of data cells."
+    if not rowfmt:
+        return None
+    if hasattr(rowfmt, "__call__"):
+        return rowfmt(padded_cells, colwidths, colaligns)
+    else:
+        return _build_simple_row(padded_cells, rowfmt)
+
+
+def _append_basic_row(lines, padded_cells, colwidths, colaligns, rowfmt):
+    lines.append(_build_row(padded_cells, colwidths, colaligns, rowfmt))
+    return lines
+
+
+def _append_multiline_row(
+    lines, padded_multiline_cells, padded_widths, colaligns, rowfmt, pad
+):
+    colwidths = [w - 2 * pad for w in padded_widths]
+    cells_lines = [c.splitlines() for c in padded_multiline_cells]
+    nlines = max(map(len, cells_lines))  # number of lines in the row
+    # vertically pad cells where some lines are missing
+    cells_lines = [
+        (cl + [" " * w] * (nlines - len(cl))) for cl, w in zip(cells_lines, colwidths)
+    ]
+    lines_cells = [[cl[i] for cl in cells_lines] for i in range(nlines)]
+    for ln in lines_cells:
+        padded_ln = _pad_row(ln, pad)
+        _append_basic_row(lines, padded_ln, colwidths, colaligns, rowfmt)
+    return lines
+
+
+def _build_line(colwidths, colaligns, linefmt):
+    "Return a string which represents a horizontal line."
+    if not linefmt:
+        return None
+    if hasattr(linefmt, "__call__"):
+        return linefmt(colwidths, colaligns)
+    else:
+        begin, fill, sep, end = linefmt
+        cells = [fill * w for w in colwidths]
+        return _build_simple_row(cells, (begin, sep, end))
+
+
+def _append_line(lines, colwidths, colaligns, linefmt):
+    lines.append(_build_line(colwidths, colaligns, linefmt))
+    return lines
+
+
+class JupyterHTMLStr(str):
+    """Wrap the string with a _repr_html_ method so that Jupyter
+    displays the HTML table"""
+
+    def _repr_html_(self):
+        return self
+
+    @property
+    def str(self):
+        """add a .str property so that the raw string is still accessible"""
+        return self
+
+
+def _format_table(fmt, headers, rows, colwidths, colaligns, is_multiline):
+    """Produce a plain-text representation of the table."""
+    lines = []
+    hidden = fmt.with_header_hide if (headers and fmt.with_header_hide) else []
+    pad = fmt.padding
+    headerrow = fmt.headerrow
+
+    padded_widths = [(w + 2 * pad) for w in colwidths]
+    if is_multiline:
+        pad_row = lambda row, _: row  # noqa do it later, in _append_multiline_row
+        append_row = partial(_append_multiline_row, pad=pad)
+    else:
+        pad_row = _pad_row
+        append_row = _append_basic_row
+
+    padded_headers = pad_row(headers, pad)
+    padded_rows = [pad_row(row, pad) for row in rows]
+
+    if fmt.lineabove and "lineabove" not in hidden:
+        _append_line(lines, padded_widths, colaligns, fmt.lineabove)
+
+    if padded_headers:
+        append_row(lines, padded_headers, padded_widths, colaligns, headerrow)
+        if fmt.linebelowheader and "linebelowheader" not in hidden:
+            _append_line(lines, padded_widths, colaligns, fmt.linebelowheader)
+
+    if padded_rows and fmt.linebetweenrows and "linebetweenrows" not in hidden:
+        # initial rows with a line below
+        for row in padded_rows[:-1]:
+            append_row(lines, row, padded_widths, colaligns, fmt.datarow)
+            _append_line(lines, padded_widths, colaligns, fmt.linebetweenrows)
+        # the last row without a line below
+        append_row(lines, padded_rows[-1], padded_widths, colaligns, fmt.datarow)
+    else:
+        for row in padded_rows:
+            append_row(lines, row, padded_widths, colaligns, fmt.datarow)
+
+    if fmt.linebelow and "linebelow" not in hidden:
+        _append_line(lines, padded_widths, colaligns, fmt.linebelow)
+
+    if headers or rows:
+        output = "\n".join(lines)
+        if fmt.lineabove == _html_begin_table_without_header:
+            return JupyterHTMLStr(output)
+        else:
+            return output
+    else:  # a completely empty table
+        return ""
+
+
+def _main():
+    """\
+    Usage: tabulate [options] [FILE ...]
+
+    Pretty-print tabular data.
+    See also https://github.com/astanin/python-tabulate
+
+    FILE                      a filename of the file with tabular data;
+                              if "-" or missing, read data from stdin.
+
+    Options:
+
+    -h, --help                show this message
+    -1, --header              use the first row of data as a table header
+    -o FILE, --output FILE    print table to FILE (default: stdout)
+    -s REGEXP, --sep REGEXP   use a custom column separator (default: whitespace)
+    -F FPFMT, --float FPFMT   floating point number format (default: g)
+    -f FMT, --format FMT      set output table format; supported formats:
+                              plain, simple, grid, fancy_grid, pipe, orgtbl,
+                              rst, mediawiki, html, latex, latex_raw,
+                              latex_booktabs, latex_longtable, tsv
+                              (default: simple)
+    """
+    import getopt
+    import sys
+    import textwrap
+
+    usage = textwrap.dedent(_main.__doc__)
+    try:
+        opts, args = getopt.getopt(
+            sys.argv[1:],
+            "h1o:s:F:A:f:",
+            ["help", "header", "output", "sep=", "float=", "align=", "format="],
+        )
+    except getopt.GetoptError as e:
+        print(e)
+        print(usage)
+        sys.exit(2)
+    headers = []
+    floatfmt = _DEFAULT_FLOATFMT
+    colalign = None
+    tablefmt = "simple"
+    sep = r"\s+"
+    outfile = "-"
+    for opt, value in opts:
+        if opt in ["-1", "--header"]:
+            headers = "firstrow"
+        elif opt in ["-o", "--output"]:
+            outfile = value
+        elif opt in ["-F", "--float"]:
+            floatfmt = value
+        elif opt in ["-C", "--colalign"]:
+            colalign = value.split()
+        elif opt in ["-f", "--format"]:
+            if value not in tabulate_formats:
+                print("%s is not a supported table format" % value)
+                print(usage)
+                sys.exit(3)
+            tablefmt = value
+        elif opt in ["-s", "--sep"]:
+            sep = value
+        elif opt in ["-h", "--help"]:
+            print(usage)
+            sys.exit(0)
+    files = [sys.stdin] if not args else args
+    with (sys.stdout if outfile == "-" else open(outfile, "w")) as out:
+        for f in files:
+            if f == "-":
+                f = sys.stdin
+            if _is_file(f):
+                _pprint_file(
+                    f,
+                    headers=headers,
+                    tablefmt=tablefmt,
+                    sep=sep,
+                    floatfmt=floatfmt,
+                    file=out,
+                    colalign=colalign,
+                )
+            else:
+                with open(f) as fobj:
+                    _pprint_file(
+                        fobj,
+                        headers=headers,
+                        tablefmt=tablefmt,
+                        sep=sep,
+                        floatfmt=floatfmt,
+                        file=out,
+                        colalign=colalign,
+                    )
+
+
+def _pprint_file(fobject, headers, tablefmt, sep, floatfmt, file, colalign):
+    rows = fobject.readlines()
+    table = [re.split(sep, r.rstrip()) for r in rows if r.strip()]
+    print(
+        tabulate(table, headers, tablefmt, floatfmt=floatfmt, colalign=colalign),
+        file=file,
+    )
+
+
+if __name__ == "__main__":
+    _main()