Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tabulate/__init__.py: 14%

794 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +0000

1"""Pretty-print tabular data.""" 

2 

3from collections import namedtuple 

4from collections.abc import Iterable, Sized 

5from html import escape as htmlescape 

6from itertools import chain, zip_longest as izip_longest 

7from functools import reduce, partial 

8import io 

9import re 

10import math 

11import textwrap 

12import dataclasses 

13 

14try: 

15 import wcwidth # optional wide-character (CJK) support 

16except ImportError: 

17 wcwidth = None 

18 

19 

20def _is_file(f): 

21 return isinstance(f, io.IOBase) 

22 

23 

24__all__ = ["tabulate", "tabulate_formats", "simple_separated_format"] 

25try: 

26 from .version import version as __version__ # noqa: F401 

27except ImportError: 

28 pass # running __init__.py as a script, AppVeyor pytests 

29 

30 

31# minimum extra space in headers 

32MIN_PADDING = 2 

33 

34# Whether or not to preserve leading/trailing whitespace in data. 

35PRESERVE_WHITESPACE = False 

36 

37_DEFAULT_FLOATFMT = "g" 

38_DEFAULT_INTFMT = "" 

39_DEFAULT_MISSINGVAL = "" 

40# default align will be overwritten by "left", "center" or "decimal" 

41# depending on the formatter 

42_DEFAULT_ALIGN = "default" 

43 

44 

45# if True, enable wide-character (CJK) support 

46WIDE_CHARS_MODE = wcwidth is not None 

47 

48# Constant that can be used as part of passed rows to generate a separating line 

49# It is purposely an unprintable character, very unlikely to be used in a table 

50SEPARATING_LINE = "\001" 

51 

52Line = namedtuple("Line", ["begin", "hline", "sep", "end"]) 

53 

54 

55DataRow = namedtuple("DataRow", ["begin", "sep", "end"]) 

56 

57 

58# A table structure is supposed to be: 

59# 

60# --- lineabove --------- 

61# headerrow 

62# --- linebelowheader --- 

63# datarow 

64# --- linebetweenrows --- 

65# ... (more datarows) ... 

66# --- linebetweenrows --- 

67# last datarow 

68# --- linebelow --------- 

69# 

70# TableFormat's line* elements can be 

71# 

72# - either None, if the element is not used, 

73# - or a Line tuple, 

74# - or a function: [col_widths], [col_alignments] -> string. 

75# 

76# TableFormat's *row elements can be 

77# 

78# - either None, if the element is not used, 

79# - or a DataRow tuple, 

80# - or a function: [cell_values], [col_widths], [col_alignments] -> string. 

81# 

82# padding (an integer) is the amount of white space around data values. 

83# 

84# with_header_hide: 

85# 

86# - either None, to display all table elements unconditionally, 

87# - or a list of elements not to be displayed if the table has column headers. 

88# 

89TableFormat = namedtuple( 

90 "TableFormat", 

91 [ 

92 "lineabove", 

93 "linebelowheader", 

94 "linebetweenrows", 

95 "linebelow", 

96 "headerrow", 

97 "datarow", 

98 "padding", 

99 "with_header_hide", 

100 ], 

101) 

102 

103 

104def _is_separating_line(row): 

105 row_type = type(row) 

106 is_sl = (row_type == list or row_type == str) and ( 

107 (len(row) >= 1 and row[0] == SEPARATING_LINE) 

108 or (len(row) >= 2 and row[1] == SEPARATING_LINE) 

109 ) 

110 return is_sl 

111 

112 

113def _pipe_segment_with_colons(align, colwidth): 

114 """Return a segment of a horizontal line with optional colons which 

115 indicate column's alignment (as in `pipe` output format).""" 

116 w = colwidth 

117 if align in ["right", "decimal"]: 

118 return ("-" * (w - 1)) + ":" 

119 elif align == "center": 

120 return ":" + ("-" * (w - 2)) + ":" 

121 elif align == "left": 

122 return ":" + ("-" * (w - 1)) 

123 else: 

124 return "-" * w 

125 

126 

127def _pipe_line_with_colons(colwidths, colaligns): 

128 """Return a horizontal line with optional colons to indicate column's 

129 alignment (as in `pipe` output format).""" 

130 if not colaligns: # e.g. printing an empty data frame (github issue #15) 

131 colaligns = [""] * len(colwidths) 

132 segments = [_pipe_segment_with_colons(a, w) for a, w in zip(colaligns, colwidths)] 

133 return "|" + "|".join(segments) + "|" 

134 

135 

136def _mediawiki_row_with_attrs(separator, cell_values, colwidths, colaligns): 

137 alignment = { 

138 "left": "", 

139 "right": 'align="right"| ', 

140 "center": 'align="center"| ', 

141 "decimal": 'align="right"| ', 

142 } 

143 # hard-coded padding _around_ align attribute and value together 

144 # rather than padding parameter which affects only the value 

145 values_with_attrs = [ 

146 " " + alignment.get(a, "") + c + " " for c, a in zip(cell_values, colaligns) 

147 ] 

148 colsep = separator * 2 

149 return (separator + colsep.join(values_with_attrs)).rstrip() 

150 

151 

152def _textile_row_with_attrs(cell_values, colwidths, colaligns): 

153 cell_values[0] += " " 

154 alignment = {"left": "<.", "right": ">.", "center": "=.", "decimal": ">."} 

155 values = (alignment.get(a, "") + v for a, v in zip(colaligns, cell_values)) 

156 return "|" + "|".join(values) + "|" 

157 

158 

159def _html_begin_table_without_header(colwidths_ignore, colaligns_ignore): 

160 # this table header will be suppressed if there is a header row 

161 return "<table>\n<tbody>" 

162 

163 

164def _html_row_with_attrs(celltag, unsafe, cell_values, colwidths, colaligns): 

165 alignment = { 

166 "left": "", 

167 "right": ' style="text-align: right;"', 

168 "center": ' style="text-align: center;"', 

169 "decimal": ' style="text-align: right;"', 

170 } 

171 if unsafe: 

172 values_with_attrs = [ 

173 "<{0}{1}>{2}</{0}>".format(celltag, alignment.get(a, ""), c) 

174 for c, a in zip(cell_values, colaligns) 

175 ] 

176 else: 

177 values_with_attrs = [ 

178 "<{0}{1}>{2}</{0}>".format(celltag, alignment.get(a, ""), htmlescape(c)) 

179 for c, a in zip(cell_values, colaligns) 

180 ] 

181 rowhtml = "<tr>{}</tr>".format("".join(values_with_attrs).rstrip()) 

182 if celltag == "th": # it's a header row, create a new table header 

183 rowhtml = f"<table>\n<thead>\n{rowhtml}\n</thead>\n<tbody>" 

184 return rowhtml 

185 

186 

187def _moin_row_with_attrs(celltag, cell_values, colwidths, colaligns, header=""): 

188 alignment = { 

189 "left": "", 

190 "right": '<style="text-align: right;">', 

191 "center": '<style="text-align: center;">', 

192 "decimal": '<style="text-align: right;">', 

193 } 

194 values_with_attrs = [ 

195 "{}{} {} ".format(celltag, alignment.get(a, ""), header + c + header) 

196 for c, a in zip(cell_values, colaligns) 

197 ] 

198 return "".join(values_with_attrs) + "||" 

199 

200 

201def _latex_line_begin_tabular(colwidths, colaligns, booktabs=False, longtable=False): 

202 alignment = {"left": "l", "right": "r", "center": "c", "decimal": "r"} 

203 tabular_columns_fmt = "".join([alignment.get(a, "l") for a in colaligns]) 

204 return "\n".join( 

205 [ 

206 ("\\begin{tabular}{" if not longtable else "\\begin{longtable}{") 

207 + tabular_columns_fmt 

208 + "}", 

209 "\\toprule" if booktabs else "\\hline", 

210 ] 

211 ) 

212 

213 

214def _asciidoc_row(is_header, *args): 

215 """handle header and data rows for asciidoc format""" 

216 

217 def make_header_line(is_header, colwidths, colaligns): 

218 # generate the column specifiers 

219 

220 alignment = {"left": "<", "right": ">", "center": "^", "decimal": ">"} 

221 # use the column widths generated by tabulate for the asciidoc column width specifiers 

222 asciidoc_alignments = zip( 

223 colwidths, [alignment[colalign] for colalign in colaligns] 

224 ) 

225 asciidoc_column_specifiers = [ 

226 "{:d}{}".format(width, align) for width, align in asciidoc_alignments 

227 ] 

228 header_list = ['cols="' + (",".join(asciidoc_column_specifiers)) + '"'] 

229 

230 # generate the list of options (currently only "header") 

231 options_list = [] 

232 

233 if is_header: 

234 options_list.append("header") 

235 

236 if options_list: 

237 header_list += ['options="' + ",".join(options_list) + '"'] 

238 

239 # generate the list of entries in the table header field 

240 

241 return "[{}]\n|====".format(",".join(header_list)) 

242 

243 if len(args) == 2: 

244 # two arguments are passed if called in the context of aboveline 

245 # print the table header with column widths and optional header tag 

246 return make_header_line(False, *args) 

247 

248 elif len(args) == 3: 

249 # three arguments are passed if called in the context of dataline or headerline 

250 # print the table line and make the aboveline if it is a header 

251 

252 cell_values, colwidths, colaligns = args 

253 data_line = "|" + "|".join(cell_values) 

254 

255 if is_header: 

256 return make_header_line(True, colwidths, colaligns) + "\n" + data_line 

257 else: 

258 return data_line 

259 

260 else: 

261 raise ValueError( 

262 " _asciidoc_row() requires two (colwidths, colaligns) " 

263 + "or three (cell_values, colwidths, colaligns) arguments) " 

264 ) 

265 

266 

267LATEX_ESCAPE_RULES = { 

268 r"&": r"\&", 

269 r"%": r"\%", 

270 r"$": r"\$", 

271 r"#": r"\#", 

272 r"_": r"\_", 

273 r"^": r"\^{}", 

274 r"{": r"\{", 

275 r"}": r"\}", 

276 r"~": r"\textasciitilde{}", 

277 "\\": r"\textbackslash{}", 

278 r"<": r"\ensuremath{<}", 

279 r">": r"\ensuremath{>}", 

280} 

281 

282 

283def _latex_row(cell_values, colwidths, colaligns, escrules=LATEX_ESCAPE_RULES): 

284 def escape_char(c): 

285 return escrules.get(c, c) 

286 

287 escaped_values = ["".join(map(escape_char, cell)) for cell in cell_values] 

288 rowfmt = DataRow("", "&", "\\\\") 

289 return _build_simple_row(escaped_values, rowfmt) 

290 

291 

292def _rst_escape_first_column(rows, headers): 

293 def escape_empty(val): 

294 if isinstance(val, (str, bytes)) and not val.strip(): 

295 return ".." 

296 else: 

297 return val 

298 

299 new_headers = list(headers) 

300 new_rows = [] 

301 if headers: 

302 new_headers[0] = escape_empty(headers[0]) 

303 for row in rows: 

304 new_row = list(row) 

305 if new_row: 

306 new_row[0] = escape_empty(row[0]) 

307 new_rows.append(new_row) 

308 return new_rows, new_headers 

309 

310 

