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

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 # -*- coding: utf-8 -*-
2
3 """Pretty-print tabular data."""
4
5 from __future__ import print_function
6 from __future__ import unicode_literals
7 from collections import namedtuple
8 import sys
9 import re
10 import math
11
12
13 if sys.version_info >= (3, 3):
14 from collections.abc import Iterable
15 else:
16 from collections import Iterable
17
18 if sys.version_info[0] < 3:
19 from itertools import izip_longest
20 from functools import partial
21
22 _none_type = type(None)
23 _bool_type = bool
24 _int_type = int
25 _long_type = long # noqa
26 _float_type = float
27 _text_type = unicode # noqa
28 _binary_type = str
29
30 def _is_file(f):
31 return hasattr(f, "read")
32
33
34 else:
35 from itertools import zip_longest as izip_longest
36 from functools import reduce, partial
37
38 _none_type = type(None)
39 _bool_type = bool
40 _int_type = int
41 _long_type = int
42 _float_type = float
43 _text_type = str
44 _binary_type = bytes
45 basestring = str
46
47 import io
48
49 def _is_file(f):
50 return isinstance(f, io.IOBase)
51
52
53 try:
54 import wcwidth # optional wide-character (CJK) support
55 except ImportError:
56 wcwidth = None
57
58 try:
59 from html import escape as htmlescape
60 except ImportError:
61 from cgi import escape as htmlescape
62
63
64 __all__ = ["tabulate", "tabulate_formats", "simple_separated_format"]
65 __version__ = "0.8.9"
66
67
68 # minimum extra space in headers
69 MIN_PADDING = 2
70
71 # Whether or not to preserve leading/trailing whitespace in data.
72 PRESERVE_WHITESPACE = False
73
74 _DEFAULT_FLOATFMT = "g"
75 _DEFAULT_MISSINGVAL = ""
76 # default align will be overwritten by "left", "center" or "decimal"
77 # depending on the formatter
78 _DEFAULT_ALIGN = "default"
79
80
81 # if True, enable wide-character (CJK) support
82 WIDE_CHARS_MODE = wcwidth is not None
83
84
85 Line = namedtuple("Line", ["begin", "hline", "sep", "end"])
86
87
88 DataRow = namedtuple("DataRow", ["begin", "sep", "end"])
89
90
91 # A table structure is suppposed to be:
92 #
93 # --- lineabove ---------
94 # headerrow
95 # --- linebelowheader ---
96 # datarow
97 # --- linebetweenrows ---
98 # ... (more datarows) ...
99 # --- linebetweenrows ---
100 # last datarow
101 # --- linebelow ---------
102 #
103 # TableFormat's line* elements can be
104 #
105 # - either None, if the element is not used,
106 # - or a Line tuple,
107 # - or a function: [col_widths], [col_alignments] -> string.
108 #
109 # TableFormat's *row elements can be
110 #
111 # - either None, if the element is not used,
112 # - or a DataRow tuple,
113 # - or a function: [cell_values], [col_widths], [col_alignments] -> string.
114 #
115 # padding (an integer) is the amount of white space around data values.
116 #
117 # with_header_hide:
118 #
119 # - either None, to display all table elements unconditionally,
120 # - or a list of elements not to be displayed if the table has column headers.
121 #
122 TableFormat = namedtuple(
123 "TableFormat",
124 [
125 "lineabove",
126 "linebelowheader",
127 "linebetweenrows",
128 "linebelow",
129 "headerrow",
130 "datarow",
131 "padding",
132 "with_header_hide",
133 ],
134 )
135
136
137 def _pipe_segment_with_colons(align, colwidth):
138 """Return a segment of a horizontal line with optional colons which
139 indicate column's alignment (as in `pipe` output format)."""
140 w = colwidth
141 if align in ["right", "decimal"]:
142 return ("-" * (w - 1)) + ":"
143 elif align == "center":
144 return ":" + ("-" * (w - 2)) + ":"
145 elif align == "left":
146 return ":" + ("-" * (w - 1))
147 else:
148 return "-" * w
149
150
151 def _pipe_line_with_colons(colwidths, colaligns):
152 """Return a horizontal line with optional colons to indicate column's
153 alignment (as in `pipe` output format)."""
154 if not colaligns: # e.g. printing an empty data frame (github issue #15)
155 colaligns = [""] * len(colwidths)
156 segments = [_pipe_segment_with_colons(a, w) for a, w in zip(colaligns, colwidths)]
157 return "|" + "|".join(segments) + "|"
158
159
160 def _mediawiki_row_with_attrs(separator, cell_values, colwidths, colaligns):
161 alignment = {
162 "left": "",
163 "right": 'align="right"| ',
164 "center": 'align="center"| ',
165 "decimal": 'align="right"| ',
166 }
167 # hard-coded padding _around_ align attribute and value together
168 # rather than padding parameter which affects only the value
169 values_with_attrs = [
170 " " + alignment.get(a, "") + c + " " for c, a in zip(cell_values, colaligns)
171 ]
172 colsep = separator * 2
173 return (separator + colsep.join(values_with_attrs)).rstrip()
174
175
176 def _textile_row_with_attrs(cell_values, colwidths, colaligns):
177 cell_values[0] += " "
178 alignment = {"left": "<.", "right": ">.", "center": "=.", "decimal": ">."}
179 values = (alignment.get(a, "") + v for a, v in zip(colaligns, cell_values))
180 return "|" + "|".join(values) + "|"
181
182
183 def _html_begin_table_without_header(colwidths_ignore, colaligns_ignore):
184 # this table header will be suppressed if there is a header row
185 return "<table>\n<tbody>"
186
187
188 def _html_row_with_attrs(celltag, unsafe, cell_values, colwidths, colaligns):
189 alignment = {
190 "left": "",
191 "right": ' style="text-align: right;"',
192 "center": ' style="text-align: center;"',
193 "decimal": ' style="text-align: right;"',
194 }
195 if unsafe:
196 values_with_attrs = [
197 "<{0}{1}>{2}</{0}>".format(celltag, alignment.get(a, ""), c)
198 for c, a in zip(cell_values, colaligns)
199 ]
200 else:
201 values_with_attrs = [
202 "<{0}{1}>{2}</{0}>".format(celltag, alignment.get(a, ""), htmlescape(c))
203 for c, a in zip(cell_values, colaligns)
204 ]
205 rowhtml = "<tr>{}</tr>".format("".join(values_with_attrs).rstrip())
206 if celltag == "th": # it's a header row, create a new table header
207 rowhtml = "<table>\n<thead>\n{}\n</thead>\n<tbody>".format(rowhtml)
208 return rowhtml
209
210
211 def _moin_row_with_attrs(celltag, cell_values, colwidths, colaligns, header=""):
212 alignment = {
213 "left": "",
214 "right": '<style="text-align: right;">',
215 "center": '<style="text-align: center;">',
216 "decimal": '<style="text-align: right;">',
217 }
218 values_with_attrs = [
219 "{0}{1} {2} ".format(celltag, alignment.get(a, ""), header + c + header)
220 for c, a in zip(cell_values, colaligns)
221 ]
222 return "".join(values_with_attrs) + "||"
223
224
225 def _latex_line_begin_tabular(colwidths, colaligns, booktabs=False, longtable=False):
226 alignment = {"left": "l", "right": "r", "center": "c", "decimal": "r"}
227 tabular_columns_fmt = "".join([alignment.get(a, "l") for a in colaligns])
228 return "\n".join(
229 [
230 ("\\begin{tabular}{" if not longtable else "\\begin{longtable}{")
231 + tabular_columns_fmt
232 + "}",
233 "\\toprule" if booktabs else "\\hline",
234 ]
235 )
236
237
238 LATEX_ESCAPE_RULES = {
239 r"&": r"\&",
240 r"%": r"\%",
241 r"$": r"\$",
242 r"#": r"\#",
243 r"_": r"\_",
244 r"^": r"\^{}",
245 r"{": r"\{",
246 r"}": r"\}",
247 r"~": r"\textasciitilde{}",
248 "\\": r"\textbackslash{}",
249 r"<": r"\ensuremath{<}",
250 r">": r"\ensuremath{>}",
251 }
252
253
254 def _latex_row(cell_values, colwidths, colaligns, escrules=LATEX_ESCAPE_RULES):
255 def escape_char(c):
256 return escrules.get(c, c)
257
258 escaped_values = ["".join(map(escape_char, cell)) for cell in cell_values]
259 rowfmt = DataRow("", "&", "\\\\")
260 return _build_simple_row(escaped_values, rowfmt)
261
262
263 def _rst_escape_first_column(rows, headers):
264 def escape_empty(val):
265 if isinstance(val, (_text_type, _binary_type)) and not val.strip():
266 return ".."
267 else:
268 return val
269
270 new_headers = list(headers)
271 new_rows = []
272 if headers:
273 new_headers[0] = escape_empty(headers[0])
274 for row in rows:
275 new_row = list(row)
276 if new_row:
277 new_row[0] = escape_empty(row[0])
278 new_rows.append(new_row)
279 return new_rows, new_headers
280
281
282 _table_formats = {
283 "simple": TableFormat(
284 lineabove=Line("", "-", " ", ""),
285 linebelowheader=Line("", "-", " ", ""),
286 linebetweenrows=None,
287 linebelow=Line("", "-", " ", ""),
288 headerrow=DataRow("", " ", ""),
289 datarow=DataRow("", " ", ""),
290 padding=0,
291 with_header_hide=["lineabove", "linebelow"],
292 ),
293 "plain": TableFormat(
294 lineabove=None,
295 linebelowheader=None,
296 linebetweenrows=None,
297 linebelow=None,
298 headerrow=DataRow("", " ", ""),
299 datarow=DataRow("", " ", ""),
300 padding=0,
301 with_header_hide=None,
302 ),
303 "grid": TableFormat(
304 lineabove=Line("+", "-", "+", "+"),
305 linebelowheader=Line("+", "=", "+", "+"),
306 linebetweenrows=Line("+", "-", "+", "+"),
307 linebelow=Line("+", "-", "+", "+"),
308 headerrow=DataRow("|", "|", "|"),
309 datarow=DataRow("|", "|", "|"),
310 padding=1,
311 with_header_hide=None,
312 ),
313 "fancy_grid": TableFormat(
314 lineabove=Line("╒", "═", "╤", "╕"),
315 linebelowheader=Line("╞", "═", "╪", "╡"),
316 linebetweenrows=Line("├", "─", "┼", "┤"),
317 linebelow=Line("╘", "═", "╧", "╛"),
318 headerrow=DataRow("│", "│", "│"),
319 datarow=DataRow("│", "│", "│"),
320 padding=1,
321 with_header_hide=None,
322 ),
323 "fancy_outline": TableFormat(
324 lineabove=Line("╒", "═", "╤", "╕"),
325 linebelowheader=Line("╞", "═", "╪", "╡"),
326 linebetweenrows=None,
327 linebelow=Line("╘", "═", "╧", "╛"),
328 headerrow=DataRow("│", "│", "│"),
329 datarow=DataRow("│", "│", "│"),
330 padding=1,
331 with_header_hide=None,
332 ),
333 "github": TableFormat(
334 lineabove=Line("|", "-", "|", "|"),
335 linebelowheader=Line("|", "-", "|", "|"),
336 linebetweenrows=None,
337 linebelow=None,
338 headerrow=DataRow("|", "|", "|"),
339 datarow=DataRow("|", "|", "|"),
340 padding=1,
341 with_header_hide=["lineabove"],
342 ),
343 "pipe": TableFormat(
344 lineabove=_pipe_line_with_colons,
345 linebelowheader=_pipe_line_with_colons,
346 linebetweenrows=None,
347 linebelow=None,
348 headerrow=DataRow("|", "|", "|"),
349 datarow=DataRow("|", "|", "|"),
350 padding=1,
351 with_header_hide=["lineabove"],
352 ),
353 "orgtbl": TableFormat(
354 lineabove=None,
355 linebelowheader=Line("|", "-", "+", "|"),
356 linebetweenrows=None,
357 linebelow=None,
358 headerrow=DataRow("|", "|", "|"),
359 datarow=DataRow("|", "|", "|"),
360 padding=1,
361 with_header_hide=None,
362 ),
363 "jira": TableFormat(
364 lineabove=None,
365 linebelowheader=None,
366 linebetweenrows=None,
367 linebelow=None,
368 headerrow=DataRow("||", "||", "||"),
369 datarow=DataRow("|", "|", "|"),
370 padding=1,
371 with_header_hide=None,
372 ),
373 "presto": TableFormat(
374 lineabove=None,
375 linebelowheader=Line("", "-", "+", ""),
376 linebetweenrows=None,
377 linebelow=None,
378 headerrow=DataRow("", "|", ""),
379 datarow=DataRow("", "|", ""),
380 padding=1,
381 with_header_hide=None,
382 ),
383 "pretty": TableFormat(
384 lineabove=Line("+", "-", "+", "+"),
385 linebelowheader=Line("+", "-", "+", "+"),
386 linebetweenrows=None,
387 linebelow=Line("+", "-", "+", "+"),
388 headerrow=DataRow("|", "|", "|"),
389 datarow=DataRow("|", "|", "|"),
390 padding=1,
391 with_header_hide=None,
392 ),
393 "psql": TableFormat(
394 lineabove=Line("+", "-", "+", "+"),
395 linebelowheader=Line("|", "-", "+", "|"),
396 linebetweenrows=None,
397 linebelow=Line("+", "-", "+", "+"),
398 headerrow=DataRow("|", "|", "|"),
399 datarow=DataRow("|", "|", "|"),
400 padding=1,
401 with_header_hide=None,
402 ),
403 "rst": TableFormat(
404 lineabove=Line("", "=", " ", ""),
405 linebelowheader=Line("", "=", " ", ""),
406 linebetweenrows=None,
407 linebelow=Line("", "=", " ", ""),
408 headerrow=DataRow("", " ", ""),
409 datarow=DataRow("", " ", ""),
410 padding=0,
411 with_header_hide=None,
412 ),
413 "mediawiki": TableFormat(
414 lineabove=Line(
415 '{| class="wikitable" style="text-align: left;"',
416 "",
417 "",
418 "\n|+ <!-- caption -->\n|-",
419 ),
420 linebelowheader=Line("|-", "", "", ""),
421 linebetweenrows=Line("|-", "", "", ""),
422 linebelow=Line("|}", "", "", ""),
423 headerrow=partial(_mediawiki_row_with_attrs, "!"),
424 datarow=partial(_mediawiki_row_with_attrs, "|"),
425 padding=0,
426 with_header_hide=None,
427 ),
428 "moinmoin": TableFormat(
429 lineabove=None,
430 linebelowheader=None,
431 linebetweenrows=None,
432 linebelow=None,
433 headerrow=partial(_moin_row_with_attrs, "||", header="'''"),
434 datarow=partial(_moin_row_with_attrs, "||"),
435 padding=1,
436 with_header_hide=None,
437 ),
438 "youtrack": TableFormat(
439 lineabove=None,
440 linebelowheader=None,
441 linebetweenrows=None,
442 linebelow=None,
443 headerrow=DataRow("|| ", " || ", " || "),
444 datarow=DataRow("| ", " | ", " |"),
445 padding=1,
446 with_header_hide=None,
447 ),
448 "html": TableFormat(
449 lineabove=_html_begin_table_without_header,
450 linebelowheader="",
451 linebetweenrows=None,
452 linebelow=Line("</tbody>\n</table>", "", "", ""),
453 headerrow=partial(_html_row_with_attrs, "th", False),
454 datarow=partial(_html_row_with_attrs, "td", False),
455 padding=0,
456 with_header_hide=["lineabove"],
457 ),
458 "unsafehtml": TableFormat(
459 lineabove=_html_begin_table_without_header,
460 linebelowheader="",
461 linebetweenrows=None,
462 linebelow=Line("</tbody>\n</table>", "", "", ""),
463 headerrow=partial(_html_row_with_attrs, "th", True),
464 datarow=partial(_html_row_with_attrs, "td", True),
465 padding=0,
466 with_header_hide=["lineabove"],
467 ),
468 "latex": TableFormat(
469 lineabove=_latex_line_begin_tabular,
470 linebelowheader=Line("\\hline", "", "", ""),
471 linebetweenrows=None,
472 linebelow=Line("\\hline\n\\end{tabular}", "", "", ""),
473 headerrow=_latex_row,
474 datarow=_latex_row,
475 padding=1,
476 with_header_hide=None,
477 ),
478 "latex_raw": TableFormat(
479 lineabove=_latex_line_begin_tabular,
480 linebelowheader=Line("\\hline", "", "", ""),
481 linebetweenrows=None,
482 linebelow=Line("\\hline\n\\end{tabular}", "", "", ""),
483 headerrow=partial(_latex_row, escrules={}),
484 datarow=partial(_latex_row, escrules={}),
485 padding=1,
486 with_header_hide=None,
487 ),
488 "latex_booktabs": TableFormat(
489 lineabove=partial(_latex_line_begin_tabular, booktabs=True),
490 linebelowheader=Line("\\midrule", "", "", ""),
491 linebetweenrows=None,
492 linebelow=Line("\\bottomrule\n\\end{tabular}", "", "", ""),
493 headerrow=_latex_row,
494 datarow=_latex_row,
495 padding=1,
496 with_header_hide=None,
497 ),
498 "latex_longtable": TableFormat(
499 lineabove=partial(_latex_line_begin_tabular, longtable=True),
500 linebelowheader=Line("\\hline\n\\endhead", "", "", ""),
501 linebetweenrows=None,
502 linebelow=Line("\\hline\n\\end{longtable}", "", "", ""),
503 headerrow=_latex_row,
504 datarow=_latex_row,
505 padding=1,
506 with_header_hide=None,
507 ),
508 "tsv": TableFormat(
509 lineabove=None,
510 linebelowheader=None,
511 linebetweenrows=None,
512 linebelow=None,
513 headerrow=DataRow("", "\t", ""),
514 datarow=DataRow("", "\t", ""),
515 padding=0,
516 with_header_hide=None,
517 ),
518 "textile": TableFormat(
519 lineabove=None,
520 linebelowheader=None,
521 linebetweenrows=None,
522 linebelow=None,
523 headerrow=DataRow("|_. ", "|_.", "|"),
524 datarow=_textile_row_with_attrs,
525 padding=1,
526 with_header_hide=None,
527 ),
528 }
529
530
531 tabulate_formats = list(sorted(_table_formats.keys()))
532
533 # The table formats for which multiline cells will be folded into subsequent
534 # table rows. The key is the original format specified at the API. The value is
535 # the format that will be used to represent the original format.
536 multiline_formats = {
537 "plain": "plain",
538 "simple": "simple",
539 "grid": "grid",
540 "fancy_grid": "fancy_grid",
541 "pipe": "pipe",
542 "orgtbl": "orgtbl",
543 "jira": "jira",
544 "presto": "presto",
545 "pretty": "pretty",
546 "psql": "psql",
547 "rst": "rst",
548 }
549
550 # TODO: Add multiline support for the remaining table formats:
551 # - mediawiki: Replace \n with <br>
552 # - moinmoin: TBD
553 # - youtrack: TBD
554 # - html: Replace \n with <br>
555 # - latex*: Use "makecell" package: In header, replace X\nY with
556 # \thead{X\\Y} and in data row, replace X\nY with \makecell{X\\Y}
557 # - tsv: TBD
558 # - textile: Replace \n with <br/> (must be well-formed XML)
559
560 _multiline_codes = re.compile(r"\r|\n|\r\n")
561 _multiline_codes_bytes = re.compile(b"\r|\n|\r\n")
562 _invisible_codes = re.compile(
563 r"\x1b\[\d+[;\d]*m|\x1b\[\d*\;\d*\;\d*m|\x1b\]8;;(.*?)\x1b\\"
564 ) # ANSI color codes
565 _invisible_codes_bytes = re.compile(
566 b"\x1b\\[\\d+\\[;\\d]*m|\x1b\\[\\d*;\\d*;\\d*m|\\x1b\\]8;;(.*?)\\x1b\\\\"
567 ) # ANSI color codes
568 _invisible_codes_link = re.compile(
569 r"\x1B]8;[a-zA-Z0-9:]*;[^\x1B]+\x1B\\([^\x1b]+)\x1B]8;;\x1B\\"
570 ) # Terminal hyperlinks
571
572
573 def simple_separated_format(separator):
574 """Construct a simple TableFormat with columns separated by a separator.
575
576 >>> tsv = simple_separated_format("\\t") ; \
577 tabulate([["foo", 1], ["spam", 23]], tablefmt=tsv) == 'foo \\t 1\\nspam\\t23'
578 True
579
580 """
581 return TableFormat(
582 None,
583 None,
584 None,
585 None,
586 headerrow=DataRow("", separator, ""),
587 datarow=DataRow("", separator, ""),
588 padding=0,
589 with_header_hide=None,
590 )
591
592
593 def _isconvertible(conv, string):
594 try:
595 conv(string)
596 return True
597 except (ValueError, TypeError):
598 return False
599
600
601 def _isnumber(string):
602 """
603 >>> _isnumber("123.45")
604 True
605 >>> _isnumber("123")
606 True
607 >>> _isnumber("spam")
608 False
609 >>> _isnumber("123e45678")
610 False
611 >>> _isnumber("inf")
612 True
613 """
614 if not _isconvertible(float, string):
615 return False
616 elif isinstance(string, (_text_type, _binary_type)) and (
617 math.isinf(float(string)) or math.isnan(float(string))
618 ):
619 return string.lower() in ["inf", "-inf", "nan"]
620 return True
621
622
623 def _isint(string, inttype=int):
624 """
625 >>> _isint("123")
626 True
627 >>> _isint("123.45")
628 False
629 """
630 return (
631 type(string) is inttype
632 or (isinstance(string, _binary_type) or isinstance(string, _text_type))
633 and _isconvertible(inttype, string)
634 )
635
636
637 def _isbool(string):
638 """
639 >>> _isbool(True)
640 True
641 >>> _isbool("False")
642 True
643 >>> _isbool(1)
644 False
645 """
646 return type(string) is _bool_type or (
647 isinstance(string, (_binary_type, _text_type)) and string in ("True", "False")
648 )
649
650
651 def _type(string, has_invisible=True, numparse=True):
652 """The least generic type (type(None), int, float, str, unicode).
653
654 >>> _type(None) is type(None)
655 True
656 >>> _type("foo") is type("")
657 True
658 >>> _type("1") is type(1)
659 True
660 >>> _type('\x1b[31m42\x1b[0m') is type(42)
661 True
662 >>> _type('\x1b[31m42\x1b[0m') is type(42)
663 True
664
665 """
666
667 if has_invisible and (
668 isinstance(string, _text_type) or isinstance(string, _binary_type)
669 ):
670 string = _strip_invisible(string)
671
672 if string is None:
673 return _none_type
674 elif hasattr(string, "isoformat"): # datetime.datetime, date, and time
675 return _text_type
676 elif _isbool(string):
677 return _bool_type
678 elif _isint(string) and numparse:
679 return int
680 elif _isint(string, _long_type) and numparse:
681 return int
682 elif _isnumber(string) and numparse:
683 return float
684 elif isinstance(string, _binary_type):
685 return _binary_type
686 else:
687 return _text_type
688
689
690 def _afterpoint(string):
691 """Symbols after a decimal point, -1 if the string lacks the decimal point.
692
693 >>> _afterpoint("123.45")
694 2
695 >>> _afterpoint("1001")
696 -1
697 >>> _afterpoint("eggs")
698 -1
699 >>> _afterpoint("123e45")
700 2
701
702 """
703 if _isnumber(string):
704 if _isint(string):
705 return -1
706 else:
707 pos = string.rfind(".")
708 pos = string.lower().rfind("e") if pos < 0 else pos
709 if pos >= 0:
710 return len(string) - pos - 1
711 else:
712 return -1 # no point
713 else:
714 return -1 # not a number
715
716
717 def _padleft(width, s):
718 """Flush right.
719
720 >>> _padleft(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430'
721 True
722
723 """
724 fmt = "{0:>%ds}" % width
725 return fmt.format(s)
726
727
728 def _padright(width, s):
729 """Flush left.
730
731 >>> _padright(6, '\u044f\u0439\u0446\u0430') == '\u044f\u0439\u0446\u0430 '
732 True
733
734 """
735 fmt = "{0:<%ds}" % width
736 return fmt.format(s)
737
738
739 def _padboth(width, s):
740 """Center string.
741
742 >>> _padboth(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430 '
743 True
744
745 """
746 fmt = "{0:^%ds}" % width
747 return fmt.format(s)
748
749
750 def _padnone(ignore_width, s):
751 return s
752
753
754 def _strip_invisible(s):
755 r"""Remove invisible ANSI color codes.
756
757 >>> str(_strip_invisible('\x1B]8;;https://example.com\x1B\\This is a link\x1B]8;;\x1B\\'))
758 'This is a link'
759
760 """
761 if isinstance(s, _text_type):
762 links_removed = re.sub(_invisible_codes_link, "\\1", s)
763 return re.sub(_invisible_codes, "", links_removed)
764 else: # a bytestring
765 return re.sub(_invisible_codes_bytes, "", s)
766
767
768 def _visible_width(s):
769 """Visible width of a printed string. ANSI color codes are removed.
770
771 >>> _visible_width('\x1b[31mhello\x1b[0m'), _visible_width("world")
772 (5, 5)
773
774 """
775 # optional wide-character support
776 if wcwidth is not None and WIDE_CHARS_MODE:
777 len_fn = wcwidth.wcswidth
778 else:
779 len_fn = len
780 if isinstance(s, _text_type) or isinstance(s, _binary_type):
781 return len_fn(_strip_invisible(s))
782 else:
783 return len_fn(_text_type(s))
784
785
786 def _is_multiline(s):
787 if isinstance(s, _text_type):
788 return bool(re.search(_multiline_codes, s))
789 else: # a bytestring
790 return bool(re.search(_multiline_codes_bytes, s))
791
792
793 def _multiline_width(multiline_s, line_width_fn=len):
794 """Visible width of a potentially multiline content."""
795 return max(map(line_width_fn, re.split("[\r\n]", multiline_s)))
796
797
798 def _choose_width_fn(has_invisible, enable_widechars, is_multiline):
799 """Return a function to calculate visible cell width."""
800 if has_invisible:
801 line_width_fn = _visible_width
802 elif enable_widechars: # optional wide-character support if available
803 line_width_fn = wcwidth.wcswidth
804 else:
805 line_width_fn = len
806 if is_multiline:
807 width_fn = lambda s: _multiline_width(s, line_width_fn) # noqa
808 else:
809 width_fn = line_width_fn
810 return width_fn
811
812
813 def _align_column_choose_padfn(strings, alignment, has_invisible):
814 if alignment == "right":
815 if not PRESERVE_WHITESPACE:
816 strings = [s.strip() for s in strings]
817 padfn = _padleft
818 elif alignment == "center":
819 if not PRESERVE_WHITESPACE:
820 strings = [s.strip() for s in strings]
821 padfn = _padboth
822 elif alignment == "decimal":
823 if has_invisible:
824 decimals = [_afterpoint(_strip_invisible(s)) for s in strings]
825 else:
826 decimals = [_afterpoint(s) for s in strings]
827 maxdecimals = max(decimals)
828 strings = [s + (maxdecimals - decs) * " " for s, decs in zip(strings, decimals)]
829 padfn = _padleft
830 elif not alignment:
831 padfn = _padnone
832 else:
833 if not PRESERVE_WHITESPACE:
834 strings = [s.strip() for s in strings]
835 padfn = _padright
836 return strings, padfn
837
838
839 def _align_column_choose_width_fn(has_invisible, enable_widechars, is_multiline):
840 if has_invisible:
841 line_width_fn = _visible_width
842 elif enable_widechars: # optional wide-character support if available
843 line_width_fn = wcwidth.wcswidth
844 else:
845 line_width_fn = len
846 if is_multiline:
847 width_fn = lambda s: _align_column_multiline_width(s, line_width_fn) # noqa
848 else:
849 width_fn = line_width_fn
850 return width_fn
851
852
853 def _align_column_multiline_width(multiline_s, line_width_fn=len):
854 """Visible width of a potentially multiline content."""
855 return list(map(line_width_fn, re.split("[\r\n]", multiline_s)))
856
857
858 def _flat_list(nested_list):
859 ret = []
860 for item in nested_list:
861 if isinstance(item, list):
862 for subitem in item:
863 ret.append(subitem)
864 else:
865 ret.append(item)
866 return ret
867
868
869 def _align_column(
870 strings,
871 alignment,
872 minwidth=0,
873 has_invisible=True,
874 enable_widechars=False,
875 is_multiline=False,
876 ):
877 """[string] -> [padded_string]"""
878 strings, padfn = _align_column_choose_padfn(strings, alignment, has_invisible)
879 width_fn = _align_column_choose_width_fn(
880 has_invisible, enable_widechars, is_multiline
881 )
882
883 s_widths = list(map(width_fn, strings))
884 maxwidth = max(max(_flat_list(s_widths)), minwidth)
885 # TODO: refactor column alignment in single-line and multiline modes
886 if is_multiline:
887 if not enable_widechars and not has_invisible:
888 padded_strings = [
889 "\n".join([padfn(maxwidth, s) for s in ms.splitlines()])
890 for ms in strings
891 ]
892 else:
893 # enable wide-character width corrections
894 s_lens = [[len(s) for s in re.split("[\r\n]", ms)] for ms in strings]
895 visible_widths = [
896 [maxwidth - (w - l) for w, l in zip(mw, ml)]
897 for mw, ml in zip(s_widths, s_lens)
898 ]
899 # wcswidth and _visible_width don't count invisible characters;
900 # padfn doesn't need to apply another correction
901 padded_strings = [
902 "\n".join([padfn(w, s) for s, w in zip((ms.splitlines() or ms), mw)])
903 for ms, mw in zip(strings, visible_widths)
904 ]
905 else: # single-line cell values
906 if not enable_widechars and not has_invisible:
907 padded_strings = [padfn(maxwidth, s) for s in strings]
908 else:
909 # enable wide-character width corrections
910 s_lens = list(map(len, strings))
911 visible_widths = [maxwidth - (w - l) for w, l in zip(s_widths, s_lens)]
912 # wcswidth and _visible_width don't count invisible characters;
913 # padfn doesn't need to apply another correction
914 padded_strings = [padfn(w, s) for s, w in zip(strings, visible_widths)]
915 return padded_strings
916
917
918 def _more_generic(type1, type2):
919 types = {
920 _none_type: 0,
921 _bool_type: 1,
922 int: 2,
923 float: 3,
924 _binary_type: 4,
925 _text_type: 5,
926 }
927 invtypes = {
928 5: _text_type,
929 4: _binary_type,
930 3: float,
931 2: int,
932 1: _bool_type,
933 0: _none_type,
934 }
935 moregeneric = max(types.get(type1, 5), types.get(type2, 5))
936 return invtypes[moregeneric]
937
938
939 def _column_type(strings, has_invisible=True, numparse=True):
940 """The least generic type all column values are convertible to.
941
942 >>> _column_type([True, False]) is _bool_type
943 True
944 >>> _column_type(["1", "2"]) is _int_type
945 True
946 >>> _column_type(["1", "2.3"]) is _float_type
947 True
948 >>> _column_type(["1", "2.3", "four"]) is _text_type
949 True
950 >>> _column_type(["four", '\u043f\u044f\u0442\u044c']) is _text_type
951 True
952 >>> _column_type([None, "brux"]) is _text_type
953 True
954 >>> _column_type([1, 2, None]) is _int_type
955 True
956 >>> import datetime as dt
957 >>> _column_type([dt.datetime(1991,2,19), dt.time(17,35)]) is _text_type
958 True
959
960 """
961 types = [_type(s, has_invisible, numparse) for s in strings]
962 return reduce(_more_generic, types, _bool_type)
963
964
965 def _format(val, valtype, floatfmt, missingval="", has_invisible=True):
966 """Format a value according to its type.
967
968 Unicode is supported:
969
970 >>> hrow = ['\u0431\u0443\u043a\u0432\u0430', '\u0446\u0438\u0444\u0440\u0430'] ; \
971 tbl = [['\u0430\u0437', 2], ['\u0431\u0443\u043a\u0438', 4]] ; \
972 good_result = '\\u0431\\u0443\\u043a\\u0432\\u0430 \\u0446\\u0438\\u0444\\u0440\\u0430\\n------- -------\\n\\u0430\\u0437 2\\n\\u0431\\u0443\\u043a\\u0438 4' ; \
973 tabulate(tbl, headers=hrow) == good_result
974 True
975
976 """ # noqa
977 if val is None:
978 return missingval
979
980 if valtype in [int, _text_type]:
981 return "{0}".format(val)
982 elif valtype is _binary_type:
983 try:
984 return _text_type(val, "ascii")
985 except TypeError:
986 return _text_type(val)
987 elif valtype is float:
988 is_a_colored_number = has_invisible and isinstance(
989 val, (_text_type, _binary_type)
990 )
991 if is_a_colored_number:
992 raw_val = _strip_invisible(val)
993 formatted_val = format(float(raw_val), floatfmt)
994 return val.replace(raw_val, formatted_val)
995 else:
996 return format(float(val), floatfmt)
997 else:
998 return "{0}".format(val)
999
1000
1001 def _align_header(
1002 header, alignment, width, visible_width, is_multiline=False, width_fn=None
1003 ):
1004 "Pad string header to width chars given known visible_width of the header."
1005 if is_multiline:
1006 header_lines = re.split(_multiline_codes, header)
1007 padded_lines = [
1008 _align_header(h, alignment, width, width_fn(h)) for h in header_lines
1009 ]
1010 return "\n".join(padded_lines)
1011 # else: not multiline
1012 ninvisible = len(header) - visible_width
1013 width += ninvisible
1014 if alignment == "left":
1015 return _padright(width, header)
1016 elif alignment == "center":
1017 return _padboth(width, header)
1018 elif not alignment:
1019 return "{0}".format(header)
1020 else:
1021 return _padleft(width, header)
1022
1023
1024 def _prepend_row_index(rows, index):
1025 """Add a left-most index column."""
1026 if index is None or index is False:
1027 return rows
1028 if len(index) != len(rows):
1029 print("index=", index)
1030 print("rows=", rows)
1031 raise ValueError("index must be as long as the number of data rows")
1032 rows = [[v] + list(row) for v, row in zip(index, rows)]
1033 return rows
1034
1035
1036 def _bool(val):
1037 "A wrapper around standard bool() which doesn't throw on NumPy arrays"
1038 try:
1039 return bool(val)
1040 except ValueError: # val is likely to be a numpy array with many elements
1041 return False
1042
1043
1044 def _normalize_tabular_data(tabular_data, headers, showindex="default"):
1045 """Transform a supported data type to a list of lists, and a list of headers.
1046
1047 Supported tabular data types:
1048
1049 * list-of-lists or another iterable of iterables
1050
1051 * list of named tuples (usually used with headers="keys")
1052
1053 * list of dicts (usually used with headers="keys")
1054
1055 * list of OrderedDicts (usually used with headers="keys")
1056
1057 * 2D NumPy arrays
1058
1059 * NumPy record arrays (usually used with headers="keys")
1060
1061 * dict of iterables (usually used with headers="keys")
1062
1063 * pandas.DataFrame (usually used with headers="keys")
1064
1065 The first row can be used as headers if headers="firstrow",
1066 column indices can be used as headers if headers="keys".
1067
1068 If showindex="default", show row indices of the pandas.DataFrame.
1069 If showindex="always", show row indices for all types of data.
1070 If showindex="never", don't show row indices for all types of data.
1071 If showindex is an iterable, show its values as row indices.
1072
1073 """
1074
1075 try:
1076 bool(headers)
1077 is_headers2bool_broken = False # noqa
1078 except ValueError: # numpy.ndarray, pandas.core.index.Index, ...
1079 is_headers2bool_broken = True # noqa
1080 headers = list(headers)
1081
1082 index = None
1083 if hasattr(tabular_data, "keys") and hasattr(tabular_data, "values"):
1084 # dict-like and pandas.DataFrame?
1085 if hasattr(tabular_data.values, "__call__"):
1086 # likely a conventional dict
1087 keys = tabular_data.keys()
1088 rows = list(
1089 izip_longest(*tabular_data.values())
1090 ) # columns have to be transposed
1091 elif hasattr(tabular_data, "index"):
1092 # values is a property, has .index => it's likely a pandas.DataFrame (pandas 0.11.0)
1093 keys = list(tabular_data)
1094 if (
1095 showindex in ["default", "always", True]
1096 and tabular_data.index.name is not None
1097 ):
1098 if isinstance(tabular_data.index.name, list):
1099 keys[:0] = tabular_data.index.name
1100 else:
1101 keys[:0] = [tabular_data.index.name]
1102 vals = tabular_data.values # values matrix doesn't need to be transposed
1103 # for DataFrames add an index per default
1104 index = list(tabular_data.index)
1105 rows = [list(row) for row in vals]
1106 else:
1107 raise ValueError("tabular data doesn't appear to be a dict or a DataFrame")
1108
1109 if headers == "keys":
1110 headers = list(map(_text_type, keys)) # headers should be strings
1111
1112 else: # it's a usual an iterable of iterables, or a NumPy array
1113 rows = list(tabular_data)
1114
1115 if headers == "keys" and not rows:
1116 # an empty table (issue #81)
1117 headers = []
1118 elif (
1119 headers == "keys"
1120 and hasattr(tabular_data, "dtype")
1121 and getattr(tabular_data.dtype, "names")
1122 ):
1123 # numpy record array
1124 headers = tabular_data.dtype.names
1125 elif (
1126 headers == "keys"
1127 and len(rows) > 0
1128 and isinstance(rows[0], tuple)
1129 and hasattr(rows[0], "_fields")
1130 ):
1131 # namedtuple
1132 headers = list(map(_text_type, rows[0]._fields))
1133 elif len(rows) > 0 and hasattr(rows[0], "keys") and hasattr(rows[0], "values"):
1134 # dict-like object
1135 uniq_keys = set() # implements hashed lookup
1136 keys = [] # storage for set
1137 if headers == "firstrow":
1138 firstdict = rows[0] if len(rows) > 0 else {}
1139 keys.extend(firstdict.keys())
1140 uniq_keys.update(keys)
1141 rows = rows[1:]
1142 for row in rows:
1143 for k in row.keys():
1144 # Save unique items in input order
1145 if k not in uniq_keys:
1146 keys.append(k)
1147 uniq_keys.add(k)
1148 if headers == "keys":
1149 headers = keys
1150 elif isinstance(headers, dict):
1151 # a dict of headers for a list of dicts
1152 headers = [headers.get(k, k) for k in keys]
1153 headers = list(map(_text_type, headers))
1154 elif headers == "firstrow":
1155 if len(rows) > 0:
1156 headers = [firstdict.get(k, k) for k in keys]
1157 headers = list(map(_text_type, headers))
1158 else:
1159 headers = []
1160 elif headers:
1161 raise ValueError(
1162 "headers for a list of dicts is not a dict or a keyword"
1163 )
1164 rows = [[row.get(k) for k in keys] for row in rows]
1165
1166 elif (
1167 headers == "keys"
1168 and hasattr(tabular_data, "description")
1169 and hasattr(tabular_data, "fetchone")
1170 and hasattr(tabular_data, "rowcount")
1171 ):
1172 # Python Database API cursor object (PEP 0249)
1173 # print tabulate(cursor, headers='keys')
1174 headers = [column[0] for column in tabular_data.description]
1175
1176 elif headers == "keys" and len(rows) > 0:
1177 # keys are column indices
1178 headers = list(map(_text_type, range(len(rows[0]))))
1179
1180 # take headers from the first row if necessary
1181 if headers == "firstrow" and len(rows) > 0:
1182 if index is not None:
1183 headers = [index[0]] + list(rows[0])
1184 index = index[1:]
1185 else:
1186 headers = rows[0]
1187 headers = list(map(_text_type, headers)) # headers should be strings
1188 rows = rows[1:]
1189
1190 headers = list(map(_text_type, headers))
1191 rows = list(map(list, rows))
1192
1193 # add or remove an index column
1194 showindex_is_a_str = type(showindex) in [_text_type, _binary_type]
1195 if showindex == "default" and index is not None:
1196 rows = _prepend_row_index(rows, index)
1197 elif isinstance(showindex, Iterable) and not showindex_is_a_str:
1198 rows = _prepend_row_index(rows, list(showindex))
1199 elif showindex == "always" or (_bool(showindex) and not showindex_is_a_str):
1200 if index is None:
1201 index = list(range(len(rows)))
1202 rows = _prepend_row_index(rows, index)
1203 elif showindex == "never" or (not _bool(showindex) and not showindex_is_a_str):
1204 pass
1205
1206 # pad with empty headers for initial columns if necessary
1207 if headers and len(rows) > 0:
1208 nhs = len(headers)
1209 ncols = len(rows[0])
1210 if nhs < ncols:
1211 headers = [""] * (ncols - nhs) + headers
1212
1213 return rows, headers
1214
1215
1216 def tabulate(
1217 tabular_data,
1218 headers=(),
1219 tablefmt="simple",
1220 floatfmt=_DEFAULT_FLOATFMT,
1221 numalign=_DEFAULT_ALIGN,
1222 stralign=_DEFAULT_ALIGN,
1223 missingval=_DEFAULT_MISSINGVAL,
1224 showindex="default",
1225 disable_numparse=False,
1226 colalign=None,
1227 ):
1228 """Format a fixed width table for pretty printing.
1229
1230 >>> print(tabulate([[1, 2.34], [-56, "8.999"], ["2", "10001"]]))
1231 --- ---------
1232 1 2.34
1233 -56 8.999
1234 2 10001
1235 --- ---------
1236
1237 The first required argument (`tabular_data`) can be a
1238 list-of-lists (or another iterable of iterables), a list of named
1239 tuples, a dictionary of iterables, an iterable of dictionaries,
1240 a two-dimensional NumPy array, NumPy record array, or a Pandas'
1241 dataframe.
1242
1243
1244 Table headers
1245 -------------
1246
1247 To print nice column headers, supply the second argument (`headers`):
1248
1249 - `headers` can be an explicit list of column headers
1250 - if `headers="firstrow"`, then the first row of data is used
1251 - if `headers="keys"`, then dictionary keys or column indices are used
1252
1253 Otherwise a headerless table is produced.
1254
1255 If the number of headers is less than the number of columns, they
1256 are supposed to be names of the last columns. This is consistent
1257 with the plain-text format of R and Pandas' dataframes.
1258
1259 >>> print(tabulate([["sex","age"],["Alice","F",24],["Bob","M",19]],
1260 ... headers="firstrow"))
1261 sex age
1262 ----- ----- -----
1263 Alice F 24
1264 Bob M 19
1265
1266 By default, pandas.DataFrame data have an additional column called
1267 row index. To add a similar column to all other types of data,
1268 use `showindex="always"` or `showindex=True`. To suppress row indices
1269 for all types of data, pass `showindex="never" or `showindex=False`.
1270 To add a custom row index column, pass `showindex=some_iterable`.
1271
1272 >>> print(tabulate([["F",24],["M",19]], showindex="always"))
1273 - - --
1274 0 F 24
1275 1 M 19
1276 - - --
1277
1278
1279 Column alignment
1280 ----------------
1281
1282 `tabulate` tries to detect column types automatically, and aligns
1283 the values properly. By default it aligns decimal points of the
1284 numbers (or flushes integer numbers to the right), and flushes
1285 everything else to the left. Possible column alignments
1286 (`numalign`, `stralign`) are: "right", "center", "left", "decimal"
1287 (only for `numalign`), and None (to disable alignment).
1288
1289
1290 Table formats
1291 -------------
1292
1293 `floatfmt` is a format specification used for columns which
1294 contain numeric data with a decimal point. This can also be
1295 a list or tuple of format strings, one per column.
1296
1297 `None` values are replaced with a `missingval` string (like
1298 `floatfmt`, this can also be a list of values for different
1299 columns):
1300
1301 >>> print(tabulate([["spam", 1, None],
1302 ... ["eggs", 42, 3.14],
1303 ... ["other", None, 2.7]], missingval="?"))
1304 ----- -- ----
1305 spam 1 ?
1306 eggs 42 3.14
1307 other ? 2.7
1308 ----- -- ----
1309
1310 Various plain-text table formats (`tablefmt`) are supported:
1311 'plain', 'simple', 'grid', 'pipe', 'orgtbl', 'rst', 'mediawiki',
1312 'latex', 'latex_raw', 'latex_booktabs', 'latex_longtable' and tsv.
1313 Variable `tabulate_formats`contains the list of currently supported formats.
1314
1315 "plain" format doesn't use any pseudographics to draw tables,
1316 it separates columns with a double space:
1317
1318 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1319 ... ["strings", "numbers"], "plain"))
1320 strings numbers
1321 spam 41.9999
1322 eggs 451
1323
1324 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="plain"))
1325 spam 41.9999
1326 eggs 451
1327
1328 "simple" format is like Pandoc simple_tables:
1329
1330 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1331 ... ["strings", "numbers"], "simple"))
1332 strings numbers
1333 --------- ---------
1334 spam 41.9999
1335 eggs 451
1336
1337 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="simple"))
1338 ---- --------
1339 spam 41.9999
1340 eggs 451
1341 ---- --------
1342
1343 "grid" is similar to tables produced by Emacs table.el package or
1344 Pandoc grid_tables:
1345
1346 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1347 ... ["strings", "numbers"], "grid"))
1348 +-----------+-----------+
1349 | strings | numbers |
1350 +===========+===========+
1351 | spam | 41.9999 |
1352 +-----------+-----------+
1353 | eggs | 451 |
1354 +-----------+-----------+
1355
1356 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="grid"))
1357 +------+----------+
1358 | spam | 41.9999 |
1359 +------+----------+
1360 | eggs | 451 |
1361 +------+----------+
1362
1363 "fancy_grid" draws a grid using box-drawing characters:
1364
1365 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1366 ... ["strings", "numbers"], "fancy_grid"))
1367 ╒═══════════╤═══════════╕
1368 │ strings │ numbers │
1369 ╞═══════════╪═══════════╡
1370 │ spam │ 41.9999 │
1371 ├───────────┼───────────┤
1372 │ eggs │ 451 │
1373 ╘═══════════╧═══════════╛
1374
1375 "pipe" is like tables in PHP Markdown Extra extension or Pandoc
1376 pipe_tables:
1377
1378 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1379 ... ["strings", "numbers"], "pipe"))
1380 | strings | numbers |
1381 |:----------|----------:|
1382 | spam | 41.9999 |
1383 | eggs | 451 |
1384
1385 "presto" is like tables produce by the Presto CLI:
1386
1387 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1388 ... ["strings", "numbers"], "presto"))
1389 strings | numbers
1390 -----------+-----------
1391 spam | 41.9999
1392 eggs | 451
1393
1394 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="pipe"))
1395 |:-----|---------:|
1396 | spam | 41.9999 |
1397 | eggs | 451 |
1398
1399 "orgtbl" is like tables in Emacs org-mode and orgtbl-mode. They
1400 are slightly different from "pipe" format by not using colons to
1401 define column alignment, and using a "+" sign to indicate line
1402 intersections:
1403
1404 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1405 ... ["strings", "numbers"], "orgtbl"))
1406 | strings | numbers |
1407 |-----------+-----------|
1408 | spam | 41.9999 |
1409 | eggs | 451 |
1410
1411
1412 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="orgtbl"))
1413 | spam | 41.9999 |
1414 | eggs | 451 |
1415
1416 "rst" is like a simple table format from reStructuredText; please
1417 note that reStructuredText accepts also "grid" tables:
1418
1419 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1420 ... ["strings", "numbers"], "rst"))
1421 ========= =========
1422 strings numbers
1423 ========= =========
1424 spam 41.9999
1425 eggs 451
1426 ========= =========
1427
1428 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="rst"))
1429 ==== ========
1430 spam 41.9999
1431 eggs 451
1432 ==== ========
1433
1434 "mediawiki" produces a table markup used in Wikipedia and on other
1435 MediaWiki-based sites:
1436
1437 >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]],
1438 ... headers="firstrow", tablefmt="mediawiki"))
1439 {| class="wikitable" style="text-align: left;"
1440 |+ <!-- caption -->
1441 |-
1442 ! strings !! align="right"| numbers
1443 |-
1444 | spam || align="right"| 41.9999
1445 |-
1446 | eggs || align="right"| 451
1447 |}
1448
1449 "html" produces HTML markup as an html.escape'd str
1450 with a ._repr_html_ method so that Jupyter Lab and Notebook display the HTML
1451 and a .str property so that the raw HTML remains accessible
1452 the unsafehtml table format can be used if an unescaped HTML format is required:
1453
1454 >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]],
1455 ... headers="firstrow", tablefmt="html"))
1456 <table>
1457 <thead>
1458 <tr><th>strings </th><th style="text-align: right;"> numbers</th></tr>
1459 </thead>
1460 <tbody>
1461 <tr><td>spam </td><td style="text-align: right;"> 41.9999</td></tr>
1462 <tr><td>eggs </td><td style="text-align: right;"> 451 </td></tr>
1463 </tbody>
1464 </table>
1465
1466 "latex" produces a tabular environment of LaTeX document markup:
1467
1468 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex"))
1469 \\begin{tabular}{lr}
1470 \\hline
1471 spam & 41.9999 \\\\
1472 eggs & 451 \\\\
1473 \\hline
1474 \\end{tabular}
1475
1476 "latex_raw" is similar to "latex", but doesn't escape special characters,
1477 such as backslash and underscore, so LaTeX commands may embedded into
1478 cells' values:
1479
1480 >>> print(tabulate([["spam$_9$", 41.9999], ["\\\\emph{eggs}", "451.0"]], tablefmt="latex_raw"))
1481 \\begin{tabular}{lr}
1482 \\hline
1483 spam$_9$ & 41.9999 \\\\
1484 \\emph{eggs} & 451 \\\\
1485 \\hline
1486 \\end{tabular}
1487
1488 "latex_booktabs" produces a tabular environment of LaTeX document markup
1489 using the booktabs.sty package:
1490
1491 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex_booktabs"))
1492 \\begin{tabular}{lr}
1493 \\toprule
1494 spam & 41.9999 \\\\
1495 eggs & 451 \\\\
1496 \\bottomrule
1497 \\end{tabular}
1498
1499 "latex_longtable" produces a tabular environment that can stretch along
1500 multiple pages, using the longtable package for LaTeX.
1501
1502 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex_longtable"))
1503 \\begin{longtable}{lr}
1504 \\hline
1505 spam & 41.9999 \\\\
1506 eggs & 451 \\\\
1507 \\hline
1508 \\end{longtable}
1509
1510
1511 Number parsing
1512 --------------
1513 By default, anything which can be parsed as a number is a number.
1514 This ensures numbers represented as strings are aligned properly.
1515 This can lead to weird results for particular strings such as
1516 specific git SHAs e.g. "42992e1" will be parsed into the number
1517 429920 and aligned as such.
1518
1519 To completely disable number parsing (and alignment), use
1520 `disable_numparse=True`. For more fine grained control, a list column
1521 indices is used to disable number parsing only on those columns
1522 e.g. `disable_numparse=[0, 2]` would disable number parsing only on the
1523 first and third columns.
1524 """
1525
1526 if tabular_data is None:
1527 tabular_data = []
1528 list_of_lists, headers = _normalize_tabular_data(
1529 tabular_data, headers, showindex=showindex
1530 )
1531
1532 # empty values in the first column of RST tables should be escaped (issue #82)
1533 # "" should be escaped as "\\ " or ".."
1534 if tablefmt == "rst":
1535 list_of_lists, headers = _rst_escape_first_column(list_of_lists, headers)
1536
1537 # PrettyTable formatting does not use any extra padding.
1538 # Numbers are not parsed and are treated the same as strings for alignment.
1539 # Check if pretty is the format being used and override the defaults so it
1540 # does not impact other formats.
1541 min_padding = MIN_PADDING
1542 if tablefmt == "pretty":
1543 min_padding = 0
1544 disable_numparse = True
1545 numalign = "center" if numalign == _DEFAULT_ALIGN else numalign
1546 stralign = "center" if stralign == _DEFAULT_ALIGN else stralign
1547 else:
1548 numalign = "decimal" if numalign == _DEFAULT_ALIGN else numalign
1549 stralign = "left" if stralign == _DEFAULT_ALIGN else stralign
1550
1551 # optimization: look for ANSI control codes once,
1552 # enable smart width functions only if a control code is found
1553 plain_text = "\t".join(
1554 ["\t".join(map(_text_type, headers))]
1555 + ["\t".join(map(_text_type, row)) for row in list_of_lists]
1556 )
1557
1558 has_invisible = re.search(_invisible_codes, plain_text)
1559 if not has_invisible:
1560 has_invisible = re.search(_invisible_codes_link, plain_text)
1561 enable_widechars = wcwidth is not None and WIDE_CHARS_MODE
1562 if (
1563 not isinstance(tablefmt, TableFormat)
1564 and tablefmt in multiline_formats
1565 and _is_multiline(plain_text)
1566 ):
1567 tablefmt = multiline_formats.get(tablefmt, tablefmt)
1568 is_multiline = True
1569 else:
1570 is_multiline = False
1571 width_fn = _choose_width_fn(has_invisible, enable_widechars, is_multiline)
1572
1573 # format rows and columns, convert numeric values to strings
1574 cols = list(izip_longest(*list_of_lists))
1575 numparses = _expand_numparse(disable_numparse, len(cols))
1576 coltypes = [_column_type(col, numparse=np) for col, np in zip(cols, numparses)]
1577 if isinstance(floatfmt, basestring): # old version
1578 float_formats = len(cols) * [
1579 floatfmt
1580 ] # just duplicate the string to use in each column
1581 else: # if floatfmt is list, tuple etc we have one per column
1582 float_formats = list(floatfmt)
1583 if len(float_formats) < len(cols):
1584 float_formats.extend((len(cols) - len(float_formats)) * [_DEFAULT_FLOATFMT])
1585 if isinstance(missingval, basestring):
1586 missing_vals = len(cols) * [missingval]
1587 else:
1588 missing_vals = list(missingval)
1589 if len(missing_vals) < len(cols):
1590 missing_vals.extend((len(cols) - len(missing_vals)) * [_DEFAULT_MISSINGVAL])
1591 cols = [
1592 [_format(v, ct, fl_fmt, miss_v, has_invisible) for v in c]
1593 for c, ct, fl_fmt, miss_v in zip(cols, coltypes, float_formats, missing_vals)
1594 ]
1595
1596 # align columns
1597 aligns = [numalign if ct in [int, float] else stralign for ct in coltypes]
1598 if colalign is not None:
1599 assert isinstance(colalign, Iterable)
1600 for idx, align in enumerate(colalign):
1601 aligns[idx] = align
1602 minwidths = (
1603 [width_fn(h) + min_padding for h in headers] if headers else [0] * len(cols)
1604 )
1605 cols = [
1606 _align_column(c, a, minw, has_invisible, enable_widechars, is_multiline)
1607 for c, a, minw in zip(cols, aligns, minwidths)
1608 ]
1609
1610 if headers:
1611 # align headers and add headers
1612 t_cols = cols or [[""]] * len(headers)
1613 t_aligns = aligns or [stralign] * len(headers)
1614 minwidths = [
1615 max(minw, max(width_fn(cl) for cl in c))
1616 for minw, c in zip(minwidths, t_cols)
1617 ]
1618 headers = [
1619 _align_header(h, a, minw, width_fn(h), is_multiline, width_fn)
1620 for h, a, minw in zip(headers, t_aligns, minwidths)
1621 ]
1622 rows = list(zip(*cols))
1623 else:
1624 minwidths = [max(width_fn(cl) for cl in c) for c in cols]
1625 rows = list(zip(*cols))
1626
1627 if not isinstance(tablefmt, TableFormat):
1628 tablefmt = _table_formats.get(tablefmt, _table_formats["simple"])
1629
1630 return _format_table(tablefmt, headers, rows, minwidths, aligns, is_multiline)
1631
1632
1633 def _expand_numparse(disable_numparse, column_count):
1634 """
1635 Return a list of bools of length `column_count` which indicates whether
1636 number parsing should be used on each column.
1637 If `disable_numparse` is a list of indices, each of those indices are False,
1638 and everything else is True.
1639 If `disable_numparse` is a bool, then the returned list is all the same.
1640 """
1641 if isinstance(disable_numparse, Iterable):
1642 numparses = [True] * column_count
1643 for index in disable_numparse:
1644 numparses[index] = False
1645 return numparses
1646 else:
1647 return [not disable_numparse] * column_count
1648
1649
1650 def _pad_row(cells, padding):
1651 if cells:
1652 pad = " " * padding
1653 padded_cells = [pad + cell + pad for cell in cells]
1654 return padded_cells
1655 else:
1656 return cells
1657
1658
1659 def _build_simple_row(padded_cells, rowfmt):
1660 "Format row according to DataRow format without padding."
1661 begin, sep, end = rowfmt
1662 return (begin + sep.join(padded_cells) + end).rstrip()
1663
1664
1665 def _build_row(padded_cells, colwidths, colaligns, rowfmt):
1666 "Return a string which represents a row of data cells."
1667 if not rowfmt:
1668 return None
1669 if hasattr(rowfmt, "__call__"):
1670 return rowfmt(padded_cells, colwidths, colaligns)
1671 else:
1672 return _build_simple_row(padded_cells, rowfmt)
1673
1674
1675 def _append_basic_row(lines, padded_cells, colwidths, colaligns, rowfmt):
1676 lines.append(_build_row(padded_cells, colwidths, colaligns, rowfmt))
1677 return lines
1678
1679
1680 def _append_multiline_row(
1681 lines, padded_multiline_cells, padded_widths, colaligns, rowfmt, pad
1682 ):
1683 colwidths = [w - 2 * pad for w in padded_widths]
1684 cells_lines = [c.splitlines() for c in padded_multiline_cells]
1685 nlines = max(map(len, cells_lines)) # number of lines in the row
1686 # vertically pad cells where some lines are missing
1687 cells_lines = [
1688 (cl + [" " * w] * (nlines - len(cl))) for cl, w in zip(cells_lines, colwidths)
1689 ]
1690 lines_cells = [[cl[i] for cl in cells_lines] for i in range(nlines)]
1691 for ln in lines_cells:
1692 padded_ln = _pad_row(ln, pad)
1693 _append_basic_row(lines, padded_ln, colwidths, colaligns, rowfmt)
1694 return lines
1695
1696
1697 def _build_line(colwidths, colaligns, linefmt):
1698 "Return a string which represents a horizontal line."
1699 if not linefmt:
1700 return None
1701 if hasattr(linefmt, "__call__"):
1702 return linefmt(colwidths, colaligns)
1703 else:
1704 begin, fill, sep, end = linefmt
1705 cells = [fill * w for w in colwidths]
1706 return _build_simple_row(cells, (begin, sep, end))
1707
1708
1709 def _append_line(lines, colwidths, colaligns, linefmt):
1710 lines.append(_build_line(colwidths, colaligns, linefmt))
1711 return lines
1712
1713
1714 class JupyterHTMLStr(str):
1715 """Wrap the string with a _repr_html_ method so that Jupyter
1716 displays the HTML table"""
1717
1718 def _repr_html_(self):
1719 return self
1720
1721 @property
1722 def str(self):
1723 """add a .str property so that the raw string is still accessible"""
1724 return self
1725
1726
1727 def _format_table(fmt, headers, rows, colwidths, colaligns, is_multiline):
1728 """Produce a plain-text representation of the table."""
1729 lines = []
1730 hidden = fmt.with_header_hide if (headers and fmt.with_header_hide) else []
1731 pad = fmt.padding
1732 headerrow = fmt.headerrow
1733
1734 padded_widths = [(w + 2 * pad) for w in colwidths]
1735 if is_multiline:
1736 pad_row = lambda row, _: row # noqa do it later, in _append_multiline_row
1737 append_row = partial(_append_multiline_row, pad=pad)
1738 else:
1739 pad_row = _pad_row
1740 append_row = _append_basic_row
1741
1742 padded_headers = pad_row(headers, pad)
1743 padded_rows = [pad_row(row, pad) for row in rows]
1744
1745 if fmt.lineabove and "lineabove" not in hidden:
1746 _append_line(lines, padded_widths, colaligns, fmt.lineabove)
1747
1748 if padded_headers:
1749 append_row(lines, padded_headers, padded_widths, colaligns, headerrow)
1750 if fmt.linebelowheader and "linebelowheader" not in hidden:
1751 _append_line(lines, padded_widths, colaligns, fmt.linebelowheader)
1752
1753 if padded_rows and fmt.linebetweenrows and "linebetweenrows" not in hidden:
1754 # initial rows with a line below
1755 for row in padded_rows[:-1]:
1756 append_row(lines, row, padded_widths, colaligns, fmt.datarow)
1757 _append_line(lines, padded_widths, colaligns, fmt.linebetweenrows)
1758 # the last row without a line below
1759 append_row(lines, padded_rows[-1], padded_widths, colaligns, fmt.datarow)
1760 else:
1761 for row in padded_rows:
1762 append_row(lines, row, padded_widths, colaligns, fmt.datarow)
1763
1764 if fmt.linebelow and "linebelow" not in hidden:
1765 _append_line(lines, padded_widths, colaligns, fmt.linebelow)
1766
1767 if headers or rows:
1768 output = "\n".join(lines)
1769 if fmt.lineabove == _html_begin_table_without_header:
1770 return JupyterHTMLStr(output)
1771 else:
1772 return output
1773 else: # a completely empty table
1774 return ""
1775
1776
1777 def _main():
1778 """\
1779 Usage: tabulate [options] [FILE ...]
1780
1781 Pretty-print tabular data.
1782 See also https://github.com/astanin/python-tabulate
1783
1784 FILE a filename of the file with tabular data;
1785 if "-" or missing, read data from stdin.
1786
1787 Options:
1788
1789 -h, --help show this message
1790 -1, --header use the first row of data as a table header
1791 -o FILE, --output FILE print table to FILE (default: stdout)
1792 -s REGEXP, --sep REGEXP use a custom column separator (default: whitespace)
1793 -F FPFMT, --float FPFMT floating point number format (default: g)
1794 -f FMT, --format FMT set output table format; supported formats:
1795 plain, simple, grid, fancy_grid, pipe, orgtbl,
1796 rst, mediawiki, html, latex, latex_raw,
1797 latex_booktabs, latex_longtable, tsv
1798 (default: simple)
1799 """
1800 import getopt
1801 import sys
1802 import textwrap
1803
1804 usage = textwrap.dedent(_main.__doc__)
1805 try:
1806 opts, args = getopt.getopt(
1807 sys.argv[1:],
1808 "h1o:s:F:A:f:",
1809 ["help", "header", "output", "sep=", "float=", "align=", "format="],
1810 )
1811 except getopt.GetoptError as e:
1812 print(e)
1813 print(usage)
1814 sys.exit(2)
1815 headers = []
1816 floatfmt = _DEFAULT_FLOATFMT
1817 colalign = None
1818 tablefmt = "simple"
1819 sep = r"\s+"
1820 outfile = "-"
1821 for opt, value in opts:
1822 if opt in ["-1", "--header"]:
1823 headers = "firstrow"
1824 elif opt in ["-o", "--output"]:
1825 outfile = value
1826 elif opt in ["-F", "--float"]:
1827 floatfmt = value
1828 elif opt in ["-C", "--colalign"]:
1829 colalign = value.split()
1830 elif opt in ["-f", "--format"]:
1831 if value not in tabulate_formats:
1832 print("%s is not a supported table format" % value)
1833 print(usage)
1834 sys.exit(3)
1835 tablefmt = value
1836 elif opt in ["-s", "--sep"]:
1837 sep = value
1838 elif opt in ["-h", "--help"]:
1839 print(usage)
1840 sys.exit(0)
1841 files = [sys.stdin] if not args else args
1842 with (sys.stdout if outfile == "-" else open(outfile, "w")) as out:
1843 for f in files:
1844 if f == "-":
1845 f = sys.stdin
1846 if _is_file(f):
1847 _pprint_file(
1848 f,
1849 headers=headers,
1850 tablefmt=tablefmt,
1851 sep=sep,
1852 floatfmt=floatfmt,
1853 file=out,
1854 colalign=colalign,
1855 )
1856 else:
1857 with open(f) as fobj:
1858 _pprint_file(
1859 fobj,
1860 headers=headers,
1861 tablefmt=tablefmt,
1862 sep=sep,
1863 floatfmt=floatfmt,
1864 file=out,
1865 colalign=colalign,
1866 )
1867
1868
1869 def _pprint_file(fobject, headers, tablefmt, sep, floatfmt, file, colalign):
1870 rows = fobject.readlines()
1871 table = [re.split(sep, r.rstrip()) for r in rows if r.strip()]
1872 print(
1873 tabulate(table, headers, tablefmt, floatfmt=floatfmt, colalign=colalign),
1874 file=file,
1875 )
1876
1877
1878 if __name__ == "__main__":
1879 _main()