blob: 976ead1dc2da057ceb6e5cbac8bef42d88474fb7 [file] [log] [blame] [edit]
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import math
import re
class Table(object):
"""
This class allows you to format tables for console output.
Example:
table = Table(prefix=" ")
table.header("equation", "result")
table.row("1+2", "3")
table.row("21*2", "42")
print table
The column width will automatically be determined to be fitting for every
cell.
"""
def __init__(self, prefix=""):
"""
creates an empty table. Prefix will be printed before any new line
of the table (i.e. it can be used to indent the table)
"""
self.prefix = prefix
self.title = None
self.footer = None
self.entries = []
def header(self, *args):
"""
Add a new table header row. Every parameter will be the title of one
cell. Equivalent of <tr><th>arg1</th><th>arg2</th>...</tr>
"""
self.entries.append(("header", args))
def row(self, *args):
"""
Add a new table data row. Every parameter will be the title of one
cell. Equivalent of <tr><td>arg1</td><td>arg2</td>...</tr>
"""
self.entries.append(("row", args))
def __str__(self):
"""
format the table using all entries added using header/row.
"""
# The entries are structured in the following way:
# self.entries = [
# ("header", ["header1", "header2", ...]),
# ("row", ["cell1", "cell2", ...]),
# ("row", ["cell1", "cell2", ...])
# ]
# calculate dimensions and number of columns
column_count = max([len(entry[1]) for entry in self.entries])
column_sizes = []
for i in range(column_count):
column_size = 2 + max([len(str(entry[1][i]))
for entry in self.entries
if i < len(entry[1])])
column_sizes.append(column_size)
# overall width of table including all decorations
width = 4 + sum(column_sizes) + 3 * (column_count - 1);
# format strings for all parts of the table
title_format = self.prefix + "{0:^%d}" % width
footer_format = (self.prefix + "| {0:^%d} |" % (width - 4) + "\n" +
self.prefix + "+-" + ("-" * (width - 4)) + "-+")
th_format = (self.prefix + "+-" +
"-+-".join(["{%d:-^%d}" % (i, c)
for i, c in enumerate(column_sizes)]) +
"-+")
td_format = (self.prefix + "| " +
" | ".join(["{%d:<%d}" % (i, c)
for i, c in enumerate(column_sizes)]) +
" |\x1b[0m")
tf_format = (self.prefix + "+-" +
"-+-".join(["-"*c for c in column_sizes]) +
"-+")
# build table from entries
parts = []
if self.title:
parts.append(title_format.format(self.title))
for entry in self.entries:
if entry[0] == "header":
format = th_format
if entry[0] == "row":
format = td_format
# pad every element with one space
data = []
color = "0"
for i in range(column_count):
if i < len(entry[1]):
value = str(entry[1][i])
# add padding for console escape characters
value = re.sub("(\x1b\[.m)", " \\1", value)
value = re.sub("(\x1b\[..m)", " \\1", value)
value = re.sub("(\x1b\[...m)", " \\1", value)
if "success" in value:
color = "32"
elif "failure" in value:
color = "31"
elif "bad" in value:
color = "33"
elif "disabled" in value or "incomplete" in value:
color = "34"
data.append(" " + value + " ")
else:
data.append("")
line = format.format(*data)
if color != "0":
line = line.replace("|", "\x1b[m|\x1b[" + color + "m")
parts.append(line)
parts.append(tf_format)
if self.footer:
parts.append(footer_format.format(self.footer))
return "\n".join(parts)