311_table_formats = { 

312 "simple": TableFormat( 

313 lineabove=Line("", "-", " ", ""), 

314 linebelowheader=Line("", "-", " ", ""), 

315 linebetweenrows=None, 

316 linebelow=Line("", "-", " ", ""), 

317 headerrow=DataRow("", " ", ""), 

318 datarow=DataRow("", " ", ""), 

319 padding=0, 

320 with_header_hide=["lineabove", "linebelow"], 

321 ), 

322 "plain": TableFormat( 

323 lineabove=None, 

324 linebelowheader=None, 

325 linebetweenrows=None, 

326 linebelow=None, 

327 headerrow=DataRow("", " ", ""), 

328 datarow=DataRow("", " ", ""), 

329 padding=0, 

330 with_header_hide=None, 

331 ), 

332 "grid": TableFormat( 

333 lineabove=Line("+", "-", "+", "+"), 

334 linebelowheader=Line("+", "=", "+", "+"), 

335 linebetweenrows=Line("+", "-", "+", "+"), 

336 linebelow=Line("+", "-", "+", "+"), 

337 headerrow=DataRow("|", "|", "|"), 

338 datarow=DataRow("|", "|", "|"), 

339 padding=1, 

340 with_header_hide=None, 

341 ), 

342 "simple_grid": TableFormat( 

343 lineabove=Line("┌", "─", "┬", "┐"), 

344 linebelowheader=Line("├", "─", "┼", "┤"), 

345 linebetweenrows=Line("├", "─", "┼", "┤"), 

346 linebelow=Line("└", "─", "┴", "┘"), 

347 headerrow=DataRow("│", "│", "│"), 

348 datarow=DataRow("│", "│", "│"), 

349 padding=1, 

350 with_header_hide=None, 

351 ), 

352 "rounded_grid": TableFormat( 

353 lineabove=Line("╭", "─", "┬", "╮"), 

354 linebelowheader=Line("├", "─", "┼", "┤"), 

355 linebetweenrows=Line("├", "─", "┼", "┤"), 

356 linebelow=Line("╰", "─", "┴", "╯"), 

357 headerrow=DataRow("│", "│", "│"), 

358 datarow=DataRow("│", "│", "│"), 

359 padding=1, 

360 with_header_hide=None, 

361 ), 

362 "heavy_grid": TableFormat( 

363 lineabove=Line("┏", "━", "┳", "┓"), 

364 linebelowheader=Line("┣", "━", "╋", "┫"), 

365 linebetweenrows=Line("┣", "━", "╋", "┫"), 

366 linebelow=Line("┗", "━", "┻", "┛"), 

367 headerrow=DataRow("┃", "┃", "┃"), 

368 datarow=DataRow("┃", "┃", "┃"), 

369 padding=1, 

370 with_header_hide=None, 

371 ), 

372 "mixed_grid": TableFormat( 

373 lineabove=Line("┍", "━", "┯", "┑"), 

374 linebelowheader=Line("┝", "━", "┿", "┥"), 

375 linebetweenrows=Line("├", "─", "┼", "┤"), 

376 linebelow=Line("┕", "━", "┷", "┙"), 

377 headerrow=DataRow("│", "│", "│"), 

378 datarow=DataRow("│", "│", "│"), 

379 padding=1, 

380 with_header_hide=None, 

381 ), 

382 "double_grid": TableFormat( 

383 lineabove=Line("╔", "═", "╦", "╗"), 

384 linebelowheader=Line("╠", "═", "╬", "╣"), 

385 linebetweenrows=Line("╠", "═", "╬", "╣"), 

386 linebelow=Line("╚", "═", "╩", "╝"), 

387 headerrow=DataRow("║", "║", "║"), 

388 datarow=DataRow("║", "║", "║"), 

389 padding=1, 

390 with_header_hide=None, 

391 ), 

392 "fancy_grid": TableFormat( 

393 lineabove=Line("╒", "═", "╤", "╕"), 

394 linebelowheader=Line("╞", "═", "╪", "╡"), 

395 linebetweenrows=Line("├", "─", "┼", "┤"), 

396 linebelow=Line("╘", "═", "╧", "╛"), 

397 headerrow=DataRow("│", "│", "│"), 

398 datarow=DataRow("│", "│", "│"), 

399 padding=1, 

400 with_header_hide=None, 

401 ), 

402 "outline": TableFormat( 

403 lineabove=Line("+", "-", "+", "+"), 

404 linebelowheader=Line("+", "=", "+", "+"), 

405 linebetweenrows=None, 

406 linebelow=Line("+", "-", "+", "+"), 

407 headerrow=DataRow("|", "|", "|"), 

408 datarow=DataRow("|", "|", "|"), 

409 padding=1, 

410 with_header_hide=None, 

411 ), 

412 "simple_outline": TableFormat( 

413 lineabove=Line("┌", "─", "┬", "┐"), 

414 linebelowheader=Line("├", "─", "┼", "┤"), 

415 linebetweenrows=None, 

416 linebelow=Line("└", "─", "┴", "┘"), 

417 headerrow=DataRow("│", "│", "│"), 

418 datarow=DataRow("│", "│", "│"), 

419 padding=1, 

420 with_header_hide=None, 

421 ), 

422 "rounded_outline": TableFormat( 

423 lineabove=Line("╭", "─", "┬", "╮"), 

424 linebelowheader=Line("├", "─", "┼", "┤"), 

425 linebetweenrows=None, 

426 linebelow=Line("╰", "─", "┴", "╯"), 

427 headerrow=DataRow("│", "│", "│"), 

428 datarow=DataRow("│", "│", "│"), 

429 padding=1, 

430 with_header_hide=None, 

431 ), 

432 "heavy_outline": TableFormat( 

433 lineabove=Line("┏", "━", "┳", "┓"), 

434 linebelowheader=Line("┣", "━", "╋", "┫"), 

435 linebetweenrows=None, 

436 linebelow=Line("┗", "━", "┻", "┛"), 

437 headerrow=DataRow("┃", "┃", "┃"), 

438 datarow=DataRow("┃", "┃", "┃"), 

439 padding=1, 

440 with_header_hide=None, 

441 ), 

442 "mixed_outline": TableFormat( 

443 lineabove=Line("┍", "━", "┯", "┑"), 

444 linebelowheader=Line("┝", "━", "┿", "┥"), 

445 linebetweenrows=None, 

446 linebelow=Line("┕", "━", "┷", "┙"), 

447 headerrow=DataRow("│", "│", "│"), 

448 datarow=DataRow("│", "│", "│"), 

449 padding=1, 

450 with_header_hide=None, 

451 ), 

452 "double_outline": TableFormat( 

453 lineabove=Line("╔", "═", "╦", "╗"), 

454 linebelowheader=Line("╠", "═", "╬", "╣"), 

455 linebetweenrows=None, 

456 linebelow=Line("╚", "═", "╩", "╝"), 

457 headerrow=DataRow("║", "║", "║"), 

458 datarow=DataRow("║", "║", "║"), 

459 padding=1, 

460 with_header_hide=None, 

461 ), 

462 "fancy_outline": TableFormat( 

463 lineabove=Line("╒", "═", "╤", "╕"), 

464 linebelowheader=Line("╞", "═", "╪", "╡"), 

465 linebetweenrows=None, 

466 linebelow=Line("╘", "═", "╧", "╛"), 

467 headerrow=DataRow("│", "│", "│"), 

468 datarow=DataRow("│", "│", "│"), 

469 padding=1, 

470 with_header_hide=None, 

471 ), 

472 "github": TableFormat( 

473 lineabove=Line("|", "-", "|", "|"), 

474 linebelowheader=Line("|", "-", "|", "|"), 

475 linebetweenrows=None, 

476 linebelow=None, 

477 headerrow=DataRow("|", "|", "|"), 

478 datarow=DataRow("|", "|", "|"), 

479 padding=1, 

480 with_header_hide=["lineabove"], 

481 ), 

482 "pipe": TableFormat( 

483 lineabove=_pipe_line_with_colons, 

484 linebelowheader=_pipe_line_with_colons, 

485 linebetweenrows=None, 

486 linebelow=None, 

487 headerrow=DataRow("|", "|", "|"), 

488 datarow=DataRow("|", "|", "|"), 

489 padding=1, 

490 with_header_hide=["lineabove"], 

491 ), 

492 "orgtbl": TableFormat( 

493 lineabove=None, 

494 linebelowheader=Line("|", "-", "+", "|"), 

495 linebetweenrows=None, 

496 linebelow=None, 

497 headerrow=DataRow("|", "|", "|"), 

498 datarow=DataRow("|", "|", "|"), 

499 padding=1, 

500 with_header_hide=None, 

501 ), 

502 "jira": TableFormat( 

503 lineabove=None, 

504 linebelowheader=None, 

505 linebetweenrows=None, 

506 linebelow=None, 

507 headerrow=DataRow("||", "||", "||"), 

508 datarow=DataRow("|", "|", "|"), 

509 padding=1, 

510 with_header_hide=None, 

511 ), 

512 "presto": TableFormat( 

513 lineabove=None, 

514 linebelowheader=Line("", "-", "+", ""), 

515 linebetweenrows=None, 

516 linebelow=None, 

517 headerrow=DataRow("", "|", ""), 

518 datarow=DataRow("", "|", ""), 

519 padding=1, 

520 with_header_hide=None, 

521 ), 

522 "pretty": TableFormat( 

523 lineabove=Line("+", "-", "+", "+"), 

524 linebelowheader=Line("+", "-", "+", "+"), 

525 linebetweenrows=None, 

526 linebelow=Line("+", "-", "+", "+"), 

527 headerrow=DataRow("|", "|", "|"), 

528 datarow=DataRow("|", "|", "|"), 

529 padding=1, 

530 with_header_hide=None, 

531 ), 

532 "psql": TableFormat( 

533 lineabove=Line("+", "-", "+", "+"), 

534 linebelowheader=Line("|", "-", "+", "|"), 

535 linebetweenrows=None, 

536 linebelow=Line("+", "-", "+", "+"), 

537 headerrow=DataRow("|", "|", "|"), 

538 datarow=DataRow("|", "|", "|"), 

539 padding=1, 

540 with_header_hide=None, 

541 ), 

542 "rst": TableFormat( 

543 lineabove=Line("", "=", " ", ""), 

544 linebelowheader=Line("", "=", " ", ""), 

545 linebetweenrows=None, 

546 linebelow=Line("", "=", " ", ""), 

547 headerrow=DataRow("", " ", ""), 

548 datarow=DataRow("", " ", ""), 

549 padding=0, 

550 with_header_hide=None, 

551 ), 

552 "mediawiki": TableFormat( 

553 lineabove=Line( 

554 '{| class="wikitable" style="text-align: left;"', 

555 "", 

556 "", 

557 "\n|+ <!-- caption -->\n|-", 

558 ), 

559 linebelowheader=Line("|-", "", "", ""), 

560 linebetweenrows=Line("|-", "", "", ""), 

561 linebelow=Line("|}", "", "", ""), 

562 headerrow=partial(_mediawiki_row_with_attrs, "!"), 

563 datarow=partial(_mediawiki_row_with_attrs, "|"), 

564 padding=0, 

565 with_header_hide=None, 

566 ), 

567 "moinmoin": TableFormat( 

568 lineabove=None, 

569 linebelowheader=None, 

570 linebetweenrows=None, 

571 linebelow=None, 

572 headerrow=partial(_moin_row_with_attrs, "||", header="'''"), 

573 datarow=partial(_moin_row_with_attrs, "||"), 

574 padding=1, 

575 with_header_hide=None, 

576 ), 

577 "youtrack": TableFormat( 

578 lineabove=None, 

579 linebelowheader=None, 

580 linebetweenrows=None, 

581 linebelow=None, 

582 headerrow=DataRow("|| ", " || ", " || "), 

583 datarow=DataRow("| ", " | ", " |"), 

584 padding=1, 

585 with_header_hide=None, 

586 ), 

587 "html": TableFormat( 

588 lineabove=_html_begin_table_without_header, 

589 linebelowheader="", 

590 linebetweenrows=None, 

591 linebelow=Line("</tbody>\n</table>", "", "", ""), 

592 headerrow=partial(_html_row_with_attrs, "th", False), 

593 datarow=partial(_html_row_with_attrs, "td", False), 

594 padding=0, 

595 with_header_hide=["lineabove"], 

596 ), 

597 "unsafehtml": TableFormat( 

598 lineabove=_html_begin_table_without_header, 

599 linebelowheader="", 

600 linebetweenrows=None, 

601 linebelow=Line("</tbody>\n</table>", "", "", ""), 

602 headerrow=partial(_html_row_with_attrs, "th", True), 

603 datarow=partial(_html_row_with_attrs, "td", True), 

604 padding=0, 

605 with_header_hide=["lineabove"], 

606 ), 

607 "latex": TableFormat( 

608 lineabove=_latex_line_begin_tabular, 

609 linebelowheader=Line("\\hline", "", "", ""), 

610 linebetweenrows=None, 

611 linebelow=Line("\\hline\n\\end{tabular}", "", "", ""), 

612 headerrow=_latex_row, 

613 datarow=_latex_row, 

614 padding=1, 

615 with_header_hide=None, 

616 ), 

617 "latex_raw": TableFormat( 

618 lineabove=_latex_line_begin_tabular, 

619 linebelowheader=Line("\\hline", "", "", ""), 

620 linebetweenrows=None, 

621 linebelow=Line("\\hline\n\\end{tabular}", "", "", ""), 

622 headerrow=partial(_latex_row, escrules={}), 

623 datarow=partial(_latex_row, escrules={}), 

624 padding=1, 

625 with_header_hide=None, 

626 ), 

627 "latex_booktabs": TableFormat( 

628 lineabove=partial(_latex_line_begin_tabular, booktabs=True), 

629 linebelowheader=Line("\\midrule", "", "", ""), 

630 linebetweenrows=None, 

631 linebelow=Line("\\bottomrule\n\\end{tabular}", "", "", ""), 

632 headerrow=_latex_row, 

633 datarow=_latex_row, 

634 padding=1, 

635 with_header_hide=None, 

636 ), 

637 "latex_longtable": TableFormat( 

638 lineabove=partial(_latex_line_begin_tabular, longtable=True), 

639 linebelowheader=Line("\\hline\n\\endhead", "", "", ""), 

640 linebetweenrows=None, 

641 linebelow=Line("\\hline\n\\end{longtable}", "", "", ""), 

642 headerrow=_latex_row, 

643 datarow=_latex_row, 

644 padding=1, 

645 with_header_hide=None, 

646 ), 

647 "tsv": TableFormat( 

648 lineabove=None, 

649 linebelowheader=None, 

650 linebetweenrows=None, 

651 linebelow=None, 

652 headerrow=DataRow("", "\t", ""), 

653 datarow=DataRow("", "\t", ""), 

654 padding=0, 

655 with_header_hide=None, 

656 ), 

657 "textile": TableFormat( 

658 lineabove=None, 

659 linebelowheader=None, 

660 linebetweenrows=None, 

661 linebelow=None, 

662 headerrow=DataRow("|_. ", "|_.", "|"), 

663 datarow=_textile_row_with_attrs, 

664 padding=1, 

665 with_header_hide=None, 

666 ), 

667 "asciidoc": TableFormat( 

668 lineabove=partial(_asciidoc_row, False), 

669 linebelowheader=None, 

670 linebetweenrows=None, 

671 linebelow=Line("|====", "", "", ""), 

672 headerrow=partial(_asciidoc_row, True), 

673 datarow=partial(_asciidoc_row, False), 

674 padding=1, 

675 with_header_hide=["lineabove"], 

676 ), 

677} 

