#!/usr/bin/env python
""" Prints data in a table format.
This module serves as utility for other scripts.
"""
from __future__ import print_function
import sys, re, os.path, stat, math
try:
from html import escape
except ImportError:
from cgi import escape # Python 2.7
from optparse import OptionParser
from color import getColorizer, dummyColorizer
class tblCell(object):
def __init__(self, text, value = None, props = None):
self.text = text
self.value = value
self.props = props
class tblColumn(object):
def __init__(self, caption, title = None, props = None):
self.text = caption
self.title = title
self.props = props
class tblRow(object):
def __init__(self, colsNum, props = None):
self.cells = [None] * colsNum
self.props = props
def htmlEncode(str):
return '
'.join([escape(s) for s in str])
class table(object):
def_align = "left"
def_valign = "middle"
def_color = None
def_colspan = 1
def_rowspan = 1
def_bold = False
def_italic = False
def_text="-"
def __init__(self, caption = None, format=None):
self.format = format
self.is_markdown = self.format == 'markdown'
self.is_tabs = self.format == 'tabs'
self.columns = {}
self.rows = []
self.ridx = -1;
self.caption = caption
pass
def newRow(self, **properties):
if len(self.rows) - 1 == self.ridx:
self.rows.append(tblRow(len(self.columns), properties))
else:
self.rows[self.ridx + 1].props = properties
self.ridx += 1
return self.rows[self.ridx]
def trimLastRow(self):
if self.rows:
self.rows.pop()
if self.ridx >= len(self.rows):
self.ridx = len(self.rows) - 1
def newColumn(self, name, caption, title = None, **properties):
if name in self.columns:
index = self.columns[name].index
else:
index = len(self.columns)
if isinstance(caption, tblColumn):
caption.index = index
self.columns[name] = caption
return caption
else:
col = tblColumn(caption, title, properties)
col.index = index
self.columns[name] = col
return col
def getColumn(self, name):
if isinstance(name, str):
return self.columns.get(name, None)
else:
vals = [v for v in self.columns.values() if v.index == name]
if vals:
return vals[0]
return None
def newCell(self, col_name, text, value = None, **properties):
if self.ridx < 0:
self.newRow()
col = self.getColumn(col_name)
row = self.rows[self.ridx]
if not col:
return None
if isinstance(text, tblCell):
cl = text
else:
cl = tblCell(text, value, properties)
row.cells[col.index] = cl
return cl
def layoutTable(self):
columns = self.columns.values()
columns = sorted(columns, key=lambda c: c.index)
colspanned = []
rowspanned = []
self.headerHeight = 1
rowsToAppend = 0
for col in columns:
self.measureCell(col)
if col.height > self.headerHeight:
self.headerHeight = col.height
col.minwidth = col.width
col.line = None
for r in range(len(self.rows)):
row = self.rows[r]
row.minheight = 1
for i in range(len(row.cells)):
cell = row.cells[i]
if row.cells[i] is None:
continue
cell.line = None
self.measureCell(cell)
colspan = int(self.getValue("colspan", cell))
rowspan = int(self.getValue("rowspan", cell))
if colspan > 1:
colspanned.append((r,i))
if i + colspan > len(columns):
colspan = len(columns) - i
cell.colspan = colspan
#clear spanned cells
for j in range(i+1, min(len(row.cells), i + colspan)):
row.cells[j] = None
elif columns[i].minwidth < cell.width:
columns[i].minwidth = cell.width
if rowspan > 1:
rowspanned.append((r,i))
rowsToAppend2 = r + colspan - len(self.rows)
if rowsToAppend2 > rowsToAppend:
rowsToAppend = rowsToAppend2
cell.rowspan = rowspan
#clear spanned cells
for j in range(r+1, min(len(self.rows), r + rowspan)):
if len(self.rows[j].cells) > i:
self.rows[j].cells[i] = None
elif row.minheight < cell.height:
row.minheight = cell.height
self.ridx = len(self.rows) - 1
for r in range(rowsToAppend):
self.newRow()
self.rows[len(self.rows) - 1].minheight = 1
while colspanned:
colspanned_new = []
for r, c in colspanned:
cell = self.rows[r].cells[c]
sum([col.minwidth for col in columns[c:c + cell.colspan]])
cell.awailable = sum([col.minwidth for col in columns[c:c + cell.colspan]]) + cell.colspan - 1
if cell.awailable < cell.width:
colspanned_new.append((r,c))
colspanned = colspanned_new
if colspanned:
r,c = colspanned[0]
cell = self.rows[r].cells[c]
cols = columns[c:c + cell.colspan]
total = cell.awailable - cell.colspan + 1
budget = cell.width - cell.awailable
spent = 0
s = 0
for col in cols:
s += col.minwidth
addition = s * budget / total - spent
spent += addition
col.minwidth += addition
while rowspanned:
rowspanned_new = []
for r, c in rowspanned:
cell = self.rows[r].cells[c]
cell.awailable = sum([row.minheight for row in self.rows[r:r + cell.rowspan]])
if cell.awailable < cell.height:
rowspanned_new.append((r,c))
rowspanned = rowspanned_new
if rowspanned:
r,c = rowspanned[0]
cell = self.rows[r].cells[c]
rows = self.rows[r:r + cell.rowspan]
total = cell.awailable
budget = cell.height - cell.awailable
spent = 0
s = 0
for row in rows:
s += row.minheight
addition = s * budget / total - spent
spent += addition
row.minheight += addition
return columns
def measureCell(self, cell):
text = self.getValue("text", cell)
cell.text = self.reformatTextValue(text)
cell.height = len(cell.text)
cell.width = len(max(cell.text, key = lambda line: len(line)))
def reformatTextValue(self, value):
if sys.version_info >= (2,7):
unicode = str
if isinstance(value, str):
vstr = value
elif isinstance(value, unicode):
vstr = str(value)
else:
try:
vstr = '\n'.join([str(v) for v in value])
except TypeError:
vstr = str(value)
return vstr.splitlines()
def adjustColWidth(self, cols, width):
total = sum([c.minWidth for c in cols])
if total + len(cols) - 1 >= width:
return
budget = width - len(cols) + 1 - total
spent = 0
s = 0
for col in cols:
s += col.minWidth
addition = s * budget / total - spent
spent += addition
col.minWidth += addition
def getValue(self, name, *elements):
for el in elements:
try:
return getattr(el, name)
except AttributeError:
pass
try:
val = el.props[name]
if val:
return val
except AttributeError:
pass
except KeyError:
pass
try:
return getattr(self.__class__, "def_" + name)
except AttributeError:
return None
def consolePrintTable(self, out):
columns = self.layoutTable()
colrizer = getColorizer(out) if not (self.is_markdown or self.is_tabs) else dummyColorizer(out)
if self.caption:
out.write("%s%s%s" % ( os.linesep, os.linesep.join(self.reformatTextValue(self.caption)), os.linesep * 2))
headerRow = tblRow(len(columns), {"align": "center", "valign": "top", "bold": True, "header": True})
headerRow.cells = columns
headerRow.minheight = self.headerHeight
self.consolePrintRow2(colrizer, headerRow, columns)
for i in range(0, len(self.rows)):
self.consolePrintRow2(colrizer, i, columns)
def consolePrintRow2(self, out, r, columns):
if isinstance(r, tblRow):
row = r
r = -1
else:
row = self.rows[r]
#evaluate initial values for line numbers
i = 0
while i < len(row.cells):
cell = row.cells[i]
colspan = self.getValue("colspan", cell)
if cell is not None:
cell.wspace = sum([col.minwidth for col in columns[i:i + colspan]]) + colspan - 1
if cell.line is None:
if r < 0:
rows = [row]
else:
rows = self.rows[r:r + self.getValue("rowspan", cell)]
cell.line = self.evalLine(cell, rows, columns[i])
if len(rows) > 1:
for rw in rows:
rw.cells[i] = cell
i += colspan
#print content
if self.is_markdown:
out.write("|")
for c in row.cells:
text = ' '.join(self.getValue('text', c) or [])
out.write(text + "|")
out.write(os.linesep)
elif self.is_tabs:
cols_to_join=[' '.join(self.getValue('text', c) or []) for c in row.cells]
out.write('\t'.join(cols_to_join))
out.write(os.linesep)
else:
for ln in range(row.minheight):
i = 0
while i < len(row.cells):
if i > 0:
out.write(" ")
cell = row.cells[i]
column = columns[i]
if cell is None:
out.write(" " * column.minwidth)
i += 1
else:
self.consolePrintLine(cell, row, column, out)
i += self.getValue("colspan", cell)
if self.is_markdown:
out.write("|")
out.write(os.linesep)
if self.is_markdown and row.props.get('header', False):
out.write("|")
for th in row.cells:
align = self.getValue("align", th)
if align == 'center':
out.write(":-:|")
elif align == 'right':
out.write("--:|")
else:
out.write("---|")
out.write(os.linesep)
def consolePrintLine(self, cell, row, column, out):
if cell.line < 0 or cell.line >= cell.height:
line = ""
else:
line = cell.text[cell.line]
width = cell.wspace
align = self.getValue("align", ((None, cell)[isinstance(cell, tblCell)]), row, column)
if align == "right":
pattern = "%" + str(width) + "s"
elif align == "center":
pattern = "%" + str((width - len(line)) // 2 + len(line)) + "s" + " " * (width - len(line) - (width - len(line)) // 2)
else:
pattern = "%-" + str(width) + "s"
out.write(pattern % line, color = self.getValue("color", cell, row, column))
cell.line += 1
def evalLine(self, cell, rows, column):
height = cell.height
valign = self.getValue("valign", cell, rows[0], column)
space = sum([row.minheight for row in rows])
if valign == "bottom":
return height - space
if valign == "middle":
return (height - space + 1) // 2
return 0
def htmlPrintTable(self, out, embeedcss = False):
columns = self.layoutTable()
if embeedcss:
out.write("
\n" % (attr, css)) if th is not None: out.write(" %s\n" % htmlEncode(th.text)) out.write(" | \n") out.write("
---|
\n" % (attr, css)) if td is not None: out.write(" %s\n" % htmlEncode(td.text)) out.write(" | \n") i += colspan out.write("