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