678 

679 

680tabulate_formats = list(sorted(_table_formats.keys())) 

681 

682# The table formats for which multiline cells will be folded into subsequent 

683# table rows. The key is the original format specified at the API. The value is 

684# the format that will be used to represent the original format. 

685multiline_formats = { 

686 "plain": "plain", 

687 "simple": "simple", 

688 "grid": "grid", 

689 "simple_grid": "simple_grid", 

690 "rounded_grid": "rounded_grid", 

691 "heavy_grid": "heavy_grid", 

692 "mixed_grid": "mixed_grid", 

693 "double_grid": "double_grid", 

694 "fancy_grid": "fancy_grid", 

695 "pipe": "pipe", 

696 "orgtbl": "orgtbl", 

697 "jira": "jira", 

698 "presto": "presto", 

699 "pretty": "pretty", 

700 "psql": "psql", 

701 "rst": "rst", 

702} 

703 

704# TODO: Add multiline support for the remaining table formats: 

705# - mediawiki: Replace \n with <br> 

706# - moinmoin: TBD 

707# - youtrack: TBD 

708# - html: Replace \n with <br> 

709# - latex*: Use "makecell" package: In header, replace X\nY with 

710# \thead{X\\Y} and in data row, replace X\nY with \makecell{X\\Y} 

711# - tsv: TBD 

712# - textile: Replace \n with <br/> (must be well-formed XML) 

713 

714_multiline_codes = re.compile(r"\r|\n|\r\n") 

715_multiline_codes_bytes = re.compile(b"\r|\n|\r\n") 

716 

717# Handle ANSI escape sequences for both control sequence introducer (CSI) and 

718# operating system command (OSC). Both of these begin with 0x1b (or octal 033), 

719# which will be shown below as ESC. 

720# 

721# CSI ANSI escape codes have the following format, defined in section 5.4 of ECMA-48: 

722# 

723# CSI: ESC followed by the '[' character (0x5b) 

724# Parameter Bytes: 0..n bytes in the range 0x30-0x3f 

725# Intermediate Bytes: 0..n bytes in the range 0x20-0x2f 

726# Final Byte: a single byte in the range 0x40-0x7e 

727# 

728# Also include the terminal hyperlink sequences as described here: 

729# https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda 

730# 

731# OSC 8 ; params ; uri ST display_text OSC 8 ;; ST 

732# 

733# Example: \x1b]8;;https://example.com\x5ctext to show\x1b]8;;\x5c 

734# 

735# Where: 

736# OSC: ESC followed by the ']' character (0x5d) 

737# params: 0..n optional key value pairs separated by ':' (e.g. foo=bar:baz=qux:abc=123) 

738# URI: the actual URI with protocol scheme (e.g. https://, file://, ftp://) 

739# ST: ESC followed by the '\' character (0x5c) 

740_esc = r"\x1b" 

741_csi = rf"{_esc}\[" 

742_osc = rf"{_esc}\]" 

743_st = rf"{_esc}\\" 

744 

745_ansi_escape_pat = rf""" 

746 ( 

747 # terminal colors, etc 

748 {_csi} # CSI 

749 [\x30-\x3f]* # parameter bytes 

750 [\x20-\x2f]* # intermediate bytes 

751 [\x40-\x7e] # final byte 

752 | 

753 # terminal hyperlinks 

754 {_osc}8; # OSC opening 

755 (\w+=\w+:?)* # key=value params list (submatch 2) 

756 ; # delimiter 

757 ([^{_esc}]+) # URI - anything but ESC (submatch 3) 

758 {_st} # ST 

759 ([^{_esc}]+) # link text - anything but ESC (submatch 4) 

760 {_osc}8;;{_st} # "closing" OSC sequence 

761 ) 

762""" 

763_ansi_codes = re.compile(_ansi_escape_pat, re.VERBOSE) 

764_ansi_codes_bytes = re.compile(_ansi_escape_pat.encode("utf8"), re.VERBOSE) 

765_ansi_color_reset_code = "\033[0m" 

766 

767_float_with_thousands_separators = re.compile( 

768 r"^(([+-]?[0-9]{1,3})(?:,([0-9]{3}))*)?(?(1)\.[0-9]*|\.[0-9]+)?$" 

769) 

770 

771 

772def simple_separated_format(separator): 

773 """Construct a simple TableFormat with columns separated by a separator. 

774 

775 >>> tsv = simple_separated_format("\\t") ; \ 

776 tabulate([["foo", 1], ["spam", 23]], tablefmt=tsv) == 'foo \\t 1\\nspam\\t23' 

777 True 

778 

779 """ 

780 return TableFormat( 

781 None, 

782 None, 

783 None, 

784 None, 

785 headerrow=DataRow("", separator, ""), 

786 datarow=DataRow("", separator, ""), 

787 padding=0, 

788 with_header_hide=None, 

789 ) 

790 

791 

792def _isnumber_with_thousands_separator(string): 

793 """ 

794 >>> _isnumber_with_thousands_separator(".") 

795 False 

796 >>> _isnumber_with_thousands_separator("1") 

797 True 

798 >>> _isnumber_with_thousands_separator("1.") 

799 True 

800 >>> _isnumber_with_thousands_separator(".1") 

801 True 

802 >>> _isnumber_with_thousands_separator("1000") 

803 False 

804 >>> _isnumber_with_thousands_separator("1,000") 

805 True 

806 >>> _isnumber_with_thousands_separator("1,0000") 

807 False 

808 >>> _isnumber_with_thousands_separator("1,000.1234") 

809 True 

810 >>> _isnumber_with_thousands_separator(b"1,000.1234") 

811 True 

812 >>> _isnumber_with_thousands_separator("+1,000.1234") 

813 True 

814 >>> _isnumber_with_thousands_separator("-1,000.1234") 

815 True 

816 """ 

817 try: 

818 string = string.decode() 

819 except (UnicodeDecodeError, AttributeError): 

820 pass 

821 

822 return bool(re.match(_float_with_thousands_separators, string)) 

823 

824 

825def _isconvertible(conv, string): 

826 try: 

827 conv(string) 

828 return True 

829 except (ValueError, TypeError): 

830 return False 

831 

832 

833def _isnumber(string): 

834 """ 

835 >>> _isnumber("123.45") 

836 True 

837 >>> _isnumber("123") 

838 True 

839 >>> _isnumber("spam") 

840 False 

841 >>> _isnumber("123e45678") 

842 False 

843 >>> _isnumber("inf") 

844 True 

845 """ 

846 if not _isconvertible(float, string): 

847 return False 

848 elif isinstance(string, (str, bytes)) and ( 

849 math.isinf(float(string)) or math.isnan(float(string)) 

850 ): 

851 return string.lower() in ["inf", "-inf", "nan"] 

852 return True 

853 

854 

855def _isint(string, inttype=int): 

856 """ 

857 >>> _isint("123") 

858 True 

859 >>> _isint("123.45") 

860 False 

861 """ 

862 return ( 

863 type(string) is inttype 

864 or isinstance(string, (bytes, str)) 

865 and _isconvertible(inttype, string) 

866 ) 

867 

868 

869def _isbool(string): 

870 """ 

871 >>> _isbool(True) 

872 True 

873 >>> _isbool("False") 

874 True 

875 >>> _isbool(1) 

876 False 

877 """ 

878 return type(string) is bool or ( 

879 isinstance(string, (bytes, str)) and string in ("True", "False") 

880 ) 

881 

882 

883def _type(string, has_invisible=True, numparse=True): 

884 """The least generic type (type(None), int, float, str, unicode). 

885 

886 >>> _type(None) is type(None) 

887 True 

888 >>> _type("foo") is type("") 

889 True 

890 >>> _type("1") is type(1) 

891 True 

892 >>> _type('\x1b[31m42\x1b[0m') is type(42) 

893 True 

894 >>> _type('\x1b[31m42\x1b[0m') is type(42) 

895 True 

896 

897 """ 

898 

899 if has_invisible and isinstance(string, (str, bytes)): 

900 string = _strip_ansi(string) 

901 

902 if string is None: 

903 return type(None) 

904 elif hasattr(string, "isoformat"): # datetime.datetime, date, and time 

905 return str 

906 elif _isbool(string): 

907 return bool 

908 elif _isint(string) and numparse: 

909 return int 

910 elif _isnumber(string) and numparse: 

911 return float 

912 elif isinstance(string, bytes): 

913 return bytes 

914 else: 

915 return str 

916 

917 

918def _afterpoint(string): 

919 """Symbols after a decimal point, -1 if the string lacks the decimal point. 

920 

921 >>> _afterpoint("123.45") 

922 2 

923 >>> _afterpoint("1001") 

924 -1 

925 >>> _afterpoint("eggs") 

926 -1 

927 >>> _afterpoint("123e45") 

928 2 

929 >>> _afterpoint("123,456.78") 

930 2 

931 

932 """ 

933 if _isnumber(string) or _isnumber_with_thousands_separator(string): 

934 if _isint(string): 

935 return -1 

936 else: 

937 pos = string.rfind(".") 

938 pos = string.lower().rfind("e") if pos < 0 else pos 

939 if pos >= 0: 

940 return len(string) - pos - 1 

941 else: 

942 return -1 # no point 

943 else: 

944 return -1 # not a number 

945 

946 

947def _padleft(width, s): 

948 """Flush right. 

949 

950 >>> _padleft(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430' 

951 True 

952 

953 """ 

954 fmt = "{0:>%ds}" % width 

955 return fmt.format(s) 

956 

957 

958def _padright(width, s): 

959 """Flush left. 

960 

961 >>> _padright(6, '\u044f\u0439\u0446\u0430') == '\u044f\u0439\u0446\u0430 ' 

962 True 

963 

964 """ 

965 fmt = "{0:<%ds}" % width 

966 return fmt.format(s) 

967 

968 

969def _padboth(width, s): 

970 """Center string. 

971 

972 >>> _padboth(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430 ' 

973 True 

974 

975 """ 

976 fmt = "{0:^%ds}" % width 

977 return fmt.format(s) 

978 

979 

980def _padnone(ignore_width, s): 

981 return s 

982 

983 

984def _strip_ansi(s): 

985 r"""Remove ANSI escape sequences, both CSI (color codes, etc) and OSC hyperlinks. 

986 

987 CSI sequences are simply removed from the output, while OSC hyperlinks are replaced 

988 with the link text. Note: it may be desirable to show the URI instead but this is not 

989 supported. 

990 

991 >>> repr(_strip_ansi('\x1B]8;;https://example.com\x1B\\This is a link\x1B]8;;\x1B\\')) 

992 "'This is a link'" 

993 

994 >>> repr(_strip_ansi('\x1b[31mred\x1b[0m text')) 

995 "'red text'" 

996 

997 """ 

998 if isinstance(s, str): 

999 return _ansi_codes.sub(r"\4", s) 

1000 else: # a bytestring 

1001 return _ansi_codes_bytes.sub(r"\4", s) 

1002 

1003 

1004def _visible_width(s): 

1005 """Visible width of a printed string. ANSI color codes are removed. 

1006 

1007 >>> _visible_width('\x1b[31mhello\x1b[0m'), _visible_width("world") 

1008 (5, 5) 

1009 

1010 """ 

1011 # optional wide-character support 

1012 if wcwidth is not None and WIDE_CHARS_MODE: 

1013 len_fn = wcwidth.wcswidth 

1014 else: 

1015 len_fn = len 

1016 if isinstance(s, (str, bytes)): 

1017 return len_fn(_strip_ansi(s)) 

1018 else: 

1019 return len_fn(str(s)) 

1020 

1021 

1022def _is_multiline(s): 

1023 if isinstance(s, str): 

1024 return bool(re.search(_multiline_codes, s)) 

1025 else: # a bytestring 

1026 return bool(re.search(_multiline_codes_bytes, s)) 

1027 

1028 

1029def _multiline_width(multiline_s, line_width_fn=len): 

1030 """Visible width of a potentially multiline content.""" 

1031 return max(map(line_width_fn, re.split("[\r\n]", multiline_s))) 

1032 

1033 

1034def _choose_width_fn(has_invisible, enable_widechars, is_multiline): 

1035 """Return a function to calculate visible cell width.""" 

1036 if has_invisible: 

1037 line_width_fn = _visible_width 

1038 elif enable_widechars: # optional wide-character support if available 

1039 line_width_fn = wcwidth.wcswidth 

1040 else: 

1041 line_width_fn = len 

1042 if is_multiline: 

1043 width_fn = lambda s: _multiline_width(s, line_width_fn) # noqa 

1044 else: 

1045 width_fn = line_width_fn 

1046 return width_fn 

1047 

1048 

1049def _align_column_choose_padfn(strings, alignment, has_invisible): 

1050 if alignment == "right": 

1051 if not PRESERVE_WHITESPACE: 

1052 strings = [s.strip() for s in strings] 

1053 padfn = _padleft 

1054 elif alignment == "center": 

1055 if not PRESERVE_WHITESPACE: 

1056 strings = [s.strip() for s in strings] 

1057 padfn = _padboth 

1058 elif alignment == "decimal": 

1059 if has_invisible: 

1060 decimals = [_afterpoint(_strip_ansi(s)) for s in strings] 

