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

821 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:03 +0000

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

2 

3import warnings 

4from collections import namedtuple 

5from collections.abc import Iterable, Sized 

6from html import escape as htmlescape 

7from itertools import chain, zip_longest as izip_longest 

8from functools import reduce, partial 

9import io 

10import re 

11import math 

12import textwrap 

13import dataclasses 

14 

15try: 

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

17except ImportError: 

18 wcwidth = None 

19 

20 

21def _is_file(f): 

22 return isinstance(f, io.IOBase) 

23 

24 

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

26try: 

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

28except ImportError: 

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

30 

31 

32# minimum extra space in headers 

33MIN_PADDING = 2 

34 

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

36PRESERVE_WHITESPACE = False 

37 

38_DEFAULT_FLOATFMT = "g" 

39_DEFAULT_INTFMT = "" 

40_DEFAULT_MISSINGVAL = "" 

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

42# depending on the formatter 

43_DEFAULT_ALIGN = "default" 

44 

45 

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

47WIDE_CHARS_MODE = wcwidth is not None 

48 

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

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

51SEPARATING_LINE = "\001" 

52 

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

54 

55 

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

57 

58 

59# A table structure is supposed to be: 

60# 

61# --- lineabove --------- 

62# headerrow 

63# --- linebelowheader --- 

64# datarow 

65# --- linebetweenrows --- 

66# ... (more datarows) ... 

67# --- linebetweenrows --- 

68# last datarow 

69# --- linebelow --------- 

70# 

71# TableFormat's line* elements can be 

72# 

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

74# - or a Line tuple, 

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

76# 

77# TableFormat's *row elements can be 

78# 

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

80# - or a DataRow tuple, 

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

82# 

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

84# 

85# with_header_hide: 

86# 

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

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

89# 

90TableFormat = namedtuple( 

91 "TableFormat", 

92 [ 

93 "lineabove", 

94 "linebelowheader", 

95 "linebetweenrows", 

96 "linebelow", 

97 "headerrow", 

98 "datarow", 

99 "padding", 

100 "with_header_hide", 

101 ], 

102) 

103 

104 

105def _is_separating_line(row): 

106 row_type = type(row) 

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

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

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

110 ) 

111 return is_sl 

112 

113 

114def _pipe_segment_with_colons(align, colwidth): 

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

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

117 w = colwidth 

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

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

120 elif align == "center": 

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

122 elif align == "left": 

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

124 else: 

125 return "-" * w 

126 

127 

128def _pipe_line_with_colons(colwidths, colaligns): 

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

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

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

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

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

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

135 

136 

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

138 alignment = { 

139 "left": "", 

140 "right": 'style="text-align: right;"| ', 

141 "center": 'style="text-align: center;"| ', 

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

143 } 

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

145 # rather than padding parameter which affects only the value 

146 values_with_attrs = [ 

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

148 ] 

149 colsep = separator * 2 

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

151 

152 

153def _textile_row_with_attrs(cell_values, colwidths, colaligns): 

154 cell_values[0] += " " 

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

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

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

158 

159 

160def _html_begin_table_without_header(colwidths_ignore, colaligns_ignore): 

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

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

163 

164 

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

166 alignment = { 

167 "left": "", 

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

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

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

171 } 

172 if unsafe: 

173 values_with_attrs = [ 

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

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

176 ] 

177 else: 

178 values_with_attrs = [ 

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

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

181 ] 

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

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

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

185 return rowhtml 

186 

187 

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

189 alignment = { 

190 "left": "", 

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

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

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

194 } 

195 values_with_attrs = [ 

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

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

198 ] 

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

200 

201 

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

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

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

205 return "\n".join( 

206 [ 

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

208 + tabular_columns_fmt 

209 + "}", 

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

211 ] 

212 ) 

213 

214 

215def _asciidoc_row(is_header, *args): 

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

217 

218 def make_header_line(is_header, colwidths, colaligns): 

219 # generate the column specifiers 

220 

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

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

223 asciidoc_alignments = zip( 

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

225 ) 

226 asciidoc_column_specifiers = [ 

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

228 ] 

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

230 

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

232 options_list = [] 

233 

234 if is_header: 

235 options_list.append("header") 

236 

237 if options_list: 

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

239 

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

241 

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

243 

244 if len(args) == 2: 

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

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

247 return make_header_line(False, *args) 

248 

249 elif len(args) == 3: 

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

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

252 

253 cell_values, colwidths, colaligns = args 

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

255 

256 if is_header: 

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

258 else: 

259 return data_line 

260 

261 else: 

262 raise ValueError( 

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

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

265 ) 

266 

267 

268LATEX_ESCAPE_RULES = { 

269 r"&": r"\&", 

270 r"%": r"\%", 

271 r"$": r"\$", 

272 r"#": r"\#", 

273 r"_": r"\_", 

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

275 r"{": r"\{", 

276 r"}": r"\}", 

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

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

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

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

281} 

282 

283 

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

285 def escape_char(c): 

286 return escrules.get(c, c) 

287 

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

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

290 return _build_simple_row(escaped_values, rowfmt) 

291 

292 

293def _rst_escape_first_column(rows, headers): 

294 def escape_empty(val): 

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

296 return ".." 

297 else: 

298 return val 

299 

300 new_headers = list(headers) 

301 new_rows = [] 

302 if headers: 

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

304 for row in rows: 

305 new_row = list(row) 

306 if new_row: 

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

308 new_rows.append(new_row) 

309 return new_rows, new_headers 

310 

311 

