prettytable.py 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # Copyright (c) 2009-2014, Luke Maurits <luke@maurits.id.au>
  5. # All rights reserved.
  6. # With contributions from:
  7. # * Chris Clark
  8. # * Klein Stephane
  9. # * John Filleau
  10. # * Vladimir Vrzić
  11. #
  12. # Redistribution and use in source and binary forms, with or without
  13. # modification, are permitted provided that the following conditions are met:
  14. #
  15. # * Redistributions of source code must retain the above copyright notice,
  16. # this list of conditions and the following disclaimer.
  17. # * Redistributions in binary form must reproduce the above copyright notice,
  18. # this list of conditions and the following disclaimer in the documentation
  19. # and/or other materials provided with the distribution.
  20. # * The name of the author may not be used to endorse or promote products
  21. # derived from this software without specific prior written permission.
  22. #
  23. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  24. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  25. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  26. # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  27. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  28. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  29. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  30. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  31. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  32. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  33. # POSSIBILITY OF SUCH DAMAGE.
  34. import copy
  35. import csv
  36. import itertools
  37. import json
  38. import math
  39. import random
  40. import re
  41. import sys
  42. import textwrap
  43. import pkg_resources
  44. import wcwidth
  45. __version__ = pkg_resources.get_distribution(__name__).version
  46. py3k = sys.version_info[0] >= 3
  47. if py3k:
  48. unicode = str
  49. basestring = str
  50. itermap = map
  51. iterzip = zip
  52. uni_chr = chr
  53. from html import escape
  54. from html.parser import HTMLParser
  55. from io import StringIO
  56. else:
  57. itermap = itertools.imap
  58. iterzip = itertools.izip
  59. uni_chr = unichr # noqa: F821
  60. from cgi import escape
  61. from HTMLParser import HTMLParser
  62. from StringIO import StringIO
  63. # hrule styles
  64. FRAME = 0
  65. ALL = 1
  66. NONE = 2
  67. HEADER = 3
  68. # Table styles
  69. DEFAULT = 10
  70. MSWORD_FRIENDLY = 11
  71. PLAIN_COLUMNS = 12
  72. MARKDOWN = 13
  73. ORGMODE = 14
  74. RANDOM = 20
  75. _re = re.compile(r"\033\[[0-9;]*m")
  76. def _get_size(text):
  77. lines = text.split("\n")
  78. height = len(lines)
  79. width = max(_str_block_width(line) for line in lines)
  80. return width, height
  81. class PrettyTable(object):
  82. def __init__(self, field_names=None, **kwargs):
  83. """Return a new PrettyTable instance
  84. Arguments:
  85. encoding - Unicode encoding scheme used to decode any encoded input
  86. title - optional table title
  87. field_names - list or tuple of field names
  88. fields - list or tuple of field names to include in displays
  89. start - index of first data row to include in output
  90. end - index of last data row to include in output PLUS ONE (list slice style)
  91. header - print a header showing field names (True or False)
  92. header_style - stylisation to apply to field names in header
  93. ("cap", "title", "upper", "lower" or None)
  94. border - print a border around the table (True or False)
  95. hrules - controls printing of horizontal rules after rows.
  96. Allowed values: FRAME, HEADER, ALL, NONE
  97. vrules - controls printing of vertical rules between columns.
  98. Allowed values: FRAME, ALL, NONE
  99. int_format - controls formatting of integer data
  100. float_format - controls formatting of floating point data
  101. min_table_width - minimum desired table width, in characters
  102. max_table_width - maximum desired table width, in characters
  103. padding_width - number of spaces on either side of column data
  104. (only used if left and right paddings are None)
  105. left_padding_width - number of spaces on left hand side of column data
  106. right_padding_width - number of spaces on right hand side of column data
  107. vertical_char - single character string used to draw vertical lines
  108. horizontal_char - single character string used to draw horizontal lines
  109. junction_char - single character string used to draw line junctions
  110. sortby - name of field to sort rows by
  111. sort_key - sorting key function, applied to data points before sorting
  112. valign - default valign for each row (None, "t", "m" or "b")
  113. reversesort - True or False to sort in descending or ascending order
  114. oldsortslice - Slice rows before sorting in the "old style" """
  115. self.encoding = kwargs.get("encoding", "UTF-8")
  116. # Data
  117. self._field_names = []
  118. self._rows = []
  119. self.align = {}
  120. self.valign = {}
  121. self.max_width = {}
  122. self.min_width = {}
  123. self.int_format = {}
  124. self.float_format = {}
  125. if field_names:
  126. self.field_names = field_names
  127. else:
  128. self._widths = []
  129. # Options
  130. self._options = (
  131. "title start end fields header border sortby reversesort "
  132. "sort_key attributes format hrules vrules".split()
  133. )
  134. self._options.extend(
  135. "int_format float_format min_table_width max_table_width padding_width "
  136. "left_padding_width right_padding_width".split()
  137. )
  138. self._options.extend(
  139. "vertical_char horizontal_char junction_char header_style valign xhtml "
  140. "print_empty oldsortslice".split()
  141. )
  142. self._options.extend("align valign max_width min_width".split())
  143. for option in self._options:
  144. if option in kwargs:
  145. self._validate_option(option, kwargs[option])
  146. else:
  147. kwargs[option] = None
  148. self._title = kwargs["title"] or None
  149. self._start = kwargs["start"] or 0
  150. self._end = kwargs["end"] or None
  151. self._fields = kwargs["fields"] or None
  152. if kwargs["header"] in (True, False):
  153. self._header = kwargs["header"]
  154. else:
  155. self._header = True
  156. self._header_style = kwargs["header_style"] or None
  157. if kwargs["border"] in (True, False):
  158. self._border = kwargs["border"]
  159. else:
  160. self._border = True
  161. self._hrules = kwargs["hrules"] or FRAME
  162. self._vrules = kwargs["vrules"] or ALL
  163. self._sortby = kwargs["sortby"] or None
  164. if kwargs["reversesort"] in (True, False):
  165. self._reversesort = kwargs["reversesort"]
  166. else:
  167. self._reversesort = False
  168. self._sort_key = kwargs["sort_key"] or (lambda x: x)
  169. # Column specific arguments, use property.setters
  170. self.align = kwargs["align"] or {}
  171. self.valign = kwargs["valign"] or {}
  172. self.max_width = kwargs["max_width"] or {}
  173. self.min_width = kwargs["min_width"] or {}
  174. self.int_format = kwargs["int_format"] or {}
  175. self.float_format = kwargs["float_format"] or {}
  176. self._min_table_width = kwargs["min_table_width"] or None
  177. self._max_table_width = kwargs["max_table_width"] or None
  178. self._padding_width = kwargs["padding_width"] or 1
  179. self._left_padding_width = kwargs["left_padding_width"] or None
  180. self._right_padding_width = kwargs["right_padding_width"] or None
  181. self._vertical_char = kwargs["vertical_char"] or self._unicode("|")
  182. self._horizontal_char = kwargs["horizontal_char"] or self._unicode("-")
  183. self._junction_char = kwargs["junction_char"] or self._unicode("+")
  184. if kwargs["print_empty"] in (True, False):
  185. self._print_empty = kwargs["print_empty"]
  186. else:
  187. self._print_empty = True
  188. if kwargs["oldsortslice"] in (True, False):
  189. self._oldsortslice = kwargs["oldsortslice"]
  190. else:
  191. self._oldsortslice = False
  192. self._format = kwargs["format"] or False
  193. self._xhtml = kwargs["xhtml"] or False
  194. self._attributes = kwargs["attributes"] or {}
  195. def _unicode(self, value):
  196. if not isinstance(value, basestring):
  197. value = str(value)
  198. if not isinstance(value, unicode):
  199. value = unicode(value, self.encoding, "strict")
  200. return value
  201. def _justify(self, text, width, align):
  202. excess = width - _str_block_width(text)
  203. if align == "l":
  204. return text + excess * " "
  205. elif align == "r":
  206. return excess * " " + text
  207. else:
  208. if excess % 2:
  209. # Uneven padding
  210. # Put more space on right if text is of odd length...
  211. if _str_block_width(text) % 2:
  212. return (excess // 2) * " " + text + (excess // 2 + 1) * " "
  213. # and more space on left if text is of even length
  214. else:
  215. return (excess // 2 + 1) * " " + text + (excess // 2) * " "
  216. # Why distribute extra space this way? To match the behaviour of
  217. # the inbuilt str.center() method.
  218. else:
  219. # Equal padding on either side
  220. return (excess // 2) * " " + text + (excess // 2) * " "
  221. def __getattr__(self, name):
  222. if name == "rowcount":
  223. return len(self._rows)
  224. elif name == "colcount":
  225. if self._field_names:
  226. return len(self._field_names)
  227. elif self._rows:
  228. return len(self._rows[0])
  229. else:
  230. return 0
  231. else:
  232. raise AttributeError(name)
  233. def __getitem__(self, index):
  234. new = PrettyTable()
  235. new.field_names = self.field_names
  236. for attr in self._options:
  237. setattr(new, "_" + attr, getattr(self, "_" + attr))
  238. setattr(new, "_align", getattr(self, "_align"))
  239. if isinstance(index, slice):
  240. for row in self._rows[index]:
  241. new.add_row(row)
  242. elif isinstance(index, int):
  243. new.add_row(self._rows[index])
  244. else:
  245. raise Exception(
  246. "Index %s is invalid, must be an integer or slice" % str(index)
  247. )
  248. return new
  249. if py3k:
  250. def __str__(self):
  251. return self.__unicode__()
  252. else:
  253. def __str__(self):
  254. return self.__unicode__().encode(self.encoding)
  255. def __unicode__(self):
  256. return self.get_string()
  257. ##############################
  258. # ATTRIBUTE VALIDATORS #
  259. ##############################
  260. # The method _validate_option is all that should be used elsewhere in the code base
  261. # to validate options. It will call the appropriate validation method for that
  262. # option. The individual validation methods should never need to be called directly
  263. # (although nothing bad will happen if they *are*).
  264. # Validation happens in TWO places.
  265. # Firstly, in the property setters defined in the ATTRIBUTE MANAGEMENT section.
  266. # Secondly, in the _get_options method, where keyword arguments are mixed with
  267. # persistent settings
  268. def _validate_option(self, option, val):
  269. if option == "field_names":
  270. self._validate_field_names(val)
  271. elif option in (
  272. "start",
  273. "end",
  274. "max_width",
  275. "min_width",
  276. "min_table_width",
  277. "max_table_width",
  278. "padding_width",
  279. "left_padding_width",
  280. "right_padding_width",
  281. "format",
  282. ):
  283. self._validate_nonnegative_int(option, val)
  284. elif option == "sortby":
  285. self._validate_field_name(option, val)
  286. elif option == "sort_key":
  287. self._validate_function(option, val)
  288. elif option == "hrules":
  289. self._validate_hrules(option, val)
  290. elif option == "vrules":
  291. self._validate_vrules(option, val)
  292. elif option == "fields":
  293. self._validate_all_field_names(option, val)
  294. elif option in (
  295. "header",
  296. "border",
  297. "reversesort",
  298. "xhtml",
  299. "print_empty",
  300. "oldsortslice",
  301. ):
  302. self._validate_true_or_false(option, val)
  303. elif option == "header_style":
  304. self._validate_header_style(val)
  305. elif option == "int_format":
  306. self._validate_int_format(option, val)
  307. elif option == "float_format":
  308. self._validate_float_format(option, val)
  309. elif option in ("vertical_char", "horizontal_char", "junction_char"):
  310. self._validate_single_char(option, val)
  311. elif option == "attributes":
  312. self._validate_attributes(option, val)
  313. def _validate_field_names(self, val):
  314. # Check for appropriate length
  315. if self._field_names:
  316. try:
  317. assert len(val) == len(self._field_names)
  318. except AssertionError:
  319. raise Exception(
  320. "Field name list has incorrect number of values, "
  321. "(actual) %d!=%d (expected)" % (len(val), len(self._field_names))
  322. )
  323. if self._rows:
  324. try:
  325. assert len(val) == len(self._rows[0])
  326. except AssertionError:
  327. raise Exception(
  328. "Field name list has incorrect number of values, "
  329. "(actual) %d!=%d (expected)" % (len(val), len(self._rows[0]))
  330. )
  331. # Check for uniqueness
  332. try:
  333. assert len(val) == len(set(val))
  334. except AssertionError:
  335. raise Exception("Field names must be unique!")
  336. def _validate_header_style(self, val):
  337. try:
  338. assert val in ("cap", "title", "upper", "lower", None)
  339. except AssertionError:
  340. raise Exception(
  341. "Invalid header style, use cap, title, upper, lower or None!"
  342. )
  343. def _validate_align(self, val):
  344. try:
  345. assert val in ["l", "c", "r"]
  346. except AssertionError:
  347. raise Exception("Alignment %s is invalid, use l, c or r!" % val)
  348. def _validate_valign(self, val):
  349. try:
  350. assert val in ["t", "m", "b", None]
  351. except AssertionError:
  352. raise Exception("Alignment %s is invalid, use t, m, b or None!" % val)
  353. def _validate_nonnegative_int(self, name, val):
  354. try:
  355. assert int(val) >= 0
  356. except AssertionError:
  357. raise Exception(
  358. "Invalid value for {}: {}!".format(name, self._unicode(val))
  359. )
  360. def _validate_true_or_false(self, name, val):
  361. try:
  362. assert val in (True, False)
  363. except AssertionError:
  364. raise Exception("Invalid value for %s! Must be True or False." % name)
  365. def _validate_int_format(self, name, val):
  366. if val == "":
  367. return
  368. try:
  369. assert type(val) in (str, unicode)
  370. assert val.isdigit()
  371. except AssertionError:
  372. raise Exception(
  373. "Invalid value for %s! Must be an integer format string." % name
  374. )
  375. def _validate_float_format(self, name, val):
  376. if val == "":
  377. return
  378. try:
  379. assert type(val) in (str, unicode)
  380. assert "." in val
  381. bits = val.split(".")
  382. assert len(bits) <= 2
  383. assert bits[0] == "" or bits[0].isdigit()
  384. assert (
  385. bits[1] == ""
  386. or bits[1].isdigit()
  387. or (bits[1][-1] == "f" and bits[1].rstrip("f").isdigit())
  388. )
  389. except AssertionError:
  390. raise Exception(
  391. "Invalid value for %s! Must be a float format string." % name
  392. )
  393. def _validate_function(self, name, val):
  394. try:
  395. assert hasattr(val, "__call__")
  396. except AssertionError:
  397. raise Exception("Invalid value for %s! Must be a function." % name)
  398. def _validate_hrules(self, name, val):
  399. try:
  400. assert val in (ALL, FRAME, HEADER, NONE)
  401. except AssertionError:
  402. raise Exception(
  403. "Invalid value for %s! Must be ALL, FRAME, HEADER or NONE." % name
  404. )
  405. def _validate_vrules(self, name, val):
  406. try:
  407. assert val in (ALL, FRAME, NONE)
  408. except AssertionError:
  409. raise Exception(
  410. "Invalid value for %s! Must be ALL, FRAME, or NONE." % name
  411. )
  412. def _validate_field_name(self, name, val):
  413. try:
  414. assert (val in self._field_names) or (val is None)
  415. except AssertionError:
  416. raise Exception("Invalid field name: %s!" % val)
  417. def _validate_all_field_names(self, name, val):
  418. try:
  419. for x in val:
  420. self._validate_field_name(name, x)
  421. except AssertionError:
  422. raise Exception("fields must be a sequence of field names!")
  423. def _validate_single_char(self, name, val):
  424. try:
  425. assert _str_block_width(val) == 1
  426. except AssertionError:
  427. raise Exception(
  428. "Invalid value for %s! Must be a string of length 1." % name
  429. )
  430. def _validate_attributes(self, name, val):
  431. try:
  432. assert isinstance(val, dict)
  433. except AssertionError:
  434. raise Exception("attributes must be a dictionary of name/value pairs!")
  435. ##############################
  436. # ATTRIBUTE MANAGEMENT #
  437. ##############################
  438. @property
  439. def field_names(self):
  440. """List or tuple of field names
  441. When setting field_names, if there are already field names the new list
  442. of field names must be the same length. Columns are renamed and row data
  443. remains unchanged."""
  444. return self._field_names
  445. @field_names.setter
  446. def field_names(self, val):
  447. val = [self._unicode(x) for x in val]
  448. self._validate_option("field_names", val)
  449. old_names = None
  450. if self._field_names:
  451. old_names = self._field_names[:]
  452. self._field_names = val
  453. if self._align and old_names:
  454. for old_name, new_name in zip(old_names, val):
  455. self._align[new_name] = self._align[old_name]
  456. for old_name in old_names:
  457. if old_name not in self._align:
  458. self._align.pop(old_name)
  459. else:
  460. self.align = "c"
  461. if self._valign and old_names:
  462. for old_name, new_name in zip(old_names, val):
  463. self._valign[new_name] = self._valign[old_name]
  464. for old_name in old_names:
  465. if old_name not in self._valign:
  466. self._valign.pop(old_name)
  467. else:
  468. self.valign = "t"
  469. @property
  470. def align(self):
  471. """Controls alignment of fields
  472. Arguments:
  473. align - alignment, one of "l", "c", or "r" """
  474. return self._align
  475. @align.setter
  476. def align(self, val):
  477. if not self._field_names:
  478. self._align = {}
  479. elif val is None or (isinstance(val, dict) and len(val) == 0):
  480. for field in self._field_names:
  481. self._align[field] = "c"
  482. else:
  483. self._validate_align(val)
  484. for field in self._field_names:
  485. self._align[field] = val
  486. @property
  487. def valign(self):
  488. """Controls vertical alignment of fields
  489. Arguments:
  490. valign - vertical alignment, one of "t", "m", or "b" """
  491. return self._valign
  492. @valign.setter
  493. def valign(self, val):
  494. if not self._field_names:
  495. self._valign = {}
  496. elif val is None or (isinstance(val, dict) and len(val) == 0):
  497. for field in self._field_names:
  498. self._valign[field] = "t"
  499. else:
  500. self._validate_valign(val)
  501. for field in self._field_names:
  502. self._valign[field] = val
  503. @property
  504. def max_width(self):
  505. """Controls maximum width of fields
  506. Arguments:
  507. max_width - maximum width integer"""
  508. return self._max_width
  509. @max_width.setter
  510. def max_width(self, val):
  511. if val is None or (isinstance(val, dict) and len(val) == 0):
  512. self._max_width = {}
  513. else:
  514. self._validate_option("max_width", val)
  515. for field in self._field_names:
  516. self._max_width[field] = val
  517. @property
  518. def min_width(self):
  519. """Controls minimum width of fields
  520. Arguments:
  521. min_width - minimum width integer"""
  522. return self._min_width
  523. @min_width.setter
  524. def min_width(self, val):
  525. if val is None or (isinstance(val, dict) and len(val) == 0):
  526. self._min_width = {}
  527. else:
  528. self._validate_option("min_width", val)
  529. for field in self._field_names:
  530. self._min_width[field] = val
  531. @property
  532. def min_table_width(self):
  533. return self._min_table_width
  534. @min_table_width.setter
  535. def min_table_width(self, val):
  536. self._validate_option("min_table_width", val)
  537. self._min_table_width = val
  538. @property
  539. def max_table_width(self):
  540. return self._max_table_width
  541. @max_table_width.setter
  542. def max_table_width(self, val):
  543. self._validate_option("max_table_width", val)
  544. self._max_table_width = val
  545. @property
  546. def fields(self):
  547. """List or tuple of field names to include in displays"""
  548. return self._fields
  549. @fields.setter
  550. def fields(self, val):
  551. self._validate_option("fields", val)
  552. self._fields = val
  553. @property
  554. def title(self):
  555. """Optional table title
  556. Arguments:
  557. title - table title"""
  558. return self._title
  559. @title.setter
  560. def title(self, val):
  561. self._title = self._unicode(val)
  562. @property
  563. def start(self):
  564. """Start index of the range of rows to print
  565. Arguments:
  566. start - index of first data row to include in output"""
  567. return self._start
  568. @start.setter
  569. def start(self, val):
  570. self._validate_option("start", val)
  571. self._start = val
  572. @property
  573. def end(self):
  574. """End index of the range of rows to print
  575. Arguments:
  576. end - index of last data row to include in output PLUS ONE (list slice style)"""
  577. return self._end
  578. @end.setter
  579. def end(self, val):
  580. self._validate_option("end", val)
  581. self._end = val
  582. @property
  583. def sortby(self):
  584. """Name of field by which to sort rows
  585. Arguments:
  586. sortby - field name to sort by"""
  587. return self._sortby
  588. @sortby.setter
  589. def sortby(self, val):
  590. self._validate_option("sortby", val)
  591. self._sortby = val
  592. @property
  593. def reversesort(self):
  594. """Controls direction of sorting (ascending vs descending)
  595. Arguments:
  596. reveresort - set to True to sort by descending order, or False to sort by
  597. ascending order"""
  598. return self._reversesort
  599. @reversesort.setter
  600. def reversesort(self, val):
  601. self._validate_option("reversesort", val)
  602. self._reversesort = val
  603. @property
  604. def sort_key(self):
  605. """Sorting key function, applied to data points before sorting
  606. Arguments:
  607. sort_key - a function which takes one argument and returns something to be
  608. sorted"""
  609. return self._sort_key
  610. @sort_key.setter
  611. def sort_key(self, val):
  612. self._validate_option("sort_key", val)
  613. self._sort_key = val
  614. @property
  615. def header(self):
  616. """Controls printing of table header with field names
  617. Arguments:
  618. header - print a header showing field names (True or False)"""
  619. return self._header
  620. @header.setter
  621. def header(self, val):
  622. self._validate_option("header", val)
  623. self._header = val
  624. @property
  625. def header_style(self):
  626. """Controls stylisation applied to field names in header
  627. Arguments:
  628. header_style - stylisation to apply to field names in header
  629. ("cap", "title", "upper", "lower" or None)"""
  630. return self._header_style
  631. @header_style.setter
  632. def header_style(self, val):
  633. self._validate_header_style(val)
  634. self._header_style = val
  635. @property
  636. def border(self):
  637. """Controls printing of border around table
  638. Arguments:
  639. border - print a border around the table (True or False)"""
  640. return self._border
  641. @border.setter
  642. def border(self, val):
  643. self._validate_option("border", val)
  644. self._border = val
  645. @property
  646. def hrules(self):
  647. """Controls printing of horizontal rules after rows
  648. Arguments:
  649. hrules - horizontal rules style. Allowed values: FRAME, ALL, HEADER, NONE"""
  650. return self._hrules
  651. @hrules.setter
  652. def hrules(self, val):
  653. self._validate_option("hrules", val)
  654. self._hrules = val
  655. @property
  656. def vrules(self):
  657. """Controls printing of vertical rules between columns
  658. Arguments:
  659. vrules - vertical rules style. Allowed values: FRAME, ALL, NONE"""
  660. return self._vrules
  661. @vrules.setter
  662. def vrules(self, val):
  663. self._validate_option("vrules", val)
  664. self._vrules = val
  665. @property
  666. def int_format(self):
  667. """Controls formatting of integer data
  668. Arguments:
  669. int_format - integer format string"""
  670. return self._int_format
  671. @int_format.setter
  672. def int_format(self, val):
  673. if val is None or (isinstance(val, dict) and len(val) == 0):
  674. self._int_format = {}
  675. else:
  676. self._validate_option("int_format", val)
  677. for field in self._field_names:
  678. self._int_format[field] = val
  679. @property
  680. def float_format(self):
  681. """Controls formatting of floating point data
  682. Arguments:
  683. float_format - floating point format string"""
  684. return self._float_format
  685. @float_format.setter
  686. def float_format(self, val):
  687. if val is None or (isinstance(val, dict) and len(val) == 0):
  688. self._float_format = {}
  689. else:
  690. self._validate_option("float_format", val)
  691. for field in self._field_names:
  692. self._float_format[field] = val
  693. @property
  694. def padding_width(self):
  695. """The number of empty spaces between a column's edge and its content
  696. Arguments:
  697. padding_width - number of spaces, must be a positive integer"""
  698. return self._padding_width
  699. @padding_width.setter
  700. def padding_width(self, val):
  701. self._validate_option("padding_width", val)
  702. self._padding_width = val
  703. @property
  704. def left_padding_width(self):
  705. """The number of empty spaces between a column's left edge and its content
  706. Arguments:
  707. left_padding - number of spaces, must be a positive integer"""
  708. return self._left_padding_width
  709. @left_padding_width.setter
  710. def left_padding_width(self, val):
  711. self._validate_option("left_padding_width", val)
  712. self._left_padding_width = val
  713. @property
  714. def right_padding_width(self):
  715. """The number of empty spaces between a column's right edge and its content
  716. Arguments:
  717. right_padding - number of spaces, must be a positive integer"""
  718. return self._right_padding_width
  719. @right_padding_width.setter
  720. def right_padding_width(self, val):
  721. self._validate_option("right_padding_width", val)
  722. self._right_padding_width = val
  723. @property
  724. def vertical_char(self):
  725. """The character used when printing table borders to draw vertical lines
  726. Arguments:
  727. vertical_char - single character string used to draw vertical lines"""
  728. return self._vertical_char
  729. @vertical_char.setter
  730. def vertical_char(self, val):
  731. val = self._unicode(val)
  732. self._validate_option("vertical_char", val)
  733. self._vertical_char = val
  734. @property
  735. def horizontal_char(self):
  736. """The character used when printing table borders to draw horizontal lines
  737. Arguments:
  738. horizontal_char - single character string used to draw horizontal lines"""
  739. return self._horizontal_char
  740. @horizontal_char.setter
  741. def horizontal_char(self, val):
  742. val = self._unicode(val)
  743. self._validate_option("horizontal_char", val)
  744. self._horizontal_char = val
  745. @property
  746. def junction_char(self):
  747. """The character used when printing table borders to draw line junctions
  748. Arguments:
  749. junction_char - single character string used to draw line junctions"""
  750. return self._junction_char
  751. @junction_char.setter
  752. def junction_char(self, val):
  753. val = self._unicode(val)
  754. self._validate_option("vertical_char", val)
  755. self._junction_char = val
  756. @property
  757. def format(self):
  758. """Controls whether or not HTML tables are formatted to match styling options
  759. Arguments:
  760. format - True or False"""
  761. return self._format
  762. @format.setter
  763. def format(self, val):
  764. self._validate_option("format", val)
  765. self._format = val
  766. @property
  767. def print_empty(self):
  768. """Controls whether or not empty tables produce a header and frame or just an
  769. empty string
  770. Arguments:
  771. print_empty - True or False"""
  772. return self._print_empty
  773. @print_empty.setter
  774. def print_empty(self, val):
  775. self._validate_option("print_empty", val)
  776. self._print_empty = val
  777. @property
  778. def attributes(self):
  779. """A dictionary of HTML attribute name/value pairs to be included in the
  780. <table> tag when printing HTML
  781. Arguments:
  782. attributes - dictionary of attributes"""
  783. return self._attributes
  784. @attributes.setter
  785. def attributes(self, val):
  786. self._validate_option("attributes", val)
  787. self._attributes = val
  788. @property
  789. def oldsortslice(self):
  790. """ oldsortslice - Slice rows before sorting in the "old style" """
  791. return self._oldsortslice
  792. @oldsortslice.setter
  793. def oldsortslice(self, val):
  794. self._validate_option("oldsortslice", val)
  795. self._oldsortslice = val
  796. ##############################
  797. # OPTION MIXER #
  798. ##############################
  799. def _get_options(self, kwargs):
  800. options = {}
  801. for option in self._options:
  802. if option in kwargs:
  803. self._validate_option(option, kwargs[option])
  804. options[option] = kwargs[option]
  805. else:
  806. options[option] = getattr(self, "_" + option)
  807. return options
  808. ##############################
  809. # PRESET STYLE LOGIC #
  810. ##############################
  811. def set_style(self, style):
  812. if style == DEFAULT:
  813. self._set_default_style()
  814. elif style == MSWORD_FRIENDLY:
  815. self._set_msword_style()
  816. elif style == PLAIN_COLUMNS:
  817. self._set_columns_style()
  818. elif style == MARKDOWN:
  819. self._set_markdown_style()
  820. elif style == ORGMODE:
  821. self._set_orgmode_style()
  822. elif style == RANDOM:
  823. self._set_random_style()
  824. else:
  825. raise Exception("Invalid pre-set style!")
  826. def _set_orgmode_style(self):
  827. self._set_default_style()
  828. self.orgmode = True
  829. def _set_markdown_style(self):
  830. self.header = True
  831. self.border = True
  832. self._hrules = None
  833. self.padding_width = 1
  834. self.left_padding_width = 1
  835. self.right_padding_width = 1
  836. self.vertical_char = "|"
  837. self.junction_char = "|"
  838. def _set_default_style(self):
  839. self.header = True
  840. self.border = True
  841. self._hrules = FRAME
  842. self._vrules = ALL
  843. self.padding_width = 1
  844. self.left_padding_width = 1
  845. self.right_padding_width = 1
  846. self.vertical_char = "|"
  847. self.horizontal_char = "-"
  848. self.junction_char = "+"
  849. def _set_msword_style(self):
  850. self.header = True
  851. self.border = True
  852. self._hrules = NONE
  853. self.padding_width = 1
  854. self.left_padding_width = 1
  855. self.right_padding_width = 1
  856. self.vertical_char = "|"
  857. def _set_columns_style(self):
  858. self.header = True
  859. self.border = False
  860. self.padding_width = 1
  861. self.left_padding_width = 0
  862. self.right_padding_width = 8
  863. def _set_random_style(self):
  864. # Just for fun!
  865. self.header = random.choice((True, False))
  866. self.border = random.choice((True, False))
  867. self._hrules = random.choice((ALL, FRAME, HEADER, NONE))
  868. self._vrules = random.choice((ALL, FRAME, NONE))
  869. self.left_padding_width = random.randint(0, 5)
  870. self.right_padding_width = random.randint(0, 5)
  871. self.vertical_char = random.choice(r"~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
  872. self.horizontal_char = random.choice(r"~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
  873. self.junction_char = random.choice(r"~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
  874. ##############################
  875. # DATA INPUT METHODS #
  876. ##############################
  877. def add_row(self, row):
  878. """Add a row to the table
  879. Arguments:
  880. row - row of data, should be a list with as many elements as the table
  881. has fields"""
  882. if self._field_names and len(row) != len(self._field_names):
  883. raise Exception(
  884. "Row has incorrect number of values, (actual) %d!=%d (expected)"
  885. % (len(row), len(self._field_names))
  886. )
  887. if not self._field_names:
  888. self.field_names = [("Field %d" % (n + 1)) for n in range(0, len(row))]
  889. self._rows.append(list(row))
  890. def del_row(self, row_index):
  891. """Delete a row from the table
  892. Arguments:
  893. row_index - The index of the row you want to delete. Indexing starts at 0."""
  894. if row_index > len(self._rows) - 1:
  895. raise Exception(
  896. "Can't delete row at index %d, table only has %d rows!"
  897. % (row_index, len(self._rows))
  898. )
  899. del self._rows[row_index]
  900. def add_column(self, fieldname, column, align="c", valign="t"):
  901. """Add a column to the table.
  902. Arguments:
  903. fieldname - name of the field to contain the new column of data
  904. column - column of data, should be a list with as many elements as the
  905. table has rows
  906. align - desired alignment for this column - "l" for left, "c" for centre and
  907. "r" for right
  908. valign - desired vertical alignment for new columns - "t" for top,
  909. "m" for middle and "b" for bottom"""
  910. if len(self._rows) in (0, len(column)):
  911. self._validate_align(align)
  912. self._validate_valign(valign)
  913. self._field_names.append(fieldname)
  914. self._align[fieldname] = align
  915. self._valign[fieldname] = valign
  916. for i in range(0, len(column)):
  917. if len(self._rows) < i + 1:
  918. self._rows.append([])
  919. self._rows[i].append(column[i])
  920. else:
  921. raise Exception(
  922. "Column length %d does not match number of rows %d!"
  923. % (len(column), len(self._rows))
  924. )
  925. def del_column(self, fieldname):
  926. """Delete a column from the table
  927. Arguments:
  928. fieldname - The field name of the column you want to delete."""
  929. if fieldname not in self._field_names:
  930. raise Exception(
  931. "Can't delete column %r which is not a field name of this table."
  932. " Field names are: %s"
  933. % (fieldname, ", ".join(map(repr, self._field_names)))
  934. )
  935. col_index = self._field_names.index(fieldname)
  936. del self._field_names[col_index]
  937. for row in self._rows:
  938. del row[col_index]
  939. def clear_rows(self):
  940. """Delete all rows from the table but keep the current field names"""
  941. self._rows = []
  942. def clear(self):
  943. """Delete all rows and field names from the table, maintaining nothing but
  944. styling options"""
  945. self._rows = []
  946. self._field_names = []
  947. self._widths = []
  948. ##############################
  949. # MISC PUBLIC METHODS #
  950. ##############################
  951. def copy(self):
  952. return copy.deepcopy(self)
  953. ##############################
  954. # MISC PRIVATE METHODS #
  955. ##############################
  956. def _format_value(self, field, value):
  957. if isinstance(value, int) and field in self._int_format:
  958. value = self._unicode(("%%%sd" % self._int_format[field]) % value)
  959. elif isinstance(value, float) and field in self._float_format:
  960. value = self._unicode(("%%%sf" % self._float_format[field]) % value)
  961. return self._unicode(value)
  962. def _compute_table_width(self, options):
  963. table_width = 2 if options["vrules"] in (FRAME, ALL) else 0
  964. per_col_padding = sum(self._get_padding_widths(options))
  965. for index, fieldname in enumerate(self.field_names):
  966. if not options["fields"] or (
  967. options["fields"] and fieldname in options["fields"]
  968. ):
  969. table_width += self._widths[index] + per_col_padding
  970. return table_width
  971. def _compute_widths(self, rows, options):
  972. if options["header"]:
  973. widths = [_get_size(field)[0] for field in self._field_names]
  974. else:
  975. widths = len(self.field_names) * [0]
  976. for row in rows:
  977. for index, value in enumerate(row):
  978. fieldname = self.field_names[index]
  979. if fieldname in self.max_width:
  980. widths[index] = max(
  981. widths[index],
  982. min(_get_size(value)[0], self.max_width[fieldname]),
  983. )
  984. else:
  985. widths[index] = max(widths[index], _get_size(value)[0])
  986. if fieldname in self.min_width:
  987. widths[index] = max(widths[index], self.min_width[fieldname])
  988. self._widths = widths
  989. # Are we exceeding max_table_width?
  990. if self._max_table_width:
  991. table_width = self._compute_table_width(options)
  992. if table_width > self._max_table_width:
  993. # Shrink widths in proportion
  994. scale = 1.0 * self._max_table_width / table_width
  995. widths = [int(math.floor(w * scale)) for w in widths]
  996. self._widths = widths
  997. # Are we under min_table_width or title width?
  998. if self._min_table_width or options["title"]:
  999. if options["title"]:
  1000. title_width = len(options["title"]) + sum(
  1001. self._get_padding_widths(options)
  1002. )
  1003. if options["vrules"] in (FRAME, ALL):
  1004. title_width += 2
  1005. else:
  1006. title_width = 0
  1007. min_table_width = self.min_table_width or 0
  1008. min_width = max(title_width, min_table_width)
  1009. table_width = self._compute_table_width(options)
  1010. if table_width < min_width:
  1011. # Grow widths in proportion
  1012. scale = 1.0 * min_width / table_width
  1013. widths = [int(math.ceil(w * scale)) for w in widths]
  1014. self._widths = widths
  1015. def _get_padding_widths(self, options):
  1016. if options["left_padding_width"] is not None:
  1017. lpad = options["left_padding_width"]
  1018. else:
  1019. lpad = options["padding_width"]
  1020. if options["right_padding_width"] is not None:
  1021. rpad = options["right_padding_width"]
  1022. else:
  1023. rpad = options["padding_width"]
  1024. return lpad, rpad
  1025. def _get_rows(self, options):
  1026. """Return only those data rows that should be printed, based on slicing and
  1027. sorting.
  1028. Arguments:
  1029. options - dictionary of option settings."""
  1030. if options["oldsortslice"]:
  1031. rows = copy.deepcopy(self._rows[options["start"] : options["end"]])
  1032. else:
  1033. rows = copy.deepcopy(self._rows)
  1034. # Sort
  1035. if options["sortby"]:
  1036. sortindex = self._field_names.index(options["sortby"])
  1037. # Decorate
  1038. rows = [[row[sortindex]] + row for row in rows]
  1039. # Sort
  1040. rows.sort(reverse=options["reversesort"], key=options["sort_key"])
  1041. # Undecorate
  1042. rows = [row[1:] for row in rows]
  1043. # Slice if necessary
  1044. if not options["oldsortslice"]:
  1045. rows = rows[options["start"] : options["end"]]
  1046. return rows
  1047. def _format_row(self, row, options):
  1048. return [
  1049. self._format_value(field, value)
  1050. for (field, value) in zip(self._field_names, row)
  1051. ]
  1052. def _format_rows(self, rows, options):
  1053. return [self._format_row(row, options) for row in rows]
  1054. ##############################
  1055. # PLAIN TEXT STRING METHODS #
  1056. ##############################
  1057. def get_string(self, **kwargs):
  1058. """Return string representation of table in current state.
  1059. Arguments:
  1060. title - optional table title
  1061. start - index of first data row to include in output
  1062. end - index of last data row to include in output PLUS ONE (list slice style)
  1063. fields - names of fields (columns) to include
  1064. header - print a header showing field names (True or False)
  1065. border - print a border around the table (True or False)
  1066. hrules - controls printing of horizontal rules after rows.
  1067. Allowed values: ALL, FRAME, HEADER, NONE
  1068. vrules - controls printing of vertical rules between columns.
  1069. Allowed values: FRAME, ALL, NONE
  1070. int_format - controls formatting of integer data
  1071. float_format - controls formatting of floating point data
  1072. padding_width - number of spaces on either side of column data (only used if
  1073. left and right paddings are None)
  1074. left_padding_width - number of spaces on left hand side of column data
  1075. right_padding_width - number of spaces on right hand side of column data
  1076. vertical_char - single character string used to draw vertical lines
  1077. horizontal_char - single character string used to draw horizontal lines
  1078. junction_char - single character string used to draw line junctions
  1079. sortby - name of field to sort rows by
  1080. sort_key - sorting key function, applied to data points before sorting
  1081. reversesort - True or False to sort in descending or ascending order
  1082. print empty - if True, stringify just the header for an empty table,
  1083. if False return an empty string"""
  1084. options = self._get_options(kwargs)
  1085. lines = []
  1086. # Don't think too hard about an empty table
  1087. # Is this the desired behaviour? Maybe we should still print the header?
  1088. if self.rowcount == 0 and (not options["print_empty"] or not options["border"]):
  1089. return ""
  1090. # Get the rows we need to print, taking into account slicing, sorting, etc.
  1091. rows = self._get_rows(options)
  1092. # Turn all data in all rows into Unicode, formatted as desired
  1093. formatted_rows = self._format_rows(rows, options)
  1094. # Compute column widths
  1095. self._compute_widths(formatted_rows, options)
  1096. self._hrule = self._stringify_hrule(options)
  1097. # Add title
  1098. title = options["title"] or self._title
  1099. if title:
  1100. lines.append(self._stringify_title(title, options))
  1101. # Add header or top of border
  1102. if options["header"]:
  1103. lines.append(self._stringify_header(options))
  1104. elif options["border"] and options["hrules"] in (ALL, FRAME):
  1105. lines.append(self._hrule)
  1106. # Add rows
  1107. for row in formatted_rows:
  1108. lines.append(self._stringify_row(row, options))
  1109. # Add bottom of border
  1110. if options["border"] and options["hrules"] == FRAME:
  1111. lines.append(self._hrule)
  1112. if "orgmode" in self.__dict__ and self.orgmode is True:
  1113. tmp = list()
  1114. for line in lines:
  1115. tmp.extend(line.split("\n"))
  1116. lines = ["|" + line[1:-1] + "|" for line in tmp]
  1117. return self._unicode("\n").join(lines)
  1118. def _stringify_hrule(self, options):
  1119. if not options["border"]:
  1120. return ""
  1121. lpad, rpad = self._get_padding_widths(options)
  1122. if options["vrules"] in (ALL, FRAME):
  1123. bits = [options["junction_char"]]
  1124. else:
  1125. bits = [options["horizontal_char"]]
  1126. # For tables with no data or fieldnames
  1127. if not self._field_names:
  1128. bits.append(options["junction_char"])
  1129. return "".join(bits)
  1130. for field, width in zip(self._field_names, self._widths):
  1131. if options["fields"] and field not in options["fields"]:
  1132. continue
  1133. bits.append((width + lpad + rpad) * options["horizontal_char"])
  1134. if options["vrules"] == ALL:
  1135. bits.append(options["junction_char"])
  1136. else:
  1137. bits.append(options["horizontal_char"])
  1138. if options["vrules"] == FRAME:
  1139. bits.pop()
  1140. bits.append(options["junction_char"])
  1141. return "".join(bits)
  1142. def _stringify_title(self, title, options):
  1143. lines = []
  1144. lpad, rpad = self._get_padding_widths(options)
  1145. if options["border"]:
  1146. if options["vrules"] == ALL:
  1147. options["vrules"] = FRAME
  1148. lines.append(self._stringify_hrule(options))
  1149. options["vrules"] = ALL
  1150. elif options["vrules"] == FRAME:
  1151. lines.append(self._stringify_hrule(options))
  1152. bits = []
  1153. endpoint = (
  1154. options["vertical_char"] if options["vrules"] in (ALL, FRAME) else " "
  1155. )
  1156. bits.append(endpoint)
  1157. title = " " * lpad + title + " " * rpad
  1158. bits.append(self._justify(title, len(self._hrule) - 2, "c"))
  1159. bits.append(endpoint)
  1160. lines.append("".join(bits))
  1161. return "\n".join(lines)
  1162. def _stringify_header(self, options):
  1163. bits = []
  1164. lpad, rpad = self._get_padding_widths(options)
  1165. if options["border"]:
  1166. if options["hrules"] in (ALL, FRAME):
  1167. bits.append(self._hrule)
  1168. bits.append("\n")
  1169. if options["vrules"] in (ALL, FRAME):
  1170. bits.append(options["vertical_char"])
  1171. else:
  1172. bits.append(" ")
  1173. # For tables with no data or field names
  1174. if not self._field_names:
  1175. if options["vrules"] in (ALL, FRAME):
  1176. bits.append(options["vertical_char"])
  1177. else:
  1178. bits.append(" ")
  1179. for (field, width) in zip(self._field_names, self._widths):
  1180. if options["fields"] and field not in options["fields"]:
  1181. continue
  1182. if self._header_style == "cap":
  1183. fieldname = field.capitalize()
  1184. elif self._header_style == "title":
  1185. fieldname = field.title()
  1186. elif self._header_style == "upper":
  1187. fieldname = field.upper()
  1188. elif self._header_style == "lower":
  1189. fieldname = field.lower()
  1190. else:
  1191. fieldname = field
  1192. bits.append(
  1193. " " * lpad
  1194. + self._justify(fieldname, width, self._align[field])
  1195. + " " * rpad
  1196. )
  1197. if options["border"]:
  1198. if options["vrules"] == ALL:
  1199. bits.append(options["vertical_char"])
  1200. else:
  1201. bits.append(" ")
  1202. # If vrules is FRAME, then we just appended a space at the end
  1203. # of the last field, when we really want a vertical character
  1204. if options["border"] and options["vrules"] == FRAME:
  1205. bits.pop()
  1206. bits.append(options["vertical_char"])
  1207. if options["border"] and options["hrules"] != NONE:
  1208. bits.append("\n")
  1209. bits.append(self._hrule)
  1210. return "".join(bits)
  1211. def _stringify_row(self, row, options):
  1212. for (index, field, value, width) in zip(
  1213. range(0, len(row)), self._field_names, row, self._widths
  1214. ):
  1215. # Enforce max widths
  1216. lines = value.split("\n")
  1217. new_lines = []
  1218. for line in lines:
  1219. if _str_block_width(line) > width:
  1220. line = textwrap.fill(line, width)
  1221. new_lines.append(line)
  1222. lines = new_lines
  1223. value = "\n".join(lines)
  1224. row[index] = value
  1225. row_height = 0
  1226. for c in row:
  1227. h = _get_size(c)[1]
  1228. if h > row_height:
  1229. row_height = h
  1230. bits = []
  1231. lpad, rpad = self._get_padding_widths(options)
  1232. for y in range(0, row_height):
  1233. bits.append([])
  1234. if options["border"]:
  1235. if options["vrules"] in (ALL, FRAME):
  1236. bits[y].append(self.vertical_char)
  1237. else:
  1238. bits[y].append(" ")
  1239. for (field, value, width) in zip(self._field_names, row, self._widths):
  1240. valign = self._valign[field]
  1241. lines = value.split("\n")
  1242. d_height = row_height - len(lines)
  1243. if d_height:
  1244. if valign == "m":
  1245. lines = (
  1246. [""] * int(d_height / 2)
  1247. + lines
  1248. + [""] * (d_height - int(d_height / 2))
  1249. )
  1250. elif valign == "b":
  1251. lines = [""] * d_height + lines
  1252. else:
  1253. lines = lines + [""] * d_height
  1254. y = 0
  1255. for line in lines:
  1256. if options["fields"] and field not in options["fields"]:
  1257. continue
  1258. bits[y].append(
  1259. " " * lpad
  1260. + self._justify(line, width, self._align[field])
  1261. + " " * rpad
  1262. )
  1263. if options["border"]:
  1264. if options["vrules"] == ALL:
  1265. bits[y].append(self.vertical_char)
  1266. else:
  1267. bits[y].append(" ")
  1268. y += 1
  1269. # If vrules is FRAME, then we just appended a space at the end
  1270. # of the last field, when we really want a vertical character
  1271. for y in range(0, row_height):
  1272. if options["border"] and options["vrules"] == FRAME:
  1273. bits[y].pop()
  1274. bits[y].append(options["vertical_char"])
  1275. if options["border"] and options["hrules"] == ALL:
  1276. bits[row_height - 1].append("\n")
  1277. bits[row_height - 1].append(self._hrule)
  1278. for y in range(0, row_height):
  1279. bits[y] = "".join(bits[y])
  1280. return "\n".join(bits)
  1281. def paginate(self, page_length=58, **kwargs):
  1282. pages = []
  1283. kwargs["start"] = kwargs.get("start", 0)
  1284. true_end = kwargs.get("end", self.rowcount)
  1285. while True:
  1286. kwargs["end"] = min(kwargs["start"] + page_length, true_end)
  1287. pages.append(self.get_string(**kwargs))
  1288. if kwargs["end"] == true_end:
  1289. break
  1290. kwargs["start"] += page_length
  1291. return "\f".join(pages)
  1292. ##############################
  1293. # CSV STRING METHODS #
  1294. ##############################
  1295. def get_csv_string(self, **kwargs):
  1296. """Return string representation of CSV formatted table in the current state
  1297. Keyword arguments are first interpreted as table formatting options, and
  1298. then any unused keyword arguments are passed to csv.writer(). For
  1299. example, get_csv_string(header=False, delimiter='\t') would use
  1300. header as a PrettyTable formatting option (skip the header row) and
  1301. delimiter as a csv.writer keyword argument.
  1302. """
  1303. options = self._get_options(kwargs)
  1304. csv_options = {
  1305. key: value for key, value in kwargs.items() if key not in options
  1306. }
  1307. csv_buffer = StringIO()
  1308. csv_writer = csv.writer(csv_buffer, **csv_options)
  1309. if options.get("header"):
  1310. csv_writer.writerow(self._field_names)
  1311. for row in self._get_rows(options):
  1312. csv_writer.writerow(row)
  1313. return csv_buffer.getvalue()
  1314. ##############################
  1315. # JSON STRING METHODS #
  1316. ##############################
  1317. def get_json_string(self, **kwargs):
  1318. """Return string representation of JSON formatted table in the current state
  1319. Arguments:
  1320. none yet"""
  1321. options = self._get_options(kwargs)
  1322. objects = [self.field_names]
  1323. for row in self._get_rows(options):
  1324. objects.append(dict(zip(self._field_names, row)))
  1325. return json.dumps(objects, indent=4, separators=(",", ": "), sort_keys=True)
  1326. ##############################
  1327. # HTML STRING METHODS #
  1328. ##############################
  1329. def get_html_string(self, **kwargs):
  1330. """Return string representation of HTML formatted version of table in current
  1331. state.
  1332. Arguments:
  1333. title - optional table title
  1334. start - index of first data row to include in output
  1335. end - index of last data row to include in output PLUS ONE (list slice style)
  1336. fields - names of fields (columns) to include
  1337. header - print a header showing field names (True or False)
  1338. border - print a border around the table (True or False)
  1339. hrules - controls printing of horizontal rules after rows.
  1340. Allowed values: ALL, FRAME, HEADER, NONE
  1341. vrules - controls printing of vertical rules between columns.
  1342. Allowed values: FRAME, ALL, NONE
  1343. int_format - controls formatting of integer data
  1344. float_format - controls formatting of floating point data
  1345. padding_width - number of spaces on either side of column data (only used if
  1346. left and right paddings are None)
  1347. left_padding_width - number of spaces on left hand side of column data
  1348. right_padding_width - number of spaces on right hand side of column data
  1349. sortby - name of field to sort rows by
  1350. sort_key - sorting key function, applied to data points before sorting
  1351. attributes - dictionary of name/value pairs to include as HTML attributes in the
  1352. <table> tag
  1353. xhtml - print <br/> tags if True, <br> tags if false"""
  1354. options = self._get_options(kwargs)
  1355. if options["format"]:
  1356. string = self._get_formatted_html_string(options)
  1357. else:
  1358. string = self._get_simple_html_string(options)
  1359. return string
  1360. def _get_simple_html_string(self, options):
  1361. lines = []
  1362. if options["xhtml"]:
  1363. linebreak = "<br/>"
  1364. else:
  1365. linebreak = "<br>"
  1366. open_tag = ["<table"]
  1367. if options["attributes"]:
  1368. for attr_name in options["attributes"]:
  1369. open_tag.append(
  1370. ' {}="{}"'.format(attr_name, options["attributes"][attr_name])
  1371. )
  1372. open_tag.append(">")
  1373. lines.append("".join(open_tag))
  1374. # Title
  1375. title = options["title"] or self._title
  1376. if title:
  1377. cols = (
  1378. len(options["fields"]) if options["fields"] else len(self.field_names)
  1379. )
  1380. lines.append(" <tr>")
  1381. lines.append(" <td colspan=%d>%s</td>" % (cols, title))
  1382. lines.append(" </tr>")
  1383. # Headers
  1384. if options["header"]:
  1385. lines.append(" <tr>")
  1386. for field in self._field_names:
  1387. if options["fields"] and field not in options["fields"]:
  1388. continue
  1389. lines.append(
  1390. " <th>%s</th>" % escape(field).replace("\n", linebreak)
  1391. )
  1392. lines.append(" </tr>")
  1393. # Data
  1394. rows = self._get_rows(options)
  1395. formatted_rows = self._format_rows(rows, options)
  1396. for row in formatted_rows:
  1397. lines.append(" <tr>")
  1398. for field, datum in zip(self._field_names, row):
  1399. if options["fields"] and field not in options["fields"]:
  1400. continue
  1401. lines.append(
  1402. " <td>%s</td>" % escape(datum).replace("\n", linebreak)
  1403. )
  1404. lines.append(" </tr>")
  1405. lines.append("</table>")
  1406. return self._unicode("\n").join(lines)
  1407. def _get_formatted_html_string(self, options):
  1408. lines = []
  1409. lpad, rpad = self._get_padding_widths(options)
  1410. if options["xhtml"]:
  1411. linebreak = "<br/>"
  1412. else:
  1413. linebreak = "<br>"
  1414. open_tag = ["<table"]
  1415. if options["border"]:
  1416. if options["hrules"] == ALL and options["vrules"] == ALL:
  1417. open_tag.append(' frame="box" rules="all"')
  1418. elif options["hrules"] == FRAME and options["vrules"] == FRAME:
  1419. open_tag.append(' frame="box"')
  1420. elif options["hrules"] == FRAME and options["vrules"] == ALL:
  1421. open_tag.append(' frame="box" rules="cols"')
  1422. elif options["hrules"] == FRAME:
  1423. open_tag.append(' frame="hsides"')
  1424. elif options["hrules"] == ALL:
  1425. open_tag.append(' frame="hsides" rules="rows"')
  1426. elif options["vrules"] == FRAME:
  1427. open_tag.append(' frame="vsides"')
  1428. elif options["vrules"] == ALL:
  1429. open_tag.append(' frame="vsides" rules="cols"')
  1430. if options["attributes"]:
  1431. for attr_name in options["attributes"]:
  1432. open_tag.append(
  1433. ' {}="{}"'.format(attr_name, options["attributes"][attr_name])
  1434. )
  1435. open_tag.append(">")
  1436. lines.append("".join(open_tag))
  1437. # Title
  1438. title = options["title"] or self._title
  1439. if title:
  1440. cols = (
  1441. len(options["fields"]) if options["fields"] else len(self.field_names)
  1442. )
  1443. lines.append(" <tr>")
  1444. lines.append(" <td colspan=%d>%s</td>" % (cols, title))
  1445. lines.append(" </tr>")
  1446. # Headers
  1447. if options["header"]:
  1448. lines.append(" <tr>")
  1449. for field in self._field_names:
  1450. if options["fields"] and field not in options["fields"]:
  1451. continue
  1452. lines.append(
  1453. ' <th style="padding-left: %dem; padding-right: %dem; text-align: center">%s</th>' # noqa: E501
  1454. % (lpad, rpad, escape(field).replace("\n", linebreak))
  1455. )
  1456. lines.append(" </tr>")
  1457. # Data
  1458. rows = self._get_rows(options)
  1459. formatted_rows = self._format_rows(rows, options)
  1460. aligns = []
  1461. valigns = []
  1462. for field in self._field_names:
  1463. aligns.append(
  1464. {"l": "left", "r": "right", "c": "center"}[self._align[field]]
  1465. )
  1466. valigns.append(
  1467. {"t": "top", "m": "middle", "b": "bottom"}[self._valign[field]]
  1468. )
  1469. for row in formatted_rows:
  1470. lines.append(" <tr>")
  1471. for field, datum, align, valign in zip(
  1472. self._field_names, row, aligns, valigns
  1473. ):
  1474. if options["fields"] and field not in options["fields"]:
  1475. continue
  1476. lines.append(
  1477. ' <td style="padding-left: %dem; padding-right: %dem; text-align: %s; vertical-align: %s">%s</td>' # noqa: E501
  1478. % (
  1479. lpad,
  1480. rpad,
  1481. align,
  1482. valign,
  1483. escape(datum).replace("\n", linebreak),
  1484. )
  1485. )
  1486. lines.append(" </tr>")
  1487. lines.append("</table>")
  1488. return self._unicode("\n").join(lines)
  1489. ##############################
  1490. # UNICODE WIDTH FUNCTION #
  1491. ##############################
  1492. def _str_block_width(val):
  1493. return wcwidth.wcswidth(_re.sub("", val))
  1494. ##############################
  1495. # TABLE FACTORIES #
  1496. ##############################
  1497. def from_csv(fp, field_names=None, **kwargs):
  1498. fmtparams = {}
  1499. for param in [
  1500. "delimiter",
  1501. "doublequote",
  1502. "escapechar",
  1503. "lineterminator",
  1504. "quotechar",
  1505. "quoting",
  1506. "skipinitialspace",
  1507. "strict",
  1508. ]:
  1509. if param in kwargs:
  1510. fmtparams[param] = kwargs.pop(param)
  1511. if fmtparams:
  1512. reader = csv.reader(fp, **fmtparams)
  1513. else:
  1514. dialect = csv.Sniffer().sniff(fp.read(1024))
  1515. fp.seek(0)
  1516. reader = csv.reader(fp, dialect)
  1517. table = PrettyTable(**kwargs)
  1518. if field_names:
  1519. table.field_names = field_names
  1520. else:
  1521. if py3k:
  1522. table.field_names = [x.strip() for x in next(reader)]
  1523. else:
  1524. table.field_names = [x.strip() for x in reader.next()]
  1525. for row in reader:
  1526. table.add_row([x.strip() for x in row])
  1527. return table
  1528. def from_db_cursor(cursor, **kwargs):
  1529. if cursor.description:
  1530. table = PrettyTable(**kwargs)
  1531. table.field_names = [col[0] for col in cursor.description]
  1532. for row in cursor.fetchall():
  1533. table.add_row(row)
  1534. return table
  1535. def from_json(json_string, **kwargs):
  1536. table = PrettyTable(**kwargs)
  1537. objects = json.loads(json_string)
  1538. table.field_names = objects[0]
  1539. for obj in objects[1:]:
  1540. row = [obj[key] for key in table.field_names]
  1541. table.add_row(row)
  1542. return table
  1543. class TableHandler(HTMLParser):
  1544. def __init__(self, **kwargs):
  1545. HTMLParser.__init__(self)
  1546. self.kwargs = kwargs
  1547. self.tables = []
  1548. self.last_row = []
  1549. self.rows = []
  1550. self.max_row_width = 0
  1551. self.active = None
  1552. self.last_content = ""
  1553. self.is_last_row_header = False
  1554. self.colspan = 0
  1555. def handle_starttag(self, tag, attrs):
  1556. self.active = tag
  1557. if tag == "th":
  1558. self.is_last_row_header = True
  1559. for (key, value) in attrs:
  1560. if key == "colspan":
  1561. self.colspan = int(value)
  1562. def handle_endtag(self, tag):
  1563. if tag in ["th", "td"]:
  1564. stripped_content = self.last_content.strip()
  1565. self.last_row.append(stripped_content)
  1566. if self.colspan:
  1567. for i in range(1, self.colspan):
  1568. self.last_row.append("")
  1569. self.colspan = 0
  1570. if tag == "tr":
  1571. self.rows.append((self.last_row, self.is_last_row_header))
  1572. self.max_row_width = max(self.max_row_width, len(self.last_row))
  1573. self.last_row = []
  1574. self.is_last_row_header = False
  1575. if tag == "table":
  1576. table = self.generate_table(self.rows)
  1577. self.tables.append(table)
  1578. self.rows = []
  1579. self.last_content = " "
  1580. self.active = None
  1581. def handle_data(self, data):
  1582. self.last_content += data
  1583. def generate_table(self, rows):
  1584. """
  1585. Generates from a list of rows a PrettyTable object.
  1586. """
  1587. table = PrettyTable(**self.kwargs)
  1588. for row in self.rows:
  1589. if len(row[0]) < self.max_row_width:
  1590. appends = self.max_row_width - len(row[0])
  1591. for i in range(1, appends):
  1592. row[0].append("-")
  1593. if row[1]:
  1594. self.make_fields_unique(row[0])
  1595. table.field_names = row[0]
  1596. else:
  1597. table.add_row(row[0])
  1598. return table
  1599. def make_fields_unique(self, fields):
  1600. """
  1601. iterates over the row and make each field unique
  1602. """
  1603. for i in range(0, len(fields)):
  1604. for j in range(i + 1, len(fields)):
  1605. if fields[i] == fields[j]:
  1606. fields[j] += "'"
  1607. def from_html(html_code, **kwargs):
  1608. """
  1609. Generates a list of PrettyTables from a string of HTML code. Each <table> in
  1610. the HTML becomes one PrettyTable object.
  1611. """
  1612. parser = TableHandler(**kwargs)
  1613. parser.feed(html_code)
  1614. return parser.tables
  1615. def from_html_one(html_code, **kwargs):
  1616. """
  1617. Generates a PrettyTables from a string of HTML code which contains only a
  1618. single <table>
  1619. """
  1620. tables = from_html(html_code, **kwargs)
  1621. try:
  1622. assert len(tables) == 1
  1623. except AssertionError:
  1624. raise Exception(
  1625. "More than one <table> in provided HTML code! Use from_html instead."
  1626. )
  1627. return tables[0]
  1628. ##############################
  1629. # MAIN (TEST FUNCTION) #
  1630. ##############################
  1631. def main():
  1632. print("Generated using setters:")
  1633. x = PrettyTable(["City name", "Area", "Population", "Annual Rainfall"])
  1634. x.title = "Australian capital cities"
  1635. x.sortby = "Population"
  1636. x.reversesort = True
  1637. x.int_format["Area"] = "04"
  1638. x.float_format = "6.1"
  1639. x.align["City name"] = "l" # Left align city names
  1640. x.add_row(["Adelaide", 1295, 1158259, 600.5])
  1641. x.add_row(["Brisbane", 5905, 1857594, 1146.4])
  1642. x.add_row(["Darwin", 112, 120900, 1714.7])
  1643. x.add_row(["Hobart", 1357, 205556, 619.5])
  1644. x.add_row(["Sydney", 2058, 4336374, 1214.8])
  1645. x.add_row(["Melbourne", 1566, 3806092, 646.9])
  1646. x.add_row(["Perth", 5386, 1554769, 869.4])
  1647. print(x)
  1648. print()
  1649. print("Generated using constructor arguments:")
  1650. y = PrettyTable(
  1651. ["City name", "Area", "Population", "Annual Rainfall"],
  1652. title="Australian capital cities",
  1653. sortby="Population",
  1654. reversesort=True,
  1655. int_format="04",
  1656. float_format="6.1",
  1657. max_width=12,
  1658. min_width=4,
  1659. align="c",
  1660. valign="t",
  1661. )
  1662. y.align["City name"] = "l" # Left align city names
  1663. y.add_row(["Adelaide", 1295, 1158259, 600.5])
  1664. y.add_row(["Brisbane", 5905, 1857594, 1146.4])
  1665. y.add_row(["Darwin", 112, 120900, 1714.7])
  1666. y.add_row(["Hobart", 1357, 205556, 619.5])
  1667. y.add_row(["Sydney", 2058, 4336374, 1214.8])
  1668. y.add_row(["Melbourne", 1566, 3806092, 646.9])
  1669. y.add_row(["Perth", 5386, 1554769, 869.4])
  1670. print(y)
  1671. if __name__ == "__main__":
  1672. main()