| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879 |
- # -*- 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()
|