Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tabulate/__init__.py: 14%
794 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1"""Pretty-print tabular data."""
3from collections import namedtuple
4from collections.abc import Iterable, Sized
5from html import escape as htmlescape
6from itertools import chain, zip_longest as izip_longest
7from functools import reduce, partial
8import io
9import re
10import math
11import textwrap
12import dataclasses
14try:
15 import wcwidth # optional wide-character (CJK) support
16except ImportError:
17 wcwidth = None
20def _is_file(f):
21 return isinstance(f, io.IOBase)
24__all__ = ["tabulate", "tabulate_formats", "simple_separated_format"]
25try:
26 from .version import version as __version__ # noqa: F401
27except ImportError:
28 pass # running __init__.py as a script, AppVeyor pytests
31# minimum extra space in headers
32MIN_PADDING = 2
34# Whether or not to preserve leading/trailing whitespace in data.
35PRESERVE_WHITESPACE = False
37_DEFAULT_FLOATFMT = "g"
38_DEFAULT_INTFMT = ""
39_DEFAULT_MISSINGVAL = ""
40# default align will be overwritten by "left", "center" or "decimal"
41# depending on the formatter
42_DEFAULT_ALIGN = "default"
45# if True, enable wide-character (CJK) support
46WIDE_CHARS_MODE = wcwidth is not None
48# Constant that can be used as part of passed rows to generate a separating line
49# It is purposely an unprintable character, very unlikely to be used in a table
50SEPARATING_LINE = "\001"
52Line = namedtuple("Line", ["begin", "hline", "sep", "end"])
55DataRow = namedtuple("DataRow", ["begin", "sep", "end"])
58# A table structure is supposed to be:
59#
60# --- lineabove ---------
61# headerrow
62# --- linebelowheader ---
63# datarow
64# --- linebetweenrows ---
65# ... (more datarows) ...
66# --- linebetweenrows ---
67# last datarow
68# --- linebelow ---------
69#
70# TableFormat's line* elements can be
71#
72# - either None, if the element is not used,
73# - or a Line tuple,
74# - or a function: [col_widths], [col_alignments] -> string.
75#
76# TableFormat's *row elements can be
77#
78# - either None, if the element is not used,
79# - or a DataRow tuple,
80# - or a function: [cell_values], [col_widths], [col_alignments] -> string.
81#
82# padding (an integer) is the amount of white space around data values.
83#
84# with_header_hide:
85#
86# - either None, to display all table elements unconditionally,
87# - or a list of elements not to be displayed if the table has column headers.
88#
89TableFormat = namedtuple(
90 "TableFormat",
91 [
92 "lineabove",
93 "linebelowheader",
94 "linebetweenrows",
95 "linebelow",
96 "headerrow",
97 "datarow",
98 "padding",
99 "with_header_hide",
100 ],
101)
104def _is_separating_line(row):
105 row_type = type(row)
106 is_sl = (row_type == list or row_type == str) and (
107 (len(row) >= 1 and row[0] == SEPARATING_LINE)
108 or (len(row) >= 2 and row[1] == SEPARATING_LINE)
109 )
110 return is_sl
113def _pipe_segment_with_colons(align, colwidth):
114 """Return a segment of a horizontal line with optional colons which
115 indicate column's alignment (as in `pipe` output format)."""
116 w = colwidth
117 if align in ["right", "decimal"]:
118 return ("-" * (w - 1)) + ":"
119 elif align == "center":
120 return ":" + ("-" * (w - 2)) + ":"
121 elif align == "left":
122 return ":" + ("-" * (w - 1))
123 else:
124 return "-" * w
127def _pipe_line_with_colons(colwidths, colaligns):
128 """Return a horizontal line with optional colons to indicate column's
129 alignment (as in `pipe` output format)."""
130 if not colaligns: # e.g. printing an empty data frame (github issue #15)
131 colaligns = [""] * len(colwidths)
132 segments = [_pipe_segment_with_colons(a, w) for a, w in zip(colaligns, colwidths)]
133 return "|" + "|".join(segments) + "|"
136def _mediawiki_row_with_attrs(separator, cell_values, colwidths, colaligns):
137 alignment = {
138 "left": "",
139 "right": 'align="right"| ',
140 "center": 'align="center"| ',
141 "decimal": 'align="right"| ',
142 }
143 # hard-coded padding _around_ align attribute and value together
144 # rather than padding parameter which affects only the value
145 values_with_attrs = [
146 " " + alignment.get(a, "") + c + " " for c, a in zip(cell_values, colaligns)
147 ]
148 colsep = separator * 2
149 return (separator + colsep.join(values_with_attrs)).rstrip()
152def _textile_row_with_attrs(cell_values, colwidths, colaligns):
153 cell_values[0] += " "
154 alignment = {"left": "<.", "right": ">.", "center": "=.", "decimal": ">."}
155 values = (alignment.get(a, "") + v for a, v in zip(colaligns, cell_values))
156 return "|" + "|".join(values) + "|"
159def _html_begin_table_without_header(colwidths_ignore, colaligns_ignore):
160 # this table header will be suppressed if there is a header row
161 return "<table>\n<tbody>"
164def _html_row_with_attrs(celltag, unsafe, cell_values, colwidths, colaligns):
165 alignment = {
166 "left": "",
167 "right": ' style="text-align: right;"',
168 "center": ' style="text-align: center;"',
169 "decimal": ' style="text-align: right;"',
170 }
171 if unsafe:
172 values_with_attrs = [
173 "<{0}{1}>{2}</{0}>".format(celltag, alignment.get(a, ""), c)
174 for c, a in zip(cell_values, colaligns)
175 ]
176 else:
177 values_with_attrs = [
178 "<{0}{1}>{2}</{0}>".format(celltag, alignment.get(a, ""), htmlescape(c))
179 for c, a in zip(cell_values, colaligns)
180 ]
181 rowhtml = "<tr>{}</tr>".format("".join(values_with_attrs).rstrip())
182 if celltag == "th": # it's a header row, create a new table header
183 rowhtml = f"<table>\n<thead>\n{rowhtml}\n</thead>\n<tbody>"
184 return rowhtml
187def _moin_row_with_attrs(celltag, cell_values, colwidths, colaligns, header=""):
188 alignment = {
189 "left": "",
190 "right": '<style="text-align: right;">',
191 "center": '<style="text-align: center;">',
192 "decimal": '<style="text-align: right;">',
193 }
194 values_with_attrs = [
195 "{}{} {} ".format(celltag, alignment.get(a, ""), header + c + header)
196 for c, a in zip(cell_values, colaligns)
197 ]
198 return "".join(values_with_attrs) + "||"
201def _latex_line_begin_tabular(colwidths, colaligns, booktabs=False, longtable=False):
202 alignment = {"left": "l", "right": "r", "center": "c", "decimal": "r"}
203 tabular_columns_fmt = "".join([alignment.get(a, "l") for a in colaligns])
204 return "\n".join(
205 [
206 ("\\begin{tabular}{" if not longtable else "\\begin{longtable}{")
207 + tabular_columns_fmt
208 + "}",
209 "\\toprule" if booktabs else "\\hline",
210 ]
211 )
214def _asciidoc_row(is_header, *args):
215 """handle header and data rows for asciidoc format"""
217 def make_header_line(is_header, colwidths, colaligns):
218 # generate the column specifiers
220 alignment = {"left": "<", "right": ">", "center": "^", "decimal": ">"}
221 # use the column widths generated by tabulate for the asciidoc column width specifiers
222 asciidoc_alignments = zip(
223 colwidths, [alignment[colalign] for colalign in colaligns]
224 )
225 asciidoc_column_specifiers = [
226 "{:d}{}".format(width, align) for width, align in asciidoc_alignments
227 ]
228 header_list = ['cols="' + (",".join(asciidoc_column_specifiers)) + '"']
230 # generate the list of options (currently only "header")
231 options_list = []
233 if is_header:
234 options_list.append("header")
236 if options_list:
237 header_list += ['options="' + ",".join(options_list) + '"']
239 # generate the list of entries in the table header field
241 return "[{}]\n|====".format(",".join(header_list))
243 if len(args) == 2:
244 # two arguments are passed if called in the context of aboveline
245 # print the table header with column widths and optional header tag
246 return make_header_line(False, *args)
248 elif len(args) == 3:
249 # three arguments are passed if called in the context of dataline or headerline
250 # print the table line and make the aboveline if it is a header
252 cell_values, colwidths, colaligns = args
253 data_line = "|" + "|".join(cell_values)
255 if is_header:
256 return make_header_line(True, colwidths, colaligns) + "\n" + data_line
257 else:
258 return data_line
260 else:
261 raise ValueError(
262 " _asciidoc_row() requires two (colwidths, colaligns) "
263 + "or three (cell_values, colwidths, colaligns) arguments) "
264 )
267LATEX_ESCAPE_RULES = {
268 r"&": r"\&",
269 r"%": r"\%",
270 r"$": r"\$",
271 r"#": r"\#",
272 r"_": r"\_",
273 r"^": r"\^{}",
274 r"{": r"\{",
275 r"}": r"\}",
276 r"~": r"\textasciitilde{}",
277 "\\": r"\textbackslash{}",
278 r"<": r"\ensuremath{<}",
279 r">": r"\ensuremath{>}",
280}
283def _latex_row(cell_values, colwidths, colaligns, escrules=LATEX_ESCAPE_RULES):
284 def escape_char(c):
285 return escrules.get(c, c)
287 escaped_values = ["".join(map(escape_char, cell)) for cell in cell_values]
288 rowfmt = DataRow("", "&", "\\\\")
289 return _build_simple_row(escaped_values, rowfmt)
292def _rst_escape_first_column(rows, headers):
293 def escape_empty(val):
294 if isinstance(val, (str, bytes)) and not val.strip():
295 return ".."
296 else:
297 return val
299 new_headers = list(headers)
300 new_rows = []
301 if headers:
302 new_headers[0] = escape_empty(headers[0])
303 for row in rows:
304 new_row = list(row)
305 if new_row:
306 new_row[0] = escape_empty(row[0])
307 new_rows.append(new_row)
308 return new_rows, new_headers
311_table_formats = {
312 "simple": TableFormat(
313 lineabove=Line("", "-", " ", ""),
314 linebelowheader=Line("", "-", " ", ""),
315 linebetweenrows=None,
316 linebelow=Line("", "-", " ", ""),
317 headerrow=DataRow("", " ", ""),
318 datarow=DataRow("", " ", ""),
319 padding=0,
320 with_header_hide=["lineabove", "linebelow"],
321 ),
322 "plain": TableFormat(
323 lineabove=None,
324 linebelowheader=None,
325 linebetweenrows=None,
326 linebelow=None,
327 headerrow=DataRow("", " ", ""),
328 datarow=DataRow("", " ", ""),
329 padding=0,
330 with_header_hide=None,
331 ),
332 "grid": TableFormat(
333 lineabove=Line("+", "-", "+", "+"),
334 linebelowheader=Line("+", "=", "+", "+"),
335 linebetweenrows=Line("+", "-", "+", "+"),
336 linebelow=Line("+", "-", "+", "+"),
337 headerrow=DataRow("|", "|", "|"),
338 datarow=DataRow("|", "|", "|"),
339 padding=1,
340 with_header_hide=None,
341 ),
342 "simple_grid": TableFormat(
343 lineabove=Line("┌", "─", "┬", "┐"),
344 linebelowheader=Line("├", "─", "┼", "┤"),
345 linebetweenrows=Line("├", "─", "┼", "┤"),
346 linebelow=Line("└", "─", "┴", "┘"),
347 headerrow=DataRow("│", "│", "│"),
348 datarow=DataRow("│", "│", "│"),
349 padding=1,
350 with_header_hide=None,
351 ),
352 "rounded_grid": TableFormat(
353 lineabove=Line("╭", "─", "┬", "╮"),
354 linebelowheader=Line("├", "─", "┼", "┤"),
355 linebetweenrows=Line("├", "─", "┼", "┤"),
356 linebelow=Line("╰", "─", "┴", "╯"),
357 headerrow=DataRow("│", "│", "│"),
358 datarow=DataRow("│", "│", "│"),
359 padding=1,
360 with_header_hide=None,
361 ),
362 "heavy_grid": TableFormat(
363 lineabove=Line("┏", "━", "┳", "┓"),
364 linebelowheader=Line("┣", "━", "╋", "┫"),
365 linebetweenrows=Line("┣", "━", "╋", "┫"),
366 linebelow=Line("┗", "━", "┻", "┛"),
367 headerrow=DataRow("┃", "┃", "┃"),
368 datarow=DataRow("┃", "┃", "┃"),
369 padding=1,
370 with_header_hide=None,
371 ),
372 "mixed_grid": TableFormat(
373 lineabove=Line("┍", "━", "┯", "┑"),
374 linebelowheader=Line("┝", "━", "┿", "┥"),
375 linebetweenrows=Line("├", "─", "┼", "┤"),
376 linebelow=Line("┕", "━", "┷", "┙"),
377 headerrow=DataRow("│", "│", "│"),
378 datarow=DataRow("│", "│", "│"),
379 padding=1,
380 with_header_hide=None,
381 ),
382 "double_grid": TableFormat(
383 lineabove=Line("╔", "═", "╦", "╗"),
384 linebelowheader=Line("╠", "═", "╬", "╣"),
385 linebetweenrows=Line("╠", "═", "╬", "╣"),
386 linebelow=Line("╚", "═", "╩", "╝"),
387 headerrow=DataRow("║", "║", "║"),
388 datarow=DataRow("║", "║", "║"),
389 padding=1,
390 with_header_hide=None,
391 ),
392 "fancy_grid": TableFormat(
393 lineabove=Line("╒", "═", "╤", "╕"),
394 linebelowheader=Line("╞", "═", "╪", "╡"),
395 linebetweenrows=Line("├", "─", "┼", "┤"),
396 linebelow=Line("╘", "═", "╧", "╛"),
397 headerrow=DataRow("│", "│", "│"),
398 datarow=DataRow("│", "│", "│"),
399 padding=1,
400 with_header_hide=None,
401 ),
402 "outline": TableFormat(
403 lineabove=Line("+", "-", "+", "+"),
404 linebelowheader=Line("+", "=", "+", "+"),
405 linebetweenrows=None,
406 linebelow=Line("+", "-", "+", "+"),
407 headerrow=DataRow("|", "|", "|"),
408 datarow=DataRow("|", "|", "|"),
409 padding=1,
410 with_header_hide=None,
411 ),
412 "simple_outline": TableFormat(
413 lineabove=Line("┌", "─", "┬", "┐"),
414 linebelowheader=Line("├", "─", "┼", "┤"),
415 linebetweenrows=None,
416 linebelow=Line("└", "─", "┴", "┘"),
417 headerrow=DataRow("│", "│", "│"),
418 datarow=DataRow("│", "│", "│"),
419 padding=1,
420 with_header_hide=None,
421 ),
422 "rounded_outline": TableFormat(
423 lineabove=Line("╭", "─", "┬", "╮"),
424 linebelowheader=Line("├", "─", "┼", "┤"),
425 linebetweenrows=None,
426 linebelow=Line("╰", "─", "┴", "╯"),
427 headerrow=DataRow("│", "│", "│"),
428 datarow=DataRow("│", "│", "│"),
429 padding=1,
430 with_header_hide=None,
431 ),
432 "heavy_outline": TableFormat(
433 lineabove=Line("┏", "━", "┳", "┓"),
434 linebelowheader=Line("┣", "━", "╋", "┫"),
435 linebetweenrows=None,
436 linebelow=Line("┗", "━", "┻", "┛"),
437 headerrow=DataRow("┃", "┃", "┃"),
438 datarow=DataRow("┃", "┃", "┃"),
439 padding=1,
440 with_header_hide=None,
441 ),
442 "mixed_outline": TableFormat(
443 lineabove=Line("┍", "━", "┯", "┑"),
444 linebelowheader=Line("┝", "━", "┿", "┥"),
445 linebetweenrows=None,
446 linebelow=Line("┕", "━", "┷", "┙"),
447 headerrow=DataRow("│", "│", "│"),
448 datarow=DataRow("│", "│", "│"),
449 padding=1,
450 with_header_hide=None,
451 ),
452 "double_outline": TableFormat(
453 lineabove=Line("╔", "═", "╦", "╗"),
454 linebelowheader=Line("╠", "═", "╬", "╣"),
455 linebetweenrows=None,
456 linebelow=Line("╚", "═", "╩", "╝"),
457 headerrow=DataRow("║", "║", "║"),
458 datarow=DataRow("║", "║", "║"),
459 padding=1,
460 with_header_hide=None,
461 ),
462 "fancy_outline": TableFormat(
463 lineabove=Line("╒", "═", "╤", "╕"),
464 linebelowheader=Line("╞", "═", "╪", "╡"),
465 linebetweenrows=None,
466 linebelow=Line("╘", "═", "╧", "╛"),
467 headerrow=DataRow("│", "│", "│"),
468 datarow=DataRow("│", "│", "│"),
469 padding=1,
470 with_header_hide=None,
471 ),
472 "github": TableFormat(
473 lineabove=Line("|", "-", "|", "|"),
474 linebelowheader=Line("|", "-", "|", "|"),
475 linebetweenrows=None,
476 linebelow=None,
477 headerrow=DataRow("|", "|", "|"),
478 datarow=DataRow("|", "|", "|"),
479 padding=1,
480 with_header_hide=["lineabove"],
481 ),
482 "pipe": TableFormat(
483 lineabove=_pipe_line_with_colons,
484 linebelowheader=_pipe_line_with_colons,
485 linebetweenrows=None,
486 linebelow=None,
487 headerrow=DataRow("|", "|", "|"),
488 datarow=DataRow("|", "|", "|"),
489 padding=1,
490 with_header_hide=["lineabove"],
491 ),
492 "orgtbl": TableFormat(
493 lineabove=None,
494 linebelowheader=Line("|", "-", "+", "|"),
495 linebetweenrows=None,
496 linebelow=None,
497 headerrow=DataRow("|", "|", "|"),
498 datarow=DataRow("|", "|", "|"),
499 padding=1,
500 with_header_hide=None,
501 ),
502 "jira": TableFormat(
503 lineabove=None,
504 linebelowheader=None,
505 linebetweenrows=None,
506 linebelow=None,
507 headerrow=DataRow("||", "||", "||"),
508 datarow=DataRow("|", "|", "|"),
509 padding=1,
510 with_header_hide=None,
511 ),
512 "presto": TableFormat(
513 lineabove=None,
514 linebelowheader=Line("", "-", "+", ""),
515 linebetweenrows=None,
516 linebelow=None,
517 headerrow=DataRow("", "|", ""),
518 datarow=DataRow("", "|", ""),
519 padding=1,
520 with_header_hide=None,
521 ),
522 "pretty": TableFormat(
523 lineabove=Line("+", "-", "+", "+"),
524 linebelowheader=Line("+", "-", "+", "+"),
525 linebetweenrows=None,
526 linebelow=Line("+", "-", "+", "+"),
527 headerrow=DataRow("|", "|", "|"),
528 datarow=DataRow("|", "|", "|"),
529 padding=1,
530 with_header_hide=None,
531 ),
532 "psql": TableFormat(
533 lineabove=Line("+", "-", "+", "+"),
534 linebelowheader=Line("|", "-", "+", "|"),
535 linebetweenrows=None,
536 linebelow=Line("+", "-", "+", "+"),
537 headerrow=DataRow("|", "|", "|"),
538 datarow=DataRow("|", "|", "|"),
539 padding=1,
540 with_header_hide=None,
541 ),
542 "rst": TableFormat(
543 lineabove=Line("", "=", " ", ""),
544 linebelowheader=Line("", "=", " ", ""),
545 linebetweenrows=None,
546 linebelow=Line("", "=", " ", ""),
547 headerrow=DataRow("", " ", ""),
548 datarow=DataRow("", " ", ""),
549 padding=0,
550 with_header_hide=None,
551 ),
552 "mediawiki": TableFormat(
553 lineabove=Line(
554 '{| class="wikitable" style="text-align: left;"',
555 "",
556 "",
557 "\n|+ <!-- caption -->\n|-",
558 ),
559 linebelowheader=Line("|-", "", "", ""),
560 linebetweenrows=Line("|-", "", "", ""),
561 linebelow=Line("|}", "", "", ""),
562 headerrow=partial(_mediawiki_row_with_attrs, "!"),
563 datarow=partial(_mediawiki_row_with_attrs, "|"),
564 padding=0,
565 with_header_hide=None,
566 ),
567 "moinmoin": TableFormat(
568 lineabove=None,
569 linebelowheader=None,
570 linebetweenrows=None,
571 linebelow=None,
572 headerrow=partial(_moin_row_with_attrs, "||", header="'''"),
573 datarow=partial(_moin_row_with_attrs, "||"),
574 padding=1,
575 with_header_hide=None,
576 ),
577 "youtrack": TableFormat(
578 lineabove=None,
579 linebelowheader=None,
580 linebetweenrows=None,
581 linebelow=None,
582 headerrow=DataRow("|| ", " || ", " || "),
583 datarow=DataRow("| ", " | ", " |"),
584 padding=1,
585 with_header_hide=None,
586 ),
587 "html": TableFormat(
588 lineabove=_html_begin_table_without_header,
589 linebelowheader="",
590 linebetweenrows=None,
591 linebelow=Line("</tbody>\n</table>", "", "", ""),
592 headerrow=partial(_html_row_with_attrs, "th", False),
593 datarow=partial(_html_row_with_attrs, "td", False),
594 padding=0,
595 with_header_hide=["lineabove"],
596 ),
597 "unsafehtml": TableFormat(
598 lineabove=_html_begin_table_without_header,
599 linebelowheader="",
600 linebetweenrows=None,
601 linebelow=Line("</tbody>\n</table>", "", "", ""),
602 headerrow=partial(_html_row_with_attrs, "th", True),
603 datarow=partial(_html_row_with_attrs, "td", True),
604 padding=0,
605 with_header_hide=["lineabove"],
606 ),
607 "latex": TableFormat(
608 lineabove=_latex_line_begin_tabular,
609 linebelowheader=Line("\\hline", "", "", ""),
610 linebetweenrows=None,
611 linebelow=Line("\\hline\n\\end{tabular}", "", "", ""),
612 headerrow=_latex_row,
613 datarow=_latex_row,
614 padding=1,
615 with_header_hide=None,
616 ),
617 "latex_raw": TableFormat(
618 lineabove=_latex_line_begin_tabular,
619 linebelowheader=Line("\\hline", "", "", ""),
620 linebetweenrows=None,
621 linebelow=Line("\\hline\n\\end{tabular}", "", "", ""),
622 headerrow=partial(_latex_row, escrules={}),
623 datarow=partial(_latex_row, escrules={}),
624 padding=1,
625 with_header_hide=None,
626 ),
627 "latex_booktabs": TableFormat(
628 lineabove=partial(_latex_line_begin_tabular, booktabs=True),
629 linebelowheader=Line("\\midrule", "", "", ""),
630 linebetweenrows=None,
631 linebelow=Line("\\bottomrule\n\\end{tabular}", "", "", ""),
632 headerrow=_latex_row,
633 datarow=_latex_row,
634 padding=1,
635 with_header_hide=None,
636 ),
637 "latex_longtable": TableFormat(
638 lineabove=partial(_latex_line_begin_tabular, longtable=True),
639 linebelowheader=Line("\\hline\n\\endhead", "", "", ""),
640 linebetweenrows=None,
641 linebelow=Line("\\hline\n\\end{longtable}", "", "", ""),
642 headerrow=_latex_row,
643 datarow=_latex_row,
644 padding=1,
645 with_header_hide=None,
646 ),
647 "tsv": TableFormat(
648 lineabove=None,
649 linebelowheader=None,
650 linebetweenrows=None,
651 linebelow=None,
652 headerrow=DataRow("", "\t", ""),
653 datarow=DataRow("", "\t", ""),
654 padding=0,
655 with_header_hide=None,
656 ),
657 "textile": TableFormat(
658 lineabove=None,
659 linebelowheader=None,
660 linebetweenrows=None,
661 linebelow=None,
662 headerrow=DataRow("|_. ", "|_.", "|"),
663 datarow=_textile_row_with_attrs,
664 padding=1,
665 with_header_hide=None,
666 ),
667 "asciidoc": TableFormat(
668 lineabove=partial(_asciidoc_row, False),
669 linebelowheader=None,
670 linebetweenrows=None,
671 linebelow=Line("|====", "", "", ""),
672 headerrow=partial(_asciidoc_row, True),
673 datarow=partial(_asciidoc_row, False),
674 padding=1,
675 with_header_hide=["lineabove"],
676 ),
677}
680tabulate_formats = list(sorted(_table_formats.keys()))
682# The table formats for which multiline cells will be folded into subsequent
683# table rows. The key is the original format specified at the API. The value is
684# the format that will be used to represent the original format.
685multiline_formats = {
686 "plain": "plain",
687 "simple": "simple",
688 "grid": "grid",
689 "simple_grid": "simple_grid",
690 "rounded_grid": "rounded_grid",
691 "heavy_grid": "heavy_grid",
692 "mixed_grid": "mixed_grid",
693 "double_grid": "double_grid",
694 "fancy_grid": "fancy_grid",
695 "pipe": "pipe",
696 "orgtbl": "orgtbl",
697 "jira": "jira",
698 "presto": "presto",
699 "pretty": "pretty",
700 "psql": "psql",
701 "rst": "rst",
702}
704# TODO: Add multiline support for the remaining table formats:
705# - mediawiki: Replace \n with <br>
706# - moinmoin: TBD
707# - youtrack: TBD
708# - html: Replace \n with <br>
709# - latex*: Use "makecell" package: In header, replace X\nY with
710# \thead{X\\Y} and in data row, replace X\nY with \makecell{X\\Y}
711# - tsv: TBD
712# - textile: Replace \n with <br/> (must be well-formed XML)
714_multiline_codes = re.compile(r"\r|\n|\r\n")
715_multiline_codes_bytes = re.compile(b"\r|\n|\r\n")
717# Handle ANSI escape sequences for both control sequence introducer (CSI) and
718# operating system command (OSC). Both of these begin with 0x1b (or octal 033),
719# which will be shown below as ESC.
720#
721# CSI ANSI escape codes have the following format, defined in section 5.4 of ECMA-48:
722#
723# CSI: ESC followed by the '[' character (0x5b)
724# Parameter Bytes: 0..n bytes in the range 0x30-0x3f
725# Intermediate Bytes: 0..n bytes in the range 0x20-0x2f
726# Final Byte: a single byte in the range 0x40-0x7e
727#
728# Also include the terminal hyperlink sequences as described here:
729# https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
730#
731# OSC 8 ; params ; uri ST display_text OSC 8 ;; ST
732#
733# Example: \x1b]8;;https://example.com\x5ctext to show\x1b]8;;\x5c
734#
735# Where:
736# OSC: ESC followed by the ']' character (0x5d)
737# params: 0..n optional key value pairs separated by ':' (e.g. foo=bar:baz=qux:abc=123)
738# URI: the actual URI with protocol scheme (e.g. https://, file://, ftp://)
739# ST: ESC followed by the '\' character (0x5c)
740_esc = r"\x1b"
741_csi = rf"{_esc}\["
742_osc = rf"{_esc}\]"
743_st = rf"{_esc}\\"
745_ansi_escape_pat = rf"""
746 (
747 # terminal colors, etc
748 {_csi} # CSI
749 [\x30-\x3f]* # parameter bytes
750 [\x20-\x2f]* # intermediate bytes
751 [\x40-\x7e] # final byte
752 |
753 # terminal hyperlinks
754 {_osc}8; # OSC opening
755 (\w+=\w+:?)* # key=value params list (submatch 2)
756 ; # delimiter
757 ([^{_esc}]+) # URI - anything but ESC (submatch 3)
758 {_st} # ST
759 ([^{_esc}]+) # link text - anything but ESC (submatch 4)
760 {_osc}8;;{_st} # "closing" OSC sequence
761 )
762"""
763_ansi_codes = re.compile(_ansi_escape_pat, re.VERBOSE)
764_ansi_codes_bytes = re.compile(_ansi_escape_pat.encode("utf8"), re.VERBOSE)
765_ansi_color_reset_code = "\033[0m"
767_float_with_thousands_separators = re.compile(
768 r"^(([+-]?[0-9]{1,3})(?:,([0-9]{3}))*)?(?(1)\.[0-9]*|\.[0-9]+)?$"
769)
772def simple_separated_format(separator):
773 """Construct a simple TableFormat with columns separated by a separator.
775 >>> tsv = simple_separated_format("\\t") ; \
776 tabulate([["foo", 1], ["spam", 23]], tablefmt=tsv) == 'foo \\t 1\\nspam\\t23'
777 True
779 """
780 return TableFormat(
781 None,
782 None,
783 None,
784 None,
785 headerrow=DataRow("", separator, ""),
786 datarow=DataRow("", separator, ""),
787 padding=0,
788 with_header_hide=None,
789 )
792def _isnumber_with_thousands_separator(string):
793 """
794 >>> _isnumber_with_thousands_separator(".")
795 False
796 >>> _isnumber_with_thousands_separator("1")
797 True
798 >>> _isnumber_with_thousands_separator("1.")
799 True
800 >>> _isnumber_with_thousands_separator(".1")
801 True
802 >>> _isnumber_with_thousands_separator("1000")
803 False
804 >>> _isnumber_with_thousands_separator("1,000")
805 True
806 >>> _isnumber_with_thousands_separator("1,0000")
807 False
808 >>> _isnumber_with_thousands_separator("1,000.1234")
809 True
810 >>> _isnumber_with_thousands_separator(b"1,000.1234")
811 True
812 >>> _isnumber_with_thousands_separator("+1,000.1234")
813 True
814 >>> _isnumber_with_thousands_separator("-1,000.1234")
815 True
816 """
817 try:
818 string = string.decode()
819 except (UnicodeDecodeError, AttributeError):
820 pass
822 return bool(re.match(_float_with_thousands_separators, string))
825def _isconvertible(conv, string):
826 try:
827 conv(string)
828 return True
829 except (ValueError, TypeError):
830 return False
833def _isnumber(string):
834 """
835 >>> _isnumber("123.45")
836 True
837 >>> _isnumber("123")
838 True
839 >>> _isnumber("spam")
840 False
841 >>> _isnumber("123e45678")
842 False
843 >>> _isnumber("inf")
844 True
845 """
846 if not _isconvertible(float, string):
847 return False
848 elif isinstance(string, (str, bytes)) and (
849 math.isinf(float(string)) or math.isnan(float(string))
850 ):
851 return string.lower() in ["inf", "-inf", "nan"]
852 return True
855def _isint(string, inttype=int):
856 """
857 >>> _isint("123")
858 True
859 >>> _isint("123.45")
860 False
861 """
862 return (
863 type(string) is inttype
864 or isinstance(string, (bytes, str))
865 and _isconvertible(inttype, string)
866 )
869def _isbool(string):
870 """
871 >>> _isbool(True)
872 True
873 >>> _isbool("False")
874 True
875 >>> _isbool(1)
876 False
877 """
878 return type(string) is bool or (
879 isinstance(string, (bytes, str)) and string in ("True", "False")
880 )
883def _type(string, has_invisible=True, numparse=True):
884 """The least generic type (type(None), int, float, str, unicode).
886 >>> _type(None) is type(None)
887 True
888 >>> _type("foo") is type("")
889 True
890 >>> _type("1") is type(1)
891 True
892 >>> _type('\x1b[31m42\x1b[0m') is type(42)
893 True
894 >>> _type('\x1b[31m42\x1b[0m') is type(42)
895 True
897 """
899 if has_invisible and isinstance(string, (str, bytes)):
900 string = _strip_ansi(string)
902 if string is None:
903 return type(None)
904 elif hasattr(string, "isoformat"): # datetime.datetime, date, and time
905 return str
906 elif _isbool(string):
907 return bool
908 elif _isint(string) and numparse:
909 return int
910 elif _isnumber(string) and numparse:
911 return float
912 elif isinstance(string, bytes):
913 return bytes
914 else:
915 return str
918def _afterpoint(string):
919 """Symbols after a decimal point, -1 if the string lacks the decimal point.
921 >>> _afterpoint("123.45")
922 2
923 >>> _afterpoint("1001")
924 -1
925 >>> _afterpoint("eggs")
926 -1
927 >>> _afterpoint("123e45")
928 2
929 >>> _afterpoint("123,456.78")
930 2
932 """
933 if _isnumber(string) or _isnumber_with_thousands_separator(string):
934 if _isint(string):
935 return -1
936 else:
937 pos = string.rfind(".")
938 pos = string.lower().rfind("e") if pos < 0 else pos
939 if pos >= 0:
940 return len(string) - pos - 1
941 else:
942 return -1 # no point
943 else:
944 return -1 # not a number
947def _padleft(width, s):
948 """Flush right.
950 >>> _padleft(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430'
951 True
953 """
954 fmt = "{0:>%ds}" % width
955 return fmt.format(s)
958def _padright(width, s):
959 """Flush left.
961 >>> _padright(6, '\u044f\u0439\u0446\u0430') == '\u044f\u0439\u0446\u0430 '
962 True
964 """
965 fmt = "{0:<%ds}" % width
966 return fmt.format(s)
969def _padboth(width, s):
970 """Center string.
972 >>> _padboth(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430 '
973 True
975 """
976 fmt = "{0:^%ds}" % width
977 return fmt.format(s)
980def _padnone(ignore_width, s):
981 return s
984def _strip_ansi(s):
985 r"""Remove ANSI escape sequences, both CSI (color codes, etc) and OSC hyperlinks.
987 CSI sequences are simply removed from the output, while OSC hyperlinks are replaced
988 with the link text. Note: it may be desirable to show the URI instead but this is not
989 supported.
991 >>> repr(_strip_ansi('\x1B]8;;https://example.com\x1B\\This is a link\x1B]8;;\x1B\\'))
992 "'This is a link'"
994 >>> repr(_strip_ansi('\x1b[31mred\x1b[0m text'))
995 "'red text'"
997 """
998 if isinstance(s, str):
999 return _ansi_codes.sub(r"\4", s)
1000 else: # a bytestring
1001 return _ansi_codes_bytes.sub(r"\4", s)
1004def _visible_width(s):
1005 """Visible width of a printed string. ANSI color codes are removed.
1007 >>> _visible_width('\x1b[31mhello\x1b[0m'), _visible_width("world")
1008 (5, 5)
1010 """
1011 # optional wide-character support
1012 if wcwidth is not None and WIDE_CHARS_MODE:
1013 len_fn = wcwidth.wcswidth
1014 else:
1015 len_fn = len
1016 if isinstance(s, (str, bytes)):
1017 return len_fn(_strip_ansi(s))
1018 else:
1019 return len_fn(str(s))
1022def _is_multiline(s):
1023 if isinstance(s, str):
1024 return bool(re.search(_multiline_codes, s))
1025 else: # a bytestring
1026 return bool(re.search(_multiline_codes_bytes, s))
1029def _multiline_width(multiline_s, line_width_fn=len):
1030 """Visible width of a potentially multiline content."""
1031 return max(map(line_width_fn, re.split("[\r\n]", multiline_s)))
1034def _choose_width_fn(has_invisible, enable_widechars, is_multiline):
1035 """Return a function to calculate visible cell width."""
1036 if has_invisible:
1037 line_width_fn = _visible_width
1038 elif enable_widechars: # optional wide-character support if available
1039 line_width_fn = wcwidth.wcswidth
1040 else:
1041 line_width_fn = len
1042 if is_multiline:
1043 width_fn = lambda s: _multiline_width(s, line_width_fn) # noqa
1044 else:
1045 width_fn = line_width_fn
1046 return width_fn
1049def _align_column_choose_padfn(strings, alignment, has_invisible):
1050 if alignment == "right":
1051 if not PRESERVE_WHITESPACE:
1052 strings = [s.strip() for s in strings]
1053 padfn = _padleft
1054 elif alignment == "center":
1055 if not PRESERVE_WHITESPACE:
1056 strings = [s.strip() for s in strings]
1057 padfn = _padboth
1058 elif alignment == "decimal":
1059 if has_invisible:
1060 decimals = [_afterpoint(_strip_ansi(s)) for s in strings]
1061 else:
1062 decimals = [_afterpoint(s) for s in strings]
1063 maxdecimals = max(decimals)
1064 strings = [s + (maxdecimals - decs) * " " for s, decs in zip(strings, decimals)]
1065 padfn = _padleft
1066 elif not alignment:
1067 padfn = _padnone
1068 else:
1069 if not PRESERVE_WHITESPACE:
1070 strings = [s.strip() for s in strings]
1071 padfn = _padright
1072 return strings, padfn
1075def _align_column_choose_width_fn(has_invisible, enable_widechars, is_multiline):
1076 if has_invisible:
1077 line_width_fn = _visible_width
1078 elif enable_widechars: # optional wide-character support if available
1079 line_width_fn = wcwidth.wcswidth
1080 else:
1081 line_width_fn = len
1082 if is_multiline:
1083 width_fn = lambda s: _align_column_multiline_width(s, line_width_fn) # noqa
1084 else:
1085 width_fn = line_width_fn
1086 return width_fn
1089def _align_column_multiline_width(multiline_s, line_width_fn=len):
1090 """Visible width of a potentially multiline content."""
1091 return list(map(line_width_fn, re.split("[\r\n]", multiline_s)))
1094def _flat_list(nested_list):
1095 ret = []
1096 for item in nested_list:
1097 if isinstance(item, list):
1098 for subitem in item:
1099 ret.append(subitem)
1100 else:
1101 ret.append(item)
1102 return ret
1105def _align_column(
1106 strings,
1107 alignment,
1108 minwidth=0,
1109 has_invisible=True,
1110 enable_widechars=False,
1111 is_multiline=False,
1112):
1113 """[string] -> [padded_string]"""
1114 strings, padfn = _align_column_choose_padfn(strings, alignment, has_invisible)
1115 width_fn = _align_column_choose_width_fn(
1116 has_invisible, enable_widechars, is_multiline
1117 )
1119 s_widths = list(map(width_fn, strings))
1120 maxwidth = max(max(_flat_list(s_widths)), minwidth)
1121 # TODO: refactor column alignment in single-line and multiline modes
1122 if is_multiline:
1123 if not enable_widechars and not has_invisible:
1124 padded_strings = [
1125 "\n".join([padfn(maxwidth, s) for s in ms.splitlines()])
1126 for ms in strings
1127 ]
1128 else:
1129 # enable wide-character width corrections
1130 s_lens = [[len(s) for s in re.split("[\r\n]", ms)] for ms in strings]
1131 visible_widths = [
1132 [maxwidth - (w - l) for w, l in zip(mw, ml)]
1133 for mw, ml in zip(s_widths, s_lens)
1134 ]
1135 # wcswidth and _visible_width don't count invisible characters;
1136 # padfn doesn't need to apply another correction
1137 padded_strings = [
1138 "\n".join([padfn(w, s) for s, w in zip((ms.splitlines() or ms), mw)])
1139 for ms, mw in zip(strings, visible_widths)
1140 ]
1141 else: # single-line cell values
1142 if not enable_widechars and not has_invisible:
1143 padded_strings = [padfn(maxwidth, s) for s in strings]
1144 else:
1145 # enable wide-character width corrections
1146 s_lens = list(map(len, strings))
1147 visible_widths = [maxwidth - (w - l) for w, l in zip(s_widths, s_lens)]
1148 # wcswidth and _visible_width don't count invisible characters;
1149 # padfn doesn't need to apply another correction
1150 padded_strings = [padfn(w, s) for s, w in zip(strings, visible_widths)]
1151 return padded_strings
1154def _more_generic(type1, type2):
1155 types = {
1156 type(None): 0,
1157 bool: 1,
1158 int: 2,
1159 float: 3,
1160 bytes: 4,
1161 str: 5,
1162 }
1163 invtypes = {
1164 5: str,
1165 4: bytes,
1166 3: float,
1167 2: int,
1168 1: bool,
1169 0: type(None),
1170 }
1171 moregeneric = max(types.get(type1, 5), types.get(type2, 5))
1172 return invtypes[moregeneric]
1175def _column_type(strings, has_invisible=True, numparse=True):
1176 """The least generic type all column values are convertible to.
1178 >>> _column_type([True, False]) is bool
1179 True
1180 >>> _column_type(["1", "2"]) is int
1181 True
1182 >>> _column_type(["1", "2.3"]) is float
1183 True
1184 >>> _column_type(["1", "2.3", "four"]) is str
1185 True
1186 >>> _column_type(["four", '\u043f\u044f\u0442\u044c']) is str
1187 True
1188 >>> _column_type([None, "brux"]) is str
1189 True
1190 >>> _column_type([1, 2, None]) is int
1191 True
1192 >>> import datetime as dt
1193 >>> _column_type([dt.datetime(1991,2,19), dt.time(17,35)]) is str
1194 True
1196 """
1197 types = [_type(s, has_invisible, numparse) for s in strings]
1198 return reduce(_more_generic, types, bool)
1201def _format(val, valtype, floatfmt, intfmt, missingval="", has_invisible=True):
1202 """Format a value according to its type.
1204 Unicode is supported:
1206 >>> hrow = ['\u0431\u0443\u043a\u0432\u0430', '\u0446\u0438\u0444\u0440\u0430'] ; \
1207 tbl = [['\u0430\u0437', 2], ['\u0431\u0443\u043a\u0438', 4]] ; \
1208 good_result = '\\u0431\\u0443\\u043a\\u0432\\u0430 \\u0446\\u0438\\u0444\\u0440\\u0430\\n------- -------\\n\\u0430\\u0437 2\\n\\u0431\\u0443\\u043a\\u0438 4' ; \
1209 tabulate(tbl, headers=hrow) == good_result
1210 True
1212 """ # noqa
1213 if val is None:
1214 return missingval
1216 if valtype is str:
1217 return f"{val}"
1218 elif valtype is int:
1219 return format(val, intfmt)
1220 elif valtype is bytes:
1221 try:
1222 return str(val, "ascii")
1223 except (TypeError, UnicodeDecodeError):
1224 return str(val)
1225 elif valtype is float:
1226 is_a_colored_number = has_invisible and isinstance(val, (str, bytes))
1227 if is_a_colored_number:
1228 raw_val = _strip_ansi(val)
1229 formatted_val = format(float(raw_val), floatfmt)
1230 return val.replace(raw_val, formatted_val)
1231 else:
1232 return format(float(val), floatfmt)
1233 else:
1234 return f"{val}"
1237def _align_header(
1238 header, alignment, width, visible_width, is_multiline=False, width_fn=None
1239):
1240 "Pad string header to width chars given known visible_width of the header."
1241 if is_multiline:
1242 header_lines = re.split(_multiline_codes, header)
1243 padded_lines = [
1244 _align_header(h, alignment, width, width_fn(h)) for h in header_lines
1245 ]
1246 return "\n".join(padded_lines)
1247 # else: not multiline
1248 ninvisible = len(header) - visible_width
1249 width += ninvisible
1250 if alignment == "left":
1251 return _padright(width, header)
1252 elif alignment == "center":
1253 return _padboth(width, header)
1254 elif not alignment:
1255 return f"{header}"
1256 else:
1257 return _padleft(width, header)
1260def _remove_separating_lines(rows):
1261 if type(rows) == list:
1262 separating_lines = []
1263 sans_rows = []
1264 for index, row in enumerate(rows):
1265 if _is_separating_line(row):
1266 separating_lines.append(index)
1267 else:
1268 sans_rows.append(row)
1269 return sans_rows, separating_lines
1270 else:
1271 return rows, None
1274def _reinsert_separating_lines(rows, separating_lines):
1275 if separating_lines:
1276 for index in separating_lines:
1277 rows.insert(index, SEPARATING_LINE)
1280def _prepend_row_index(rows, index):
1281 """Add a left-most index column."""
1282 if index is None or index is False:
1283 return rows
1284 if isinstance(index, Sized) and len(index) != len(rows):
1285 raise ValueError(
1286 "index must be as long as the number of data rows: "
1287 + "len(index)={} len(rows)={}".format(len(index), len(rows))
1288 )
1289 sans_rows, separating_lines = _remove_separating_lines(rows)
1290 new_rows = []
1291 index_iter = iter(index)
1292 for row in sans_rows:
1293 index_v = next(index_iter)
1294 new_rows.append([index_v] + list(row))
1295 rows = new_rows
1296 _reinsert_separating_lines(rows, separating_lines)
1297 return rows
1300def _bool(val):
1301 "A wrapper around standard bool() which doesn't throw on NumPy arrays"
1302 try:
1303 return bool(val)
1304 except ValueError: # val is likely to be a numpy array with many elements
1305 return False
1308def _normalize_tabular_data(tabular_data, headers, showindex="default"):
1309 """Transform a supported data type to a list of lists, and a list of headers.
1311 Supported tabular data types:
1313 * list-of-lists or another iterable of iterables
1315 * list of named tuples (usually used with headers="keys")
1317 * list of dicts (usually used with headers="keys")
1319 * list of OrderedDicts (usually used with headers="keys")
1321 * list of dataclasses (Python 3.7+ only, usually used with headers="keys")
1323 * 2D NumPy arrays
1325 * NumPy record arrays (usually used with headers="keys")
1327 * dict of iterables (usually used with headers="keys")
1329 * pandas.DataFrame (usually used with headers="keys")
1331 The first row can be used as headers if headers="firstrow",
1332 column indices can be used as headers if headers="keys".
1334 If showindex="default", show row indices of the pandas.DataFrame.
1335 If showindex="always", show row indices for all types of data.
1336 If showindex="never", don't show row indices for all types of data.
1337 If showindex is an iterable, show its values as row indices.
1339 """
1341 try:
1342 bool(headers)
1343 is_headers2bool_broken = False # noqa
1344 except ValueError: # numpy.ndarray, pandas.core.index.Index, ...
1345 is_headers2bool_broken = True # noqa
1346 headers = list(headers)
1348 index = None
1349 if hasattr(tabular_data, "keys") and hasattr(tabular_data, "values"):
1350 # dict-like and pandas.DataFrame?
1351 if hasattr(tabular_data.values, "__call__"):
1352 # likely a conventional dict
1353 keys = tabular_data.keys()
1354 rows = list(
1355 izip_longest(*tabular_data.values())
1356 ) # columns have to be transposed
1357 elif hasattr(tabular_data, "index"):
1358 # values is a property, has .index => it's likely a pandas.DataFrame (pandas 0.11.0)
1359 keys = list(tabular_data)
1360 if (
1361 showindex in ["default", "always", True]
1362 and tabular_data.index.name is not None
1363 ):
1364 if isinstance(tabular_data.index.name, list):
1365 keys[:0] = tabular_data.index.name
1366 else:
1367 keys[:0] = [tabular_data.index.name]
1368 vals = tabular_data.values # values matrix doesn't need to be transposed
1369 # for DataFrames add an index per default
1370 index = list(tabular_data.index)
1371 rows = [list(row) for row in vals]
1372 else:
1373 raise ValueError("tabular data doesn't appear to be a dict or a DataFrame")
1375 if headers == "keys":
1376 headers = list(map(str, keys)) # headers should be strings
1378 else: # it's a usual iterable of iterables, or a NumPy array, or an iterable of dataclasses
1379 rows = list(tabular_data)
1381 if headers == "keys" and not rows:
1382 # an empty table (issue #81)
1383 headers = []
1384 elif (
1385 headers == "keys"
1386 and hasattr(tabular_data, "dtype")
1387 and getattr(tabular_data.dtype, "names")
1388 ):
1389 # numpy record array
1390 headers = tabular_data.dtype.names
1391 elif (
1392 headers == "keys"
1393 and len(rows) > 0
1394 and isinstance(rows[0], tuple)
1395 and hasattr(rows[0], "_fields")
1396 ):
1397 # namedtuple
1398 headers = list(map(str, rows[0]._fields))
1399 elif len(rows) > 0 and hasattr(rows[0], "keys") and hasattr(rows[0], "values"):
1400 # dict-like object
1401 uniq_keys = set() # implements hashed lookup
1402 keys = [] # storage for set
1403 if headers == "firstrow":
1404 firstdict = rows[0] if len(rows) > 0 else {}
1405 keys.extend(firstdict.keys())
1406 uniq_keys.update(keys)
1407 rows = rows[1:]
1408 for row in rows:
1409 for k in row.keys():
1410 # Save unique items in input order
1411 if k not in uniq_keys:
1412 keys.append(k)
1413 uniq_keys.add(k)
1414 if headers == "keys":
1415 headers = keys
1416 elif isinstance(headers, dict):
1417 # a dict of headers for a list of dicts
1418 headers = [headers.get(k, k) for k in keys]
1419 headers = list(map(str, headers))
1420 elif headers == "firstrow":
1421 if len(rows) > 0:
1422 headers = [firstdict.get(k, k) for k in keys]
1423 headers = list(map(str, headers))
1424 else:
1425 headers = []
1426 elif headers:
1427 raise ValueError(
1428 "headers for a list of dicts is not a dict or a keyword"
1429 )
1430 rows = [[row.get(k) for k in keys] for row in rows]
1432 elif (
1433 headers == "keys"
1434 and hasattr(tabular_data, "description")
1435 and hasattr(tabular_data, "fetchone")
1436 and hasattr(tabular_data, "rowcount")
1437 ):
1438 # Python Database API cursor object (PEP 0249)
1439 # print tabulate(cursor, headers='keys')
1440 headers = [column[0] for column in tabular_data.description]
1442 elif (
1443 dataclasses is not None
1444 and len(rows) > 0
1445 and dataclasses.is_dataclass(rows[0])
1446 ):
1447 # Python 3.7+'s dataclass
1448 field_names = [field.name for field in dataclasses.fields(rows[0])]
1449 if headers == "keys":
1450 headers = field_names
1451 rows = [[getattr(row, f) for f in field_names] for row in rows]
1453 elif headers == "keys" and len(rows) > 0:
1454 # keys are column indices
1455 headers = list(map(str, range(len(rows[0]))))
1457 # take headers from the first row if necessary
1458 if headers == "firstrow" and len(rows) > 0:
1459 if index is not None:
1460 headers = [index[0]] + list(rows[0])
1461 index = index[1:]
1462 else:
1463 headers = rows[0]
1464 headers = list(map(str, headers)) # headers should be strings
1465 rows = rows[1:]
1466 elif headers == "firstrow":
1467 headers = []
1469 headers = list(map(str, headers))
1470 # rows = list(map(list, rows))
1471 rows = list(map(lambda r: r if _is_separating_line(r) else list(r), rows))
1473 # add or remove an index column
1474 showindex_is_a_str = type(showindex) in [str, bytes]
1475 if showindex == "default" and index is not None:
1476 rows = _prepend_row_index(rows, index)
1477 elif isinstance(showindex, Sized) and not showindex_is_a_str:
1478 rows = _prepend_row_index(rows, list(showindex))
1479 elif isinstance(showindex, Iterable) and not showindex_is_a_str:
1480 rows = _prepend_row_index(rows, showindex)
1481 elif showindex == "always" or (_bool(showindex) and not showindex_is_a_str):
1482 if index is None:
1483 index = list(range(len(rows)))
1484 rows = _prepend_row_index(rows, index)
1485 elif showindex == "never" or (not _bool(showindex) and not showindex_is_a_str):
1486 pass
1488 # pad with empty headers for initial columns if necessary
1489 if headers and len(rows) > 0:
1490 nhs = len(headers)
1491 ncols = len(rows[0])
1492 if nhs < ncols:
1493 headers = [""] * (ncols - nhs) + headers
1495 return rows, headers
1498def _wrap_text_to_colwidths(list_of_lists, colwidths, numparses=True):
1499 numparses = _expand_iterable(numparses, len(list_of_lists[0]), True)
1501 result = []
1503 for row in list_of_lists:
1504 new_row = []
1505 for cell, width, numparse in zip(row, colwidths, numparses):
1506 if _isnumber(cell) and numparse:
1507 new_row.append(cell)
1508 continue
1510 if width is not None:
1511 wrapper = _CustomTextWrap(width=width)
1512 # Cast based on our internal type handling
1513 # Any future custom formatting of types (such as datetimes)
1514 # may need to be more explicit than just `str` of the object
1515 casted_cell = (
1516 str(cell) if _isnumber(cell) else _type(cell, numparse)(cell)
1517 )
1518 wrapped = wrapper.wrap(casted_cell)
1519 new_row.append("\n".join(wrapped))
1520 else:
1521 new_row.append(cell)
1522 result.append(new_row)
1524 return result
1527def _to_str(s, encoding="utf8", errors="ignore"):
1528 """
1529 A type safe wrapper for converting a bytestring to str. This is essentially just
1530 a wrapper around .decode() intended for use with things like map(), but with some
1531 specific behavior:
1533 1. if the given parameter is not a bytestring, it is returned unmodified
1534 2. decode() is called for the given parameter and assumes utf8 encoding, but the
1535 default error behavior is changed from 'strict' to 'ignore'
1537 >>> repr(_to_str(b'foo'))
1538 "'foo'"
1540 >>> repr(_to_str('foo'))
1541 "'foo'"
1543 >>> repr(_to_str(42))
1544 "'42'"
1546 """
1547 if isinstance(s, bytes):
1548 return s.decode(encoding=encoding, errors=errors)
1549 return str(s)
1552def tabulate(
1553 tabular_data,
1554 headers=(),
1555 tablefmt="simple",
1556 floatfmt=_DEFAULT_FLOATFMT,
1557 intfmt=_DEFAULT_INTFMT,
1558 numalign=_DEFAULT_ALIGN,
1559 stralign=_DEFAULT_ALIGN,
1560 missingval=_DEFAULT_MISSINGVAL,
1561 showindex="default",
1562 disable_numparse=False,
1563 colalign=None,
1564 maxcolwidths=None,
1565 rowalign=None,
1566 maxheadercolwidths=None,
1567):
1568 """Format a fixed width table for pretty printing.
1570 >>> print(tabulate([[1, 2.34], [-56, "8.999"], ["2", "10001"]]))
1571 --- ---------
1572 1 2.34
1573 -56 8.999
1574 2 10001
1575 --- ---------
1577 The first required argument (`tabular_data`) can be a
1578 list-of-lists (or another iterable of iterables), a list of named
1579 tuples, a dictionary of iterables, an iterable of dictionaries,
1580 an iterable of dataclasses (Python 3.7+), a two-dimensional NumPy array,
1581 NumPy record array, or a Pandas' dataframe.
1584 Table headers
1585 -------------
1587 To print nice column headers, supply the second argument (`headers`):
1589 - `headers` can be an explicit list of column headers
1590 - if `headers="firstrow"`, then the first row of data is used
1591 - if `headers="keys"`, then dictionary keys or column indices are used
1593 Otherwise a headerless table is produced.
1595 If the number of headers is less than the number of columns, they
1596 are supposed to be names of the last columns. This is consistent
1597 with the plain-text format of R and Pandas' dataframes.
1599 >>> print(tabulate([["sex","age"],["Alice","F",24],["Bob","M",19]],
1600 ... headers="firstrow"))
1601 sex age
1602 ----- ----- -----
1603 Alice F 24
1604 Bob M 19
1606 By default, pandas.DataFrame data have an additional column called
1607 row index. To add a similar column to all other types of data,
1608 use `showindex="always"` or `showindex=True`. To suppress row indices
1609 for all types of data, pass `showindex="never" or `showindex=False`.
1610 To add a custom row index column, pass `showindex=some_iterable`.
1612 >>> print(tabulate([["F",24],["M",19]], showindex="always"))
1613 - - --
1614 0 F 24
1615 1 M 19
1616 - - --
1619 Column alignment
1620 ----------------
1622 `tabulate` tries to detect column types automatically, and aligns
1623 the values properly. By default it aligns decimal points of the
1624 numbers (or flushes integer numbers to the right), and flushes
1625 everything else to the left. Possible column alignments
1626 (`numalign`, `stralign`) are: "right", "center", "left", "decimal"
1627 (only for `numalign`), and None (to disable alignment).
1630 Table formats
1631 -------------
1633 `intfmt` is a format specification used for columns which
1634 contain numeric data without a decimal point. This can also be
1635 a list or tuple of format strings, one per column.
1637 `floatfmt` is a format specification used for columns which
1638 contain numeric data with a decimal point. This can also be
1639 a list or tuple of format strings, one per column.
1641 `None` values are replaced with a `missingval` string (like
1642 `floatfmt`, this can also be a list of values for different
1643 columns):
1645 >>> print(tabulate([["spam", 1, None],
1646 ... ["eggs", 42, 3.14],
1647 ... ["other", None, 2.7]], missingval="?"))
1648 ----- -- ----
1649 spam 1 ?
1650 eggs 42 3.14
1651 other ? 2.7
1652 ----- -- ----
1654 Various plain-text table formats (`tablefmt`) are supported:
1655 'plain', 'simple', 'grid', 'pipe', 'orgtbl', 'rst', 'mediawiki',
1656 'latex', 'latex_raw', 'latex_booktabs', 'latex_longtable' and tsv.
1657 Variable `tabulate_formats`contains the list of currently supported formats.
1659 "plain" format doesn't use any pseudographics to draw tables,
1660 it separates columns with a double space:
1662 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1663 ... ["strings", "numbers"], "plain"))
1664 strings numbers
1665 spam 41.9999
1666 eggs 451
1668 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="plain"))
1669 spam 41.9999
1670 eggs 451
1672 "simple" format is like Pandoc simple_tables:
1674 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1675 ... ["strings", "numbers"], "simple"))
1676 strings numbers
1677 --------- ---------
1678 spam 41.9999
1679 eggs 451
1681 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="simple"))
1682 ---- --------
1683 spam 41.9999
1684 eggs 451
1685 ---- --------
1687 "grid" is similar to tables produced by Emacs table.el package or
1688 Pandoc grid_tables:
1690 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1691 ... ["strings", "numbers"], "grid"))
1692 +-----------+-----------+
1693 | strings | numbers |
1694 +===========+===========+
1695 | spam | 41.9999 |
1696 +-----------+-----------+
1697 | eggs | 451 |
1698 +-----------+-----------+
1700 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="grid"))
1701 +------+----------+
1702 | spam | 41.9999 |
1703 +------+----------+
1704 | eggs | 451 |
1705 +------+----------+
1707 "simple_grid" draws a grid using single-line box-drawing
1708 characters:
1710 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1711 ... ["strings", "numbers"], "simple_grid"))
1712 ┌───────────┬───────────┐
1713 │ strings │ numbers │
1714 ├───────────┼───────────┤
1715 │ spam │ 41.9999 │
1716 ├───────────┼───────────┤
1717 │ eggs │ 451 │
1718 └───────────┴───────────┘
1720 "rounded_grid" draws a grid using single-line box-drawing
1721 characters with rounded corners:
1723 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1724 ... ["strings", "numbers"], "rounded_grid"))
1725 ╭───────────┬───────────╮
1726 │ strings │ numbers │
1727 ├───────────┼───────────┤
1728 │ spam │ 41.9999 │
1729 ├───────────┼───────────┤
1730 │ eggs │ 451 │
1731 ╰───────────┴───────────╯
1733 "heavy_grid" draws a grid using bold (thick) single-line box-drawing
1734 characters:
1736 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1737 ... ["strings", "numbers"], "heavy_grid"))
1738 ┏━━━━━━━━━━━┳━━━━━━━━━━━┓
1739 ┃ strings ┃ numbers ┃
1740 ┣━━━━━━━━━━━╋━━━━━━━━━━━┫
1741 ┃ spam ┃ 41.9999 ┃
1742 ┣━━━━━━━━━━━╋━━━━━━━━━━━┫
1743 ┃ eggs ┃ 451 ┃
1744 ┗━━━━━━━━━━━┻━━━━━━━━━━━┛
1746 "mixed_grid" draws a grid using a mix of light (thin) and heavy (thick) lines
1747 box-drawing characters:
1749 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1750 ... ["strings", "numbers"], "mixed_grid"))
1751 ┍━━━━━━━━━━━┯━━━━━━━━━━━┑
1752 │ strings │ numbers │
1753 ┝━━━━━━━━━━━┿━━━━━━━━━━━┥
1754 │ spam │ 41.9999 │
1755 ├───────────┼───────────┤
1756 │ eggs │ 451 │
1757 ┕━━━━━━━━━━━┷━━━━━━━━━━━┙
1759 "double_grid" draws a grid using double-line box-drawing
1760 characters:
1762 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1763 ... ["strings", "numbers"], "double_grid"))
1764 ╔═══════════╦═══════════╗
1765 ║ strings ║ numbers ║
1766 ╠═══════════╬═══════════╣
1767 ║ spam ║ 41.9999 ║
1768 ╠═══════════╬═══════════╣
1769 ║ eggs ║ 451 ║
1770 ╚═══════════╩═══════════╝
1772 "fancy_grid" draws a grid using a mix of single and
1773 double-line box-drawing characters:
1775 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1776 ... ["strings", "numbers"], "fancy_grid"))
1777 ╒═══════════╤═══════════╕
1778 │ strings │ numbers │
1779 ╞═══════════╪═══════════╡
1780 │ spam │ 41.9999 │
1781 ├───────────┼───────────┤
1782 │ eggs │ 451 │
1783 ╘═══════════╧═══════════╛
1785 "outline" is the same as the "grid" format but doesn't draw lines between rows:
1787 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1788 ... ["strings", "numbers"], "outline"))
1789 +-----------+-----------+
1790 | strings | numbers |
1791 +===========+===========+
1792 | spam | 41.9999 |
1793 | eggs | 451 |
1794 +-----------+-----------+
1796 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="outline"))
1797 +------+----------+
1798 | spam | 41.9999 |
1799 | eggs | 451 |
1800 +------+----------+
1802 "simple_outline" is the same as the "simple_grid" format but doesn't draw lines between rows:
1804 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1805 ... ["strings", "numbers"], "simple_outline"))
1806 ┌───────────┬───────────┐
1807 │ strings │ numbers │
1808 ├───────────┼───────────┤
1809 │ spam │ 41.9999 │
1810 │ eggs │ 451 │
1811 └───────────┴───────────┘
1813 "rounded_outline" is the same as the "rounded_grid" format but doesn't draw lines between rows:
1815 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1816 ... ["strings", "numbers"], "rounded_outline"))
1817 ╭───────────┬───────────╮
1818 │ strings │ numbers │
1819 ├───────────┼───────────┤
1820 │ spam │ 41.9999 │
1821 │ eggs │ 451 │
1822 ╰───────────┴───────────╯
1824 "heavy_outline" is the same as the "heavy_grid" format but doesn't draw lines between rows:
1826 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1827 ... ["strings", "numbers"], "heavy_outline"))
1828 ┏━━━━━━━━━━━┳━━━━━━━━━━━┓
1829 ┃ strings ┃ numbers ┃
1830 ┣━━━━━━━━━━━╋━━━━━━━━━━━┫
1831 ┃ spam ┃ 41.9999 ┃
1832 ┃ eggs ┃ 451 ┃
1833 ┗━━━━━━━━━━━┻━━━━━━━━━━━┛
1835 "mixed_outline" is the same as the "mixed_grid" format but doesn't draw lines between rows:
1837 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1838 ... ["strings", "numbers"], "mixed_outline"))
1839 ┍━━━━━━━━━━━┯━━━━━━━━━━━┑
1840 │ strings │ numbers │
1841 ┝━━━━━━━━━━━┿━━━━━━━━━━━┥
1842 │ spam │ 41.9999 │
1843 │ eggs │ 451 │
1844 ┕━━━━━━━━━━━┷━━━━━━━━━━━┙
1846 "double_outline" is the same as the "double_grid" format but doesn't draw lines between rows:
1848 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1849 ... ["strings", "numbers"], "double_outline"))
1850 ╔═══════════╦═══════════╗
1851 ║ strings ║ numbers ║
1852 ╠═══════════╬═══════════╣
1853 ║ spam ║ 41.9999 ║
1854 ║ eggs ║ 451 ║
1855 ╚═══════════╩═══════════╝
1857 "fancy_outline" is the same as the "fancy_grid" format but doesn't draw lines between rows:
1859 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1860 ... ["strings", "numbers"], "fancy_outline"))
1861 ╒═══════════╤═══════════╕
1862 │ strings │ numbers │
1863 ╞═══════════╪═══════════╡
1864 │ spam │ 41.9999 │
1865 │ eggs │ 451 │
1866 ╘═══════════╧═══════════╛
1868 "pipe" is like tables in PHP Markdown Extra extension or Pandoc
1869 pipe_tables:
1871 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1872 ... ["strings", "numbers"], "pipe"))
1873 | strings | numbers |
1874 |:----------|----------:|
1875 | spam | 41.9999 |
1876 | eggs | 451 |
1878 "presto" is like tables produce by the Presto CLI:
1880 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1881 ... ["strings", "numbers"], "presto"))
1882 strings | numbers
1883 -----------+-----------
1884 spam | 41.9999
1885 eggs | 451
1887 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="pipe"))
1888 |:-----|---------:|
1889 | spam | 41.9999 |
1890 | eggs | 451 |
1892 "orgtbl" is like tables in Emacs org-mode and orgtbl-mode. They
1893 are slightly different from "pipe" format by not using colons to
1894 define column alignment, and using a "+" sign to indicate line
1895 intersections:
1897 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1898 ... ["strings", "numbers"], "orgtbl"))
1899 | strings | numbers |
1900 |-----------+-----------|
1901 | spam | 41.9999 |
1902 | eggs | 451 |
1905 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="orgtbl"))
1906 | spam | 41.9999 |
1907 | eggs | 451 |
1909 "rst" is like a simple table format from reStructuredText; please
1910 note that reStructuredText accepts also "grid" tables:
1912 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1913 ... ["strings", "numbers"], "rst"))
1914 ========= =========
1915 strings numbers
1916 ========= =========
1917 spam 41.9999
1918 eggs 451
1919 ========= =========
1921 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="rst"))
1922 ==== ========
1923 spam 41.9999
1924 eggs 451
1925 ==== ========
1927 "mediawiki" produces a table markup used in Wikipedia and on other
1928 MediaWiki-based sites:
1930 >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]],
1931 ... headers="firstrow", tablefmt="mediawiki"))
1932 {| class="wikitable" style="text-align: left;"
1933 |+ <!-- caption -->
1934 |-
1935 ! strings !! align="right"| numbers
1936 |-
1937 | spam || align="right"| 41.9999
1938 |-
1939 | eggs || align="right"| 451
1940 |}
1942 "html" produces HTML markup as an html.escape'd str
1943 with a ._repr_html_ method so that Jupyter Lab and Notebook display the HTML
1944 and a .str property so that the raw HTML remains accessible
1945 the unsafehtml table format can be used if an unescaped HTML format is required:
1947 >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]],
1948 ... headers="firstrow", tablefmt="html"))
1949 <table>
1950 <thead>
1951 <tr><th>strings </th><th style="text-align: right;"> numbers</th></tr>
1952 </thead>
1953 <tbody>
1954 <tr><td>spam </td><td style="text-align: right;"> 41.9999</td></tr>
1955 <tr><td>eggs </td><td style="text-align: right;"> 451 </td></tr>
1956 </tbody>
1957 </table>
1959 "latex" produces a tabular environment of LaTeX document markup:
1961 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex"))
1962 \\begin{tabular}{lr}
1963 \\hline
1964 spam & 41.9999 \\\\
1965 eggs & 451 \\\\
1966 \\hline
1967 \\end{tabular}
1969 "latex_raw" is similar to "latex", but doesn't escape special characters,
1970 such as backslash and underscore, so LaTeX commands may embedded into
1971 cells' values:
1973 >>> print(tabulate([["spam$_9$", 41.9999], ["\\\\emph{eggs}", "451.0"]], tablefmt="latex_raw"))
1974 \\begin{tabular}{lr}
1975 \\hline
1976 spam$_9$ & 41.9999 \\\\
1977 \\emph{eggs} & 451 \\\\
1978 \\hline
1979 \\end{tabular}
1981 "latex_booktabs" produces a tabular environment of LaTeX document markup
1982 using the booktabs.sty package:
1984 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex_booktabs"))
1985 \\begin{tabular}{lr}
1986 \\toprule
1987 spam & 41.9999 \\\\
1988 eggs & 451 \\\\
1989 \\bottomrule
1990 \\end{tabular}
1992 "latex_longtable" produces a tabular environment that can stretch along
1993 multiple pages, using the longtable package for LaTeX.
1995 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex_longtable"))
1996 \\begin{longtable}{lr}
1997 \\hline
1998 spam & 41.9999 \\\\
1999 eggs & 451 \\\\
2000 \\hline
2001 \\end{longtable}
2004 Number parsing
2005 --------------
2006 By default, anything which can be parsed as a number is a number.
2007 This ensures numbers represented as strings are aligned properly.
2008 This can lead to weird results for particular strings such as
2009 specific git SHAs e.g. "42992e1" will be parsed into the number
2010 429920 and aligned as such.
2012 To completely disable number parsing (and alignment), use
2013 `disable_numparse=True`. For more fine grained control, a list column
2014 indices is used to disable number parsing only on those columns
2015 e.g. `disable_numparse=[0, 2]` would disable number parsing only on the
2016 first and third columns.
2018 Column Widths and Auto Line Wrapping
2019 ------------------------------------
2020 Tabulate will, by default, set the width of each column to the length of the
2021 longest element in that column. However, in situations where fields are expected
2022 to reasonably be too long to look good as a single line, tabulate can help automate
2023 word wrapping long fields for you. Use the parameter `maxcolwidth` to provide a
2024 list of maximal column widths
2026 >>> print(tabulate( \
2027 [('1', 'John Smith', \
2028 'This is a rather long description that might look better if it is wrapped a bit')], \
2029 headers=("Issue Id", "Author", "Description"), \
2030 maxcolwidths=[None, None, 30], \
2031 tablefmt="grid" \
2032 ))
2033 +------------+------------+-------------------------------+
2034 | Issue Id | Author | Description |
2035 +============+============+===============================+
2036 | 1 | John Smith | This is a rather long |
2037 | | | description that might look |
2038 | | | better if it is wrapped a bit |
2039 +------------+------------+-------------------------------+
2041 Header column width can be specified in a similar way using `maxheadercolwidth`
2043 """
2045 if tabular_data is None:
2046 tabular_data = []
2048 list_of_lists, headers = _normalize_tabular_data(
2049 tabular_data, headers, showindex=showindex
2050 )
2051 list_of_lists, separating_lines = _remove_separating_lines(list_of_lists)
2053 if maxcolwidths is not None:
2054 num_cols = len(list_of_lists[0])
2055 if isinstance(maxcolwidths, int): # Expand scalar for all columns
2056 maxcolwidths = _expand_iterable(maxcolwidths, num_cols, maxcolwidths)
2057 else: # Ignore col width for any 'trailing' columns
2058 maxcolwidths = _expand_iterable(maxcolwidths, num_cols, None)
2060 numparses = _expand_numparse(disable_numparse, num_cols)
2061 list_of_lists = _wrap_text_to_colwidths(
2062 list_of_lists, maxcolwidths, numparses=numparses
2063 )
2065 if maxheadercolwidths is not None:
2066 num_cols = len(list_of_lists[0])
2067 if isinstance(maxheadercolwidths, int): # Expand scalar for all columns
2068 maxheadercolwidths = _expand_iterable(
2069 maxheadercolwidths, num_cols, maxheadercolwidths
2070 )
2071 else: # Ignore col width for any 'trailing' columns
2072 maxheadercolwidths = _expand_iterable(maxheadercolwidths, num_cols, None)
2074 numparses = _expand_numparse(disable_numparse, num_cols)
2075 headers = _wrap_text_to_colwidths(
2076 [headers], maxheadercolwidths, numparses=numparses
2077 )[0]
2079 # empty values in the first column of RST tables should be escaped (issue #82)
2080 # "" should be escaped as "\\ " or ".."
2081 if tablefmt == "rst":
2082 list_of_lists, headers = _rst_escape_first_column(list_of_lists, headers)
2084 # PrettyTable formatting does not use any extra padding.
2085 # Numbers are not parsed and are treated the same as strings for alignment.
2086 # Check if pretty is the format being used and override the defaults so it
2087 # does not impact other formats.
2088 min_padding = MIN_PADDING
2089 if tablefmt == "pretty":
2090 min_padding = 0
2091 disable_numparse = True
2092 numalign = "center" if numalign == _DEFAULT_ALIGN else numalign
2093 stralign = "center" if stralign == _DEFAULT_ALIGN else stralign
2094 else:
2095 numalign = "decimal" if numalign == _DEFAULT_ALIGN else numalign
2096 stralign = "left" if stralign == _DEFAULT_ALIGN else stralign
2098 # optimization: look for ANSI control codes once,
2099 # enable smart width functions only if a control code is found
2100 #
2101 # convert the headers and rows into a single, tab-delimited string ensuring
2102 # that any bytestrings are decoded safely (i.e. errors ignored)
2103 plain_text = "\t".join(
2104 chain(
2105 # headers
2106 map(_to_str, headers),
2107 # rows: chain the rows together into a single iterable after mapping
2108 # the bytestring conversino to each cell value
2109 chain.from_iterable(map(_to_str, row) for row in list_of_lists),
2110 )
2111 )
2113 has_invisible = _ansi_codes.search(plain_text) is not None
2115 enable_widechars = wcwidth is not None and WIDE_CHARS_MODE
2116 if (
2117 not isinstance(tablefmt, TableFormat)
2118 and tablefmt in multiline_formats
2119 and _is_multiline(plain_text)
2120 ):
2121 tablefmt = multiline_formats.get(tablefmt, tablefmt)
2122 is_multiline = True
2123 else:
2124 is_multiline = False
2125 width_fn = _choose_width_fn(has_invisible, enable_widechars, is_multiline)
2127 # format rows and columns, convert numeric values to strings
2128 cols = list(izip_longest(*list_of_lists))
2129 numparses = _expand_numparse(disable_numparse, len(cols))
2130 coltypes = [_column_type(col, numparse=np) for col, np in zip(cols, numparses)]
2131 if isinstance(floatfmt, str): # old version
2132 float_formats = len(cols) * [
2133 floatfmt
2134 ] # just duplicate the string to use in each column
2135 else: # if floatfmt is list, tuple etc we have one per column
2136 float_formats = list(floatfmt)
2137 if len(float_formats) < len(cols):
2138 float_formats.extend((len(cols) - len(float_formats)) * [_DEFAULT_FLOATFMT])
2139 if isinstance(intfmt, str): # old version
2140 int_formats = len(cols) * [
2141 intfmt
2142 ] # just duplicate the string to use in each column
2143 else: # if intfmt is list, tuple etc we have one per column
2144 int_formats = list(intfmt)
2145 if len(int_formats) < len(cols):
2146 int_formats.extend((len(cols) - len(int_formats)) * [_DEFAULT_INTFMT])
2147 if isinstance(missingval, str):
2148 missing_vals = len(cols) * [missingval]
2149 else:
2150 missing_vals = list(missingval)
2151 if len(missing_vals) < len(cols):
2152 missing_vals.extend((len(cols) - len(missing_vals)) * [_DEFAULT_MISSINGVAL])
2153 cols = [
2154 [_format(v, ct, fl_fmt, int_fmt, miss_v, has_invisible) for v in c]
2155 for c, ct, fl_fmt, int_fmt, miss_v in zip(
2156 cols, coltypes, float_formats, int_formats, missing_vals
2157 )
2158 ]
2160 # align columns
2161 aligns = [numalign if ct in [int, float] else stralign for ct in coltypes]
2162 if colalign is not None:
2163 assert isinstance(colalign, Iterable)
2164 for idx, align in enumerate(colalign):
2165 aligns[idx] = align
2166 minwidths = (
2167 [width_fn(h) + min_padding for h in headers] if headers else [0] * len(cols)
2168 )
2169 cols = [
2170 _align_column(c, a, minw, has_invisible, enable_widechars, is_multiline)
2171 for c, a, minw in zip(cols, aligns, minwidths)
2172 ]
2174 if headers:
2175 # align headers and add headers
2176 t_cols = cols or [[""]] * len(headers)
2177 t_aligns = aligns or [stralign] * len(headers)
2178 minwidths = [
2179 max(minw, max(width_fn(cl) for cl in c))
2180 for minw, c in zip(minwidths, t_cols)
2181 ]
2182 headers = [
2183 _align_header(h, a, minw, width_fn(h), is_multiline, width_fn)
2184 for h, a, minw in zip(headers, t_aligns, minwidths)
2185 ]
2186 rows = list(zip(*cols))
2187 else:
2188 minwidths = [max(width_fn(cl) for cl in c) for c in cols]
2189 rows = list(zip(*cols))
2191 if not isinstance(tablefmt, TableFormat):
2192 tablefmt = _table_formats.get(tablefmt, _table_formats["simple"])
2194 ra_default = rowalign if isinstance(rowalign, str) else None
2195 rowaligns = _expand_iterable(rowalign, len(rows), ra_default)
2196 _reinsert_separating_lines(rows, separating_lines)
2198 return _format_table(
2199 tablefmt, headers, rows, minwidths, aligns, is_multiline, rowaligns=rowaligns
2200 )
2203def _expand_numparse(disable_numparse, column_count):
2204 """
2205 Return a list of bools of length `column_count` which indicates whether
2206 number parsing should be used on each column.
2207 If `disable_numparse` is a list of indices, each of those indices are False,
2208 and everything else is True.
2209 If `disable_numparse` is a bool, then the returned list is all the same.
2210 """
2211 if isinstance(disable_numparse, Iterable):
2212 numparses = [True] * column_count
2213 for index in disable_numparse:
2214 numparses[index] = False
2215 return numparses
2216 else:
2217 return [not disable_numparse] * column_count
2220def _expand_iterable(original, num_desired, default):
2221 """
2222 Expands the `original` argument to return a return a list of
2223 length `num_desired`. If `original` is shorter than `num_desired`, it will
2224 be padded with the value in `default`.
2225 If `original` is not a list to begin with (i.e. scalar value) a list of
2226 length `num_desired` completely populated with `default will be returned
2227 """
2228 if isinstance(original, Iterable) and not isinstance(original, str):
2229 return original + [default] * (num_desired - len(original))
2230 else:
2231 return [default] * num_desired
2234def _pad_row(cells, padding):
2235 if cells:
2236 pad = " " * padding
2237 padded_cells = [pad + cell + pad for cell in cells]
2238 return padded_cells
2239 else:
2240 return cells
2243def _build_simple_row(padded_cells, rowfmt):
2244 "Format row according to DataRow format without padding."
2245 begin, sep, end = rowfmt
2246 return (begin + sep.join(padded_cells) + end).rstrip()
2249def _build_row(padded_cells, colwidths, colaligns, rowfmt):
2250 "Return a string which represents a row of data cells."
2251 if not rowfmt:
2252 return None
2253 if hasattr(rowfmt, "__call__"):
2254 return rowfmt(padded_cells, colwidths, colaligns)
2255 else:
2256 return _build_simple_row(padded_cells, rowfmt)
2259def _append_basic_row(lines, padded_cells, colwidths, colaligns, rowfmt, rowalign=None):
2260 # NOTE: rowalign is ignored and exists for api compatibility with _append_multiline_row
2261 lines.append(_build_row(padded_cells, colwidths, colaligns, rowfmt))
2262 return lines
2265def _align_cell_veritically(text_lines, num_lines, column_width, row_alignment):
2266 delta_lines = num_lines - len(text_lines)
2267 blank = [" " * column_width]
2268 if row_alignment == "bottom":
2269 return blank * delta_lines + text_lines
2270 elif row_alignment == "center":
2271 top_delta = delta_lines // 2
2272 bottom_delta = delta_lines - top_delta
2273 return top_delta * blank + text_lines + bottom_delta * blank
2274 else:
2275 return text_lines + blank * delta_lines
2278def _append_multiline_row(
2279 lines, padded_multiline_cells, padded_widths, colaligns, rowfmt, pad, rowalign=None
2280):
2281 colwidths = [w - 2 * pad for w in padded_widths]
2282 cells_lines = [c.splitlines() for c in padded_multiline_cells]
2283 nlines = max(map(len, cells_lines)) # number of lines in the row
2284 # vertically pad cells where some lines are missing
2285 # cells_lines = [
2286 # (cl + [" " * w] * (nlines - len(cl))) for cl, w in zip(cells_lines, colwidths)
2287 # ]
2289 cells_lines = [
2290 _align_cell_veritically(cl, nlines, w, rowalign)
2291 for cl, w in zip(cells_lines, colwidths)
2292 ]
2293 lines_cells = [[cl[i] for cl in cells_lines] for i in range(nlines)]
2294 for ln in lines_cells:
2295 padded_ln = _pad_row(ln, pad)
2296 _append_basic_row(lines, padded_ln, colwidths, colaligns, rowfmt)
2297 return lines
2300def _build_line(colwidths, colaligns, linefmt):
2301 "Return a string which represents a horizontal line."
2302 if not linefmt:
2303 return None
2304 if hasattr(linefmt, "__call__"):
2305 return linefmt(colwidths, colaligns)
2306 else:
2307 begin, fill, sep, end = linefmt
2308 cells = [fill * w for w in colwidths]
2309 return _build_simple_row(cells, (begin, sep, end))
2312def _append_line(lines, colwidths, colaligns, linefmt):
2313 lines.append(_build_line(colwidths, colaligns, linefmt))
2314 return lines
2317class JupyterHTMLStr(str):
2318 """Wrap the string with a _repr_html_ method so that Jupyter
2319 displays the HTML table"""
2321 def _repr_html_(self):
2322 return self
2324 @property
2325 def str(self):
2326 """add a .str property so that the raw string is still accessible"""
2327 return self
2330def _format_table(fmt, headers, rows, colwidths, colaligns, is_multiline, rowaligns):
2331 """Produce a plain-text representation of the table."""
2332 lines = []
2333 hidden = fmt.with_header_hide if (headers and fmt.with_header_hide) else []
2334 pad = fmt.padding
2335 headerrow = fmt.headerrow
2337 padded_widths = [(w + 2 * pad) for w in colwidths]
2338 if is_multiline:
2339 pad_row = lambda row, _: row # noqa do it later, in _append_multiline_row
2340 append_row = partial(_append_multiline_row, pad=pad)
2341 else:
2342 pad_row = _pad_row
2343 append_row = _append_basic_row
2345 padded_headers = pad_row(headers, pad)
2346 padded_rows = [pad_row(row, pad) for row in rows]
2348 if fmt.lineabove and "lineabove" not in hidden:
2349 _append_line(lines, padded_widths, colaligns, fmt.lineabove)
2351 if padded_headers:
2352 append_row(lines, padded_headers, padded_widths, colaligns, headerrow)
2353 if fmt.linebelowheader and "linebelowheader" not in hidden:
2354 _append_line(lines, padded_widths, colaligns, fmt.linebelowheader)
2356 if padded_rows and fmt.linebetweenrows and "linebetweenrows" not in hidden:
2357 # initial rows with a line below
2358 for row, ralign in zip(padded_rows[:-1], rowaligns):
2359 append_row(
2360 lines, row, padded_widths, colaligns, fmt.datarow, rowalign=ralign
2361 )
2362 _append_line(lines, padded_widths, colaligns, fmt.linebetweenrows)
2363 # the last row without a line below
2364 append_row(
2365 lines,
2366 padded_rows[-1],
2367 padded_widths,
2368 colaligns,
2369 fmt.datarow,
2370 rowalign=rowaligns[-1],
2371 )
2372 else:
2373 separating_line = (
2374 fmt.linebetweenrows
2375 or fmt.linebelowheader
2376 or fmt.linebelow
2377 or fmt.lineabove
2378 or Line("", "", "", "")
2379 )
2380 for row in padded_rows:
2381 # test to see if either the 1st column or the 2nd column (account for showindex) has
2382 # the SEPARATING_LINE flag
2383 if _is_separating_line(row):
2384 _append_line(lines, padded_widths, colaligns, separating_line)
2385 else:
2386 append_row(lines, row, padded_widths, colaligns, fmt.datarow)
2388 if fmt.linebelow and "linebelow" not in hidden:
2389 _append_line(lines, padded_widths, colaligns, fmt.linebelow)
2391 if headers or rows:
2392 output = "\n".join(lines)
2393 if fmt.lineabove == _html_begin_table_without_header:
2394 return JupyterHTMLStr(output)
2395 else:
2396 return output
2397 else: # a completely empty table
2398 return ""
2401class _CustomTextWrap(textwrap.TextWrapper):
2402 """A custom implementation of CPython's textwrap.TextWrapper. This supports
2403 both wide characters (Korea, Japanese, Chinese) - including mixed string.
2404 For the most part, the `_handle_long_word` and `_wrap_chunks` functions were
2405 copy pasted out of the CPython baseline, and updated with our custom length
2406 and line appending logic.
2407 """
2409 def __init__(self, *args, **kwargs):
2410 self._active_codes = []
2411 self.max_lines = None # For python2 compatibility
2412 textwrap.TextWrapper.__init__(self, *args, **kwargs)
2414 @staticmethod
2415 def _len(item):
2416 """Custom len that gets console column width for wide
2417 and non-wide characters as well as ignores color codes"""
2418 stripped = _strip_ansi(item)
2419 if wcwidth:
2420 return wcwidth.wcswidth(stripped)
2421 else:
2422 return len(stripped)
2424 def _update_lines(self, lines, new_line):
2425 """Adds a new line to the list of lines the text is being wrapped into
2426 This function will also track any ANSI color codes in this string as well
2427 as add any colors from previous lines order to preserve the same formatting
2428 as a single unwrapped string.
2429 """
2430 code_matches = [x for x in _ansi_codes.finditer(new_line)]
2431 color_codes = [
2432 code.string[code.span()[0] : code.span()[1]] for code in code_matches
2433 ]
2435 # Add color codes from earlier in the unwrapped line, and then track any new ones we add.
2436 new_line = "".join(self._active_codes) + new_line
2438 for code in color_codes:
2439 if code != _ansi_color_reset_code:
2440 self._active_codes.append(code)
2441 else: # A single reset code resets everything
2442 self._active_codes = []
2444 # Always ensure each line is color terminted if any colors are
2445 # still active, otherwise colors will bleed into other cells on the console
2446 if len(self._active_codes) > 0:
2447 new_line = new_line + _ansi_color_reset_code
2449 lines.append(new_line)
2451 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2452 """_handle_long_word(chunks : [string],
2453 cur_line : [string],
2454 cur_len : int, width : int)
2455 Handle a chunk of text (most likely a word, not whitespace) that
2456 is too long to fit in any line.
2457 """
2458 # Figure out when indent is larger than the specified width, and make
2459 # sure at least one character is stripped off on every pass
2460 if width < 1:
2461 space_left = 1
2462 else:
2463 space_left = width - cur_len
2465 # If we're allowed to break long words, then do so: put as much
2466 # of the next chunk onto the current line as will fit.
2467 if self.break_long_words:
2468 # Tabulate Custom: Build the string up piece-by-piece in order to
2469 # take each charcter's width into account
2470 chunk = reversed_chunks[-1]
2471 i = 1
2472 while self._len(chunk[:i]) <= space_left:
2473 i = i + 1
2474 cur_line.append(chunk[: i - 1])
2475 reversed_chunks[-1] = chunk[i - 1 :]
2477 # Otherwise, we have to preserve the long word intact. Only add
2478 # it to the current line if there's nothing already there --
2479 # that minimizes how much we violate the width constraint.
2480 elif not cur_line:
2481 cur_line.append(reversed_chunks.pop())
2483 # If we're not allowed to break long words, and there's already
2484 # text on the current line, do nothing. Next time through the
2485 # main loop of _wrap_chunks(), we'll wind up here again, but
2486 # cur_len will be zero, so the next line will be entirely
2487 # devoted to the long word that we can't handle right now.
2489 def _wrap_chunks(self, chunks):
2490 """_wrap_chunks(chunks : [string]) -> [string]
2491 Wrap a sequence of text chunks and return a list of lines of
2492 length 'self.width' or less. (If 'break_long_words' is false,
2493 some lines may be longer than this.) Chunks correspond roughly
2494 to words and the whitespace between them: each chunk is
2495 indivisible (modulo 'break_long_words'), but a line break can
2496 come between any two chunks. Chunks should not have internal
2497 whitespace; ie. a chunk is either all whitespace or a "word".
2498 Whitespace chunks will be removed from the beginning and end of
2499 lines, but apart from that whitespace is preserved.
2500 """
2501 lines = []
2502 if self.width <= 0:
2503 raise ValueError("invalid width %r (must be > 0)" % self.width)
2504 if self.max_lines is not None:
2505 if self.max_lines > 1:
2506 indent = self.subsequent_indent
2507 else:
2508 indent = self.initial_indent
2509 if self._len(indent) + self._len(self.placeholder.lstrip()) > self.width:
2510 raise ValueError("placeholder too large for max width")
2512 # Arrange in reverse order so items can be efficiently popped
2513 # from a stack of chucks.
2514 chunks.reverse()
2516 while chunks:
2518 # Start the list of chunks that will make up the current line.
2519 # cur_len is just the length of all the chunks in cur_line.
2520 cur_line = []
2521 cur_len = 0
2523 # Figure out which static string will prefix this line.
2524 if lines:
2525 indent = self.subsequent_indent
2526 else:
2527 indent = self.initial_indent
2529 # Maximum width for this line.
2530 width = self.width - self._len(indent)
2532 # First chunk on line is whitespace -- drop it, unless this
2533 # is the very beginning of the text (ie. no lines started yet).
2534 if self.drop_whitespace and chunks[-1].strip() == "" and lines:
2535 del chunks[-1]
2537 while chunks:
2538 chunk_len = self._len(chunks[-1])
2540 # Can at least squeeze this chunk onto the current line.
2541 if cur_len + chunk_len <= width:
2542 cur_line.append(chunks.pop())
2543 cur_len += chunk_len
2545 # Nope, this line is full.
2546 else:
2547 break
2549 # The current line is full, and the next chunk is too big to
2550 # fit on *any* line (not just this one).
2551 if chunks and self._len(chunks[-1]) > width:
2552 self._handle_long_word(chunks, cur_line, cur_len, width)
2553 cur_len = sum(map(self._len, cur_line))
2555 # If the last chunk on this line is all whitespace, drop it.
2556 if self.drop_whitespace and cur_line and cur_line[-1].strip() == "":
2557 cur_len -= self._len(cur_line[-1])
2558 del cur_line[-1]
2560 if cur_line:
2561 if (
2562 self.max_lines is None
2563 or len(lines) + 1 < self.max_lines
2564 or (
2565 not chunks
2566 or self.drop_whitespace
2567 and len(chunks) == 1
2568 and not chunks[0].strip()
2569 )
2570 and cur_len <= width
2571 ):
2572 # Convert current line back to a string and store it in
2573 # list of all lines (return value).
2574 self._update_lines(lines, indent + "".join(cur_line))
2575 else:
2576 while cur_line:
2577 if (
2578 cur_line[-1].strip()
2579 and cur_len + self._len(self.placeholder) <= width
2580 ):
2581 cur_line.append(self.placeholder)
2582 self._update_lines(lines, indent + "".join(cur_line))
2583 break
2584 cur_len -= self._len(cur_line[-1])
2585 del cur_line[-1]
2586 else:
2587 if lines:
2588 prev_line = lines[-1].rstrip()
2589 if (
2590 self._len(prev_line) + self._len(self.placeholder)
2591 <= self.width
2592 ):
2593 lines[-1] = prev_line + self.placeholder
2594 break
2595 self._update_lines(lines, indent + self.placeholder.lstrip())
2596 break
2598 return lines
2601def _main():
2602 """\
2603 Usage: tabulate [options] [FILE ...]
2605 Pretty-print tabular data.
2606 See also https://github.com/astanin/python-tabulate
2608 FILE a filename of the file with tabular data;
2609 if "-" or missing, read data from stdin.
2611 Options:
2613 -h, --help show this message
2614 -1, --header use the first row of data as a table header
2615 -o FILE, --output FILE print table to FILE (default: stdout)
2616 -s REGEXP, --sep REGEXP use a custom column separator (default: whitespace)
2617 -F FPFMT, --float FPFMT floating point number format (default: g)
2618 -I INTFMT, --int INTFMT integer point number format (default: "")
2619 -f FMT, --format FMT set output table format; supported formats:
2620 plain, simple, grid, fancy_grid, pipe, orgtbl,
2621 rst, mediawiki, html, latex, latex_raw,
2622 latex_booktabs, latex_longtable, tsv
2623 (default: simple)
2624 """
2625 import getopt
2626 import sys
2627 import textwrap
2629 usage = textwrap.dedent(_main.__doc__)
2630 try:
2631 opts, args = getopt.getopt(
2632 sys.argv[1:],
2633 "h1o:s:F:A:f:",
2634 ["help", "header", "output", "sep=", "float=", "int=", "align=", "format="],
2635 )
2636 except getopt.GetoptError as e:
2637 print(e)
2638 print(usage)
2639 sys.exit(2)
2640 headers = []
2641 floatfmt = _DEFAULT_FLOATFMT
2642 intfmt = _DEFAULT_INTFMT
2643 colalign = None
2644 tablefmt = "simple"
2645 sep = r"\s+"
2646 outfile = "-"
2647 for opt, value in opts:
2648 if opt in ["-1", "--header"]:
2649 headers = "firstrow"
2650 elif opt in ["-o", "--output"]:
2651 outfile = value
2652 elif opt in ["-F", "--float"]:
2653 floatfmt = value
2654 elif opt in ["-I", "--int"]:
2655 intfmt = value
2656 elif opt in ["-C", "--colalign"]:
2657 colalign = value.split()
2658 elif opt in ["-f", "--format"]:
2659 if value not in tabulate_formats:
2660 print("%s is not a supported table format" % value)
2661 print(usage)
2662 sys.exit(3)
2663 tablefmt = value
2664 elif opt in ["-s", "--sep"]:
2665 sep = value
2666 elif opt in ["-h", "--help"]:
2667 print(usage)
2668 sys.exit(0)
2669 files = [sys.stdin] if not args else args
2670 with (sys.stdout if outfile == "-" else open(outfile, "w")) as out:
2671 for f in files:
2672 if f == "-":
2673 f = sys.stdin
2674 if _is_file(f):
2675 _pprint_file(
2676 f,
2677 headers=headers,
2678 tablefmt=tablefmt,
2679 sep=sep,
2680 floatfmt=floatfmt,
2681 intfmt=intfmt,
2682 file=out,
2683 colalign=colalign,
2684 )
2685 else:
2686 with open(f) as fobj:
2687 _pprint_file(
2688 fobj,
2689 headers=headers,
2690 tablefmt=tablefmt,
2691 sep=sep,
2692 floatfmt=floatfmt,
2693 intfmt=intfmt,
2694 file=out,
2695 colalign=colalign,
2696 )
2699def _pprint_file(fobject, headers, tablefmt, sep, floatfmt, intfmt, file, colalign):
2700 rows = fobject.readlines()
2701 table = [re.split(sep, r.rstrip()) for r in rows if r.strip()]
2702 print(
2703 tabulate(
2704 table,
2705 headers,
2706 tablefmt,
2707 floatfmt=floatfmt,
2708 intfmt=intfmt,
2709 colalign=colalign,
2710 ),
2711 file=file,
2712 )
2715if __name__ == "__main__":
2716 _main()