"""cli2 also offers a simple table rendering data that will do it's best to wordwrap cell data so that it fits in the terminal.This Table module behaves like a list, and is pretty simple. Its purpose is totabulate data and it's going to brute force output sizes until it finds amatch.As such, it's not a good module to display really a lot of data because itsacrifies performance for human readability... code-block:: python cli2.Table.factory(['foo', 'bar'], ['much longer', 'test']).print().. code-block:: foo bar much longer test.. note:: This would look better with :py:mod:`~cli2.color`.. code-block:: python cli2.Table.factory(dict(foo=1, bar=2), dict(foo=3, bar=4)).print()Renders:.. code-block:: foo bar === === 1 2 3 4"""importosimporttextwrapfrom.colorsimportcolors
# size taken by the length of every columndefsumsize(columns):sumsize=sum([c.maxlengthforcincolumns])iflen(columns)>1:# add one space in-between each columnsumsize+=len(columns)-1returnsumsize
[docs]@classmethoddeffactory(cls,*items):""" Instanciate a table with a bunch of items. :params items: Iterable of lists or dicts or tuples """self=cls()first=Truekind=Noneforiteminitems:ifnotkind:kind=type(item)elifkind!=type(item):raiseException('Data contains different types')ifisinstance(item,(list,tuple)):self.append([str(item)foriteminitem])elifisinstance(item,dict):iffirst:self.append([keyforkeyinitem.keys()])self.append(['='forkeyinitem.keys()])first=Falseself.append([str(value)forvalueinitem.values()])returnself
[docs]defcalculate_columns(self,termsize):""" Calculate columns size based on termsize. """columns=self.columns=[]forrowinself:forcolnum,iteminenumerate(row):iflen(columns)==colnum:column=Column()columns.append(column)else:column=columns[colnum]data=itemifisinstance(data,(list,tuple)):data=data[1]else:data=itemdata=str(data)minlength=max([len(word)forwordindata.split(' ')])ifminlength>column.minlength:column.minlength=minlengthlength=len(data)iflength>column.maxlength:column.maxlength=lengthcurrent=-1done=set()whilesumsize(columns)>termsizeandlen(done)<len(columns):current=currentifcurrent>=0elselen(columns)-1ifcurrentnotindone:# Let's start shrinking columns from the lastminlength=columns[current].minlengthmaxlength=columns[current].maxlengthifmaxlength-1>minlength:columns[current].maxlength=maxlength-1else:columns[current].maxlength=minlengthdone.add(columns[current])current-=1returncolumns
[docs]defprint(self,print_function=None,termsize=None):""" Print the table. """print_function=print_functionorprintifnottermsize:try:termsize=os.get_terminal_size().columnsexcept:# noqatermsize=80columns=self.calculate_columns(termsize=termsize)# separate columns with 2 spaces if possibleifsumsize(columns)+(len(columns)-1)*2<=termsize:numspaces=2else:numspaces=1rows=list(self)whilerows:row=rows.pop(0)line=[]leftovers=[''forcincolumns]forcolnum,columninenumerate(columns):data=row[colnum]ifisinstance(data,(list,tuple)):color=data[0]data=data[1]else:color=''data=row[colnum]data=str(data)wrapped=textwrap.wrap(data,column.maxlength)words=wrapped[0]ifwrappedelse''ifwords=='=':words='='*column.maxlengthline.append(words)ifcolnum+1<len(columns):# add spacesline[-1]+=' '*(column.maxlength-len(words))iflen(wrapped)>1:leftovers[colnum]=' '.join(wrapped[1:])ifcolor:line[-1]=str(color)+line[-1]+colors.resetforleftoverinleftovers:iflen(leftover):rows.insert(0,leftovers)breakprint_function((' '*numspaces).join(line))