1061 else: 

1062 decimals = [_afterpoint(s) for s in strings] 

1063 maxdecimals = max(decimals) 

1064 strings = [s + (maxdecimals - decs) * " " for s, decs in zip(strings, decimals)] 

1065 padfn = _padleft 

1066 elif not alignment: 

1067 padfn = _padnone 

1068 else: 

1069 if not PRESERVE_WHITESPACE: 

1070 strings = [s.strip() for s in strings] 

1071 padfn = _padright 

1072 return strings, padfn 

1073 

1074 

1075def _align_column_choose_width_fn(has_invisible, enable_widechars, is_multiline): 

1076 if has_invisible: 

1077 line_width_fn = _visible_width 

1078 elif enable_widechars: # optional wide-character support if available 

1079 line_width_fn = wcwidth.wcswidth 

1080 else: 

1081 line_width_fn = len 

1082 if is_multiline: 

1083 width_fn = lambda s: _align_column_multiline_width(s, line_width_fn) # noqa 

1084 else: 

1085 width_fn = line_width_fn 

1086 return width_fn 

1087 

1088 

1089def _align_column_multiline_width(multiline_s, line_width_fn=len): 

1090 """Visible width of a potentially multiline content.""" 

1091 return list(map(line_width_fn, re.split("[\r\n]", multiline_s))) 

1092 

1093 

1094def _flat_list(nested_list): 

1095 ret = [] 

1096 for item in nested_list: 

1097 if isinstance(item, list): 

1098 for subitem in item: 

1099 ret.append(subitem) 

1100 else: 

1101 ret.append(item) 

1102 return ret 

1103 

1104 

1105def _align_column( 

1106 strings, 

1107 alignment, 

1108 minwidth=0, 

1109 has_invisible=True, 

1110 enable_widechars=False, 

1111 is_multiline=False, 

1112): 

1113 """[string] -> [padded_string]""" 

1114 strings, padfn = _align_column_choose_padfn(strings, alignment, has_invisible) 

1115 width_fn = _align_column_choose_width_fn( 

1116 has_invisible, enable_widechars, is_multiline 

1117 ) 

1118 

1119 s_widths = list(map(width_fn, strings)) 

1120 maxwidth = max(max(_flat_list(s_widths)), minwidth) 

1121 # TODO: refactor column alignment in single-line and multiline modes 

1122 if is_multiline: 

1123 if not enable_widechars and not has_invisible: 

1124 padded_strings = [ 

1125 "\n".join([padfn(maxwidth, s) for s in ms.splitlines()]) 

1126 for ms in strings 

1127 ] 

1128 else: 

1129 # enable wide-character width corrections 

1130 s_lens = [[len(s) for s in re.split("[\r\n]", ms)] for ms in strings] 

1131 visible_widths = [ 

1132 [maxwidth - (w - l) for w, l in zip(mw, ml)] 

1133 for mw, ml in zip(s_widths, s_lens) 

1134 ] 

1135 # wcswidth and _visible_width don't count invisible characters; 

1136 # padfn doesn't need to apply another correction 

1137 padded_strings = [ 

1138 "\n".join([padfn(w, s) for s, w in zip((ms.splitlines() or ms), mw)]) 

1139 for ms, mw in zip(strings, visible_widths) 

1140 ] 

1141 else: # single-line cell values 

1142 if not enable_widechars and not has_invisible: 

1143 padded_strings = [padfn(maxwidth, s) for s in strings] 

1144 else: 

1145 # enable wide-character width corrections 

1146 s_lens = list(map(len, strings)) 

1147 visible_widths = [maxwidth - (w - l) for w, l in zip(s_widths, s_lens)] 

1148 # wcswidth and _visible_width don't count invisible characters; 

1149 # padfn doesn't need to apply another correction 

1150 padded_strings = [padfn(w, s) for s, w in zip(strings, visible_widths)] 

1151 return padded_strings 

1152 

1153 

1154def _more_generic(type1, type2): 

1155 types = { 

1156 type(None): 0, 

1157 bool: 1, 

1158 int: 2, 

1159 float: 3, 

1160 bytes: 4, 

1161 str: 5, 

1162 } 

1163 invtypes = { 

1164 5: str, 

1165 4: bytes, 

1166 3: float, 

1167 2: int, 

1168 1: bool, 

1169 0: type(None), 

1170 } 

1171 moregeneric = max(types.get(type1, 5), types.get(type2, 5)) 

1172 return invtypes[moregeneric] 

1173 

1174 

1175def _column_type(strings, has_invisible=True, numparse=True): 

1176 """The least generic type all column values are convertible to. 

1177 

1178 >>> _column_type([True, False]) is bool 

1179 True 

1180 >>> _column_type(["1", "2"]) is int 

1181 True 

1182 >>> _column_type(["1", "2.3"]) is float 

1183 True 

1184 >>> _column_type(["1", "2.3", "four"]) is str 

1185 True 

1186 >>> _column_type(["four", '\u043f\u044f\u0442\u044c']) is str 

1187 True 

1188 >>> _column_type([None, "brux"]) is str 

1189 True 

1190 >>> _column_type([1, 2, None]) is int 

1191 True 

1192 >>> import datetime as dt 

1193 >>> _column_type([dt.datetime(1991,2,19), dt.time(17,35)]) is str 

1194 True 

1195 

1196 """ 

1197 types = [_type(s, has_invisible, numparse) for s in strings] 

1198 return reduce(_more_generic, types, bool) 

1199 

1200 

1201def _format(val, valtype, floatfmt, intfmt, missingval="", has_invisible=True): 

1202 """Format a value according to its type. 

1203 

1204 Unicode is supported: 

1205 

1206 >>> hrow = ['\u0431\u0443\u043a\u0432\u0430', '\u0446\u0438\u0444\u0440\u0430'] ; \ 

1207 tbl = [['\u0430\u0437', 2], ['\u0431\u0443\u043a\u0438', 4]] ; \ 

1208 good_result = '\\u0431\\u0443\\u043a\\u0432\\u0430 \\u0446\\u0438\\u0444\\u0440\\u0430\\n------- -------\\n\\u0430\\u0437 2\\n\\u0431\\u0443\\u043a\\u0438 4' ; \ 

1209 tabulate(tbl, headers=hrow) == good_result 

1210 True 

1211 

1212 """ # noqa 

1213 if val is None: 

1214 return missingval 

1215 

1216 if valtype is str: 

1217 return f"{val}" 

1218 elif valtype is int: 

1219 return format(val, intfmt) 

1220 elif valtype is bytes: 

1221 try: 

1222 return str(val, "ascii") 

1223 except (TypeError, UnicodeDecodeError): 

1224 return str(val) 

1225 elif valtype is float: 

1226 is_a_colored_number = has_invisible and isinstance(val, (str, bytes)) 

1227 if is_a_colored_number: 

1228 raw_val = _strip_ansi(val) 

1229 formatted_val = format(float(raw_val), floatfmt) 

1230 return val.replace(raw_val, formatted_val) 

1231 else: 

1232 return format(float(val), floatfmt) 

1233 else: 

1234 return f"{val}" 

1235 

1236 

1237def _align_header( 

1238 header, alignment, width, visible_width, is_multiline=False, width_fn=None 

1239): 

1240 "Pad string header to width chars given known visible_width of the header." 

1241 if is_multiline: 

1242 header_lines = re.split(_multiline_codes, header) 

1243 padded_lines = [ 

1244 _align_header(h, alignment, width, width_fn(h)) for h in header_lines 

1245 ] 

1246 return "\n".join(padded_lines) 

1247 # else: not multiline 

1248 ninvisible = len(header) - visible_width 

1249 width += ninvisible 

1250 if alignment == "left": 

1251 return _padright(width, header) 

1252 elif alignment == "center": 

1253 return _padboth(width, header) 

1254 elif not alignment: 

1255 return f"{header}" 

1256 else: 

1257 return _padleft(width, header) 

1258 

1259 

1260def _remove_separating_lines(rows): 

1261 if type(rows) == list: 

1262 separating_lines = [] 

1263 sans_rows = [] 

1264 for index, row in enumerate(rows): 

1265 if _is_separating_line(row): 

1266 separating_lines.append(index) 

1267 else: 

1268 sans_rows.append(row) 

1269 return sans_rows, separating_lines 

1270 else: 

1271 return rows, None 

1272 

1273 

1274def _reinsert_separating_lines(rows, separating_lines): 

1275 if separating_lines: 

1276 for index in separating_lines: 

1277 rows.insert(index, SEPARATING_LINE) 

1278 

1279 

1280def _prepend_row_index(rows, index): 

1281 """Add a left-most index column.""" 

1282 if index is None or index is False: 

1283 return rows 

1284 if isinstance(index, Sized) and len(index) != len(rows): 

1285 raise ValueError( 

1286 "index must be as long as the number of data rows: " 

1287 + "len(index)={} len(rows)={}".format(len(index), len(rows)) 

1288 ) 

1289 sans_rows, separating_lines = _remove_separating_lines(rows) 

1290 new_rows = [] 

1291 index_iter = iter(index) 

1292 for row in sans_rows: 

1293 index_v = next(index_iter) 

1294 new_rows.append([index_v] + list(row)) 

1295 rows = new_rows 

1296 _reinsert_separating_lines(rows, separating_lines) 

1297 return rows 

1298 

1299 

1300def _bool(val): 

1301 "A wrapper around standard bool() which doesn't throw on NumPy arrays" 

1302 try: 

1303 return bool(val) 

1304 except ValueError: # val is likely to be a numpy array with many elements 

1305 return False 

1306 

1307 

1308def _normalize_tabular_data(tabular_data, headers, showindex="default"): 

1309 """Transform a supported data type to a list of lists, and a list of headers. 

1310 

1311 Supported tabular data types: 

1312 

1313 * list-of-lists or another iterable of iterables 

1314 

1315 * list of named tuples (usually used with headers="keys") 

1316 

1317 * list of dicts (usually used with headers="keys") 

1318 

1319 * list of OrderedDicts (usually used with headers="keys") 

1320 

1321 * list of dataclasses (Python 3.7+ only, usually used with headers="keys") 

1322 

1323 * 2D NumPy arrays 

1324 

1325 * NumPy record arrays (usually used with headers="keys") 

1326 

1327 * dict of iterables (usually used with headers="keys") 

1328 

1329 * pandas.DataFrame (usually used with headers="keys") 

1330 

1331 The first row can be used as headers if headers="firstrow", 

1332 column indices can be used as headers if headers="keys". 

1333 

1334 If showindex="default", show row indices of the pandas.DataFrame. 

1335 If showindex="always", show row indices for all types of data. 

1336 If showindex="never", don't show row indices for all types of data. 

1337 If showindex is an iterable, show its values as row indices. 

1338 

1339 """ 

1340 

1341 try: 

1342 bool(headers) 

1343 is_headers2bool_broken = False # noqa 

1344 except ValueError: # numpy.ndarray, pandas.core.index.Index, ... 

1345 is_headers2bool_broken = True # noqa 

1346 headers = list(headers) 

1347 

1348 index = None 

1349 if hasattr(tabular_data, "keys") and hasattr(tabular_data, "values"): 

1350 # dict-like and pandas.DataFrame? 

1351 if hasattr(tabular_data.values, "__call__"): 

1352 # likely a conventional dict 

1353 keys = tabular_data.keys() 

1354 rows = list( 

1355 izip_longest(*tabular_data.values()) 

1356 ) # columns have to be transposed 

1357 elif hasattr(tabular_data, "index"): 

1358 # values is a property, has .index => it's likely a pandas.DataFrame (pandas 0.11.0) 

1359 keys = list(tabular_data) 

1360 if ( 

1361 showindex in ["default", "always", True] 

1362 and tabular_data.index.name is not None 

1363 ): 

1364 if isinstance(tabular_data.index.name, list): 

1365 keys[:0] = tabular_data.index.name 

1366 else: 

1367 keys[:0] = [tabular_data.index.name] 

1368 vals = tabular_data.values # values matrix doesn't need to be transposed 

1369 # for DataFrames add an index per default 

1370 index = list(tabular_data.index) 

1371 rows = [list(row) for row in vals] 

1372 else: 

1373 raise ValueError("tabular data doesn't appear to be a dict or a DataFrame") 

1374 

1375 if headers == "keys": 

1376 headers = list(map(str, keys)) # headers should be strings 

1377 

1378 else: # it's a usual iterable of iterables, or a NumPy array, or an iterable of dataclasses 

1379 rows = list(tabular_data) 

1380 

1381 if headers == "keys" and not rows: 

1382 # an empty table (issue #81) 

1383 headers = [] 

1384 elif ( 

1385 headers == "keys" 

1386 and hasattr(tabular_data, "dtype") 

1387 and getattr(tabular_data.dtype, "names") 

1388 ): 

1389 # numpy record array 

1390 headers = tabular_data.dtype.names 

1391 elif ( 

1392 headers == "keys" 

1393 and len(rows) > 0 

1394 and isinstance(rows[0], tuple) 

1395 and hasattr(rows[0], "_fields") 

1396 ): 

1397 # namedtuple 

1398 headers = list(map(str, rows[0]._fields)) 

1399 elif len(rows) > 0 and hasattr(rows[0], "keys") and hasattr(rows[0], "values"): 

1400 # dict-like object 