312_table_formats = { 

313 "simple": TableFormat( 

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

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

316 linebetweenrows=None, 

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

318 headerrow=DataRow("", " ", ""), 

319 datarow=DataRow("", " ", ""), 

320 padding=0, 

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

322 ), 

323 "plain": TableFormat( 

324 lineabove=None, 

325 linebelowheader=None, 

326 linebetweenrows=None, 

327 linebelow=None, 

328 headerrow=DataRow("", " ", ""), 

329 datarow=DataRow("", " ", ""), 

330 padding=0, 

331 with_header_hide=None, 

332 ), 

333 "grid": TableFormat( 

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

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

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

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

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

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

340 padding=1, 

341 with_header_hide=None, 

342 ), 

343 "simple_grid": TableFormat( 

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

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

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

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

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

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

350 padding=1, 

351 with_header_hide=None, 

352 ), 

353 "rounded_grid": TableFormat( 

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

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

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

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

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

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

360 padding=1, 

361 with_header_hide=None, 

362 ), 

363 "heavy_grid": TableFormat( 

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

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

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

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

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

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

370 padding=1, 

371 with_header_hide=None, 

372 ), 

373 "mixed_grid": TableFormat( 

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

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

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

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

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

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

380 padding=1, 

381 with_header_hide=None, 

382 ), 

383 "double_grid": TableFormat( 

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

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

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

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

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

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

390 padding=1, 

391 with_header_hide=None, 

392 ), 

393 "fancy_grid": TableFormat( 

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

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

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

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

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

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

400 padding=1, 

401 with_header_hide=None, 

402 ), 

403 "outline": TableFormat( 

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

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

406 linebetweenrows=None, 

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

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

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

410 padding=1, 

411 with_header_hide=None, 

412 ), 

413 "simple_outline": TableFormat( 

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

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

416 linebetweenrows=None, 

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

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

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

420 padding=1, 

421 with_header_hide=None, 

422 ), 

423 "rounded_outline": TableFormat( 

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

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

426 linebetweenrows=None, 

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

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

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

430 padding=1, 

431 with_header_hide=None, 

432 ), 

433 "heavy_outline": TableFormat( 

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

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

436 linebetweenrows=None, 

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

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

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

440 padding=1, 

441 with_header_hide=None, 

442 ), 

443 "mixed_outline": TableFormat( 

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

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

446 linebetweenrows=None, 

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

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

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

450 padding=1, 

451 with_header_hide=None, 

452 ), 

453 "double_outline": TableFormat( 

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

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

456 linebetweenrows=None, 

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

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

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

460 padding=1, 

461 with_header_hide=None, 

462 ), 

463 "fancy_outline": TableFormat( 

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

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

466 linebetweenrows=None, 

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

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

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

470 padding=1, 

471 with_header_hide=None, 

472 ), 

473 "github": TableFormat( 

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

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

476 linebetweenrows=None, 

477 linebelow=None, 

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

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

480 padding=1, 

481 with_header_hide=["lineabove"], 

482 ), 

483 "pipe": TableFormat( 

484 lineabove=_pipe_line_with_colons, 

485 linebelowheader=_pipe_line_with_colons, 

486 linebetweenrows=None, 

487 linebelow=None, 

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

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

490 padding=1, 

491 with_header_hide=["lineabove"], 

492 ), 

493 "orgtbl": TableFormat( 

494 lineabove=None, 

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

496 linebetweenrows=None, 

497 linebelow=None, 

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

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

500 padding=1, 

501 with_header_hide=None, 

502 ), 

503 "jira": TableFormat( 

504 lineabove=None, 

505 linebelowheader=None, 

506 linebetweenrows=None, 

507 linebelow=None, 

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

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

510 padding=1, 

511 with_header_hide=None, 

512 ), 

513 "presto": TableFormat( 

514 lineabove=None, 

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

516 linebetweenrows=None, 

517 linebelow=None, 

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

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

520 padding=1, 

521 with_header_hide=None, 

522 ), 

523 "pretty": TableFormat( 

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

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

526 linebetweenrows=None, 

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

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

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

530 padding=1, 

531 with_header_hide=None, 

532 ), 

533 "psql": TableFormat( 

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

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

536 linebetweenrows=None, 

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

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

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

540 padding=1, 

541 with_header_hide=None, 

542 ), 

543 "rst": TableFormat( 

544 lineabove=Line("", "=", " ", ""), 

545 linebelowheader=Line("", "=", " ", ""), 

546 linebetweenrows=None, 

547 linebelow=Line("", "=", " ", ""), 

548 headerrow=DataRow("", " ", ""), 

549 datarow=DataRow("", " ", ""), 

550 padding=0, 

551 with_header_hide=None, 

552 ), 

553 "mediawiki": TableFormat( 

554 lineabove=Line( 

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

556 "", 

557 "", 

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

559 ), 

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

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

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

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

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

565 padding=0, 

566 with_header_hide=None, 

567 ), 

568 "moinmoin": TableFormat( 

569 lineabove=None, 

570 linebelowheader=None, 

571 linebetweenrows=None, 

572 linebelow=None, 

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

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

575 padding=1, 

576 with_header_hide=None, 

577 ), 

578 "youtrack": TableFormat( 

579 lineabove=None, 

580 linebelowheader=None, 

581 linebetweenrows=None, 

582 linebelow=None, 

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

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

585 padding=1, 

586 with_header_hide=None, 

587 ), 

588 "html": TableFormat( 

589 lineabove=_html_begin_table_without_header, 

590 linebelowheader="", 

591 linebetweenrows=None, 

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

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

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

595 padding=0, 

596 with_header_hide=["lineabove"], 

597 ), 

598 "unsafehtml": TableFormat( 

599 lineabove=_html_begin_table_without_header, 

600 linebelowheader="", 

601 linebetweenrows=None, 

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

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

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

605 padding=0, 

606 with_header_hide=["lineabove"], 

607 ), 

608 "latex": TableFormat( 

609 lineabove=_latex_line_begin_tabular, 

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

611 linebetweenrows=None, 

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

613 headerrow=_latex_row, 

614 datarow=_latex_row, 

615 padding=1, 

616 with_header_hide=None, 

617 ), 

618 "latex_raw": TableFormat( 

619 lineabove=_latex_line_begin_tabular, 

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

621 linebetweenrows=None, 

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

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

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

625 padding=1, 

626 with_header_hide=None, 

627 ), 

628 "latex_booktabs": TableFormat( 

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

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

631 linebetweenrows=None, 

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

633 headerrow=_latex_row, 

634 datarow=_latex_row, 

635 padding=1, 

636 with_header_hide=None, 

637 ), 

638 "latex_longtable": TableFormat( 

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

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

641 linebetweenrows=None, 

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

643 headerrow=_latex_row, 

644 datarow=_latex_row, 

645 padding=1, 

646 with_header_hide=None, 

647 ), 

648 "tsv": TableFormat( 

649 lineabove=None, 

650 linebelowheader=None, 

651 linebetweenrows=None, 

652 linebelow=None, 

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

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

655 padding=0, 

656 with_header_hide=None, 

657 ), 

658 "textile": TableFormat( 

659 lineabove=None, 

660 linebelowheader=None, 

661 linebetweenrows=None, 

662 linebelow=None, 

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

664 datarow=_textile_row_with_attrs, 

665 padding=1, 

666 with_header_hide=None, 

667 ), 

668 "asciidoc": TableFormat( 

669 lineabove=partial(_asciidoc_row, False), 

670 linebelowheader=None, 

671 linebetweenrows=None, 

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

673 headerrow=partial(_asciidoc_row, True), 

674 datarow=partial(_asciidoc_row, False), 

675 padding=1, 

676 with_header_hide=["lineabove"], 

677 ), 

678} 

679 

680 

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

682 

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

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

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

686multiline_formats = { 

687 "plain": "plain", 

688 "simple": "simple", 

689 "grid": "grid", 

690 "simple_grid": "simple_grid", 

691 "rounded_grid": "rounded_grid", 

692 "heavy_grid": "heavy_grid", 

693 "mixed_grid": "mixed_grid", 

694 "double_grid": "double_grid", 

695 "fancy_grid": "fancy_grid", 

696 "pipe": "pipe", 

697 "orgtbl": "orgtbl", 

698 "jira": "jira", 

699 "presto": "presto", 

700 "pretty": "pretty", 

701 "psql": "psql", 

702 "rst": "rst", 

703 "outline": "outline", 

704 "simple_outline": "simple_outline", 

705 "rounded_outline": "rounded_outline", 

706 "heavy_outline": "heavy_outline", 

707 "mixed_outline": "mixed_outline", 

708 "double_outline": "double_outline", 

709 "fancy_outline": "fancy_outline", 

710} 

711 

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

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

714# - moinmoin: TBD 

715# - youtrack: TBD 

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

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

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

719# - tsv: TBD 

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

721 

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

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

724 

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

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

727# which will be shown below as ESC. 

728# 

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

730# 

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

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

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

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

735# 

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

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

738# 

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

740# 

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

742# 

743# Where: 

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

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

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

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

748_esc = r"\x1b" 

749_csi = rf"{_esc}\[" 

750_osc = rf"{_esc}\]" 

751_st = rf"{_esc}\\" 

752 

753_ansi_escape_pat = rf""" 

754 ( 

755 # terminal colors, etc 

756 {_csi} # CSI 

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

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

759 [\x40-\x7e] # final byte 

760 | 

761 # terminal hyperlinks 

762 {_osc}8; # OSC opening 

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

764 ; # delimiter 

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

766 {_st} # ST 

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

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

769 ) 

770""" 

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

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

773_ansi_color_reset_code = "\033[0m" 

774 

775_float_with_thousands_separators = re.compile( 

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

777) 

778 

779 

780def simple_separated_format(separator): 

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

782 

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

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

785 True 

786 

787 """ 

788 return TableFormat( 

789 None, 

790 None, 

791 None, 

792 None, 

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

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

795 padding=0, 

796 with_header_hide=None, 

797 ) 

798 

799 

800def _isnumber_with_thousands_separator(string): 

801 """ 

802 >>> _isnumber_with_thousands_separator(".") 

803 False 

804 >>> _isnumber_with_thousands_separator("1") 

805 True 

806 >>> _isnumber_with_thousands_separator("1.") 

807 True 

808 >>> _isnumber_with_thousands_separator(".1") 

809 True 

810 >>> _isnumber_with_thousands_separator("1000") 

811 False 

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

813 True 

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

815 False 

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

817 True 

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

819 True 

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

821 True 

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

823 True 

824 """ 

825 try: 

826 string = string.decode() 

827 except (UnicodeDecodeError, AttributeError): 

828 pass 

829 

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

831 

832 

833def _isconvertible(conv, string): 

834 try: 

835 conv(string) 

836 return True 

837 except (ValueError, TypeError): 

838 return False 

839 

840 

841def _isnumber(string): 

842 """ 

843 >>> _isnumber("123.45") 

844 True 

845 >>> _isnumber("123") 

846 True 

847 >>> _isnumber("spam") 

848 False 

849 >>> _isnumber("123e45678") 

850 False 

851 >>> _isnumber("inf") 

852 True 

853 """ 

854 if not _isconvertible(float, string): 

855 return False 

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

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

858 ): 

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

860 return True 

861 

862 

863def _isint(string, inttype=int): 

864 """ 

865 >>> _isint("123") 

866 True 

867 >>> _isint("123.45") 

868 False 

869 """ 

870 return ( 

871 type(string) is inttype 

872 or ( 

873 (hasattr(string, "is_integer") or hasattr(string, "__array__")) 

874 and str(type(string)).startswith("<class 'numpy.int") 

875 ) # numpy.int64 and similar 

876 or ( 

877 isinstance(string, (bytes, str)) and _isconvertible(inttype, string) 

878 ) # integer as string 

879 ) 

880 

881 

882def _isbool(string): 

883 """ 

