tabulate.py 93 KB

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