1401 uniq_keys = set() # implements hashed lookup 

1402 keys = [] # storage for set 

1403 if headers == "firstrow": 

1404 firstdict = rows[0] if len(rows) > 0 else {} 

1405 keys.extend(firstdict.keys()) 

1406 uniq_keys.update(keys) 

1407 rows = rows[1:] 

1408 for row in rows: 

1409 for k in row.keys(): 

1410 # Save unique items in input order 

1411 if k not in uniq_keys: 

1412 keys.append(k) 

1413 uniq_keys.add(k) 

1414 if headers == "keys": 

1415 headers = keys 

1416 elif isinstance(headers, dict): 

1417 # a dict of headers for a list of dicts 

1418 headers = [headers.get(k, k) for k in keys] 

1419 headers = list(map(str, headers)) 

1420 elif headers == "firstrow": 

1421 if len(rows) > 0: 

1422 headers = [firstdict.get(k, k) for k in keys] 

1423 headers = list(map(str, headers)) 

1424 else: 

1425 headers = [] 

1426 elif headers: 

1427 raise ValueError( 

1428 "headers for a list of dicts is not a dict or a keyword" 

1429 ) 

1430 rows = [[row.get(k) for k in keys] for row in rows] 

1431 

1432 elif ( 

1433 headers == "keys" 

1434 and hasattr(tabular_data, "description") 

1435 and hasattr(tabular_data, "fetchone") 

1436 and hasattr(tabular_data, "rowcount") 

1437 ): 

1438 # Python Database API cursor object (PEP 0249) 

1439 # print tabulate(cursor, headers='keys') 

1440 headers = [column[0] for column in tabular_data.description] 

1441 

1442 elif ( 

1443 dataclasses is not None 

1444 and len(rows) > 0 

1445 and dataclasses.is_dataclass(rows[0]) 

1446 ): 

1447 # Python 3.7+'s dataclass 

1448 field_names = [field.name for field in dataclasses.fields(rows[0])] 

1449 if headers == "keys": 

1450 headers = field_names 

1451 rows = [[getattr(row, f) for f in field_names] for row in rows] 

1452 

1453 elif headers == "keys" and len(rows) > 0: 

1454 # keys are column indices 

1455 headers = list(map(str, range(len(rows[0])))) 

1456 

1457 # take headers from the first row if necessary 

1458 if headers == "firstrow" and len(rows) > 0: 

1459 if index is not None: 

1460 headers = [index[0]] + list(rows[0]) 

1461 index = index[1:] 

1462 else: 

1463 headers = rows[0] 

1464 headers = list(map(str, headers)) # headers should be strings 

1465 rows = rows[1:] 

1466 elif headers == "firstrow": 

1467 headers = [] 

1468 

1469 headers = list(map(str, headers)) 

1470 # rows = list(map(list, rows)) 

1471 rows = list(map(lambda r: r if _is_separating_line(r) else list(r), rows)) 

1472 

1473 # add or remove an index column 

1474 showindex_is_a_str = type(showindex) in [str, bytes] 

1475 if showindex == "default" and index is not None: 

1476 rows = _prepend_row_index(rows, index) 

1477 elif isinstance(showindex, Sized) and not showindex_is_a_str: 

1478 rows = _prepend_row_index(rows, list(showindex)) 

1479 elif isinstance(showindex, Iterable) and not showindex_is_a_str: 

1480 rows = _prepend_row_index(rows, showindex) 

1481 elif showindex == "always" or (_bool(showindex) and not showindex_is_a_str): 

1482 if index is None: 

1483 index = list(range(len(rows))) 

1484 rows = _prepend_row_index(rows, index) 

1485 elif showindex == "never" or (not _bool(showindex) and not showindex_is_a_str): 

1486 pass 

1487 

1488 # pad with empty headers for initial columns if necessary 

1489 if headers and len(rows) > 0: 

1490 nhs = len(headers) 

1491 ncols = len(rows[0]) 

1492 if nhs < ncols: 

1493 headers = [""] * (ncols - nhs) + headers 

1494 

1495 return rows, headers 

1496 

1497 

1498def _wrap_text_to_colwidths(list_of_lists, colwidths, numparses=True): 

1499 numparses = _expand_iterable(numparses, len(list_of_lists[0]), True) 

1500 

1501 result = [] 

1502 

1503 for row in list_of_lists: 

1504 new_row = [] 

1505 for cell, width, numparse in zip(row, colwidths, numparses): 

1506 if _isnumber(cell) and numparse: 

1507 new_row.append(cell) 

1508 continue 

1509 

1510 if width is not None: 

1511 wrapper = _CustomTextWrap(width=width) 

1512 # Cast based on our internal type handling 

1513 # Any future custom formatting of types (such as datetimes) 

1514 # may need to be more explicit than just `str` of the object 

1515 casted_cell = ( 

1516 str(cell) if _isnumber(cell) else _type(cell, numparse)(cell) 

1517 ) 

1518 wrapped = wrapper.wrap(casted_cell) 

1519 new_row.append("\n".join(wrapped)) 

1520 else: 

1521 new_row.append(cell) 

1522 result.append(new_row) 

1523 

1524 return result 

1525 

1526 

1527def _to_str(s, encoding="utf8", errors="ignore"): 

1528 """ 

1529 A type safe wrapper for converting a bytestring to str. This is essentially just 

1530 a wrapper around .decode() intended for use with things like map(), but with some 

1531 specific behavior: 

1532 

1533 1. if the given parameter is not a bytestring, it is returned unmodified 

1534 2. decode() is called for the given parameter and assumes utf8 encoding, but the 

1535 default error behavior is changed from 'strict' to 'ignore' 

1536 

1537 >>> repr(_to_str(b'foo')) 

1538 "'foo'" 

1539 

1540 >>> repr(_to_str('foo')) 

1541 "'foo'" 

1542 

1543 >>> repr(_to_str(42)) 

1544 "'42'" 

1545 

1546 """ 

1547 if isinstance(s, bytes): 

1548 return s.decode(encoding=encoding, errors=errors) 

1549 return str(s) 

1550 

1551 

1552def tabulate( 

1553 tabular_data, 

1554 headers=(), 

1555 tablefmt="simple", 

1556 floatfmt=_DEFAULT_FLOATFMT, 

1557 intfmt=_DEFAULT_INTFMT, 

1558 numalign=_DEFAULT_ALIGN, 

1559 stralign=_DEFAULT_ALIGN, 

1560 missingval=_DEFAULT_MISSINGVAL, 

1561 showindex="default", 

1562 disable_numparse=False, 

1563 colalign=None, 

1564 maxcolwidths=None, 

1565 rowalign=None, 

1566 maxheadercolwidths=None, 

1567): 

