123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010 |
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- #
- # Copyright (c) 2009-2014, Luke Maurits <luke@maurits.id.au>
- # All rights reserved.
- # With contributions from:
- # * Chris Clark
- # * Klein Stephane
- # * John Filleau
- # * Vladimir Vrzić
- #
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions are met:
- #
- # * Redistributions of source code must retain the above copyright notice,
- # this list of conditions and the following disclaimer.
- # * Redistributions in binary form must reproduce the above copyright notice,
- # this list of conditions and the following disclaimer in the documentation
- # and/or other materials provided with the distribution.
- # * The name of the author may not be used to endorse or promote products
- # derived from this software without specific prior written permission.
- #
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- # POSSIBILITY OF SUCH DAMAGE.
- import copy
- import csv
- import itertools
- import json
- import math
- import random
- import re
- import sys
- import textwrap
- import pkg_resources
- import wcwidth
- __version__ = pkg_resources.get_distribution(__name__).version
- py3k = sys.version_info[0] >= 3
- if py3k:
- unicode = str
- basestring = str
- itermap = map
- iterzip = zip
- uni_chr = chr
- from html import escape
- from html.parser import HTMLParser
- from io import StringIO
- else:
- itermap = itertools.imap
- iterzip = itertools.izip
- uni_chr = unichr # noqa: F821
- from cgi import escape
- from HTMLParser import HTMLParser
- from StringIO import StringIO
- # hrule styles
- FRAME = 0
- ALL = 1
- NONE = 2
- HEADER = 3
- # Table styles
- DEFAULT = 10
- MSWORD_FRIENDLY = 11
- PLAIN_COLUMNS = 12
- MARKDOWN = 13
- ORGMODE = 14
- RANDOM = 20
- _re = re.compile(r"\033\[[0-9;]*m")
- def _get_size(text):
- lines = text.split("\n")
- height = len(lines)
- width = max(_str_block_width(line) for line in lines)
- return width, height
- class PrettyTable(object):
- def __init__(self, field_names=None, **kwargs):
- """Return a new PrettyTable instance
- Arguments:
- encoding - Unicode encoding scheme used to decode any encoded input
- title - optional table title
- field_names - list or tuple of field names
- fields - list or tuple of field names to include in displays
- start - index of first data row to include in output
- end - index of last data row to include in output PLUS ONE (list slice style)
- header - print a header showing field names (True or False)
- header_style - stylisation to apply to field names in header
- ("cap", "title", "upper", "lower" or None)
- border - print a border around the table (True or False)
- hrules - controls printing of horizontal rules after rows.
- Allowed values: FRAME, HEADER, ALL, NONE
- vrules - controls printing of vertical rules between columns.
- Allowed values: FRAME, ALL, NONE
- int_format - controls formatting of integer data
- float_format - controls formatting of floating point data
- min_table_width - minimum desired table width, in characters
- max_table_width - maximum desired table width, in characters
- padding_width - number of spaces on either side of column data
- (only used if left and right paddings are None)
- left_padding_width - number of spaces on left hand side of column data
- right_padding_width - number of spaces on right hand side of column data
- vertical_char - single character string used to draw vertical lines
- horizontal_char - single character string used to draw horizontal lines
- junction_char - single character string used to draw line junctions
- sortby - name of field to sort rows by
- sort_key - sorting key function, applied to data points before sorting
- valign - default valign for each row (None, "t", "m" or "b")
- reversesort - True or False to sort in descending or ascending order
- oldsortslice - Slice rows before sorting in the "old style" """
- self.encoding = kwargs.get("encoding", "UTF-8")
- # Data
- self._field_names = []
- self._rows = []
- self.align = {}
- self.valign = {}
- self.max_width = {}
- self.min_width = {}
- self.int_format = {}
- self.float_format = {}
- if field_names:
- self.field_names = field_names
- else:
- self._widths = []
- # Options
- self._options = (
- "title start end fields header border sortby reversesort "
- "sort_key attributes format hrules vrules".split()
- )
- self._options.extend(
- "int_format float_format min_table_width max_table_width padding_width "
- "left_padding_width right_padding_width".split()
- )
- self._options.extend(
- "vertical_char horizontal_char junction_char header_style valign xhtml "
- "print_empty oldsortslice".split()
- )
- self._options.extend("align valign max_width min_width".split())
- for option in self._options:
- if option in kwargs:
- self._validate_option(option, kwargs[option])
- else:
- kwargs[option] = None
- self._title = kwargs["title"] or None
- self._start = kwargs["start"] or 0
- self._end = kwargs["end"] or None
- self._fields = kwargs["fields"] or None
- if kwargs["header"] in (True, False):
- self._header = kwargs["header"]
- else:
- self._header = True
- self._header_style = kwargs["header_style"] or None
- if kwargs["border"] in (True, False):
- self._border = kwargs["border"]
- else:
- self._border = True
- self._hrules = kwargs["hrules"] or FRAME
- self._vrules = kwargs["vrules"] or ALL
- self._sortby = kwargs["sortby"] or None
- if kwargs["reversesort"] in (True, False):
- self._reversesort = kwargs["reversesort"]
- else:
- self._reversesort = False
- self._sort_key = kwargs["sort_key"] or (lambda x: x)
- # Column specific arguments, use property.setters
- self.align = kwargs["align"] or {}
- self.valign = kwargs["valign"] or {}
- self.max_width = kwargs["max_width"] or {}
- self.min_width = kwargs["min_width"] or {}
- self.int_format = kwargs["int_format"] or {}
- self.float_format = kwargs["float_format"] or {}
- self._min_table_width = kwargs["min_table_width"] or None
- self._max_table_width = kwargs["max_table_width"] or None
- self._padding_width = kwargs["padding_width"] or 1
- self._left_padding_width = kwargs["left_padding_width"] or None
- self._right_padding_width = kwargs["right_padding_width"] or None
- self._vertical_char = kwargs["vertical_char"] or self._unicode("|")
- self._horizontal_char = kwargs["horizontal_char"] or self._unicode("-")
- self._junction_char = kwargs["junction_char"] or self._unicode("+")
- if kwargs["print_empty"] in (True, False):
- self._print_empty = kwargs["print_empty"]
- else:
- self._print_empty = True
- if kwargs["oldsortslice"] in (True, False):
- self._oldsortslice = kwargs["oldsortslice"]
- else:
- self._oldsortslice = False
- self._format = kwargs["format"] or False
- self._xhtml = kwargs["xhtml"] or False
- self._attributes = kwargs["attributes"] or {}
- def _unicode(self, value):
- if not isinstance(value, basestring):
- value = str(value)
- if not isinstance(value, unicode):
- value = unicode(value, self.encoding, "strict")
- return value
- def _justify(self, text, width, align):
- excess = width - _str_block_width(text)
- if align == "l":
- return text + excess * " "
- elif align == "r":
- return excess * " " + text
- else:
- if excess % 2:
- # Uneven padding
- # Put more space on right if text is of odd length...
- if _str_block_width(text) % 2:
- return (excess // 2) * " " + text + (excess // 2 + 1) * " "
- # and more space on left if text is of even length
- else:
- return (excess // 2 + 1) * " " + text + (excess // 2) * " "
- # Why distribute extra space this way? To match the behaviour of
- # the inbuilt str.center() method.
- else:
- # Equal padding on either side
- return (excess // 2) * " " + text + (excess // 2) * " "
- def __getattr__(self, name):
- if name == "rowcount":
- return len(self._rows)
- elif name == "colcount":
- if self._field_names:
- return len(self._field_names)
- elif self._rows:
- return len(self._rows[0])
- else:
- return 0
- else:
- raise AttributeError(name)
- def __getitem__(self, index):
- new = PrettyTable()
- new.field_names = self.field_names
- for attr in self._options:
- setattr(new, "_" + attr, getattr(self, "_" + attr))
- setattr(new, "_align", getattr(self, "_align"))
- if isinstance(index, slice):
- for row in self._rows[index]:
- new.add_row(row)
- elif isinstance(index, int):
- new.add_row(self._rows[index])
- else:
- raise Exception(
- "Index %s is invalid, must be an integer or slice" % str(index)
- )
- return new
- if py3k:
- def __str__(self):
- return self.__unicode__()
- else:
- def __str__(self):
- return self.__unicode__().encode(self.encoding)
- def __unicode__(self):
- return self.get_string()
- ##############################
- # ATTRIBUTE VALIDATORS #
- ##############################
- # The method _validate_option is all that should be used elsewhere in the code base
- # to validate options. It will call the appropriate validation method for that
- # option. The individual validation methods should never need to be called directly
- # (although nothing bad will happen if they *are*).
- # Validation happens in TWO places.
- # Firstly, in the property setters defined in the ATTRIBUTE MANAGEMENT section.
- # Secondly, in the _get_options method, where keyword arguments are mixed with
- # persistent settings
- def _validate_option(self, option, val):
- if option == "field_names":
- self._validate_field_names(val)
- elif option in (
- "start",
- "end",
- "max_width",
- "min_width",
- "min_table_width",
- "max_table_width",
- "padding_width",
- "left_padding_width",
- "right_padding_width",
- "format",
- ):
- self._validate_nonnegative_int(option, val)
- elif option == "sortby":
- self._validate_field_name(option, val)
- elif option == "sort_key":
- self._validate_function(option, val)
- elif option == "hrules":
- self._validate_hrules(option, val)
- elif option == "vrules":
- self._validate_vrules(option, val)
- elif option == "fields":
- self._validate_all_field_names(option, val)
- elif option in (
- "header",
- "border",
- "reversesort",
- "xhtml",
- "print_empty",
- "oldsortslice",
- ):
- self._validate_true_or_false(option, val)
- elif option == "header_style":
- self._validate_header_style(val)
- elif option == "int_format":
- self._validate_int_format(option, val)
- elif option == "float_format":
- self._validate_float_format(option, val)
- elif option in ("vertical_char", "horizontal_char", "junction_char"):
- self._validate_single_char(option, val)
- elif option == "attributes":
- self._validate_attributes(option, val)
- def _validate_field_names(self, val):
- # Check for appropriate length
- if self._field_names:
- try:
- assert len(val) == len(self._field_names)
- except AssertionError:
- raise Exception(
- "Field name list has incorrect number of values, "
- "(actual) %d!=%d (expected)" % (len(val), len(self._field_names))
- )
- if self._rows:
- try:
- assert len(val) == len(self._rows[0])
- except AssertionError:
- raise Exception(
- "Field name list has incorrect number of values, "
- "(actual) %d!=%d (expected)" % (len(val), len(self._rows[0]))
- )
- # Check for uniqueness
- try:
- assert len(val) == len(set(val))
- except AssertionError:
- raise Exception("Field names must be unique!")
- def _validate_header_style(self, val):
- try:
- assert val in ("cap", "title", "upper", "lower", None)
- except AssertionError:
- raise Exception(
- "Invalid header style, use cap, title, upper, lower or None!"
- )
- def _validate_align(self, val):
- try:
- assert val in ["l", "c", "r"]
- except AssertionError:
- raise Exception("Alignment %s is invalid, use l, c or r!" % val)
- def _validate_valign(self, val):
- try:
- assert val in ["t", "m", "b", None]
- except AssertionError:
- raise Exception("Alignment %s is invalid, use t, m, b or None!" % val)
- def _validate_nonnegative_int(self, name, val):
- try:
- assert int(val) >= 0
- except AssertionError:
- raise Exception(
- "Invalid value for {}: {}!".format(name, self._unicode(val))
- )
- def _validate_true_or_false(self, name, val):
- try:
- assert val in (True, False)
- except AssertionError:
- raise Exception("Invalid value for %s! Must be True or False." % name)
- def _validate_int_format(self, name, val):
- if val == "":
- return
- try:
- assert type(val) in (str, unicode)
- assert val.isdigit()
- except AssertionError:
- raise Exception(
- "Invalid value for %s! Must be an integer format string." % name
- )
- def _validate_float_format(self, name, val):
- if val == "":
- return
- try:
- assert type(val) in (str, unicode)
- assert "." in val
- bits = val.split(".")
- assert len(bits) <= 2
- assert bits[0] == "" or bits[0].isdigit()
- assert (
- bits[1] == ""
- or bits[1].isdigit()
- or (bits[1][-1] == "f" and bits[1].rstrip("f").isdigit())
- )
- except AssertionError:
- raise Exception(
- "Invalid value for %s! Must be a float format string." % name
- )
- def _validate_function(self, name, val):
- try:
- assert hasattr(val, "__call__")
- except AssertionError:
- raise Exception("Invalid value for %s! Must be a function." % name)
- def _validate_hrules(self, name, val):
- try:
- assert val in (ALL, FRAME, HEADER, NONE)
- except AssertionError:
- raise Exception(
- "Invalid value for %s! Must be ALL, FRAME, HEADER or NONE." % name
- )
- def _validate_vrules(self, name, val):
- try:
- assert val in (ALL, FRAME, NONE)
- except AssertionError:
- raise Exception(
- "Invalid value for %s! Must be ALL, FRAME, or NONE." % name
- )
- def _validate_field_name(self, name, val):
- try:
- assert (val in self._field_names) or (val is None)
- except AssertionError:
- raise Exception("Invalid field name: %s!" % val)
- def _validate_all_field_names(self, name, val):
- try:
- for x in val:
- self._validate_field_name(name, x)
- except AssertionError:
- raise Exception("fields must be a sequence of field names!")
- def _validate_single_char(self, name, val):
- try:
- assert _str_block_width(val) == 1
- except AssertionError:
- raise Exception(
- "Invalid value for %s! Must be a string of length 1." % name
- )
- def _validate_attributes(self, name, val):
- try:
- assert isinstance(val, dict)
- except AssertionError:
- raise Exception("attributes must be a dictionary of name/value pairs!")
- ##############################
- # ATTRIBUTE MANAGEMENT #
- ##############################
- @property
- def field_names(self):
- """List or tuple of field names
- When setting field_names, if there are already field names the new list
- of field names must be the same length. Columns are renamed and row data
- remains unchanged."""
- return self._field_names
- @field_names.setter
- def field_names(self, val):
- val = [self._unicode(x) for x in val]
- self._validate_option("field_names", val)
- old_names = None
- if self._field_names:
- old_names = self._field_names[:]
- self._field_names = val
- if self._align and old_names:
- for old_name, new_name in zip(old_names, val):
- self._align[new_name] = self._align[old_name]
- for old_name in old_names:
- if old_name not in self._align:
- self._align.pop(old_name)
- else:
- self.align = "c"
- if self._valign and old_names:
- for old_name, new_name in zip(old_names, val):
- self._valign[new_name] = self._valign[old_name]
- for old_name in old_names:
- if old_name not in self._valign:
- self._valign.pop(old_name)
- else:
- self.valign = "t"
- @property
- def align(self):
- """Controls alignment of fields
- Arguments:
- align - alignment, one of "l", "c", or "r" """
- return self._align
- @align.setter
- def align(self, val):
- if not self._field_names:
- self._align = {}
- elif val is None or (isinstance(val, dict) and len(val) == 0):
- for field in self._field_names:
- self._align[field] = "c"
- else:
- self._validate_align(val)
- for field in self._field_names:
- self._align[field] = val
- @property
- def valign(self):
- """Controls vertical alignment of fields
- Arguments:
- valign - vertical alignment, one of "t", "m", or "b" """
- return self._valign
- @valign.setter
- def valign(self, val):
- if not self._field_names:
- self._valign = {}
- elif val is None or (isinstance(val, dict) and len(val) == 0):
- for field in self._field_names:
- self._valign[field] = "t"
- else:
- self._validate_valign(val)
- for field in self._field_names:
- self._valign[field] = val
- @property
- def max_width(self):
- """Controls maximum width of fields
- Arguments:
- max_width - maximum width integer"""
- return self._max_width
- @max_width.setter
- def max_width(self, val):
- if val is None or (isinstance(val, dict) and len(val) == 0):
- self._max_width = {}
- else:
- self._validate_option("max_width", val)
- for field in self._field_names:
- self._max_width[field] = val
- @property
- def min_width(self):
- """Controls minimum width of fields
- Arguments:
- min_width - minimum width integer"""
- return self._min_width
- @min_width.setter
- def min_width(self, val):
- if val is None or (isinstance(val, dict) and len(val) == 0):
- self._min_width = {}
- else:
- self._validate_option("min_width", val)
- for field in self._field_names:
- self._min_width[field] = val
- @property
- def min_table_width(self):
- return self._min_table_width
- @min_table_width.setter
- def min_table_width(self, val):
- self._validate_option("min_table_width", val)
- self._min_table_width = val
- @property
- def max_table_width(self):
- return self._max_table_width
- @max_table_width.setter
- def max_table_width(self, val):
- self._validate_option("max_table_width", val)
- self._max_table_width = val
- @property
- def fields(self):
- """List or tuple of field names to include in displays"""
- return self._fields
- @fields.setter
- def fields(self, val):
- self._validate_option("fields", val)
- self._fields = val
- @property
- def title(self):
- """Optional table title
- Arguments:
- title - table title"""
- return self._title
- @title.setter
- def title(self, val):
- self._title = self._unicode(val)
- @property
- def start(self):
- """Start index of the range of rows to print
- Arguments:
- start - index of first data row to include in output"""
- return self._start
- @start.setter
- def start(self, val):
- self._validate_option("start", val)
- self._start = val
- @property
- def end(self):
- """End index of the range of rows to print
- Arguments:
- end - index of last data row to include in output PLUS ONE (list slice style)"""
- return self._end
- @end.setter
- def end(self, val):
- self._validate_option("end", val)
- self._end = val
- @property
- def sortby(self):
- """Name of field by which to sort rows
- Arguments:
- sortby - field name to sort by"""
- return self._sortby
- @sortby.setter
- def sortby(self, val):
- self._validate_option("sortby", val)
- self._sortby = val
- @property
- def reversesort(self):
- """Controls direction of sorting (ascending vs descending)
- Arguments:
- reveresort - set to True to sort by descending order, or False to sort by
- ascending order"""
- return self._reversesort
- @reversesort.setter
- def reversesort(self, val):
- self._validate_option("reversesort", val)
- self._reversesort = val
- @property
- def sort_key(self):
- """Sorting key function, applied to data points before sorting
- Arguments:
- sort_key - a function which takes one argument and returns something to be
- sorted"""
- return self._sort_key
- @sort_key.setter
- def sort_key(self, val):
- self._validate_option("sort_key", val)
- self._sort_key = val
- @property
- def header(self):
- """Controls printing of table header with field names
- Arguments:
- header - print a header showing field names (True or False)"""
- return self._header
- @header.setter
- def header(self, val):
- self._validate_option("header", val)
- self._header = val
- @property
- def header_style(self):
- """Controls stylisation applied to field names in header
- Arguments:
- header_style - stylisation to apply to field names in header
- ("cap", "title", "upper", "lower" or None)"""
- return self._header_style
- @header_style.setter
- def header_style(self, val):
- self._validate_header_style(val)
- self._header_style = val
- @property
- def border(self):
- """Controls printing of border around table
- Arguments:
- border - print a border around the table (True or False)"""
- return self._border
- @border.setter
- def border(self, val):
- self._validate_option("border", val)
- self._border = val
- @property
- def hrules(self):
- """Controls printing of horizontal rules after rows
- Arguments:
- hrules - horizontal rules style. Allowed values: FRAME, ALL, HEADER, NONE"""
- return self._hrules
- @hrules.setter
- def hrules(self, val):
- self._validate_option("hrules", val)
- self._hrules = val
- @property
- def vrules(self):
- """Controls printing of vertical rules between columns
- Arguments:
- vrules - vertical rules style. Allowed values: FRAME, ALL, NONE"""
- return self._vrules
- @vrules.setter
- def vrules(self, val):
- self._validate_option("vrules", val)
- self._vrules = val
- @property
- def int_format(self):
- """Controls formatting of integer data
- Arguments:
- int_format - integer format string"""
- return self._int_format
- @int_format.setter
- def int_format(self, val):
- if val is None or (isinstance(val, dict) and len(val) == 0):
- self._int_format = {}
- else:
- self._validate_option("int_format", val)
- for field in self._field_names:
- self._int_format[field] = val
- @property
- def float_format(self):
- """Controls formatting of floating point data
- Arguments:
- float_format - floating point format string"""
- return self._float_format
- @float_format.setter
- def float_format(self, val):
- if val is None or (isinstance(val, dict) and len(val) == 0):
- self._float_format = {}
- else:
- self._validate_option("float_format", val)
- for field in self._field_names:
- self._float_format[field] = val
- @property
- def padding_width(self):
- """The number of empty spaces between a column's edge and its content
- Arguments:
- padding_width - number of spaces, must be a positive integer"""
- return self._padding_width
- @padding_width.setter
- def padding_width(self, val):
- self._validate_option("padding_width", val)
- self._padding_width = val
- @property
- def left_padding_width(self):
- """The number of empty spaces between a column's left edge and its content
- Arguments:
- left_padding - number of spaces, must be a positive integer"""
- return self._left_padding_width
- @left_padding_width.setter
- def left_padding_width(self, val):
- self._validate_option("left_padding_width", val)
- self._left_padding_width = val
- @property
- def right_padding_width(self):
- """The number of empty spaces between a column's right edge and its content
- Arguments:
- right_padding - number of spaces, must be a positive integer"""
- return self._right_padding_width
- @right_padding_width.setter
- def right_padding_width(self, val):
- self._validate_option("right_padding_width", val)
- self._right_padding_width = val
- @property
- def vertical_char(self):
- """The character used when printing table borders to draw vertical lines
- Arguments:
- vertical_char - single character string used to draw vertical lines"""
- return self._vertical_char
- @vertical_char.setter
- def vertical_char(self, val):
- val = self._unicode(val)
- self._validate_option("vertical_char", val)
- self._vertical_char = val
- @property
- def horizontal_char(self):
- """The character used when printing table borders to draw horizontal lines
- Arguments:
- horizontal_char - single character string used to draw horizontal lines"""
- return self._horizontal_char
- @horizontal_char.setter
- def horizontal_char(self, val):
- val = self._unicode(val)
- self._validate_option("horizontal_char", val)
- self._horizontal_char = val
- @property
- def junction_char(self):
- """The character used when printing table borders to draw line junctions
- Arguments:
- junction_char - single character string used to draw line junctions"""
- return self._junction_char
- @junction_char.setter
- def junction_char(self, val):
- val = self._unicode(val)
- self._validate_option("vertical_char", val)
- self._junction_char = val
- @property
- def format(self):
- """Controls whether or not HTML tables are formatted to match styling options
- Arguments:
- format - True or False"""
- return self._format
- @format.setter
- def format(self, val):
- self._validate_option("format", val)
- self._format = val
- @property
- def print_empty(self):
- """Controls whether or not empty tables produce a header and frame or just an
- empty string
- Arguments:
- print_empty - True or False"""
- return self._print_empty
- @print_empty.setter
- def print_empty(self, val):
- self._validate_option("print_empty", val)
- self._print_empty = val
- @property
- def attributes(self):
- """A dictionary of HTML attribute name/value pairs to be included in the
- <table> tag when printing HTML
- Arguments:
- attributes - dictionary of attributes"""
- return self._attributes
- @attributes.setter
- def attributes(self, val):
- self._validate_option("attributes", val)
- self._attributes = val
- @property
- def oldsortslice(self):
- """ oldsortslice - Slice rows before sorting in the "old style" """
- return self._oldsortslice
- @oldsortslice.setter
- def oldsortslice(self, val):
- self._validate_option("oldsortslice", val)
- self._oldsortslice = val
- ##############################
- # OPTION MIXER #
- ##############################
- def _get_options(self, kwargs):
- options = {}
- for option in self._options:
- if option in kwargs:
- self._validate_option(option, kwargs[option])
- options[option] = kwargs[option]
- else:
- options[option] = getattr(self, "_" + option)
- return options
- ##############################
- # PRESET STYLE LOGIC #
- ##############################
- def set_style(self, style):
- if style == DEFAULT:
- self._set_default_style()
- elif style == MSWORD_FRIENDLY:
- self._set_msword_style()
- elif style == PLAIN_COLUMNS:
- self._set_columns_style()
- elif style == MARKDOWN:
- self._set_markdown_style()
- elif style == ORGMODE:
- self._set_orgmode_style()
- elif style == RANDOM:
- self._set_random_style()
- else:
- raise Exception("Invalid pre-set style!")
- def _set_orgmode_style(self):
- self._set_default_style()
- self.orgmode = True
- def _set_markdown_style(self):
- self.header = True
- self.border = True
- self._hrules = None
- self.padding_width = 1
- self.left_padding_width = 1
- self.right_padding_width = 1
- self.vertical_char = "|"
- self.junction_char = "|"
- def _set_default_style(self):
- self.header = True
- self.border = True
- self._hrules = FRAME
- self._vrules = ALL
- self.padding_width = 1
- self.left_padding_width = 1
- self.right_padding_width = 1
- self.vertical_char = "|"
- self.horizontal_char = "-"
- self.junction_char = "+"
- def _set_msword_style(self):
- self.header = True
- self.border = True
- self._hrules = NONE
- self.padding_width = 1
- self.left_padding_width = 1
- self.right_padding_width = 1
- self.vertical_char = "|"
- def _set_columns_style(self):
- self.header = True
- self.border = False
- self.padding_width = 1
- self.left_padding_width = 0
- self.right_padding_width = 8
- def _set_random_style(self):
- # Just for fun!
- self.header = random.choice((True, False))
- self.border = random.choice((True, False))
- self._hrules = random.choice((ALL, FRAME, HEADER, NONE))
- self._vrules = random.choice((ALL, FRAME, NONE))
- self.left_padding_width = random.randint(0, 5)
- self.right_padding_width = random.randint(0, 5)
- self.vertical_char = random.choice(r"~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
- self.horizontal_char = random.choice(r"~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
- self.junction_char = random.choice(r"~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
- ##############################
- # DATA INPUT METHODS #
- ##############################
- def add_row(self, row):
- """Add a row to the table
- Arguments:
- row - row of data, should be a list with as many elements as the table
- has fields"""
- if self._field_names and len(row) != len(self._field_names):
- raise Exception(
- "Row has incorrect number of values, (actual) %d!=%d (expected)"
- % (len(row), len(self._field_names))
- )
- if not self._field_names:
- self.field_names = [("Field %d" % (n + 1)) for n in range(0, len(row))]
- self._rows.append(list(row))
- def del_row(self, row_index):
- """Delete a row from the table
- Arguments:
- row_index - The index of the row you want to delete. Indexing starts at 0."""
- if row_index > len(self._rows) - 1:
- raise Exception(
- "Can't delete row at index %d, table only has %d rows!"
- % (row_index, len(self._rows))
- )
- del self._rows[row_index]
- def add_column(self, fieldname, column, align="c", valign="t"):
- """Add a column to the table.
- Arguments:
- fieldname - name of the field to contain the new column of data
- column - column of data, should be a list with as many elements as the
- table has rows
- align - desired alignment for this column - "l" for left, "c" for centre and
- "r" for right
- valign - desired vertical alignment for new columns - "t" for top,
- "m" for middle and "b" for bottom"""
- if len(self._rows) in (0, len(column)):
- self._validate_align(align)
- self._validate_valign(valign)
- self._field_names.append(fieldname)
- self._align[fieldname] = align
- self._valign[fieldname] = valign
- for i in range(0, len(column)):
- if len(self._rows) < i + 1:
- self._rows.append([])
- self._rows[i].append(column[i])
- else:
- raise Exception(
- "Column length %d does not match number of rows %d!"
- % (len(column), len(self._rows))
- )
- def del_column(self, fieldname):
- """Delete a column from the table
- Arguments:
- fieldname - The field name of the column you want to delete."""
- if fieldname not in self._field_names:
- raise Exception(
- "Can't delete column %r which is not a field name of this table."
- " Field names are: %s"
- % (fieldname, ", ".join(map(repr, self._field_names)))
- )
- col_index = self._field_names.index(fieldname)
- del self._field_names[col_index]
- for row in self._rows:
- del row[col_index]
- def clear_rows(self):
- """Delete all rows from the table but keep the current field names"""
- self._rows = []
- def clear(self):
- """Delete all rows and field names from the table, maintaining nothing but
- styling options"""
- self._rows = []
- self._field_names = []
- self._widths = []
- ##############################
- # MISC PUBLIC METHODS #
- ##############################
- def copy(self):
- return copy.deepcopy(self)
- ##############################
- # MISC PRIVATE METHODS #
- ##############################
- def _format_value(self, field, value):
- if isinstance(value, int) and field in self._int_format:
- value = self._unicode(("%%%sd" % self._int_format[field]) % value)
- elif isinstance(value, float) and field in self._float_format:
- value = self._unicode(("%%%sf" % self._float_format[field]) % value)
- return self._unicode(value)
- def _compute_table_width(self, options):
- table_width = 2 if options["vrules"] in (FRAME, ALL) else 0
- per_col_padding = sum(self._get_padding_widths(options))
- for index, fieldname in enumerate(self.field_names):
- if not options["fields"] or (
- options["fields"] and fieldname in options["fields"]
- ):
- table_width += self._widths[index] + per_col_padding
- return table_width
- def _compute_widths(self, rows, options):
- if options["header"]:
- widths = [_get_size(field)[0] for field in self._field_names]
- else:
- widths = len(self.field_names) * [0]
- for row in rows:
- for index, value in enumerate(row):
- fieldname = self.field_names[index]
- if fieldname in self.max_width:
- widths[index] = max(
- widths[index],
- min(_get_size(value)[0], self.max_width[fieldname]),
- )
- else:
- widths[index] = max(widths[index], _get_size(value)[0])
- if fieldname in self.min_width:
- widths[index] = max(widths[index], self.min_width[fieldname])
- self._widths = widths
- # Are we exceeding max_table_width?
- if self._max_table_width:
- table_width = self._compute_table_width(options)
- if table_width > self._max_table_width:
- # Shrink widths in proportion
- scale = 1.0 * self._max_table_width / table_width
- widths = [int(math.floor(w * scale)) for w in widths]
- self._widths = widths
- # Are we under min_table_width or title width?
- if self._min_table_width or options["title"]:
- if options["title"]:
- title_width = len(options["title"]) + sum(
- self._get_padding_widths(options)
- )
- if options["vrules"] in (FRAME, ALL):
- title_width += 2
- else:
- title_width = 0
- min_table_width = self.min_table_width or 0
- min_width = max(title_width, min_table_width)
- table_width = self._compute_table_width(options)
- if table_width < min_width:
- # Grow widths in proportion
- scale = 1.0 * min_width / table_width
- widths = [int(math.ceil(w * scale)) for w in widths]
- self._widths = widths
- def _get_padding_widths(self, options):
- if options["left_padding_width"] is not None:
- lpad = options["left_padding_width"]
- else:
- lpad = options["padding_width"]
- if options["right_padding_width"] is not None:
- rpad = options["right_padding_width"]
- else:
- rpad = options["padding_width"]
- return lpad, rpad
- def _get_rows(self, options):
- """Return only those data rows that should be printed, based on slicing and
- sorting.
- Arguments:
- options - dictionary of option settings."""
- if options["oldsortslice"]:
- rows = copy.deepcopy(self._rows[options["start"] : options["end"]])
- else:
- rows = copy.deepcopy(self._rows)
- # Sort
- if options["sortby"]:
- sortindex = self._field_names.index(options["sortby"])
- # Decorate
- rows = [[row[sortindex]] + row for row in rows]
- # Sort
- rows.sort(reverse=options["reversesort"], key=options["sort_key"])
- # Undecorate
- rows = [row[1:] for row in rows]
- # Slice if necessary
- if not options["oldsortslice"]:
- rows = rows[options["start"] : options["end"]]
- return rows
- def _format_row(self, row, options):
- return [
- self._format_value(field, value)
- for (field, value) in zip(self._field_names, row)
- ]
- def _format_rows(self, rows, options):
- return [self._format_row(row, options) for row in rows]
- ##############################
- # PLAIN TEXT STRING METHODS #
- ##############################
- def get_string(self, **kwargs):
- """Return string representation of table in current state.
- Arguments:
- title - optional table title
- start - index of first data row to include in output
- end - index of last data row to include in output PLUS ONE (list slice style)
- fields - names of fields (columns) to include
- header - print a header showing field names (True or False)
- border - print a border around the table (True or False)
- hrules - controls printing of horizontal rules after rows.
- Allowed values: ALL, FRAME, HEADER, NONE
- vrules - controls printing of vertical rules between columns.
- Allowed values: FRAME, ALL, NONE
- int_format - controls formatting of integer data
- float_format - controls formatting of floating point data
- padding_width - number of spaces on either side of column data (only used if
- left and right paddings are None)
- left_padding_width - number of spaces on left hand side of column data
- right_padding_width - number of spaces on right hand side of column data
- vertical_char - single character string used to draw vertical lines
- horizontal_char - single character string used to draw horizontal lines
- junction_char - single character string used to draw line junctions
- sortby - name of field to sort rows by
- sort_key - sorting key function, applied to data points before sorting
- reversesort - True or False to sort in descending or ascending order
- print empty - if True, stringify just the header for an empty table,
- if False return an empty string"""
- options = self._get_options(kwargs)
- lines = []
- # Don't think too hard about an empty table
- # Is this the desired behaviour? Maybe we should still print the header?
- if self.rowcount == 0 and (not options["print_empty"] or not options["border"]):
- return ""
- # Get the rows we need to print, taking into account slicing, sorting, etc.
- rows = self._get_rows(options)
- # Turn all data in all rows into Unicode, formatted as desired
- formatted_rows = self._format_rows(rows, options)
- # Compute column widths
- self._compute_widths(formatted_rows, options)
- self._hrule = self._stringify_hrule(options)
- # Add title
- title = options["title"] or self._title
- if title:
- lines.append(self._stringify_title(title, options))
- # Add header or top of border
- if options["header"]:
- lines.append(self._stringify_header(options))
- elif options["border"] and options["hrules"] in (ALL, FRAME):
- lines.append(self._hrule)
- # Add rows
- for row in formatted_rows:
- lines.append(self._stringify_row(row, options))
- # Add bottom of border
- if options["border"] and options["hrules"] == FRAME:
- lines.append(self._hrule)
- if "orgmode" in self.__dict__ and self.orgmode is True:
- tmp = list()
- for line in lines:
- tmp.extend(line.split("\n"))
- lines = ["|" + line[1:-1] + "|" for line in tmp]
- return self._unicode("\n").join(lines)
- def _stringify_hrule(self, options):
- if not options["border"]:
- return ""
- lpad, rpad = self._get_padding_widths(options)
- if options["vrules"] in (ALL, FRAME):
- bits = [options["junction_char"]]
- else:
- bits = [options["horizontal_char"]]
- # For tables with no data or fieldnames
- if not self._field_names:
- bits.append(options["junction_char"])
- return "".join(bits)
- for field, width in zip(self._field_names, self._widths):
- if options["fields"] and field not in options["fields"]:
- continue
- bits.append((width + lpad + rpad) * options["horizontal_char"])
- if options["vrules"] == ALL:
- bits.append(options["junction_char"])
- else:
- bits.append(options["horizontal_char"])
- if options["vrules"] == FRAME:
- bits.pop()
- bits.append(options["junction_char"])
- return "".join(bits)
- def _stringify_title(self, title, options):
- lines = []
- lpad, rpad = self._get_padding_widths(options)
- if options["border"]:
- if options["vrules"] == ALL:
- options["vrules"] = FRAME
- lines.append(self._stringify_hrule(options))
- options["vrules"] = ALL
- elif options["vrules"] == FRAME:
- lines.append(self._stringify_hrule(options))
- bits = []
- endpoint = (
- options["vertical_char"] if options["vrules"] in (ALL, FRAME) else " "
- )
- bits.append(endpoint)
- title = " " * lpad + title + " " * rpad
- bits.append(self._justify(title, len(self._hrule) - 2, "c"))
- bits.append(endpoint)
- lines.append("".join(bits))
- return "\n".join(lines)
- def _stringify_header(self, options):
- bits = []
- lpad, rpad = self._get_padding_widths(options)
- if options["border"]:
- if options["hrules"] in (ALL, FRAME):
- bits.append(self._hrule)
- bits.append("\n")
- if options["vrules"] in (ALL, FRAME):
- bits.append(options["vertical_char"])
- else:
- bits.append(" ")
- # For tables with no data or field names
- if not self._field_names:
- if options["vrules"] in (ALL, FRAME):
- bits.append(options["vertical_char"])
- else:
- bits.append(" ")
- for (field, width) in zip(self._field_names, self._widths):
- if options["fields"] and field not in options["fields"]:
- continue
- if self._header_style == "cap":
- fieldname = field.capitalize()
- elif self._header_style == "title":
- fieldname = field.title()
- elif self._header_style == "upper":
- fieldname = field.upper()
- elif self._header_style == "lower":
- fieldname = field.lower()
- else:
- fieldname = field
- bits.append(
- " " * lpad
- + self._justify(fieldname, width, self._align[field])
- + " " * rpad
- )
- if options["border"]:
- if options["vrules"] == ALL:
- bits.append(options["vertical_char"])
- else:
- bits.append(" ")
- # If vrules is FRAME, then we just appended a space at the end
- # of the last field, when we really want a vertical character
- if options["border"] and options["vrules"] == FRAME:
- bits.pop()
- bits.append(options["vertical_char"])
- if options["border"] and options["hrules"] != NONE:
- bits.append("\n")
- bits.append(self._hrule)
- return "".join(bits)
- def _stringify_row(self, row, options):
- for (index, field, value, width) in zip(
- range(0, len(row)), self._field_names, row, self._widths
- ):
- # Enforce max widths
- lines = value.split("\n")
- new_lines = []
- for line in lines:
- if _str_block_width(line) > width:
- line = textwrap.fill(line, width)
- new_lines.append(line)
- lines = new_lines
- value = "\n".join(lines)
- row[index] = value
- row_height = 0
- for c in row:
- h = _get_size(c)[1]
- if h > row_height:
- row_height = h
- bits = []
- lpad, rpad = self._get_padding_widths(options)
- for y in range(0, row_height):
- bits.append([])
- if options["border"]:
- if options["vrules"] in (ALL, FRAME):
- bits[y].append(self.vertical_char)
- else:
- bits[y].append(" ")
- for (field, value, width) in zip(self._field_names, row, self._widths):
- valign = self._valign[field]
- lines = value.split("\n")
- d_height = row_height - len(lines)
- if d_height:
- if valign == "m":
- lines = (
- [""] * int(d_height / 2)
- + lines
- + [""] * (d_height - int(d_height / 2))
- )
- elif valign == "b":
- lines = [""] * d_height + lines
- else:
- lines = lines + [""] * d_height
- y = 0
- for line in lines:
- if options["fields"] and field not in options["fields"]:
- continue
- bits[y].append(
- " " * lpad
- + self._justify(line, width, self._align[field])
- + " " * rpad
- )
- if options["border"]:
- if options["vrules"] == ALL:
- bits[y].append(self.vertical_char)
- else:
- bits[y].append(" ")
- y += 1
- # If vrules is FRAME, then we just appended a space at the end
- # of the last field, when we really want a vertical character
- for y in range(0, row_height):
- if options["border"] and options["vrules"] == FRAME:
- bits[y].pop()
- bits[y].append(options["vertical_char"])
- if options["border"] and options["hrules"] == ALL:
- bits[row_height - 1].append("\n")
- bits[row_height - 1].append(self._hrule)
- for y in range(0, row_height):
- bits[y] = "".join(bits[y])
- return "\n".join(bits)
- def paginate(self, page_length=58, **kwargs):
- pages = []
- kwargs["start"] = kwargs.get("start", 0)
- true_end = kwargs.get("end", self.rowcount)
- while True:
- kwargs["end"] = min(kwargs["start"] + page_length, true_end)
- pages.append(self.get_string(**kwargs))
- if kwargs["end"] == true_end:
- break
- kwargs["start"] += page_length
- return "\f".join(pages)
- ##############################
- # CSV STRING METHODS #
- ##############################
- def get_csv_string(self, **kwargs):
- """Return string representation of CSV formatted table in the current state
- Keyword arguments are first interpreted as table formatting options, and
- then any unused keyword arguments are passed to csv.writer(). For
- example, get_csv_string(header=False, delimiter='\t') would use
- header as a PrettyTable formatting option (skip the header row) and
- delimiter as a csv.writer keyword argument.
- """
- options = self._get_options(kwargs)
- csv_options = {
- key: value for key, value in kwargs.items() if key not in options
- }
- csv_buffer = StringIO()
- csv_writer = csv.writer(csv_buffer, **csv_options)
- if options.get("header"):
- csv_writer.writerow(self._field_names)
- for row in self._get_rows(options):
- csv_writer.writerow(row)
- return csv_buffer.getvalue()
- ##############################
- # JSON STRING METHODS #
- ##############################
- def get_json_string(self, **kwargs):
- """Return string representation of JSON formatted table in the current state
- Arguments:
- none yet"""
- options = self._get_options(kwargs)
- objects = [self.field_names]
- for row in self._get_rows(options):
- objects.append(dict(zip(self._field_names, row)))
- return json.dumps(objects, indent=4, separators=(",", ": "), sort_keys=True)
- ##############################
- # HTML STRING METHODS #
- ##############################
- def get_html_string(self, **kwargs):
- """Return string representation of HTML formatted version of table in current
- state.
- Arguments:
- title - optional table title
- start - index of first data row to include in output
- end - index of last data row to include in output PLUS ONE (list slice style)
- fields - names of fields (columns) to include
- header - print a header showing field names (True or False)
- border - print a border around the table (True or False)
- hrules - controls printing of horizontal rules after rows.
- Allowed values: ALL, FRAME, HEADER, NONE
- vrules - controls printing of vertical rules between columns.
- Allowed values: FRAME, ALL, NONE
- int_format - controls formatting of integer data
- float_format - controls formatting of floating point data
- padding_width - number of spaces on either side of column data (only used if
- left and right paddings are None)
- left_padding_width - number of spaces on left hand side of column data
- right_padding_width - number of spaces on right hand side of column data
- sortby - name of field to sort rows by
- sort_key - sorting key function, applied to data points before sorting
- attributes - dictionary of name/value pairs to include as HTML attributes in the
- <table> tag
- xhtml - print <br/> tags if True, <br> tags if false"""
- options = self._get_options(kwargs)
- if options["format"]:
- string = self._get_formatted_html_string(options)
- else:
- string = self._get_simple_html_string(options)
- return string
- def _get_simple_html_string(self, options):
- lines = []
- if options["xhtml"]:
- linebreak = "<br/>"
- else:
- linebreak = "<br>"
- open_tag = ["<table"]
- if options["attributes"]:
- for attr_name in options["attributes"]:
- open_tag.append(
- ' {}="{}"'.format(attr_name, options["attributes"][attr_name])
- )
- open_tag.append(">")
- lines.append("".join(open_tag))
- # Title
- title = options["title"] or self._title
- if title:
- cols = (
- len(options["fields"]) if options["fields"] else len(self.field_names)
- )
- lines.append(" <tr>")
- lines.append(" <td colspan=%d>%s</td>" % (cols, title))
- lines.append(" </tr>")
- # Headers
- if options["header"]:
- lines.append(" <tr>")
- for field in self._field_names:
- if options["fields"] and field not in options["fields"]:
- continue
- lines.append(
- " <th>%s</th>" % escape(field).replace("\n", linebreak)
- )
- lines.append(" </tr>")
- # Data
- rows = self._get_rows(options)
- formatted_rows = self._format_rows(rows, options)
- for row in formatted_rows:
- lines.append(" <tr>")
- for field, datum in zip(self._field_names, row):
- if options["fields"] and field not in options["fields"]:
- continue
- lines.append(
- " <td>%s</td>" % escape(datum).replace("\n", linebreak)
- )
- lines.append(" </tr>")
- lines.append("</table>")
- return self._unicode("\n").join(lines)
- def _get_formatted_html_string(self, options):
- lines = []
- lpad, rpad = self._get_padding_widths(options)
- if options["xhtml"]:
- linebreak = "<br/>"
- else:
- linebreak = "<br>"
- open_tag = ["<table"]
- if options["border"]:
- if options["hrules"] == ALL and options["vrules"] == ALL:
- open_tag.append(' frame="box" rules="all"')
- elif options["hrules"] == FRAME and options["vrules"] == FRAME:
- open_tag.append(' frame="box"')
- elif options["hrules"] == FRAME and options["vrules"] == ALL:
- open_tag.append(' frame="box" rules="cols"')
- elif options["hrules"] == FRAME:
- open_tag.append(' frame="hsides"')
- elif options["hrules"] == ALL:
- open_tag.append(' frame="hsides" rules="rows"')
- elif options["vrules"] == FRAME:
- open_tag.append(' frame="vsides"')
- elif options["vrules"] == ALL:
- open_tag.append(' frame="vsides" rules="cols"')
- if options["attributes"]:
- for attr_name in options["attributes"]:
- open_tag.append(
- ' {}="{}"'.format(attr_name, options["attributes"][attr_name])
- )
- open_tag.append(">")
- lines.append("".join(open_tag))
- # Title
- title = options["title"] or self._title
- if title:
- cols = (
- len(options["fields"]) if options["fields"] else len(self.field_names)
- )
- lines.append(" <tr>")
- lines.append(" <td colspan=%d>%s</td>" % (cols, title))
- lines.append(" </tr>")
- # Headers
- if options["header"]:
- lines.append(" <tr>")
- for field in self._field_names:
- if options["fields"] and field not in options["fields"]:
- continue
- lines.append(
- ' <th style="padding-left: %dem; padding-right: %dem; text-align: center">%s</th>' # noqa: E501
- % (lpad, rpad, escape(field).replace("\n", linebreak))
- )
- lines.append(" </tr>")
- # Data
- rows = self._get_rows(options)
- formatted_rows = self._format_rows(rows, options)
- aligns = []
- valigns = []
- for field in self._field_names:
- aligns.append(
- {"l": "left", "r": "right", "c": "center"}[self._align[field]]
- )
- valigns.append(
- {"t": "top", "m": "middle", "b": "bottom"}[self._valign[field]]
- )
- for row in formatted_rows:
- lines.append(" <tr>")
- for field, datum, align, valign in zip(
- self._field_names, row, aligns, valigns
- ):
- if options["fields"] and field not in options["fields"]:
- continue
- lines.append(
- ' <td style="padding-left: %dem; padding-right: %dem; text-align: %s; vertical-align: %s">%s</td>' # noqa: E501
- % (
- lpad,
- rpad,
- align,
- valign,
- escape(datum).replace("\n", linebreak),
- )
- )
- lines.append(" </tr>")
- lines.append("</table>")
- return self._unicode("\n").join(lines)
- ##############################
- # UNICODE WIDTH FUNCTION #
- ##############################
- def _str_block_width(val):
- return wcwidth.wcswidth(_re.sub("", val))
- ##############################
- # TABLE FACTORIES #
- ##############################
- def from_csv(fp, field_names=None, **kwargs):
- fmtparams = {}
- for param in [
- "delimiter",
- "doublequote",
- "escapechar",
- "lineterminator",
- "quotechar",
- "quoting",
- "skipinitialspace",
- "strict",
- ]:
- if param in kwargs:
- fmtparams[param] = kwargs.pop(param)
- if fmtparams:
- reader = csv.reader(fp, **fmtparams)
- else:
- dialect = csv.Sniffer().sniff(fp.read(1024))
- fp.seek(0)
- reader = csv.reader(fp, dialect)
- table = PrettyTable(**kwargs)
- if field_names:
- table.field_names = field_names
- else:
- if py3k:
- table.field_names = [x.strip() for x in next(reader)]
- else:
- table.field_names = [x.strip() for x in reader.next()]
- for row in reader:
- table.add_row([x.strip() for x in row])
- return table
- def from_db_cursor(cursor, **kwargs):
- if cursor.description:
- table = PrettyTable(**kwargs)
- table.field_names = [col[0] for col in cursor.description]
- for row in cursor.fetchall():
- table.add_row(row)
- return table
- def from_json(json_string, **kwargs):
- table = PrettyTable(**kwargs)
- objects = json.loads(json_string)
- table.field_names = objects[0]
- for obj in objects[1:]:
- row = [obj[key] for key in table.field_names]
- table.add_row(row)
- return table
- class TableHandler(HTMLParser):
- def __init__(self, **kwargs):
- HTMLParser.__init__(self)
- self.kwargs = kwargs
- self.tables = []
- self.last_row = []
- self.rows = []
- self.max_row_width = 0
- self.active = None
- self.last_content = ""
- self.is_last_row_header = False
- self.colspan = 0
- def handle_starttag(self, tag, attrs):
- self.active = tag
- if tag == "th":
- self.is_last_row_header = True
- for (key, value) in attrs:
- if key == "colspan":
- self.colspan = int(value)
- def handle_endtag(self, tag):
- if tag in ["th", "td"]:
- stripped_content = self.last_content.strip()
- self.last_row.append(stripped_content)
- if self.colspan:
- for i in range(1, self.colspan):
- self.last_row.append("")
- self.colspan = 0
- if tag == "tr":
- self.rows.append((self.last_row, self.is_last_row_header))
- self.max_row_width = max(self.max_row_width, len(self.last_row))
- self.last_row = []
- self.is_last_row_header = False
- if tag == "table":
- table = self.generate_table(self.rows)
- self.tables.append(table)
- self.rows = []
- self.last_content = " "
- self.active = None
- def handle_data(self, data):
- self.last_content += data
- def generate_table(self, rows):
- """
- Generates from a list of rows a PrettyTable object.
- """
- table = PrettyTable(**self.kwargs)
- for row in self.rows:
- if len(row[0]) < self.max_row_width:
- appends = self.max_row_width - len(row[0])
- for i in range(1, appends):
- row[0].append("-")
- if row[1]:
- self.make_fields_unique(row[0])
- table.field_names = row[0]
- else:
- table.add_row(row[0])
- return table
- def make_fields_unique(self, fields):
- """
- iterates over the row and make each field unique
- """
- for i in range(0, len(fields)):
- for j in range(i + 1, len(fields)):
- if fields[i] == fields[j]:
- fields[j] += "'"
- def from_html(html_code, **kwargs):
- """
- Generates a list of PrettyTables from a string of HTML code. Each <table> in
- the HTML becomes one PrettyTable object.
- """
- parser = TableHandler(**kwargs)
- parser.feed(html_code)
- return parser.tables
- def from_html_one(html_code, **kwargs):
- """
- Generates a PrettyTables from a string of HTML code which contains only a
- single <table>
- """
- tables = from_html(html_code, **kwargs)
- try:
- assert len(tables) == 1
- except AssertionError:
- raise Exception(
- "More than one <table> in provided HTML code! Use from_html instead."
- )
- return tables[0]
- ##############################
- # MAIN (TEST FUNCTION) #
- ##############################
- def main():
- print("Generated using setters:")
- x = PrettyTable(["City name", "Area", "Population", "Annual Rainfall"])
- x.title = "Australian capital cities"
- x.sortby = "Population"
- x.reversesort = True
- x.int_format["Area"] = "04"
- x.float_format = "6.1"
- x.align["City name"] = "l" # Left align city names
- x.add_row(["Adelaide", 1295, 1158259, 600.5])
- x.add_row(["Brisbane", 5905, 1857594, 1146.4])
- x.add_row(["Darwin", 112, 120900, 1714.7])
- x.add_row(["Hobart", 1357, 205556, 619.5])
- x.add_row(["Sydney", 2058, 4336374, 1214.8])
- x.add_row(["Melbourne", 1566, 3806092, 646.9])
- x.add_row(["Perth", 5386, 1554769, 869.4])
- print(x)
- print()
- print("Generated using constructor arguments:")
- y = PrettyTable(
- ["City name", "Area", "Population", "Annual Rainfall"],
- title="Australian capital cities",
- sortby="Population",
- reversesort=True,
- int_format="04",
- float_format="6.1",
- max_width=12,
- min_width=4,
- align="c",
- valign="t",
- )
- y.align["City name"] = "l" # Left align city names
- y.add_row(["Adelaide", 1295, 1158259, 600.5])
- y.add_row(["Brisbane", 5905, 1857594, 1146.4])
- y.add_row(["Darwin", 112, 120900, 1714.7])
- y.add_row(["Hobart", 1357, 205556, 619.5])
- y.add_row(["Sydney", 2058, 4336374, 1214.8])
- y.add_row(["Melbourne", 1566, 3806092, 646.9])
- y.add_row(["Perth", 5386, 1554769, 869.4])
- print(y)
- if __name__ == "__main__":
- main()
|