Mercurial > repos > shellac > sam_consensus_v3
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() |