1568 """Format a fixed width table for pretty printing. 

1569 

1570 >>> print(tabulate([[1, 2.34], [-56, "8.999"], ["2", "10001"]])) 

1571 --- --------- 

1572 1 2.34 

1573 -56 8.999 

1574 2 10001 

1575 --- --------- 

1576 

1577 The first required argument (`tabular_data`) can be a 

1578 list-of-lists (or another iterable of iterables), a list of named 

1579 tuples, a dictionary of iterables, an iterable of dictionaries, 

1580 an iterable of dataclasses (Python 3.7+), a two-dimensional NumPy array, 

1581 NumPy record array, or a Pandas' dataframe. 

1582 

1583 

1584 Table headers 

1585 ------------- 

1586 

1587 To print nice column headers, supply the second argument (`headers`): 

1588 

1589 - `headers` can be an explicit list of column headers 

1590 - if `headers="firstrow"`, then the first row of data is used 

1591 - if `headers="keys"`, then dictionary keys or column indices are used 

1592 

1593 Otherwise a headerless table is produced. 

1594 

1595 If the number of headers is less than the number of columns, they 

1596 are supposed to be names of the last columns. This is consistent 

1597 with the plain-text format of R and Pandas' dataframes. 

1598 

1599 >>> print(tabulate([["sex","age"],["Alice","F",24],["Bob","M",19]], 

1600 ... headers="firstrow")) 

1601 sex age 

1602 ----- ----- ----- 

1603 Alice F 24 

1604 Bob M 19 

1605 

1606 By default, pandas.DataFrame data have an additional column called 

1607 row index. To add a similar column to all other types of data, 

1608 use `showindex="always"` or `showindex=True`. To suppress row indices 

1609 for all types of data, pass `showindex="never" or `showindex=False`. 

1610 To add a custom row index column, pass `showindex=some_iterable`. 

1611 

1612 >>> print(tabulate([["F",24],["M",19]], showindex="always")) 

1613 - - -- 

1614 0 F 24 

1615 1 M 19 

1616 - - -- 

1617 

1618 

1619 Column alignment 

1620 ---------------- 

1621 

1622 `tabulate` tries to detect column types automatically, and aligns 

1623 the values properly. By default it aligns decimal points of the 

1624 numbers (or flushes integer numbers to the right), and flushes 

1625 everything else to the left. Possible column alignments 

1626 (`numalign`, `stralign`) are: "right", "center", "left", "decimal" 

1627 (only for `numalign`), and None (to disable alignment). 

1628 

1629 

1630 Table formats 

1631 ------------- 

1632 

1633 `intfmt` is a format specification used for columns which 

1634 contain numeric data without a decimal point. This can also be 

1635 a list or tuple of format strings, one per column. 

1636 

1637 `floatfmt` is a format specification used for columns which 

1638 contain numeric data with a decimal point. This can also be 

1639 a list or tuple of format strings, one per column. 

1640 

1641 `None` values are replaced with a `missingval` string (like 

1642 `floatfmt`, this can also be a list of values for different 

1643 columns): 

1644 

1645 >>> print(tabulate([["spam", 1, None], 

1646 ... ["eggs", 42, 3.14], 

1647 ... ["other", None, 2.7]], missingval="?")) 

1648 ----- -- ---- 

1649 spam 1 ? 

1650 eggs 42 3.14 

1651 other ? 2.7 

1652 ----- -- ---- 

1653 

1654 Various plain-text table formats (`tablefmt`) are supported: 

1655 'plain', 'simple', 'grid', 'pipe', 'orgtbl', 'rst', 'mediawiki', 

1656 'latex', 'latex_raw', 'latex_booktabs', 'latex_longtable' and tsv. 

1657 Variable `tabulate_formats`contains the list of currently supported formats. 

1658 

1659 "plain" format doesn't use any pseudographics to draw tables, 

1660 it separates columns with a double space: 

1661 

1662 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1663 ... ["strings", "numbers"], "plain")) 

1664 strings numbers 

1665 spam 41.9999 

1666 eggs 451 

1667 

1668 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="plain")) 

1669 spam 41.9999 

1670 eggs 451 

1671 

1672 "simple" format is like Pandoc simple_tables: 

1673 

1674 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1675 ... ["strings", "numbers"], "simple")) 

1676 strings numbers 

1677 --------- --------- 

1678 spam 41.9999 

1679 eggs 451 

1680 

1681 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="simple")) 

1682 ---- -------- 

1683 spam 41.9999 

1684 eggs 451 

1685 ---- -------- 

1686 

1687 "grid" is similar to tables produced by Emacs table.el package or 

1688 Pandoc grid_tables: 

1689 

1690 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1691 ... ["strings", "numbers"], "grid")) 

1692 +-----------+-----------+ 

1693 | strings | numbers | 

1694 +===========+===========+ 

1695 | spam | 41.9999 | 

1696 +-----------+-----------+ 

1697 | eggs | 451 | 

1698 +-----------+-----------+ 

1699 

1700 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="grid")) 

1701 +------+----------+ 

1702 | spam | 41.9999 | 

1703 +------+----------+ 

1704 | eggs | 451 | 

1705 +------+----------+ 

1706 

1707 "simple_grid" draws a grid using single-line box-drawing 

1708 characters: 

1709 

1710 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1711 ... ["strings", "numbers"], "simple_grid")) 

1712 ┌───────────┬───────────┐ 

1713 │ strings │ numbers │ 

1714 ├───────────┼───────────┤ 

1715 │ spam │ 41.9999 │ 

1716 ├───────────┼───────────┤ 

1717 │ eggs │ 451 │ 

1718 └───────────┴───────────┘ 

1719 

1720 "rounded_grid" draws a grid using single-line box-drawing 

1721 characters with rounded corners: 

1722 

1723 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1724 ... ["strings", "numbers"], "rounded_grid")) 

1725 ╭───────────┬───────────╮ 

1726 │ strings │ numbers │ 

1727 ├───────────┼───────────┤ 

1728 │ spam │ 41.9999 │ 

1729 ├───────────┼───────────┤ 

1730 │ eggs │ 451 │ 

1731 ╰───────────┴───────────╯ 

1732 

1733 "heavy_grid" draws a grid using bold (thick) single-line box-drawing 

1734 characters: 

1735 

1736 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1737 ... ["strings", "numbers"], "heavy_grid")) 

1738 ┏━━━━━━━━━━━┳━━━━━━━━━━━┓ 

1739 ┃ strings ┃ numbers ┃ 

1740 ┣━━━━━━━━━━━╋━━━━━━━━━━━┫ 

1741 ┃ spam ┃ 41.9999 ┃ 

1742 ┣━━━━━━━━━━━╋━━━━━━━━━━━┫ 

1743 ┃ eggs ┃ 451 ┃ 

1744 ┗━━━━━━━━━━━┻━━━━━━━━━━━┛ 

1745 

1746 "mixed_grid" draws a grid using a mix of light (thin) and heavy (thick) lines 

1747 box-drawing characters: 

1748 

1749 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1750 ... ["strings", "numbers"], "mixed_grid")) 

1751 ┍━━━━━━━━━━━┯━━━━━━━━━━━┑ 

1752 │ strings │ numbers │ 

1753 ┝━━━━━━━━━━━┿━━━━━━━━━━━┥ 

1754 │ spam │ 41.9999 │ 

1755 ├───────────┼───────────┤ 

1756 │ eggs │ 451 │ 

1757 ┕━━━━━━━━━━━┷━━━━━━━━━━━┙ 

1758 

1759 "double_grid" draws a grid using double-line box-drawing 

1760 characters: 

1761 

1762 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1763 ... ["strings", "numbers"], "double_grid")) 

1764 ╔═══════════╦═══════════╗ 

1765 ║ strings ║ numbers ║ 

1766 ╠═══════════╬═══════════╣ 

1767 ║ spam ║ 41.9999 ║ 

1768 ╠═══════════╬═══════════╣ 

1769 ║ eggs ║ 451 ║ 

1770 ╚═══════════╩═══════════╝ 

1771 

1772 "fancy_grid" draws a grid using a mix of single and 

1773 double-line box-drawing characters: 

1774 

1775 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1776 ... ["strings", "numbers"], "fancy_grid")) 

1777 ╒═══════════╤═══════════╕ 

1778 │ strings │ numbers │ 

1779 ╞═══════════╪═══════════╡ 

1780 │ spam │ 41.9999 │ 

1781 ├───────────┼───────────┤ 

1782 │ eggs │ 451 │ 

1783 ╘═══════════╧═══════════╛ 

1784 

1785 "outline" is the same as the "grid" format but doesn't draw lines between rows: 

1786 

1787 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1788 ... ["strings", "numbers"], "outline")) 

1789 +-----------+-----------+ 

1790 | strings | numbers | 

1791 +===========+===========+ 

1792 | spam | 41.9999 | 

1793 | eggs | 451 | 

1794 +-----------+-----------+ 

1795 

1796 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="outline")) 

1797 +------+----------+ 

1798 | spam | 41.9999 | 

1799 | eggs | 451 | 

1800 +------+----------+ 

1801 

1802 "simple_outline" is the same as the "simple_grid" format but doesn't draw lines between rows: 

1803 

1804 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1805 ... ["strings", "numbers"], "simple_outline")) 

1806 ┌───────────┬───────────┐ 

1807 │ strings │ numbers │ 

1808 ├───────────┼───────────┤ 

1809 │ spam │ 41.9999 │ 

1810 │ eggs │ 451 │ 

1811 └───────────┴───────────┘ 

1812 

1813 "rounded_outline" is the same as the "rounded_grid" format but doesn't draw lines between rows: 

1814 

1815 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1816 ... ["strings", "numbers"], "rounded_outline")) 

1817 ╭───────────┬───────────╮ 

1818 │ strings │ numbers │ 

1819 ├───────────┼───────────┤ 

1820 │ spam │ 41.9999 │ 

1821 │ eggs │ 451 │ 

1822 ╰───────────┴───────────╯ 

1823 

1824 "heavy_outline" is the same as the "heavy_grid" format but doesn't draw lines between rows: 

1825 

1826 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1827 ... ["strings", "numbers"], "heavy_outline")) 

1828 ┏━━━━━━━━━━━┳━━━━━━━━━━━┓ 

1829 ┃ strings ┃ numbers ┃ 

1830 ┣━━━━━━━━━━━╋━━━━━━━━━━━┫ 

1831 ┃ spam ┃ 41.9999 ┃ 

1832 ┃ eggs ┃ 451 ┃ 

1833 ┗━━━━━━━━━━━┻━━━━━━━━━━━┛ 

1834 

1835 "mixed_outline" is the same as the "mixed_grid" format but doesn't draw lines between rows: 

1836 

1837 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1838 ... ["strings", "numbers"], "mixed_outline")) 

1839 ┍━━━━━━━━━━━┯━━━━━━━━━━━┑ 

1840 │ strings │ numbers │ 

1841 ┝━━━━━━━━━━━┿━━━━━━━━━━━┥ 

1842 │ spam │ 41.9999 │ 

1843 │ eggs │ 451 │ 

1844 ┕━━━━━━━━━━━┷━━━━━━━━━━━┙ 

1845 

1846 "double_outline" is the same as the "double_grid" format but doesn't draw lines between rows: 

1847 

1848 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1849 ... ["strings", "numbers"], "double_outline")) 

1850 ╔═══════════╦═══════════╗ 

1851 ║ strings ║ numbers ║ 

1852 ╠═══════════╬═══════════╣ 

1853 ║ spam ║ 41.9999 ║ 

1854 ║ eggs ║ 451 ║ 

1855 ╚═══════════╩═══════════╝ 

1856 

1857 "fancy_outline" is the same as the "fancy_grid" format but doesn't draw lines between rows: 

1858 

1859 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1860 ... ["strings", "numbers"], "fancy_outline")) 

1861 ╒═══════════╤═══════════╕ 

1862 │ strings │ numbers │ 

1863 ╞═══════════╪═══════════╡ 

1864 │ spam │ 41.9999 │ 

1865 │ eggs │ 451 │ 

1866 ╘═══════════╧═══════════╛ 

1867 

1868 "pipe" is like tables in PHP Markdown Extra extension or Pandoc 

1869 pipe_tables: 

1870 

1871 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1872 ... ["strings", "numbers"], "pipe")) 

1873 | strings | numbers | 

1874 |:----------|----------:| 

1875 | spam | 41.9999 | 

1876 | eggs | 451 | 

1877 

1878 "presto" is like tables produce by the Presto CLI: 

1879 

1880 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1881 ... ["strings", "numbers"], "presto")) 

1882 strings | numbers 

1883 -----------+----------- 

1884 spam | 41.9999 

1885 eggs | 451 

1886 

1887 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="pipe")) 

1888 |:-----|---------:| 

1889 | spam | 41.9999 | 

1890 | eggs | 451 | 

1891 

1892 "orgtbl" is like tables in Emacs org-mode and orgtbl-mode. They 

1893 are slightly different from "pipe" format by not using colons to 

1894 define column alignment, and using a "+" sign to indicate line 

1895 intersections: 

1896 

1897 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1898 ... ["strings", "numbers"], "orgtbl")) 

1899 | strings | numbers | 

1900 |-----------+-----------| 

1901 | spam | 41.9999 | 

1902 | eggs | 451 | 

1903 

1904 

1905 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="orgtbl")) 

1906 | spam | 41.9999 | 

1907 | eggs | 451 | 

1908 

1909 "rst" is like a simple table format from reStructuredText; please 

1910 note that reStructuredText accepts also "grid" tables: 

1911 

1912 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], 

1913 ... ["strings", "numbers"], "rst")) 

1914 ========= ========= 

1915 strings numbers 

1916 ========= ========= 

1917 spam 41.9999 

1918 eggs 451 

1919 ========= ========= 

1920 

1921 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="rst")) 

1922 ==== ======== 

1923 spam 41.9999 

1924 eggs 451 

1925 ==== ======== 

1926 

1927 "mediawiki" produces a table markup used in Wikipedia and on other 

1928 MediaWiki-based sites: 

1929 

1930 >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]], 

1931 ... headers="firstrow", tablefmt="mediawiki")) 

1932 {| class="wikitable" style="text-align: left;" 

1933 |+ <!-- caption --> 

1934 |- 

1935 ! strings !! align="right"| numbers 

1936 |- 

1937 | spam || align="right"| 41.9999 

1938 |- 

1939 | eggs || align="right"| 451 

1940 |} 

1941 

1942 "html" produces HTML markup as an html.escape'd str 

1943 with a ._repr_html_ method so that Jupyter Lab and Notebook display the HTML 

1944 and a .str property so that the raw HTML remains accessible 

1945 the unsafehtml table format can be used if an unescaped HTML format is required: 

1946 

1947 >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]], 

1948 ... headers="firstrow", tablefmt="html")) 

1949 <table> 

1950 <thead> 

1951 <tr><th>strings </th><th style="text-align: right;"> numbers</th></tr> 

1952 </thead> 

1953 <tbody> 

1954 <tr><td>spam </td><td style="text-align: right;"> 41.9999</td></tr> 

1955 <tr><td>eggs </td><td style="text-align: right;"> 451 </td></tr> 

1956 </tbody> 

1957 </table> 

1958 

1959 "latex" produces a tabular environment of LaTeX document markup: 

1960 

1961 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex")) 

1962 \\begin{tabular}{lr} 

1963 \\hline 

1964 spam & 41.9999 \\\\ 

1965 eggs & 451 \\\\ 

1966 \\hline 

1967 \\end{tabular} 

1968 

1969 "latex_raw" is similar to "latex", but doesn't escape special characters, 

1970 such as backslash and underscore, so LaTeX commands may embedded into 

1971 cells' values: 

1972 

1973 >>> print(tabulate([["spam$_9$", 41.9999], ["\\\\emph{eggs}", "451.0"]], tablefmt="latex_raw")) 

1974 \\begin{tabular}{lr} 

1975 \\hline 

1976 spam$_9$ & 41.9999 \\\\ 

1977 \\emph{eggs} & 451 \\\\ 

1978 \\hline 

1979 \\end{tabular} 

1980 

1981 "latex_booktabs" produces a tabular environment of LaTeX document markup 

1982 using the booktabs.sty package: 

1983 

1984 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex_booktabs")) 

1985 \\begin{tabular}{lr} 

1986 \\toprule 

1987 spam & 41.9999 \\\\ 

1988 eggs & 451 \\\\ 

1989 \\bottomrule 

1990 \\end{tabular} 

1991 

1992 "latex_longtable" produces a tabular environment that can stretch along 

1993 multiple pages, using the longtable package for LaTeX. 

1994 

1995 >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex_longtable")) 

1996 \\begin{longtable}{lr} 

1997 \\hline 

1998 spam & 41.9999 \\\\ 

1999 eggs & 451 \\\\ 

2000 \\hline 

2001 \\end{longtable} 

2002 

2003 

2004 Number parsing 

2005 -------------- 

2006 By default, anything which can be parsed as a number is a number. 

2007 This ensures numbers represented as strings are aligned properly. 

2008 This can lead to weird results for particular strings such as 

2009 specific git SHAs e.g. "42992e1" will be parsed into the number 

2010 429920 and aligned as such. 

2011 

2012 To completely disable number parsing (and alignment), use 

2013 `disable_numparse=True`. For more fine grained control, a list column 

2014 indices is used to disable number parsing only on those columns 

2015 e.g. `disable_numparse=[0, 2]` would disable number parsing only on the 

2016 first and third columns. 

2017 

2018 Column Widths and Auto Line Wrapping 

2019 ------------------------------------ 

2020 Tabulate will, by default, set the width of each column to the length of the 

2021 longest element in that column. However, in situations where fields are expected 

2022 to reasonably be too long to look good as a single line, tabulate can help automate 

2023 word wrapping long fields for you. Use the parameter `maxcolwidth` to provide a 

2024 list of maximal column widths 

2025 

2026 >>> print(tabulate( \ 

2027 [('1', 'John Smith', \ 

2028 'This is a rather long description that might look better if it is wrapped a bit')], \ 

2029 headers=("Issue Id", "Author", "Description"), \ 

2030 maxcolwidths=[None, None, 30], \ 

2031 tablefmt="grid" \ 

2032 )) 

2033 +------------+------------+-------------------------------+ 

2034 | Issue Id | Author | Description | 

2035 +============+============+===============================+ 

2036 | 1 | John Smith | This is a rather long | 

2037 | | | description that might look | 

2038 | | | better if it is wrapped a bit | 

2039 +------------+------------+-------------------------------+ 

2040 

2041 Header column width can be specified in a similar way using `maxheadercolwidth` 

2042 

2043 """ 

2044 

2045 if tabular_data is None: 

2046 tabular_data = [] 

2047 

2048 list_of_lists, headers = _normalize_tabular_data( 

2049 tabular_data, headers, showindex=showindex 

2050 ) 

2051 list_of_lists, separating_lines = _remove_separating_lines(list_of_lists) 

2052 

2053 if maxcolwidths is not None: 

2054 num_cols = len(list_of_lists[0]) 

2055 if isinstance(maxcolwidths, int): # Expand scalar for all columns 

2056 maxcolwidths = _expand_iterable(maxcolwidths, num_cols, maxcolwidths) 

2057 else: # Ignore col width for any 'trailing' columns 

2058 maxcolwidths = _expand_iterable(maxcolwidths, num_cols, None) 

2059 

2060 numparses = _expand_numparse(disable_numparse, num_cols) 

2061 list_of_lists = _wrap_text_to_colwidths( 

2062 list_of_lists, maxcolwidths, numparses=numparses 

2063 ) 

2064 

2065 if maxheadercolwidths is not None: 

2066 num_cols = len(list_of_lists[0]) 

2067 if isinstance(maxheadercolwidths, int): # Expand scalar for all columns 

2068 maxheadercolwidths = _expand_iterable( 

2069 maxheadercolwidths, num_cols, maxheadercolwidths 

2070 ) 

2071 else: # Ignore col width for any 'trailing' columns 

