#!/usr/bin/env python
""" Prints data in a table format.
This module serves as utility for other scripts.
"""
import sys, re, os.path, stat, math
from html import escape
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 isinstance(value, str):
vstr = 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("