884 >>> _isbool(True) 

885 True 

886 >>> _isbool("False") 

887 True 

888 >>> _isbool(1) 

889 False 

890 """ 

891 return type(string) is bool or ( 

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

893 ) 

894 

895 

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

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

898 

899 >>> _type(None) is type(None) 

900 True 

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

902 True 

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

904 True 

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

906 True 

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

908 True 

909 

910 """ 

911 

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

913 string = _strip_ansi(string) 

914 

915 if string is None: 

916 return type(None) 

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

918 return str 

919 elif _isbool(string): 

920 return bool 

921 elif _isint(string) and numparse: 

922 return int 

923 elif _isnumber(string) and numparse: 

924 return float 

925 elif isinstance(string, bytes): 

926 return bytes 

927 else: 

928 return str 

929 

930 

931def _afterpoint(string): 

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

933 

934 >>> _afterpoint("123.45") 

935 2 

936 >>> _afterpoint("1001") 

937 -1 

938 >>> _afterpoint("eggs") 

939 -1 

940 >>> _afterpoint("123e45") 

941 2 

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

943 2 

944 

945 """ 

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

947 if _isint(string): 

948 return -1 

949 else: 

950 pos = string.rfind(".") 

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

952 if pos >= 0: 

953 return len(string) - pos - 1 

954 else: 

955 return -1 # no point 

956 else: 

957 return -1 # not a number 

958 

959 

960def _padleft(width, s): 

961 """Flush right. 

962 

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

964 True 

965 

966 """ 

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

968 return fmt.format(s) 

969 

970 

971def _padright(width, s): 

972 """Flush left. 

973 

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

975 True 

976 

977 """ 

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

979 return fmt.format(s) 

980 

981 

982def _padboth(width, s): 

983 """Center string. 

984 

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

986 True 

987 

988 """ 

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

990 return fmt.format(s) 

991 

992 

993def _padnone(ignore_width, s): 

994 return s 

995 

996 

997def _strip_ansi(s): 

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

999 

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

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

1002 supported. 

1003 

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

1005 "'This is a link'" 

1006 

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

1008 "'red text'" 

1009 

1010 """ 

1011 if isinstance(s, str): 

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

1013 else: # a bytestring 

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

1015 

1016 

1017def _visible_width(s): 

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

1019 

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

1021 (5, 5) 

1022 

1023 """ 

1024 # optional wide-character support 

1025 if wcwidth is not None and WIDE_CHARS_MODE: 

1026 len_fn = wcwidth.wcswidth 

1027 else: 

1028 len_fn = len 

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

1030 return len_fn(_strip_ansi(s)) 

1031 else: 

1032 return len_fn(str(s)) 

1033 

1034 

1035def _is_multiline(s): 

1036 if isinstance(s, str): 

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

1038 else: # a bytestring 

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

1040 

1041 

1042def _multiline_width(multiline_s, line_width_fn=len): 

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

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

1045 

1046 

1047def _choose_width_fn(has_invisible, enable_widechars, is_multiline): 

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

1049 if has_invisible: 

1050 line_width_fn = _visible_width 

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

1052 line_width_fn = wcwidth.wcswidth 

1053 else: 

1054 line_width_fn = len 

1055 if is_multiline: 

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

1057 else: 

1058 width_fn = line_width_fn 

1059 return width_fn 

1060 

1061 

1062def _align_column_choose_padfn(strings, alignment, has_invisible): 

1063 if alignment == "right": 

1064 if not PRESERVE_WHITESPACE: 

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

1066 padfn = _padleft 

1067 elif alignment == "center": 

1068 if not PRESERVE_WHITESPACE: 

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

1070 padfn = _padboth 

1071 elif alignment == "decimal": 

1072 if has_invisible: 

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

1074 else: 

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

1076 maxdecimals = max(decimals) 

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

1078 padfn = _padleft 

1079 elif not alignment: 

1080 padfn = _padnone 

1081 else: 

1082 if not PRESERVE_WHITESPACE: 

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

1084 padfn = _padright 

1085 return strings, padfn 

1086 

1087 

1088def _align_column_choose_width_fn(has_invisible, enable_widechars, is_multiline): 

1089 if has_invisible: 

1090 line_width_fn = _visible_width 

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

1092 line_width_fn = wcwidth.wcswidth 

1093 else: 

1094 line_width_fn = len 

1095 if is_multiline: 

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

1097 else: 

1098 width_fn = line_width_fn 

1099 return width_fn 

1100 

1101 

1102def _align_column_multiline_width(multiline_s, line_width_fn=len): 

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

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

1105 

1106 

1107def _flat_list(nested_list): 

1108 ret = [] 

1109 for item in nested_list: 

1110 if isinstance(item, list): 

1111 for subitem in item: 

1112 ret.append(subitem) 

1113 else: 

1114 ret.append(item) 

1115 return ret 

1116 

1117 

1118def _align_column( 

1119 strings, 

1120 alignment, 

1121 minwidth=0, 

1122 has_invisible=True, 

1123 enable_widechars=False, 

1124 is_multiline=False, 

1125): 

1126 """[string] -> [padded_string]""" 

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

1128 width_fn = _align_column_choose_width_fn( 

1129 has_invisible, enable_widechars, is_multiline 

1130 ) 

1131 

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

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

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

1135 if is_multiline: 

1136 if not enable_widechars and not has_invisible: 

1137 padded_strings = [ 

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

1139 for ms in strings 

1140 ] 

1141 else: 

1142 # enable wide-character width corrections 

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

1144 visible_widths = [ 

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

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

1147 ] 

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

1149 # padfn doesn't need to apply another correction 

1150 padded_strings = [ 

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

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

1153 ] 

1154 else: # single-line cell values 

1155 if not enable_widechars and not has_invisible: 

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

1157 else: 

1158 # enable wide-character width corrections 

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

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

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

1162 # padfn doesn't need to apply another correction 

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

1164 return padded_strings 

1165 

1166 

1167def _more_generic(type1, type2): 

1168 types = { 

1169 type(None): 0, 

1170 bool: 1, 

1171 int: 2, 

1172 float: 3, 

1173 bytes: 4, 

1174 str: 5, 

1175 } 

1176 invtypes = { 

1177 5: str, 

1178 4: bytes, 

1179 3: float, 

1180 2: int, 

1181 1: bool, 

1182 0: type(None), 

1183 } 

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

1185 return invtypes[moregeneric] 

1186 

1187 

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

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

1190 

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

1192 True 

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

1194 True 

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

1196 True 

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

1198 True 

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

1200 True 

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

1202 True 

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

1204 True 

1205 >>> import datetime as dt 

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

1207 True 

1208 

1209 """ 

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

1211 return reduce(_more_generic, types, bool) 

1212 

1213 

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

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

1216 

1217 Unicode is supported: 

1218 

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

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

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

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

1223 True 

1224 

1225 """ # noqa 

1226 if val is None: 

1227 return missingval 

1228 

1229 if valtype is str: 

1230 return f"{val}" 

1231 elif valtype is int: 

1232 return format(val, intfmt) 

1233 elif valtype is bytes: 

1234 try: 

1235 return str(val, "ascii") 

1236 except (TypeError, UnicodeDecodeError): 

1237 return str(val) 

1238 elif valtype is float: 

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

1240 if is_a_colored_number: 

1241 raw_val = _strip_ansi(val) 

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

1243 return val.replace(raw_val, formatted_val) 

1244 else: 

1245 return format(float(val), floatfmt) 

1246 else: 

1247 return f"{val}" 

1248 

1249 

1250def _align_header( 

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

1252): 

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

1254 if is_multiline: 

1255 header_lines = re.split(_multiline_codes, header) 

1256 padded_lines = [ 

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

1258 ] 

1259 return "\n".join(padded_lines) 

1260 # else: not multiline 

1261 ninvisible = len(header) - visible_width 

1262 width += ninvisible 

1263 if alignment == "left": 

1264 return _padright(width, header) 

1265 elif alignment == "center": 

1266 return _padboth(width, header) 

1267 elif not alignment: 

1268 return f"{header}" 

1269 else: 

1270 return _padleft(width, header) 

1271 

1272 

1273def _remove_separating_lines(rows): 

1274 if type(rows) == list: 

1275 separating_lines = [] 

1276 sans_rows = [] 

1277 for index, row in enumerate(rows): 

1278 if _is_separating_line(row): 

1279 separating_lines.append(index) 

1280 else: 

1281 sans_rows.append(row) 

1282 return sans_rows, separating_lines 

1283 else: 

1284 return rows, None 

1285 

1286 

1287def _reinsert_separating_lines(rows, separating_lines): 

1288 if separating_lines: 

1289 for index in separating_lines: 

1290 rows.insert(index, SEPARATING_LINE) 

1291 

1292 

1293def _prepend_row_index(rows, index): 

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

1295 if index is None or index is False: 

1296 return rows 

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

1298 raise ValueError( 

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

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

1301 ) 

1302 sans_rows, separating_lines = _remove_separating_lines(rows) 

1303 new_rows = [] 

1304 index_iter = iter(index) 

1305 for row in sans_rows: 

1306 index_v = next(index_iter) 

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

1308 rows = new_rows 

1309 _reinsert_separating_lines(rows, separating_lines) 

1310 return rows 

1311 

1312 

1313def _bool(val): 

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

1315 try: 

1316 return bool(val) 

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

1318 return False 

1319 

1320 

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

1322 """Transform a supported data type to a list of lists, and a list of headers, with headers padding. 

