
'''Format text to be printed into columns'''

try:
    dict  ## dict was introduced in python2.2 at the same time with new classes
except NameError:
    from UserList import UserList as list

try:
    sum  ## Check if we have sum (python2.3 onward)
except NameError:
    def sum(*args):
        return reduce(lambda x,y: x+y, *args)
    
class Cell:
    alignments = ('left', 'center', 'right')
    __functions = {'left': 'ljust', 'right': 'rjust', 'center': 'center'}
    def __init__(self, data, align='left', sep=' ', onChange=None):
        self.onChange = onChange
        self.data = data
        self.align = align
        self.sep = sep
    def __setattr__(self, attr, value):
        if attr in ('data', 'align', 'sep'):
            value = value or ''
            try:    value + ''
            except: raise TypeError, "Cell.%s must be a string alike object" % attr
            if attr == 'align' and (value not in Cell.alignments):
                raise ValueError, 'Cell alignment should be one of %s' % str(Cell.alignments)
            elif attr == 'data' and self.onChange:
                self.onChange(self, value)
        self.__dict__[attr] = value
    def __repr__(self):
        return "('%s', '%s', %s)" % (self.data, self.sep, self.align)
    __str__ = __repr__
    def contents(self, width=0):
        func = self.__functions[self.align]
        return eval("'''%s'''.%s(%d)" % (self.data, func, width)) + str(self.sep)

class Row(list):
    def __init__(self, columns, columnSizes=None):
        try:
            cdata = [''] * columns
        except TypeError:
            cdata = columns
        self.columnSizes = columnSizes or len(cdata)*[0]
        if len(self.columnSizes) != len(cdata):
            raise ValueError, "Number of columns doesn't match size of columnSizes"
        ## Can't pass onChange to Cell here because list is not built yet and
        ## resizedCell will fail on self.index(cell), since cell is not yet in list
        list.__init__(self, [Cell(x) for x in cdata])
        for cell in self:
            cell.onChange = self._resizedCell
            self._resizedCell(cell, cell.data)
        self[-1].sep = ''

    ## Overwrite some list methods to make our object immutable
    def __add__(self, other):  raise TypeError, "object doesn't support concatenation"
    def __iadd__(self, other): raise TypeError, "object doesn't support concatenation"
    def __mul__(self, other):  raise TypeError, "object doesn't support multiplication"
    def __imul__(self, other): raise TypeError, "object doesn't support multiplication"
    def __delitem__(self, i):  raise TypeError, "object doesn't support item deletion"
    def __delslice__(self, i, j):  raise TypeError, "object doesn't support slice deletion"
    def __setitem__(self, i, val): raise TypeError, "object doesn't support item assignment"
    def __setslice__(self, i, j, val): raise TypeError, "object doesn't support slice assignment"
    def append(self, item):  raise TypeError, "object doesn't support appending"
    def extend(self, other): raise TypeError, "object doesn't support extending"
    def insert(self, i, item): raise TypeError, "object doesn't support inserting"
    def pop(self): raise TypeError, "object doesn't support element extracting"
    def remove(self, item): raise TypeError, "object doesn't support element removing"
    def reverse(self): raise TypeError, "object doesn't support reversing"
    def sort(self, func=None): raise TypeError, "object doesn't support sorting"
    #__iadd__ = __add__
    #__imul__ = __mul__

    def __getattr__(self, attr):
        if attr == 'separators':
            return [cell.sep for cell in self]
        elif attr == 'alignments':
            return [cell.align for cell in self]
        elif attr == 'cells':
            return [cell.data for cell in self]
        else:
            return list.__getattribute__(self, attr)

    def __setattr__(self, attr, value):
        if attr == 'separators':
            for (cell, sep) in zip(self, value): cell.sep = sep
        elif attr == 'alignments':
            for (cell, align) in zip(self, value): cell.align = align
        elif attr == 'cells':
            for (cell, data) in zip(self, value): cell.data = data
        else:
            try: ## Silly UserList
                list.__setattr__(self, attr, value)
            except AttributeError:
                self.__dict__[attr] = value

    def _resizedCell(self, cell, data):
        column = self.index(cell)
        self.columnSizes[column] = max(self.columnSizes[column], len(data))

    def contents(self):
        cells = [self[i].contents(self.columnSizes[i]) for i in range(len(self))]
        return ''.join(cells)
    __str__ = contents


class Header(Row):
    def __init__(self, columns, columnSizes=None):
        Row.__init__(self, columns, columnSizes)
        self.bodySeparator = None
    def contents(self):
        contents = Row.contents(self)
        if self.bodySeparator:
            contents += ('\n' + (self.bodySeparator * len(contents)))
        return contents
    __str__ = contents


class Table(list):
    def __init__(self, columns):
        list.__init__(self)
        try:
            count = int(columns)
        except TypeError:
            count = len(columns)
        if count < 1:
            raise ValueError, "Table should have at least 1 column"
        self.columns = count
        self._columnSizes = count*[0]
        self.header = Header(columns, columnSizes=self._columnSizes)
        self.__dict__['separators'] = count * [' ']
        self.__dict__['alignments'] = count * ['left']
        self.separators[-1] = ''

    ## Methods we don't support
    def __add__(self, other):  raise TypeError, "object doesn't support concatenation"
    def __iadd__(self, other): raise TypeError, "object doesn't support concatenation"
    def __mul__(self, other):  raise TypeError, "object doesn't support multiplication"
    def __imul__(self, other): raise TypeError, "object doesn't support multiplication"
    def __setslice__(self, i, j, val): raise TypeError, "object doesn't support slice assignment"
    def extend(self, other): raise TypeError, "object doesn't support extending"

    ## And methods we do
    def append(self, row):
        try: len(row)
        except TypeError: raise TypeError, "row should be a list of column values"
        row = Row(row, columnSizes=self._columnSizes)
        row.separators = self.separators
        row.alignments = self.alignments
        list.append(self, row)
    def insert(self, index, row):
        try: len(row)
        except TypeError: raise TypeError, "row should be a list of column values"
        row = Row(row, columnSizes=self._columnSizes)
        row.separators = self.separators
        row.alignments = self.alignments
        list.insert(self, index, row)
    def __setitem__(self, index, row):
        try: len(row)
        except TypeError: raise TypeError, "row should be a list of column values"
        row = Row(row, columnSizes=self._columnSizes)
        row.separators = self.separators
        row.alignments = self.alignments
        list.__setitem__(self, index, row)

    def __setattr__(self, attr, value):
        if attr == 'separators':
            value += self.separators[len(value):]
            for row in self: row.separators = value
        elif attr == 'alignments':
            value += self.alignments[len(value):]
            for row in self: row.alignments = value
        try: ## Silly UserList
            list.__setattr__(self, attr, value)
        except AttributeError:
            self.__dict__[attr] = value

    def contents(self):
        lines = [row.contents() for row in self]
        return '\n'.join(lines)

    def show(self):
        print self.header.contents()
        print self.contents()