2072 maxheadercolwidths = _expand_iterable(maxheadercolwidths, num_cols, None) 

2073 

2074 numparses = _expand_numparse(disable_numparse, num_cols) 

2075 headers = _wrap_text_to_colwidths( 

2076 [headers], maxheadercolwidths, numparses=numparses 

2077 )[0] 

2078 

2079 # empty values in the first column of RST tables should be escaped (issue #82) 

2080 # "" should be escaped as "\\ " or ".." 

2081 if tablefmt == "rst": 

2082 list_of_lists, headers = _rst_escape_first_column(list_of_lists, headers) 

2083 

2084 # PrettyTable formatting does not use any extra padding. 

2085 # Numbers are not parsed and are treated the same as strings for alignment. 

2086 # Check if pretty is the format being used and override the defaults so it 

2087 # does not impact other formats. 

2088 min_padding = MIN_PADDING 

2089 if tablefmt == "pretty": 

2090 min_padding = 0 

2091 disable_numparse = True 

2092 numalign = "center" if numalign == _DEFAULT_ALIGN else numalign 

2093 stralign = "center" if stralign == _DEFAULT_ALIGN else stralign 

2094 else: 

2095 numalign = "decimal" if numalign == _DEFAULT_ALIGN else numalign 

2096 stralign = "left" if stralign == _DEFAULT_ALIGN else stralign 

2097 

2098 # optimization: look for ANSI control codes once, 

2099 # enable smart width functions only if a control code is found 

2100 # 

2101 # convert the headers and rows into a single, tab-delimited string ensuring 

2102 # that any bytestrings are decoded safely (i.e. errors ignored) 

2103 plain_text = "\t".join( 

2104 chain( 

2105 # headers 

2106 map(_to_str, headers), 

2107 # rows: chain the rows together into a single iterable after mapping 

2108 # the bytestring conversino to each cell value 

2109 chain.from_iterable(map(_to_str, row) for row in list_of_lists), 

2110 ) 

2111 ) 

2112 

2113 has_invisible = _ansi_codes.search(plain_text) is not None 

2114 

2115 enable_widechars = wcwidth is not None and WIDE_CHARS_MODE 

2116 if ( 

2117 not isinstance(tablefmt, TableFormat) 

2118 and tablefmt in multiline_formats 

2119 and _is_multiline(plain_text) 

2120 ): 

2121 tablefmt = multiline_formats.get(tablefmt, tablefmt) 

2122 is_multiline = True 

2123 else: 

2124 is_multiline = False 

2125 width_fn = _choose_width_fn(has_invisible, enable_widechars, is_multiline) 

2126 

2127 # format rows and columns, convert numeric values to strings 

2128 cols = list(izip_longest(*list_of_lists)) 

2129 numparses = _expand_numparse(disable_numparse, len(cols)) 

2130 coltypes = [_column_type(col, numparse=np) for col, np in zip(cols, numparses)] 

2131 if isinstance(floatfmt, str): # old version 

2132 float_formats = len(cols) * [ 

2133 floatfmt 

2134 ] # just duplicate the string to use in each column 

2135 else: # if floatfmt is list, tuple etc we have one per column 

2136 float_formats = list(floatfmt) 

2137 if len(float_formats) < len(cols): 

2138 float_formats.extend((len(cols) - len(float_formats)) * [_DEFAULT_FLOATFMT]) 

2139 if isinstance(intfmt, str): # old version 

2140 int_formats = len(cols) * [ 

2141 intfmt 

2142 ] # just duplicate the string to use in each column 

2143 else: # if intfmt is list, tuple etc we have one per column 

2144 int_formats = list(intfmt) 

2145 if len(int_formats) < len(cols): 

2146 int_formats.extend((len(cols) - len(int_formats)) * [_DEFAULT_INTFMT]) 

2147 if isinstance(missingval, str): 

2148 missing_vals = len(cols) * [missingval] 

2149 else: 

2150 missing_vals = list(missingval) 

2151 if len(missing_vals) < len(cols): 

2152 missing_vals.extend((len(cols) - len(missing_vals)) * [_DEFAULT_MISSINGVAL]) 

2153 cols = [ 

2154 [_format(v, ct, fl_fmt, int_fmt, miss_v, has_invisible) for v in c] 

2155 for c, ct, fl_fmt, int_fmt, miss_v in zip( 

2156 cols, coltypes, float_formats, int_formats, missing_vals 

2157 ) 

2158 ] 

2159 

2160 # align columns 

2161 aligns = [numalign if ct in [int, float] else stralign for ct in coltypes] 

2162 if colalign is not None: 

2163 assert isinstance(colalign, Iterable) 

2164 for idx, align in enumerate(colalign): 

2165 aligns[idx] = align 

2166 minwidths = ( 

2167 [width_fn(h) + min_padding for h in headers] if headers else [0] * len(cols) 

2168 ) 

2169 cols = [ 

2170 _align_column(c, a, minw, has_invisible, enable_widechars, is_multiline) 

2171 for c, a, minw in zip(cols, aligns, minwidths) 

2172 ] 

2173 

2174 if headers: 

2175 # align headers and add headers 

2176 t_cols = cols or [[""]] * len(headers) 

2177 t_aligns = aligns or [stralign] * len(headers) 

2178 minwidths = [ 

2179 max(minw, max(width_fn(cl) for cl in c)) 

2180 for minw, c in zip(minwidths, t_cols) 

2181 ] 

2182 headers = [ 

2183 _align_header(h, a, minw, width_fn(h), is_multiline, width_fn) 

2184 for h, a, minw in zip(headers, t_aligns, minwidths) 

2185 ] 

2186 rows = list(zip(*cols)) 

2187 else: 

2188 minwidths = [max(width_fn(cl) for cl in c) for c in cols] 

2189 rows = list(zip(*cols)) 

2190 

2191 if not isinstance(tablefmt, TableFormat): 

2192 tablefmt = _table_formats.get(tablefmt, _table_formats["simple"]) 

2193 

2194 ra_default = rowalign if isinstance(rowalign, str) else None 

2195 rowaligns = _expand_iterable(rowalign, len(rows), ra_default) 

2196 _reinsert_separating_lines(rows, separating_lines) 

2197 

2198 return _format_table( 

2199 tablefmt, headers, rows, minwidths, aligns, is_multiline, rowaligns=rowaligns 

2200 ) 

2201 

2202 

2203def _expand_numparse(disable_numparse, column_count): 

2204 """ 

2205 Return a list of bools of length `column_count` which indicates whether 

2206 number parsing should be used on each column. 

2207 If `disable_numparse` is a list of indices, each of those indices are False, 

2208 and everything else is True. 

2209 If `disable_numparse` is a bool, then the returned list is all the same. 

2210 """ 

2211 if isinstance(disable_numparse, Iterable): 

2212 numparses = [True] * column_count 

2213 for index in disable_numparse: 

2214 numparses[index] = False 

2215 return numparses 

2216 else: 

2217 return [not disable_numparse] * column_count 

2218 

2219 

2220def _expand_iterable(original, num_desired, default): 

2221 """ 

2222 Expands the `original` argument to return a return a list of 

2223 length `num_desired`. If `original` is shorter than `num_desired`, it will 

2224 be padded with the value in `default`. 

2225 If `original` is not a list to begin with (i.e. scalar value) a list of 

2226 length `num_desired` completely populated with `default will be returned 

2227 """ 

2228 if isinstance(original, Iterable) and not isinstance(original, str): 

2229 return original + [default] * (num_desired - len(original)) 

2230 else: 

2231 return [default] * num_desired 

2232 

2233 

2234def _pad_row(cells, padding): 

2235 if cells: 

2236 pad = " " * padding 

2237 padded_cells = [pad + cell + pad for cell in cells] 

2238 return padded_cells 

2239 else: 

2240 return cells 

2241 

2242 

2243def _build_simple_row(padded_cells, rowfmt): 

2244 "Format row according to DataRow format without padding." 

2245 begin, sep, end = rowfmt 

2246 return (begin + sep.join(padded_cells) + end).rstrip() 

2247 

2248 

2249def _build_row(padded_cells, colwidths, colaligns, rowfmt): 

2250 "Return a string which represents a row of data cells." 

2251 if not rowfmt: 

2252 return None 

2253 if hasattr(rowfmt, "__call__"): 

2254 return rowfmt(padded_cells, colwidths, colaligns) 

2255 else: 

2256 return _build_simple_row(padded_cells, rowfmt) 

2257 

2258 

2259def _append_basic_row(lines, padded_cells, colwidths, colaligns, rowfmt, rowalign=None): 

2260 # NOTE: rowalign is ignored and exists for api compatibility with _append_multiline_row 

2261 lines.append(_build_row(padded_cells, colwidths, colaligns, rowfmt)) 

2262 return lines 

2263 

2264 

2265def _align_cell_veritically(text_lines, num_lines, column_width, row_alignment): 

2266 delta_lines = num_lines - len(text_lines) 

2267 blank = [" " * column_width] 

2268 if row_alignment == "bottom": 

2269 return blank * delta_lines + text_lines 

2270 elif row_alignment == "center": 

2271 top_delta = delta_lines // 2 

2272 bottom_delta = delta_lines - top_delta 

2273 return top_delta * blank + text_lines + bottom_delta * blank 

2274 else: 

2275 return text_lines + blank * delta_lines 

2276 

2277 

2278def _append_multiline_row( 

2279 lines, padded_multiline_cells, padded_widths, colaligns, rowfmt, pad, rowalign=None 

2280): 

2281 colwidths = [w - 2 * pad for w in padded_widths] 

2282 cells_lines = [c.splitlines() for c in padded_multiline_cells] 

2283 nlines = max(map(len, cells_lines)) # number of lines in the row 

2284 # vertically pad cells where some lines are missing 

2285 # cells_lines = [ 

2286 # (cl + [" " * w] * (nlines - len(cl))) for cl, w in zip(cells_lines, colwidths) 

2287 # ] 

2288 

2289 cells_lines = [ 

2290 _align_cell_veritically(cl, nlines, w, rowalign) 

2291 for cl, w in zip(cells_lines, colwidths) 

2292 ] 

2293 lines_cells = [[cl[i] for cl in cells_lines] for i in range(nlines)] 

2294 for ln in lines_cells: 

2295 padded_ln = _pad_row(ln, pad) 

2296 _append_basic_row(lines, padded_ln, colwidths, colaligns, rowfmt) 

2297 return lines 

2298 

2299 

2300def _build_line(colwidths, colaligns, linefmt): 

2301 "Return a string which represents a horizontal line." 

2302 if not linefmt: 

2303 return None 

2304 if hasattr(linefmt, "__call__"): 

2305 return linefmt(colwidths, colaligns) 

2306 else: 

2307 begin, fill, sep, end = linefmt 

2308 cells = [fill * w for w in colwidths] 

2309 return _build_simple_row(cells, (begin, sep, end)) 

2310 

2311 

2312def _append_line(lines, colwidths, colaligns, linefmt): 

2313 lines.append(_build_line(colwidths, colaligns, linefmt)) 

2314 return lines 

2315 

2316 

2317class JupyterHTMLStr(str): 

2318 """Wrap the string with a _repr_html_ method so that Jupyter 

2319 displays the HTML table""" 

2320 

2321 def _repr_html_(self): 

2322 return self 

2323 

2324 @property 

2325 def str(self): 

2326 """add a .str property so that the raw string is still accessible""" 

2327 return self 

2328 

2329 

2330def _format_table(fmt, headers, rows, colwidths, colaligns, is_multiline, rowaligns): 

2331 """Produce a plain-text representation of the table.""" 

2332 lines = [] 

2333 hidden = fmt.with_header_hide if (headers and fmt.with_header_hide) else [] 

2334 pad = fmt.padding 

2335 headerrow = fmt.headerrow 

2336 

2337 padded_widths = [(w + 2 * pad) for w in colwidths] 

2338 if is_multiline: 

2339 pad_row = lambda row, _: row # noqa do it later, in _append_multiline_row 

2340 append_row = partial(_append_multiline_row, pad=pad) 

2341 else: 

2342 pad_row = _pad_row 

2343 append_row = _append_basic_row 

2344 

2345 padded_headers = pad_row(headers, pad) 

2346 padded_rows = [pad_row(row, pad) for row in rows] 

2347 

2348 if fmt.lineabove and "lineabove" not in hidden: 

2349 _append_line(lines, padded_widths, colaligns, fmt.lineabove) 

2350 

2351 if padded_headers: 

2352 append_row(lines, padded_headers, padded_widths, colaligns, headerrow) 

2353 if fmt.linebelowheader and "linebelowheader" not in hidden: 

2354 _append_line(lines, padded_widths, colaligns, fmt.linebelowheader) 

2355 

2356 if padded_rows and fmt.linebetweenrows and "linebetweenrows" not in hidden: 

2357 # initial rows with a line below 

2358 for row, ralign in zip(padded_rows[:-1], rowaligns): 

2359 append_row( 

2360 lines, row, padded_widths, colaligns, fmt.datarow, rowalign=ralign 

2361 ) 

2362 _append_line(lines, padded_widths, colaligns, fmt.linebetweenrows) 

2363 # the last row without a line below 

2364 append_row( 

2365 lines, 

2366 padded_rows[-1], 

2367 padded_widths, 

2368 colaligns, 

2369 fmt.datarow, 

2370 rowalign=rowaligns[-1], 

2371 ) 