1323 

1324 Supported tabular data types: 

1325 

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

1327 

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

1329 

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

1331 

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

1333 

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

1335 

1336 * 2D NumPy arrays 

1337 

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

1339 

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

1341 

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

1343 

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

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

1346 

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

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

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

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

1351 

1352 """ 

1353 

1354 try: 

1355 bool(headers) 

1356 is_headers2bool_broken = False # noqa 

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

1358 is_headers2bool_broken = True # noqa 

1359 headers = list(headers) 

1360 

1361 index = None 

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

1363 # dict-like and pandas.DataFrame? 

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

1365 # likely a conventional dict 

1366 keys = tabular_data.keys() 

1367 rows = list( 

1368 izip_longest(*tabular_data.values()) 

1369 ) # columns have to be transposed 

1370 elif hasattr(tabular_data, "index"): 

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

1372 keys = list(tabular_data) 

1373 if ( 

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

1375 and tabular_data.index.name is not None 

1376 ): 

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

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

1379 else: 

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

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

1382 # for DataFrames add an index per default 

1383 index = list(tabular_data.index) 

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

1385 else: 

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

1387 

1388 if headers == "keys": 

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

1390 

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

1392 rows = list(tabular_data) 

1393 

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

1395 # an empty table (issue #81) 

1396 headers = [] 

1397 elif ( 

1398 headers == "keys" 

1399 and hasattr(tabular_data, "dtype") 

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

1401 ): 

1402 # numpy record array 

1403 headers = tabular_data.dtype.names 

1404 elif ( 

1405 headers == "keys" 

1406 and len(rows) > 0 

1407 and isinstance(rows[0], tuple) 

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

1409 ): 

1410 # namedtuple 

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

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

1413 # dict-like object 

1414 uniq_keys = set() # implements hashed lookup 

1415 keys = [] # storage for set 

1416 if headers == "firstrow": 

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

1418 keys.extend(firstdict.keys()) 

1419 uniq_keys.update(keys) 

1420 rows = rows[1:] 

1421 for row in rows: 

1422 for k in row.keys(): 

1423 # Save unique items in input order 

1424 if k not in uniq_keys: 

1425 keys.append(k) 

1426 uniq_keys.add(k) 

1427 if headers == "keys": 

1428 headers = keys 

1429 elif isinstance(headers, dict): 

1430 # a dict of headers for a list of dicts 

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

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

1433 elif headers == "firstrow": 

1434 if len(rows) > 0: 

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

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

1437 else: 

1438 headers = [] 

1439 elif headers: 

1440 raise ValueError( 

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

1442 ) 

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

1444 

1445 elif ( 

1446 headers == "keys" 

1447 and hasattr(tabular_data, "description") 

1448 and hasattr(tabular_data, "fetchone") 

1449 and hasattr(tabular_data, "rowcount") 

1450 ): 

1451 # Python Database API cursor object (PEP 0249) 

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

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

1454 

1455 elif ( 

1456 dataclasses is not None 

1457 and len(rows) > 0 

1458 and dataclasses.is_dataclass(rows[0]) 

1459 ): 

1460 # Python 3.7+'s dataclass 

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

1462 if headers == "keys": 

1463 headers = field_names 

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

1465 

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

1467 # keys are column indices 

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

1469 

1470 # take headers from the first row if necessary 

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

1472 if index is not None: 

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

1474 index = index[1:] 

1475 else: 

1476 headers = rows[0] 

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

1478 rows = rows[1:] 

1479 elif headers == "firstrow": 

1480 headers = [] 

1481 

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

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

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

1485 

1486 # add or remove an index column 

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

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

1489 rows = _prepend_row_index(rows, index) 

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

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

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

1493 rows = _prepend_row_index(rows, showindex) 

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

1495 if index is None: 

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

1497 rows = _prepend_row_index(rows, index) 

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

1499 pass 

1500 

1501 # pad with empty headers for initial columns if necessary 

1502 headers_pad = 0 

1503 if headers and len(rows) > 0: 

1504 headers_pad = max(0, len(rows[0]) - len(headers)) 

1505 headers = [""] * headers_pad + headers 

1506 

1507 return rows, headers, headers_pad 

1508 

1509 

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

1511 if len(list_of_lists): 

1512 num_cols = len(list_of_lists[0]) 

1513 else: 

1514 num_cols = 0 

1515 numparses = _expand_iterable(numparses, num_cols, True) 

1516 

1517 result = [] 

1518 

1519 for row in list_of_lists: 

1520 new_row = [] 

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

1522 if _isnumber(cell) and numparse: 

1523 new_row.append(cell) 

1524 continue 

1525 

1526 if width is not None: 

1527 wrapper = _CustomTextWrap(width=width) 

1528 # Cast based on our internal type handling 

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

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

1531 casted_cell = ( 

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

1533 ) 

1534 wrapped = [ 

1535 "\n".join(wrapper.wrap(line)) 

1536 for line in casted_cell.splitlines() 

1537 if line.strip() != "" 

1538 ] 

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

1540 else: 

1541 new_row.append(cell) 

1542 result.append(new_row) 

1543 

1544 return result 

1545 

1546 

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

1548 """ 

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

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

1551 specific behavior: 

1552 

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

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

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

1556 

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

1558 "'foo'" 

1559 

1560 >>> repr(_to_str('foo')) 

1561 "'foo'" 

1562 

1563 >>> repr(_to_str(42)) 

1564 "'42'" 

1565 

