Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. # =============================================================================
  2. # >> IMPORTS
  3. # =============================================================================
  4. # Python
  5. import itertools
  6. import sys
  7.  
  8.  
  9. # =============================================================================
  10. # >> BACKWARDS COMPATIBILITY
  11. # =============================================================================
  12. # In Python 3 izip_longest has been renamed to zip_longest.
  13. if sys.version_info >= (3,):
  14.     zip_longest = itertools.zip_longest
  15. else:
  16.     zip_longest = itertools.izip_longest
  17.  
  18.  
  19. # =============================================================================
  20. # >> CONSTANTS
  21. # =============================================================================
  22. DEFAULT_ENCODING = 'utf-8'
  23.  
  24.  
  25. # =============================================================================
  26. # >> CLASSES
  27. # =============================================================================
  28. class Alignment(object):
  29.     '''
  30.    Contains constants to specify the aligment of table items.
  31.    '''
  32.  
  33.     LEFT = unicode.ljust
  34.     RIGHT = unicode.rjust
  35.     CENTER = unicode.center
  36.  
  37.  
  38. class Column(list):
  39.     '''
  40.    Represents a column of a table.
  41.    '''
  42.  
  43.     def __init__(self, name, alignment=Alignment.CENTER, left_padding=2,
  44.             right_padding=2):
  45.         '''
  46.        Intializes the column.
  47.  
  48.        @param <name>:
  49.        Name of the column.
  50.  
  51.        @param <alignment>:
  52.        The alignment of the column name.
  53.  
  54.        @param <left_padding>:
  55.        Number of spaces for left padding.
  56.  
  57.        @param <right_padding>:
  58.        Number of spaces for right padding.
  59.        '''
  60.  
  61.         self.name = Item(name, alignment)
  62.         self.left_padding = left_padding
  63.         self.right_padding = right_padding
  64.  
  65.     def _get_max_padding(self):
  66.         '''
  67.        Returns the length of the longest item in the column (including the
  68.        name of the column).
  69.        '''
  70.  
  71.         length = len(self.name)
  72.         if not self:
  73.             return length
  74.  
  75.         return max(len(max(self, key=len)), length)
  76.  
  77.     def _format(self, encoding=DEFAULT_ENCODING):
  78.         '''
  79.        Returns a two-tuple containing the formated name of the column and a
  80.        tuple containing all formatted items of this row.
  81.        '''
  82.  
  83.         padding = self._get_max_padding()
  84.         return (
  85.             self.name._format(
  86.                 padding, self.left_padding, self.right_padding, encoding),
  87.             tuple(item._format(padding, self.left_padding,
  88.                 self.right_padding, encoding) for item in self)
  89.         )
  90.  
  91.  
  92. class Item(object):
  93.     '''
  94.    Represents a value/item of a column.
  95.    '''
  96.  
  97.     def __init__(self, value, alignment=Alignment.LEFT):
  98.         '''
  99.        Initializes the item.
  100.  
  101.        @param <value>:
  102.        The value this item should have.
  103.  
  104.        @param <aligment>:
  105.        The aligment of the item in the table.
  106.        '''
  107.  
  108.         self.value = str(value)
  109.         self.alignment = alignment
  110.  
  111.     def __len__(self):
  112.         '''
  113.        Returns the length of the value.
  114.        '''
  115.  
  116.         return len(self.value)
  117.  
  118.     def _format(self, padding, left_padding, right_padding,
  119.             encoding=DEFAULT_ENCODING):
  120.         '''
  121.        Formats the item.
  122.  
  123.        @param <padding>:
  124.        The length of the longest item in the column.
  125.  
  126.        @param <left_padding>:
  127.        A number that defines how many spaces should be added to the left of
  128.        the item.
  129.  
  130.        @param <right_padding>:
  131.        A number that defines how many spaces should be added to the right of
  132.        the item.
  133.  
  134.        @param <encoding>:
  135.        Specifies the encoding.
  136.        '''
  137.  
  138.         return unicode(' ', encoding)*left_padding \
  139.             + self.alignment(self.value.decode(encoding), padding) \
  140.             + unicode(' ', encoding)*right_padding
  141.  
  142.  
  143. class HorizontalSeparator(Item):
  144.     '''
  145.    Represents a horizontal separator.
  146.    '''
  147.  
  148.     def __init__(self, separator=unicode('-', DEFAULT_ENCODING)):
  149.         '''
  150.        Initializes the separator.
  151.  
  152.        @param <separator>:
  153.        The character that should be used to separate.
  154.        '''
  155.  
  156.         if not isinstance(separator, unicode) \
  157.                 or len(separator) != 1:
  158.             raise ValueError('Separator must be a single character and a unicode object.')
  159.  
  160.         self.separator = separator
  161.  
  162.     def __len__(self):
  163.         '''
  164.        Returns 0.
  165.        '''
  166.  
  167.         return 0
  168.  
  169.     def _format(self, padding, left_padding, right_padding,
  170.             encoding=DEFAULT_ENCODING):
  171.         '''
  172.        Formats the item.
  173.  
  174.        @param <padding>:
  175.        The length of the longest item in the column.
  176.  
  177.        @param <left_padding>:
  178.        A number that defines how many spaces should be added to the left of
  179.        the item.
  180.  
  181.        @param <right_padding>:
  182.        A number that defines how many spaces should be added to the right of
  183.        the item.
  184.  
  185.        @param <encoding>:
  186.        Specifies the encoding.
  187.        '''
  188.  
  189.         return self.separator.decode(encoding) \
  190.             * (left_padding + padding + right_padding)
  191.  
  192.  
  193. class AsciiTable(object):
  194.     '''
  195.    Represents a table.
  196.    '''
  197.  
  198.     def __init__(self, *columns):
  199.         '''
  200.        Adds the given objects as columns to the table. If a given column is
  201.        not a Column object, it will be created.
  202.        '''
  203.  
  204.         if len(columns) == 0:
  205.             raise ValueError('Table must have at least one column.')
  206.  
  207.         self._columns = []
  208.         for column in columns:
  209.             if not isinstance(column, Column):
  210.                 column = Column(column)
  211.  
  212.             self._columns.append(column)
  213.  
  214.     def __len__(self):
  215.         '''
  216.        Returns the number of columns.
  217.        '''
  218.  
  219.         return len(self._columns)
  220.  
  221.     def __iter__(self):
  222.         '''
  223.        Returns an iterator for all columns.
  224.        '''
  225.  
  226.         return iter(self._columns)
  227.  
  228.     def __getitem__(self, index):
  229.         '''
  230.        Returns the column at the given index.
  231.        '''
  232.  
  233.         return self._columns[index]
  234.  
  235.     def add_row(self, *items):
  236.         '''
  237.        Appends the given items to the table's columns.
  238.        '''
  239.  
  240.         if len(items) != len(self):
  241.             raise ValueError('You must add exactly the same number of items' \
  242.                 ' like the number of columns.')
  243.  
  244.         for index, item in enumerate(items):
  245.             if not isinstance(item, Item):
  246.                 item = Item(item)
  247.  
  248.             self[index].append(item)
  249.  
  250.  
  251.     def format(self, header_separator=unicode('=', DEFAULT_ENCODING),
  252.             column_separator=unicode('|', DEFAULT_ENCODING),
  253.             encoding=DEFAULT_ENCODING):
  254.         '''
  255.        Formats the table and returns an ASCII table string.
  256.  
  257.        @param <header_separator>:
  258.        A single character that defines the character that should be used to
  259.        create the horizontal separator.
  260.  
  261.        @param <column_separator>:
  262.        A single character that defines the character that should be used to
  263.        create the vertical separators.
  264.  
  265.        @param <encoding>:
  266.        Specifies the encoding.
  267.        '''
  268.  
  269.         if not isinstance(header_separator, unicode) \
  270.                 or len(header_separator) != 1:
  271.             raise ValueError('Header separator must be a single character a' \
  272.                 'nd a unicode object.')
  273.  
  274.         columns = []
  275.         rows = []
  276.         for column in self:
  277.             column, row = column._format(encoding)
  278.             columns.append(column)
  279.             rows.append(row)
  280.  
  281.         header = column_separator.join(columns)
  282.         return unicode('{0}\n{1}\n{2}', encoding).format(
  283.             header,
  284.             header_separator*len(header),
  285.             unicode('\n', encoding).join(column_separator.join(row) \
  286.                 for row in zip_longest(*rows, fillvalue=''))
  287.         )