import sys import itertools __rcsId__ = """$Id: column_output.py,v 1.4 2008/12/09 13:06:10 bellman Exp $""" class ColumnOutput: """Class for printing output in columns with dynamic width. Collect lines with the append() method, and, when finished, use the format() or printit() methods to calculate the widths needed for each column and format and output the lines. Note that this class assumes that each character in the input strings will occupy exactly one position on the output device. Thus, various control characters or combining characters will mess up the output. Example: >>> co = ColumnOutput(sys.stdout) >>> co.append('PERSON', 'MAIN MEAL', 'DESSERT') >>> co.append('John', 'Salmon', 'Icecream') >>> co.append('Himemiya', 'Veal', 'Chocolate pudding') >>> co.printit(prefix='[', separator='|', suffix=']') [PERSON |MAIN MEAL|DESSERT ] [John |Salmon |Icecream ] [Himemiya|Veal |Chocolate pudding] (Note: sys.stdout is the default output file for the ColumnOutput class, but we need pass it explicitly here to work around a limitation of the doctest module.) """ def __init__(self, outfile=sys.stdout): self.__outfile = outfile self.__lines = [] self.__ncolumns = None def append(self, *columns): """Append a line of output. Each column should be passed as a separate parameter. All calls to append() must specify the same number of columns. """ if self.__ncolumns is None: self.__ncolumns = len(columns) elif len(columns) != self.__ncolumns: raise ValueError("Wrong number of columns: %d instead of %d" % (len(columns), self.__ncolumns), columns) self.__lines.append(columns) def column_widths(self): """Calculate the widths needed for the columns so far. Returns a list of column widths. The space needed for separators between the columns, or the prefix and suffix on each line, is not included in the widths. """ from itertools import imap, izip colwidths = [ max(imap(len, col)) for col in izip(*self.__lines) ] return colwidths def format(self, separator="\t", prefix="", suffix=""): """Format the lines for output. Returns an iterator over the lines. The columns will be separated with SEPARATOR, and each line will be prefixed with PREFIX, and terminated with SUFFIX. Newline is not added to the end of the lines. """ separator = separator.replace("%", "%%") prefix = prefix.replace("%", "%%") suffix = suffix.replace("%", "%%") colwidths = self.column_widths() formats = ( "%%-%ds" % (width,) for width in colwidths ) fmt = prefix + separator.join(formats) + suffix # Python 2.5: # return imap(functools.partial(operator.mod, fmt), self.__lines) for line in self.__lines: yield fmt % line def printit(self, separator="\t", prefix="", suffix="", strip=True): """Print the lines gathered so far. See the format() method for parameter documentation. Additional parameter STRIP says if trailing whitespace should be stripped from the lines (default) or not. """ for line in self.format(separator, prefix, suffix): if strip: line = line.rstrip() self.__outfile.write(line + "\n") if __name__ == "__main__": import doctest doctest.testmod()