1566 """ 

1567 if isinstance(s, bytes): 

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

1569 return str(s) 

1570 

1571 

1572def tabulate( 

1573 tabular_data, 

1574 headers=(), 

1575 tablefmt="simple", 

1576 floatfmt=_DEFAULT_FLOATFMT, 

1577 intfmt=_DEFAULT_INTFMT, 

1578 numalign=_DEFAULT_ALIGN, 

1579 stralign=_DEFAULT_ALIGN, 

1580 missingval=_DEFAULT_MISSINGVAL, 

1581 showindex="default", 

1582 disable_numparse=False, 

1583 colglobalalign=None, 

1584 colalign=None, 

1585 maxcolwidths=None, 

1586 headersglobalalign=None, 

1587 headersalign=None, 

1588 rowalign=None, 

1589 maxheadercolwidths=None, 

1590): 

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

1592 

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

1594 --- --------- 

1595 1 2.34 

1596 -56 8.999 

1597 2 10001 

1598 --- --------- 

1599 

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

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

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

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

1604 NumPy record array, or a Pandas' dataframe. 

1605 

1606 

1607 Table headers 

1608 ------------- 

1609 

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

1611 

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

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

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

1615 

1616 Otherwise a headerless table is produced. 

1617 

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

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

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

1621 

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

1623 ... headers="firstrow")) 

1624 sex age 

1625 ----- ----- ----- 

1626 Alice F 24 

1627 Bob M 19 

1628 

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

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

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

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

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

1634 

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

1636 - - -- 

1637 0 F 24 

1638 1 M 19 

1639 - - -- 

1640 

1641 

1642 Column and Headers alignment 

1643 ---------------------------- 

1644 

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

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

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

1648 everything else to the left. Possible column alignments 

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

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

1651 

1652 `colglobalalign` allows for global alignment of columns, before any 

1653 specific override from `colalign`. Possible values are: None 

1654 (defaults according to coltype), "right", "center", "decimal", 

1655 "left". 

1656 `colalign` allows for column-wise override starting from left-most 

1657 column. Possible values are: "global" (no override), "right", 

1658 "center", "decimal", "left". 

1659 `headersglobalalign` allows for global headers alignment, before any 

1660 specific override from `headersalign`. Possible values are: None 

1661 (follow columns alignment), "right", "center", "left". 

1662 `headersalign` allows for header-wise override starting from left-most 

1663 given header. Possible values are: "global" (no override), "same" 

1664 (follow column alignment), "right", "center", "left". 

1665 

1666 Note on intended behaviour: If there is no `tabular_data`, any column 

1667 alignment argument is ignored. Hence, in this case, header 

1668 alignment cannot be inferred from column alignment. 

1669 

1670 Table formats 

1671 ------------- 

1672 

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

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

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

1676 

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

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

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

1680 

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

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

1683 columns): 

1684 

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

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

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

1688 ----- -- ---- 

1689 spam 1 ? 

1690 eggs 42 3.14 

1691 other ? 2.7 

1692 ----- -- ---- 

1693 

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

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

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

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

1698 

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

1700 it separates columns with a double space: 

1701 

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

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

1704 strings numbers 

1705 spam 41.9999 

1706 eggs 451 

1707 

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

1709 spam 41.9999 

1710 eggs 451 

1711 

1712 "simple" format is like Pandoc simple_tables: 

1713 

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

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

1716 strings numbers 

1717 --------- --------- 

1718 spam 41.9999 

1719 eggs 451 

1720 

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

1722 ---- -------- 

1723 spam 41.9999 

1724 eggs 451 

1725 ---- -------- 

1726 

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

1728 Pandoc grid_tables: 

1729 

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

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

1732 +-----------+-----------+ 

1733 | strings | numbers | 

1734 +===========+===========+ 

1735 | spam | 41.9999 | 

1736 +-----------+-----------+ 

1737 | eggs | 451 | 

1738 +-----------+-----------+ 

1739 

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

1741 +------+----------+ 

1742 | spam | 41.9999 | 

1743 +------+----------+ 

1744 | eggs | 451 | 

1745 +------+----------+ 

1746 

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

1748 characters: 

1749 

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

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

1752 ┌───────────┬───────────┐ 

1753 │ strings │ numbers │ 

1754 ├───────────┼───────────┤ 

1755 │ spam │ 41.9999 │ 

1756 ├───────────┼───────────┤ 

1757 │ eggs │ 451 │ 

1758 └───────────┴───────────┘ 

1759 

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

1761 characters with rounded corners: 

1762 

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

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

1765 ╭───────────┬───────────╮ 

1766 │ strings │ numbers │ 

1767 ├───────────┼───────────┤ 

1768 │ spam │ 41.9999 │ 

1769 ├───────────┼───────────┤ 

1770 │ eggs │ 451 │ 

1771 ╰───────────┴───────────╯ 

1772 

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

1774 characters: 

1775 

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

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

1778 ┏━━━━━━━━━━━┳━━━━━━━━━━━┓ 

1779 ┃ strings ┃ numbers ┃ 

1780 ┣━━━━━━━━━━━╋━━━━━━━━━━━┫ 

1781 ┃ spam ┃ 41.9999 ┃ 

1782 ┣━━━━━━━━━━━╋━━━━━━━━━━━┫ 

1783 ┃ eggs ┃ 451 ┃ 

1784 ┗━━━━━━━━━━━┻━━━━━━━━━━━┛ 

1785 

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

1787 box-drawing characters: 

1788 

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

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

1791 ┍━━━━━━━━━━━┯━━━━━━━━━━━┑ 

1792 │ strings │ numbers │ 

1793 ┝━━━━━━━━━━━┿━━━━━━━━━━━┥ 

1794 │ spam │ 41.9999 │ 

1795 ├───────────┼───────────┤ 

1796 │ eggs │ 451 │ 

1797 ┕━━━━━━━━━━━┷━━━━━━━━━━━┙ 

1798 

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

1800 characters: 

1801 

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

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

1804 ╔═══════════╦═══════════╗ 

1805 ║ strings ║ numbers ║ 

1806 ╠═══════════╬═══════════╣ 

1807 ║ spam ║ 41.9999 ║ 

1808 ╠═══════════╬═══════════╣ 

1809 ║ eggs ║ 451 ║ 

1810 ╚═══════════╩═══════════╝ 

1811 

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

1813 double-line box-drawing characters: 

1814 

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

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

1817 ╒═══════════╤═══════════╕ 

1818 │ strings │ numbers │ 

1819 ╞═══════════╪═══════════╡ 

1820 │ spam │ 41.9999 │ 

1821 ├───────────┼───────────┤ 

1822 │ eggs │ 451 │ 

1823 ╘═══════════╧═══════════╛ 

1824 

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

1826 

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

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

1829 +-----------+-----------+ 

1830 | strings | numbers | 

1831 +===========+===========+ 

1832 | spam | 41.9999 | 

1833 | eggs | 451 | 

1834 +-----------+-----------+ 

1835 

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

1837 +------+----------+ 

1838 | spam | 41.9999 | 

1839 | eggs | 451 | 

1840 +------+----------+ 

1841 

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

1843 

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

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

1846 ┌───────────┬───────────┐ 

1847 │ strings │ numbers │ 

1848 ├───────────┼───────────┤ 

1849 │ spam │ 41.9999 │ 

1850 │ eggs │ 451 │ 

1851 └───────────┴───────────┘ 

1852 

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

1854 

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

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

1857 ╭───────────┬───────────╮ 

1858 │ strings │ numbers │ 

1859 ├───────────┼───────────┤ 

1860 │ spam │ 41.9999 │ 

1861 │ eggs │ 451 │ 

1862 ╰───────────┴───────────╯ 

1863 

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

1865 

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

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

1868 ┏━━━━━━━━━━━┳━━━━━━━━━━━┓ 

1869 ┃ strings ┃ numbers ┃ 

1870 ┣━━━━━━━━━━━╋━━━━━━━━━━━┫ 

1871 ┃ spam ┃ 41.9999 ┃ 

1872 ┃ eggs ┃ 451 ┃ 

1873 ┗━━━━━━━━━━━┻━━━━━━━━━━━┛ 

1874 

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

1876 

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

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

1879 ┍━━━━━━━━━━━┯━━━━━━━━━━━┑ 

1880 │ strings │ numbers │ 

1881 ┝━━━━━━━━━━━┿━━━━━━━━━━━┥ 

1882 │ spam │ 41.9999 │ 

1883 │ eggs │ 451 │ 

1884 ┕━━━━━━━━━━━┷━━━━━━━━━━━┙ 

1885 

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

1887 

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

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

1890 ╔═══════════╦═══════════╗ 

1891 ║ strings ║ numbers ║ 

1892 ╠═══════════╬═══════════╣ 

1893 ║ spam ║ 41.9999 ║ 

1894 ║ eggs ║ 451 ║ 

1895 ╚═══════════╩═══════════╝ 

1896 

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

1898 

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

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

1901 ╒═══════════╤═══════════╕ 

1902 │ strings │ numbers │ 

1903 ╞═══════════╪═══════════╡ 

1904 │ spam │ 41.9999 │ 

1905 │ eggs │ 451 │ 

1906 ╘═══════════╧═══════════╛ 

1907 

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

1909 pipe_tables: 

1910 

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

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

1913 | strings | numbers | 

1914 |:----------|----------:| 

1915 | spam | 41.9999 | 

1916 | eggs | 451 | 

1917 

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

1919 

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

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

1922 strings | numbers 

1923 -----------+----------- 

1924 spam | 41.9999 

1925 eggs | 451 

1926 

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

1928 |:-----|---------:| 

1929 | spam | 41.9999 | 

1930 | eggs | 451 | 

1931 

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

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

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

1935 intersections: 

1936 

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

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

1939 | strings | numbers | 

1940 |-----------+-----------| 

1941 | spam | 41.9999 | 

1942 | eggs | 451 | 

1943 

1944 

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

1946 | spam | 41.9999 | 

1947 | eggs | 451 | 

1948 

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

1950 note that reStructuredText accepts also "grid" tables: 

1951 

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

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

1954 ========= ========= 

1955 strings numbers 

1956 ========= ========= 

1957 spam 41.9999 

1958 eggs 451 

1959 ========= ========= 

1960 

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

1962 ==== ======== 

1963 spam 41.9999 

1964 eggs 451 

1965 ==== ======== 

1966 

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

1968 MediaWiki-based sites: 

1969 

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

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

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

1973 |+ <!-- caption --> 

1974 |- 

1975 ! strings !! style="text-align: right;"| numbers 

1976 |- 

1977 | spam || style="text-align: right;"| 41.9999 

1978 |- 

1979 | eggs || style="text-align: right;"| 451 

1980 |} 

1981 

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

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

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

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

1986 

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

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

1989 <table> 

1990 <thead> 

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

1992 </thead> 

1993 <tbody> 

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

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

1996 </tbody> 

1997 </table> 

1998 

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

2000 

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

2002 \\begin{tabular}{lr} 

2003 \\hline 

2004 spam & 41.9999 \\\\ 

2005 eggs & 451 \\\\ 

2006 \\hline 

2007 \\end{tabular} 

2008 

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

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

2011 cells' values: 

2012 

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

2014 \\begin{tabular}{lr} 

2015 \\hline 

2016 spam$_9$ & 41.9999 \\\\ 

2017 \\emph{eggs} & 451 \\\\ 

2018 \\hline 

2019 \\end{tabular} 

2020 

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

2022 using the booktabs.sty package: 

2023 

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

2025 \\begin{tabular}{lr} 

2026 \\toprule 

2027 spam & 41.9999 \\\\ 

2028 eggs & 451 \\\\ 

2029 \\bottomrule 

2030 \\end{tabular} 

2031 

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

2033 multiple pages, using the longtable package for LaTeX. 

2034 

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

2036 \\begin{longtable}{lr} 

2037 \\hline 

2038 spam & 41.9999 \\\\ 

2039 eggs & 451 \\\\ 

2040 \\hline 

2041 \\end{longtable} 

2042 

2043 

2044 Number parsing 

2045 -------------- 

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

2047 This ensures numbers represented as strings are aligned properly. 

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

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

2050 429920 and aligned as such. 

2051 

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

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

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

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

2056 first and third columns. 

2057 

2058 Column Widths and Auto Line Wrapping 

2059 ------------------------------------ 

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

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

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

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

2064 list of maximal column widths 

2065 

2066 >>> print(tabulate( \ 

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

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

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

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

2071 tablefmt="grid" \ 

2072 )) 

2073 +------------+------------+-------------------------------+ 

2074 | Issue Id | Author | Description | 

2075 +============+============+===============================+ 

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

2077 | | | description that might look | 

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

2079 +------------+------------+-------------------------------+ 

2080 

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

2082 

2083 """ 