2372 else: 

2373 separating_line = ( 

2374 fmt.linebetweenrows 

2375 or fmt.linebelowheader 

2376 or fmt.linebelow 

2377 or fmt.lineabove 

2378 or Line("", "", "", "") 

2379 ) 

2380 for row in padded_rows: 

2381 # test to see if either the 1st column or the 2nd column (account for showindex) has 

2382 # the SEPARATING_LINE flag 

2383 if _is_separating_line(row): 

2384 _append_line(lines, padded_widths, colaligns, separating_line) 

2385 else: 

2386 append_row(lines, row, padded_widths, colaligns, fmt.datarow) 

2387 

2388 if fmt.linebelow and "linebelow" not in hidden: 

2389 _append_line(lines, padded_widths, colaligns, fmt.linebelow) 

2390 

2391 if headers or rows: 

2392 output = "\n".join(lines) 

2393 if fmt.lineabove == _html_begin_table_without_header: 

2394 return JupyterHTMLStr(output) 

2395 else: 

2396 return output 

2397 else: # a completely empty table 

2398 return "" 

2399 

2400 

2401class _CustomTextWrap(textwrap.TextWrapper): 

2402 """A custom implementation of CPython's textwrap.TextWrapper. This supports 

2403 both wide characters (Korea, Japanese, Chinese) - including mixed string. 

2404 For the most part, the `_handle_long_word` and `_wrap_chunks` functions were 

2405 copy pasted out of the CPython baseline, and updated with our custom length 

2406 and line appending logic. 

2407 """ 

2408 

2409 def __init__(self, *args, **kwargs): 

2410 self._active_codes = [] 

2411 self.max_lines = None # For python2 compatibility 

2412 textwrap.TextWrapper.__init__(self, *args, **kwargs) 

2413 

2414 @staticmethod 

2415 def _len(item): 

2416 """Custom len that gets console column width for wide 

2417 and non-wide characters as well as ignores color codes""" 

2418 stripped = _strip_ansi(item) 

2419 if wcwidth: 

2420 return wcwidth.wcswidth(stripped) 

2421 else: 

2422 return len(stripped) 

2423 

2424 def _update_lines(self, lines, new_line): 

2425 """Adds a new line to the list of lines the text is being wrapped into 

2426 This function will also track any ANSI color codes in this string as well 

2427 as add any colors from previous lines order to preserve the same formatting 

2428 as a single unwrapped string. 

2429 """ 

2430 code_matches = [x for x in _ansi_codes.finditer(new_line)] 

2431 color_codes = [ 

2432 code.string[code.span()[0] : code.span()[1]] for code in code_matches 

2433 ] 

2434 

2435 # Add color codes from earlier in the unwrapped line, and then track any new ones we add. 

2436 new_line = "".join(self._active_codes) + new_line 

2437 

2438 for code in color_codes: 

2439 if code != _ansi_color_reset_code: 

2440 self._active_codes.append(code) 

2441 else: # A single reset code resets everything 

2442 self._active_codes = [] 

2443 

2444 # Always ensure each line is color terminted if any colors are 

2445 # still active, otherwise colors will bleed into other cells on the console 

2446 if len(self._active_codes) > 0: 

2447 new_line = new_line + _ansi_color_reset_code 

2448 

2449 lines.append(new_line) 

2450 

2451 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width): 

2452 """_handle_long_word(chunks : [string], 

2453 cur_line : [string], 

2454 cur_len : int, width : int) 

2455 Handle a chunk of text (most likely a word, not whitespace) that 

2456 is too long to fit in any line. 

2457 """ 

2458 # Figure out when indent is larger than the specified width, and make 

2459 # sure at least one character is stripped off on every pass 

2460 if width < 1: 

2461 space_left = 1 

2462 else: 

2463 space_left = width - cur_len 

2464 

2465 # If we're allowed to break long words, then do so: put as much 

2466 # of the next chunk onto the current line as will fit. 

2467 if self.break_long_words: 

2468 # Tabulate Custom: Build the string up piece-by-piece in order to 

2469 # take each charcter's width into account 

2470 chunk = reversed_chunks[-1] 

2471 i = 1 

2472 while self._len(chunk[:i]) <= space_left: 

2473 i = i + 1 

2474 cur_line.append(chunk[: i - 1]) 

2475 reversed_chunks[-1] = chunk[i - 1 :] 

2476 

2477 # Otherwise, we have to preserve the long word intact. Only add 

2478 # it to the current line if there's nothing already there -- 

2479 # that minimizes how much we violate the width constraint. 

2480 elif not cur_line: 

2481 cur_line.append(reversed_chunks.pop()) 

2482 

2483 # If we're not allowed to break long words, and there's already 

2484 # text on the current line, do nothing. Next time through the 

2485 # main loop of _wrap_chunks(), we'll wind up here again, but 

2486 # cur_len will be zero, so the next line will be entirely 

2487 # devoted to the long word that we can't handle right now. 

2488 

2489 def _wrap_chunks(self, chunks): 

2490 """_wrap_chunks(chunks : [string]) -> [string] 

2491 Wrap a sequence of text chunks and return a list of lines of 

2492 length 'self.width' or less. (If 'break_long_words' is false, 

2493 some lines may be longer than this.) Chunks correspond roughly 

2494 to words and the whitespace between them: each chunk is 

2495 indivisible (modulo 'break_long_words'), but a line break can 

2496 come between any two chunks. Chunks should not have internal 

2497 whitespace; ie. a chunk is either all whitespace or a "word". 

2498 Whitespace chunks will be removed from the beginning and end of 

2499 lines, but apart from that whitespace is preserved. 

2500 """ 

2501 lines = [] 

2502 if self.width <= 0: 

2503 raise ValueError("invalid width %r (must be > 0)" % self.width) 

2504 if self.max_lines is not None: 

2505 if self.max_lines > 1: 

2506 indent = self.subsequent_indent 

2507 else: 

2508 indent = self.initial_indent 

2509 if self._len(indent) + self._len(self.placeholder.lstrip()) > self.width: 

2510 raise ValueError("placeholder too large for max width") 

2511 

2512 # Arrange in reverse order so items can be efficiently popped 

2513 # from a stack of chucks. 

2514 chunks.reverse() 

2515 

2516 while chunks: 

2517 

2518 # Start the list of chunks that will make up the current line. 

2519 # cur_len is just the length of all the chunks in cur_line. 

2520 cur_line = [] 

2521 cur_len = 0 

2522 

2523 # Figure out which static string will prefix this line. 

2524 if lines: 

2525 indent = self.subsequent_indent 

2526 else: 

2527 indent = self.initial_indent 

2528 

2529 # Maximum width for this line. 

2530 width = self.width - self._len(indent) 

2531 

2532 # First chunk on line is whitespace -- drop it, unless this 

2533 # is the very beginning of the text (ie. no lines started yet). 

2534 if self.drop_whitespace and chunks[-1].strip() == "" and lines: 

2535 del chunks[-1] 

2536 

2537 while chunks: 

2538 chunk_len = self._len(chunks[-1]) 

2539 

2540 # Can at least squeeze this chunk onto the current line. 

2541 if cur_len + chunk_len <= width: 

2542 cur_line.append(chunks.pop()) 

2543 cur_len += chunk_len 

2544 

2545 # Nope, this line is full. 

2546 else: 

2547 break 

2548 

2549 # The current line is full, and the next chunk is too big to 

2550 # fit on *any* line (not just this one). 

2551 if chunks and self._len(chunks[-1]) > width: 

2552 self._handle_long_word(chunks, cur_line, cur_len, width) 

2553 cur_len = sum(map(self._len, cur_line)) 

2554 

2555 # If the last chunk on this line is all whitespace, drop it. 

2556 if self.drop_whitespace and cur_line and cur_line[-1].strip() == "": 

2557 cur_len -= self._len(cur_line[-1]) 

2558 del cur_line[-1] 

2559 

2560 if cur_line: 

2561 if ( 

2562 self.max_lines is None 

2563 or len(lines) + 1 < self.max_lines 

2564 or ( 

2565 not chunks 

2566 or self.drop_whitespace 

2567 and len(chunks) == 1 

2568 and not chunks[0].strip() 

2569 ) 

2570 and cur_len <= width 

2571 ): 

2572 # Convert current line back to a string and store it in 

2573 # list of all lines (return value). 

2574 self._update_lines(lines, indent + "".join(cur_line)) 

2575 else: 

2576 while cur_line: 

2577 if ( 

2578 cur_line[-1].strip() 

2579 and cur_len + self._len(self.placeholder) <= width 

2580 ): 

2581 cur_line.append(self.placeholder) 

2582 self._update_lines(lines, indent + "".join(cur_line)) 

2583 break 

2584 cur_len -= self._len(cur_line[-1]) 

2585 del cur_line[-1] 

2586 else: 

2587 if lines: 

2588 prev_line = lines[-1].rstrip() 

2589 if ( 

2590 self._len(prev_line) + self._len(self.placeholder) 

2591 <= self.width 

2592 ): 

2593 lines[-1] = prev_line + self.placeholder 

2594 break 

2595 self._update_lines(lines, indent + self.placeholder.lstrip()) 

2596 break 

2597 

2598 return lines 

2599 

2600 

2601def _main(): 

2602 """\ 

2603 Usage: tabulate [options] [FILE ...] 

2604 

2605 Pretty-print tabular data. 

2606 See also https://github.com/astanin/python-tabulate 

2607 

2608 FILE a filename of the file with tabular data; 

2609 if "-" or missing, read data from stdin. 

2610 

2611 Options: 

2612 

2613 -h, --help show this message 

2614 -1, --header use the first row of data as a table header 

2615 -o FILE, --output FILE print table to FILE (default: stdout) 

2616 -s REGEXP, --sep REGEXP use a custom column separator (default: whitespace) 

2617 -F FPFMT, --float FPFMT floating point number format (default: g) 

2618 -I INTFMT, --int INTFMT integer point number format (default: "") 

2619 -f FMT, --format FMT set output table format; supported formats: 

2620 plain, simple, grid, fancy_grid, pipe, orgtbl, 

2621 rst, mediawiki, html, latex, latex_raw, 

2622 latex_booktabs, latex_longtable, tsv 

2623 (default: simple) 

2624 """ 

2625 import getopt 

2626 import sys 

2627 import textwrap 

2628 

2629 usage = textwrap.dedent(_main.__doc__) 

2630 try: 

2631 opts, args = getopt.getopt( 

2632 sys.argv[1:], 

2633 "h1o:s:F:A:f:", 

2634 ["help", "header", "output", "sep=", "float=", "int=", "align=", "format="], 

2635 ) 

2636 except getopt.GetoptError as e: 

2637 print(e) 

2638 print(usage) 

2639 sys.exit(2) 

2640 headers = [] 

2641 floatfmt = _DEFAULT_FLOATFMT 

2642 intfmt = _DEFAULT_INTFMT 

2643 colalign = None 

2644 tablefmt = "simple" 

2645 sep = r"\s+" 

2646 outfile = "-" 

2647 for opt, value in opts: 

2648 if opt in ["-1", "--header"]: 

2649 headers = "firstrow" 

2650 elif opt in ["-o", "--output"]: 

2651 outfile = value 

2652 elif opt in ["-F", "--float"]: 

2653 floatfmt = value 

2654 elif opt in ["-I", "--int"]: 

2655 intfmt = value 

2656 elif opt in ["-C", "--colalign"]: 

2657 colalign = value.split() 

2658 elif opt in ["-f", "--format"]: 

2659 if value not in tabulate_formats: 

2660 print("%s is not a supported table format" % value) 

2661 print(usage) 

2662 sys.exit(3) 

2663 tablefmt = value 

2664 elif opt in ["-s", "--sep"]: 

2665 sep = value 

2666 elif opt in ["-h", "--help"]: 

2667 print(usage) 

2668 sys.exit(0) 

2669 files = [sys.stdin] if not args else args 

2670 with (sys.stdout if outfile == "-" else open(outfile, "w")) as out: 

2671 for f in files: 

2672 if f == "-": 

2673 f = sys.stdin 

2674 if _is_file(f): 

2675 _pprint_file( 

2676 f, 

2677 headers=headers, 

2678 tablefmt=tablefmt, 

2679 sep=sep, 

2680 floatfmt=floatfmt, 

2681 intfmt=intfmt, 

2682 file=out, 

2683 colalign=colalign, 

2684 ) 

2685 else: 

2686 with open(f) as fobj: 

2687 _pprint_file( 

2688 fobj, 

2689 headers=headers, 

2690 tablefmt=tablefmt, 

2691 sep=sep, 

2692 floatfmt=floatfmt, 

2693 intfmt=intfmt, 

2694 file=out, 

2695 colalign=colalign, 

2696 ) 

2697 

2698 

2699def _pprint_file(fobject, headers, tablefmt, sep, floatfmt, intfmt, file, colalign): 

2700 rows = fobject.readlines() 

2701 table = [re.split(sep, r.rstrip()) for r in rows if r.strip()] 

2702 print( 

2703 tabulate( 

2704 table, 

2705 headers, 

2706 tablefmt, 

2707 floatfmt=floatfmt, 

2708 intfmt=intfmt, 

2709 colalign=colalign, 

2710 ), 

2711 file=file, 

2712 ) 

2713 

2714 

2715if __name__ == "__main__": 

2716 _main()