Text Table in Python

Python class to pretty-print tabluar data in a terminal.

t = Table(titles=['Name', 'Position', 'Goals'])
t.add_row(['Dave Smith', 'striker', 12])
t.add_row(['Angus McGregor', 'full back', 1])
print(t)

produces:

     Name      | Position  | Goals
----------------------------------
Dave Smith     | striker   |    12
Angus McGregor | full back |     1

Titles are centre-aligned, text left-aligned, and numbers right-aligned.

#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2016 Dean Jackson <deanishe@deanishe.net>
#
# MIT Licence. See http://opensource.org/licenses/MIT
#
# Created on 2016-12-19
#

"""Text table.

Pretty-print tabular data in the terminal. Smart alignment of numbers.
"""

from __future__ import print_function, absolute_import

import re


def unicodify(obj):
    if isinstance(obj, unicode):
        return obj
    if isinstance(obj, str):
        return obj.decode('utf-8')
    return unicode(obj)


class Table(object):

    def __init__(self, titles=None):
        self.centred_titles = True
        self.rows = []
        self._titlerows = []
        self._size = 0
        if titles:
            self.add_row(titles, True)

    def add_row(self, row, title=False):
        self.rows.append(row)
        if title:
            self._titlerows.append(True)
        else:
            self._titlerows.append(False)

    def __unicode__(self):
        # Calculate column parameters
        ncols = max([len(r) for r in self.rows])
        widths = [0] * ncols  # width of each column
        formats = [None] * ncols  # format string for each column
        # calculate column widths
        for r in self.rows:
            for i, n in enumerate([len(unicodify(c)) for c in r]):
                widths[i] = max([widths[i], n])

        # Widget
        hr = [(u'-' * w) for w in widths]
        hr = u'---'.join(hr)

        # Get row data formatted to length
        lines = []
        for y, r in enumerate(self.rows):
            title = self._titlerows[y]
            objs = list(r)
            while len(objs) < ncols:  # Ensure row is long enough
                objs.append(u'')

            # Format each cell
            data = []
            for i, o in enumerate(objs):
                u = unicodify(o)
                w = widths[i]

                # Column formatting
                # left-align by default
                fmt = u'{{:{}s}}'.format(w)
                if title and self.centred_titles:  # centre-align titles
                    fmt = u'{{:^{}s}}'.format(w)

                elif u.strip():  # Use or determine default for this column
                    if formats[i]:
                        fmt = formats[i]
                    elif re.match(r'[$€£]?\s*[0-9,.% ]+', u):  # number
                            fmt = u'{{:>{}s}}'.format(w)
                    # Save as default for this column
                    formats[i] = fmt

                u = fmt.format(u)
                data.append(u)

            # Combine cells into text for row
            u = u' | '.join(data)
            lines.append(u)
            if title:
                lines.append(hr)

        return u'\n'.join([s.rstrip() for s in lines])

    def __str__(self):
        return self.__unicode__().encode('utf-8')


if __name__ == '__main__':
    data = [
        ('Dave Smith', 22, 'carpenter'),
        ('Ronald McDonald', 7, 'schoolboy'),
        ('Davy Jones', 57, 'boxer'),
        ('Reggie Becker', 83),
        ('Ricky Bobby', 28, 'racing driver', 'fraud'),
        ('Spanish Harlan', 22, 'DJ', '100', '$30'),
        ('Fast Howie', 42, 'gunfighter', '27 kills', 'dolla dolla'),
    ]
    if False:
        t = Table(titles=['Name', 'Current Age', 'Occupation'])
        for row in data:
            t.add_row(row)
        print(t)
        print('========================')

    t2 = Table(titles=['Name', 'Current Age', 'Occupation'])
    for row in data:
        t2.add_row(row)

    # t2.centred_titles = False
    print(t2)
Add links
17d8374