2084 

2085 if tabular_data is None: 

2086 tabular_data = [] 

2087 

2088 list_of_lists, headers, headers_pad = _normalize_tabular_data( 

2089 tabular_data, headers, showindex=showindex 

2090 ) 

2091 list_of_lists, separating_lines = _remove_separating_lines(list_of_lists) 

2092 

2093 if maxcolwidths is not None: 

2094 if len(list_of_lists): 

2095 num_cols = len(list_of_lists[0]) 

2096 else: 

2097 num_cols = 0 

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

2099 maxcolwidths = _expand_iterable(maxcolwidths, num_cols, maxcolwidths) 

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

2101 maxcolwidths = _expand_iterable(maxcolwidths, num_cols, None) 

2102 

2103 numparses = _expand_numparse(disable_numparse, num_cols) 

2104 list_of_lists = _wrap_text_to_colwidths( 

2105 list_of_lists, maxcolwidths, numparses=numparses 

2106 ) 

2107 

2108 if maxheadercolwidths is not None: 

2109 num_cols = len(list_of_lists[0]) 

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

2111 maxheadercolwidths = _expand_iterable( 

2112 maxheadercolwidths, num_cols, maxheadercolwidths 

2113 ) 

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

2115 maxheadercolwidths = _expand_iterable(maxheadercolwidths, num_cols, None) 

2116 

2117 numparses = _expand_numparse(disable_numparse, num_cols) 

2118 headers = _wrap_text_to_colwidths( 

2119 [headers], maxheadercolwidths, numparses=numparses 

2120 )[0] 

2121 

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

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

2124 if tablefmt == "rst": 

2125 list_of_lists, headers = _rst_escape_first_column(list_of_lists, headers) 

2126 

2127 # PrettyTable formatting does not use any extra padding. 

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

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

2130 # does not impact other formats. 

2131 min_padding = MIN_PADDING 

2132 if tablefmt == "pretty": 

2133 min_padding = 0 

2134 disable_numparse = True 

2135 numalign = "center" if numalign == _DEFAULT_ALIGN else numalign 

2136 stralign = "center" if stralign == _DEFAULT_ALIGN else stralign 

2137 else: 

2138 numalign = "decimal" if numalign == _DEFAULT_ALIGN else numalign 

2139 stralign = "left" if stralign == _DEFAULT_ALIGN else stralign 

2140 

2141 # optimization: look for ANSI control codes once, 

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

2143 # 

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

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

2146 plain_text = "\t".join( 

2147 chain( 

2148 # headers 

2149 map(_to_str, headers), 

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

2151 # the bytestring conversino to each cell value 

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

2153 ) 

2154 ) 

2155 

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

2157 

2158 enable_widechars = wcwidth is not None and WIDE_CHARS_MODE 

2159 if ( 

2160 not isinstance(tablefmt, TableFormat) 

2161 and tablefmt in multiline_formats 

2162 and _is_multiline(plain_text) 

2163 ): 

2164 tablefmt = multiline_formats.get(tablefmt, tablefmt) 

2165 is_multiline = True 

2166 else: 

2167 is_multiline = False 

2168 width_fn = _choose_width_fn(has_invisible, enable_widechars, is_multiline) 

2169 

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

2171 cols = list(izip_longest(*list_of_lists)) 

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

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

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

2175 float_formats = len(cols) * [ 

2176 floatfmt 

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

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

2179 float_formats = list(floatfmt) 

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

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

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

2183 int_formats = len(cols) * [ 

2184 intfmt 

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

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

2187 int_formats = list(intfmt) 

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

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

2190 if isinstance(missingval, str): 

2191 missing_vals = len(cols) * [missingval] 

2192 else: 

2193 missing_vals = list(missingval) 

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

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

2196 cols = [ 

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

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

2199 cols, coltypes, float_formats, int_formats, missing_vals 

2200 ) 

2201 ] 

2202 

2203 # align columns 

2204 # first set global alignment 

2205 if colglobalalign is not None: # if global alignment provided 

2206 aligns = [colglobalalign] * len(cols) 

2207 else: # default 

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

2209 # then specific alignements 

2210 if colalign is not None: 

2211 assert isinstance(colalign, Iterable) 

2212 if isinstance(colalign, str): 

2213 warnings.warn(f"As a string, `colalign` is interpreted as {[c for c in colalign]}. Did you mean `colglobalalign = \"{colalign}\"` or `colalign = (\"{colalign}\",)`?", stacklevel=2) 

2214 for idx, align in enumerate(colalign): 

2215 if not idx < len(aligns): 

2216 break 

2217 elif align != "global": 

2218 aligns[idx] = align 

2219 minwidths = ( 

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

2221 ) 

2222 cols = [ 

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

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

2225 ] 

2226 

2227 aligns_headers = None 

2228 if headers: 

2229 # align headers and add headers 

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

2231 # first set global alignment 

2232 if headersglobalalign is not None: # if global alignment provided 

2233 aligns_headers = [headersglobalalign] * len(t_cols) 

2234 else: # default 

2235 aligns_headers = aligns or [stralign] * len(headers) 

2236 # then specific header alignements 

2237 if headersalign is not None: 

2238 assert isinstance(headersalign, Iterable) 

2239 if isinstance(headersalign, str): 

2240 warnings.warn(f"As a string, `headersalign` is interpreted as {[c for c in headersalign]}. Did you mean `headersglobalalign = \"{headersalign}\"` or `headersalign = (\"{headersalign}\",)`?", stacklevel=2) 

2241 for idx, align in enumerate(headersalign): 

2242 hidx = headers_pad + idx 

2243 if not hidx < len(aligns_headers): 

2244 break 

2245 elif align == "same" and hidx < len(aligns): # same as column align 

2246 aligns_headers[hidx] = aligns[hidx] 

2247 elif align != "global": 

2248 aligns_headers[hidx] = align 

2249 minwidths = [ 

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

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

2252 ] 

2253 headers = [ 

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

2255 for h, a, minw in zip(headers, aligns_headers, minwidths) 

2256 ] 

2257 rows = list(zip(*cols)) 

2258 else: 

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

2260 rows = list(zip(*cols)) 

2261 

2262 if not isinstance(tablefmt, TableFormat): 

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

2264 

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

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

2267 _reinsert_separating_lines(rows, separating_lines) 

2268 

2269 return _format_table( 

2270 tablefmt, headers, aligns_headers, rows, minwidths, aligns, is_multiline, rowaligns=rowaligns 

2271 ) 

2272 

2273 

2274def _expand_numparse(disable_numparse, column_count): 

2275 """ 

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

2277 number parsing should be used on each column. 

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

2279 and everything else is True. 

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

2281 """ 

2282 if isinstance(disable_numparse, Iterable): 

2283 numparses = [True] * column_count 

2284 for index in disable_numparse: 

2285 numparses[index] = False 

2286 return numparses 

2287 else: 

2288 return [not disable_numparse] * column_count 

2289 

2290 

2291def _expand_iterable(original, num_desired, default): 

2292 """ 

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

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

2295 be padded with the value in `default`. 

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

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

2298 """ 

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

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

2301 else: 

2302 return [default] * num_desired 

2303 

2304 

2305def _pad_row(cells, padding): 

2306 if cells: 

2307 pad = " " * padding 

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

2309 return padded_cells 

2310 else: 

2311 return cells 

2312 

2313 

2314def _build_simple_row(padded_cells, rowfmt): 

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

2316 begin, sep, end = rowfmt 

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

2318 

2319 

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

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

2322 if not rowfmt: 

2323 return None 

2324 if hasattr(rowfmt, "__call__"): 

2325 return rowfmt(padded_cells, colwidths, colaligns) 

2326 else: 

2327 return _build_simple_row(padded_cells, rowfmt) 

2328 

2329 

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

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

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

2333 return lines 

2334 

2335 

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

2337 delta_lines = num_lines - len(text_lines) 

2338 blank = [" " * column_width] 

2339 if row_alignment == "bottom": 

2340 return blank * delta_lines + text_lines 

2341 elif row_alignment == "center": 

2342 top_delta = delta_lines // 2 

2343 bottom_delta = delta_lines - top_delta 

2344 return top_delta * blank + text_lines + bottom_delta * blank 

2345 else: 

2346 return text_lines + blank * delta_lines 

2347 

2348 

2349def _append_multiline_row( 

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

2351): 

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

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

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

2355 # vertically pad cells where some lines are missing 

2356 # cells_lines = [ 

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

2358 # ] 

2359 

2360 cells_lines = [ 

2361 _align_cell_veritically(cl, nlines, w, rowalign) 

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

2363 ] 

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

2365 for ln in lines_cells: 

2366 padded_ln = _pad_row(ln, pad) 

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

2368 return lines 

2369 

2370 

2371def _build_line(colwidths, colaligns, linefmt): 

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

2373 if not linefmt: 

2374 return None 

2375 if hasattr(linefmt, "__call__"): 

2376 return linefmt(colwidths, colaligns) 

2377 else: 

2378 begin, fill, sep, end = linefmt 

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

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

2381 

2382 

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

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

2385 return lines 

2386 

2387 

2388class JupyterHTMLStr(str): 

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

2390 displays the HTML table""" 

2391 

2392 def _repr_html_(self): 

2393 return self 

2394 

2395 @property 

2396 def str(self): 

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

2398 return self 

2399 

2400 

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

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

2403 lines = [] 

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

2405 pad = fmt.padding 

2406 headerrow = fmt.headerrow 

2407 

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

2409 if is_multiline: 

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

2411 append_row = partial(_append_multiline_row, pad=pad) 

2412 else: 

2413 pad_row = _pad_row 

2414 append_row = _append_basic_row 

2415 

2416 padded_headers = pad_row(headers, pad) 

2417 padded_rows = [pad_row(row, pad) for row in rows] 

2418 

2419 if fmt.lineabove and "lineabove" not in hidden: 

2420 _append_line(lines, padded_widths, colaligns, fmt.lineabove) 

2421 

2422 if padded_headers: 

2423 append_row(lines, padded_headers, padded_widths, headersaligns, headerrow) 

2424 if fmt.linebelowheader and "linebelowheader" not in hidden: 

2425 _append_line(lines, padded_widths, colaligns, fmt.linebelowheader) 

2426 

2427 if padded_rows and fmt.linebetweenrows and "linebetweenrows" not in hidden: 

2428 # initial rows with a line below 

2429 for row, ralign in zip(padded_rows[:-1], rowaligns): 

2430 append_row( 

2431 lines, row, padded_widths, colaligns, fmt.datarow, rowalign=ralign 

2432 ) 

2433 _append_line(lines, padded_widths, colaligns, fmt.linebetweenrows) 

2434 # the last row without a line below 

2435 append_row( 

2436 lines, 

2437 padded_rows[-1], 

2438 padded_widths, 

2439 colaligns, 

2440 fmt.datarow, 

2441 rowalign=rowaligns[-1], 

2442 ) 

2443 else: 

2444 separating_line = ( 

2445 fmt.linebetweenrows 

2446 or fmt.linebelowheader 

2447 or fmt.linebelow 

2448 or fmt.lineabove 

2449 or Line("", "", "", "") 

2450 ) 

2451 for row in padded_rows: 

2452 # test to see if either the 1st column or the 2nd column (account for showindex) has 

2453 # the SEPARATING_LINE flag 

2454 if _is_separating_line(row): 

2455 _append_line(lines, padded_widths, colaligns, separating_line) 

2456 else: 

2457 append_row(lines, row, padded_widths, colaligns, fmt.datarow) 

2458 

2459 if fmt.linebelow and "linebelow" not in hidden: 

2460 _append_line(lines, padded_widths, colaligns, fmt.linebelow) 

2461 

2462 if headers or rows: 

2463 output = "\n".join(lines) 

2464 if fmt.lineabove == _html_begin_table_without_header: 

2465 return JupyterHTMLStr(output) 

2466 else: 

2467 return output 

2468 else: # a completely empty table 

2469 return "" 

2470 

2471 

2472class _CustomTextWrap(textwrap.TextWrapper): 

2473 """A custom implementation of CPython's textwrap.TextWrapper. This supports 

2474 both wide characters (Korea, Japanese, Chinese) - including mixed string. 

2475 For the most part, the `_handle_long_word` and `_wrap_chunks` functions were 

2476 copy pasted out of the CPython baseline, and updated with our custom length 

2477 and line appending logic. 

2478 """ 

2479 

2480 def __init__(self, *args, **kwargs): 

2481 self._active_codes = [] 

2482 self.max_lines = None # For python2 compatibility 

2483 textwrap.TextWrapper.__init__(self, *args, **kwargs) 

2484 

2485 @staticmethod 

2486 def _len(item): 

2487 """Custom len that gets console column width for wide 

2488 and non-wide characters as well as ignores color codes""" 

2489 stripped = _strip_ansi(item) 

2490 if wcwidth: 

2491 return wcwidth.wcswidth(stripped) 

2492 else: 

2493 return len(stripped) 

2494 

2495 def _update_lines(self, lines, new_line): 

2496 """Adds a new line to the list of lines the text is being wrapped into 

2497 This function will also track any ANSI color codes in this string as well 

2498 as add any colors from previous lines order to preserve the same formatting 

2499 as a single unwrapped string. 

2500 """ 

2501 code_matches = [x for x in _ansi_codes.finditer(new_line)] 

2502 color_codes = [ 

2503 code.string[code.span()[0] : code.span()[1]] for code in code_matches 

2504 ] 

2505 

2506 # Add color codes from earlier in the unwrapped line, and then track any new ones we add. 

2507 new_line = "".join(self._active_codes) + new_line 

2508 

2509 for code in color_codes: 

2510 if code != _ansi_color_reset_code: 

2511 self._active_codes.append(code) 

2512 else: # A single reset code resets everything 

2513 self._active_codes = [] 

2514 

2515 # Always ensure each line is color terminted if any colors are 

2516 # still active, otherwise colors will bleed into other cells on the console 

2517 if len(self._active_codes) > 0: 

2518 new_line = new_line + _ansi_color_reset_code 

2519 

2520 lines.append(new_line) 

2521 

2522 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width): 

2523 """_handle_long_word(chunks : [string], 

2524 cur_line : [string], 

2525 cur_len : int, width : int) 

2526 Handle a chunk of text (most likely a word, not whitespace) that 

2527 is too long to fit in any line. 

2528 """ 

2529 # Figure out when indent is larger than the specified width, and make 

2530 # sure at least one character is stripped off on every pass 

2531 if width < 1: 

2532 space_left = 1 

2533 else: 

2534 space_left = width - cur_len 

2535 

2536 # If we're allowed to break long words, then do so: put as much 

2537 # of the next chunk onto the current line as will fit. 

2538 if self.break_long_words: 

2539 # Tabulate Custom: Build the string up piece-by-piece in order to 

2540 # take each charcter's width into account 

2541 chunk = reversed_chunks[-1] 

2542 i = 1 

2543 while self._len(chunk[:i]) <= space_left: 

2544 i = i + 1 

2545 cur_line.append(chunk[: i - 1]) 

2546 reversed_chunks[-1] = chunk[i - 1 :] 

2547 

2548 # Otherwise, we have to preserve the long word intact. Only add 

2549 # it to the current line if there's nothing already there -- 

2550 # that minimizes how much we violate the width constraint. 

2551 elif not cur_line: 

2552 cur_line.append(reversed_chunks.pop()) 

2553 

2554 # If we're not allowed to break long words, and there's already 

2555 # text on the current line, do nothing. Next time through the 

2556 # main loop of _wrap_chunks(), we'll wind up here again, but 

2557 # cur_len will be zero, so the next line will be entirely 

2558 # devoted to the long word that we can't handle right now. 

2559 

2560 def _wrap_chunks(self, chunks): 

2561 """_wrap_chunks(chunks : [string]) -> [string] 

2562 Wrap a sequence of text chunks and return a list of lines of 

2563 length 'self.width' or less. (If 'break_long_words' is false, 

2564 some lines may be longer than this.) Chunks correspond roughly 

2565 to words and the whitespace between them: each chunk is 

2566 indivisible (modulo 'break_long_words'), but a line break can 

2567 come between any two chunks. Chunks should not have internal 

2568 whitespace; ie. a chunk is either all whitespace or a "word". 

2569 Whitespace chunks will be removed from the beginning and end of 

2570 lines, but apart from that whitespace is preserved. 

2571 """ 

2572 lines = [] 

2573 if self.width <= 0: 

2574 raise ValueError("invalid width %r (must be > 0)" % self.width) 

2575 if self.max_lines is not None: 

2576 if self.max_lines > 1: 

2577 indent = self.subsequent_indent 

2578 else: 

2579 indent = self.initial_indent 

2580 if self._len(indent) + self._len(self.placeholder.lstrip()) > self.width: 

2581 raise ValueError("placeholder too large for max width") 

2582 

2583 # Arrange in reverse order so items can be efficiently popped 

2584 # from a stack of chucks. 

2585 chunks.reverse() 

2586 

2587 while chunks: 

2588 

2589 # Start the list of chunks that will make up the current line. 

2590 # cur_len is just the length of all the chunks in cur_line. 

2591 cur_line = [] 

2592 cur_len = 0 

2593 

2594 # Figure out which static string will prefix this line. 

2595 if lines: 

2596 indent = self.subsequent_indent 

2597 else: 

2598 indent = self.initial_indent 

2599 

2600 # Maximum width for this line. 

2601 width = self.width - self._len(indent) 

2602 

2603 # First chunk on line is whitespace -- drop it, unless this 

2604 # is the very beginning of the text (ie. no lines started yet). 

2605 if self.drop_whitespace and chunks[-1].strip() == "" and lines: 

2606 del chunks[-1] 

2607 

2608 while chunks: 

2609 chunk_len = self._len(chunks[-1]) 

2610 

2611 # Can at least squeeze this chunk onto the current line. 

2612 if cur_len + chunk_len <= width: 

2613 cur_line.append(chunks.pop()) 

2614 cur_len += chunk_len 

2615 

2616 # Nope, this line is full. 

2617 else: 

2618 break 

2619 

2620 # The current line is full, and the next chunk is too big to 

2621 # fit on *any* line (not just this one). 

2622 if chunks and self._len(chunks[-1]) > width: 

2623 self._handle_long_word(chunks, cur_line, cur_len, width) 

2624 cur_len = sum(map(self._len, cur_line)) 

2625 

2626 # If the last chunk on this line is all whitespace, drop it. 

2627 if self.drop_whitespace and cur_line and cur_line[-1].strip() == "": 

2628 cur_len -= self._len(cur_line[-1]) 

2629 del cur_line[-1] 

2630 

2631 if cur_line: 

2632 if ( 

2633 self.max_lines is None 

2634 or len(lines) + 1 < self.max_lines 

2635 or ( 

2636 not chunks 

2637 or self.drop_whitespace 

2638 and len(chunks) == 1 

2639 and not chunks[0].strip() 

2640 ) 

2641 and cur_len <= width 

2642 ): 

2643 # Convert current line back to a string and store it in 

2644 # list of all lines (return value). 

2645 self._update_lines(lines, indent + "".join(cur_line)) 

2646 else: 

2647 while cur_line: 

2648 if ( 

2649 cur_line[-1].strip() 

2650 and cur_len + self._len(self.placeholder) <= width 

2651 ): 

2652 cur_line.append(self.placeholder) 

2653 self._update_lines(lines, indent + "".join(cur_line)) 

2654 break 

2655 cur_len -= self._len(cur_line[-1]) 

2656 del cur_line[-1] 

2657 else: 

2658 if lines: 

2659 prev_line = lines[-1].rstrip() 

2660 if ( 

2661 self._len(prev_line) + self._len(self.placeholder) 

2662 <= self.width 

2663 ): 

2664 lines[-1] = prev_line + self.placeholder 

2665 break 

2666 self._update_lines(lines, indent + self.placeholder.lstrip()) 

2667 break 

2668 

2669 return lines 

2670 

2671 

2672def _main(): 

2673 """\ 

2674 Usage: tabulate [options] [FILE ...] 

2675 

2676 Pretty-print tabular data. 

2677 See also https://github.com/astanin/python-tabulate 

2678 

2679 FILE a filename of the file with tabular data; 

2680 if "-" or missing, read data from stdin. 

2681 

2682 Options: 

2683 

2684 -h, --help show this message 

2685 -1, --header use the first row of data as a table header 

2686 -o FILE, --output FILE print table to FILE (default: stdout) 

2687 -s REGEXP, --sep REGEXP use a custom column separator (default: whitespace) 

2688 -F FPFMT, --float FPFMT floating point number format (default: g) 

2689 -I INTFMT, --int INTFMT integer point number format (default: "") 

2690 -f FMT, --format FMT set output table format; supported formats: 

2691 plain, simple, grid, fancy_grid, pipe, orgtbl, 

2692 rst, mediawiki, html, latex, latex_raw, 

2693 latex_booktabs, latex_longtable, tsv 

2694 (default: simple) 

2695 """ 

2696 import getopt 

2697 import sys 

2698 import textwrap 

2699 

2700 usage = textwrap.dedent(_main.__doc__) 

2701 try: 

2702 opts, args = getopt.getopt( 

2703 sys.argv[1:], 

2704 "h1o:s:F:A:f:", 

2705 ["help", "header", "output", "sep=", "float=", "int=", "align=", "format="], 

2706 ) 

2707 except getopt.GetoptError as e: 

2708 print(e) 

2709 print(usage) 

2710 sys.exit(2) 

2711 headers = [] 

2712 floatfmt = _DEFAULT_FLOATFMT 

2713 intfmt = _DEFAULT_INTFMT 

2714 colalign = None 

2715 tablefmt = "simple" 

2716 sep = r"\s+" 

2717 outfile = "-" 

2718 for opt, value in opts: 

2719 if opt in ["-1", "--header"]: 

2720 headers = "firstrow" 

2721 elif opt in ["-o", "--output"]: 

2722 outfile = value 

2723 elif opt in ["-F", "--float"]: 

2724 floatfmt = value 

2725 elif opt in ["-I", "--int"]: 

2726 intfmt = value 

2727 elif opt in ["-C", "--colalign"]: 

2728 colalign = value.split() 

2729 elif opt in ["-f", "--format"]: 

2730 if value not in tabulate_formats: 

2731 print("%s is not a supported table format" % value) 

2732 print(usage) 

2733 sys.exit(3) 

2734 tablefmt = value 

2735 elif opt in ["-s", "--sep"]: 

2736 sep = value 

2737 elif opt in ["-h", "--help"]: 

2738 print(usage) 

2739 sys.exit(0) 

2740 files = [sys.stdin] if not args else args 

2741 with (sys.stdout if outfile == "-" else open(outfile, "w")) as out: 

2742 for f in files: 

2743 if f == "-": 

2744 f = sys.stdin 

2745 if _is_file(f): 

2746 _pprint_file( 

2747 f, 

2748 headers=headers, 

2749 tablefmt=tablefmt, 

2750 sep=sep, 

2751 floatfmt=floatfmt, 

2752 intfmt=intfmt, 

2753 file=out, 

2754 colalign=colalign, 

2755 ) 

2756 else: 

2757 with open(f) as fobj: 

2758 _pprint_file( 

2759 fobj, 

2760 headers=headers, 

2761 tablefmt=tablefmt, 

2762 sep=sep, 

2763 floatfmt=floatfmt, 

2764 intfmt=intfmt, 

2765 file=out, 

2766 colalign=colalign, 

2767 ) 

2768 

2769 

2770def _pprint_file(fobject, headers, tablefmt, sep, floatfmt, intfmt, file, colalign): 

2771 rows = fobject.readlines() 

2772 table = [re.split(sep, r.rstrip()) for r in rows if r.strip()] 

2773 print( 

2774 tabulate( 

2775 table, 

2776 headers, 

2777 tablefmt, 

2778 floatfmt=floatfmt, 

2779 intfmt=intfmt, 

2780 colalign=colalign, 

2781 ), 

2782 file=file, 

2783 ) 

2784 

2785 

2786if __name__ == "__main__": 

2787 _main()