140 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			140 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# -*- coding: utf-8 -*-
 | 
						|
 | 
						|
#    Copyright (C) 2014 Yahoo! Inc. All Rights Reserved.
 | 
						|
#
 | 
						|
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
						|
#    not use this file except in compliance with the License. You may obtain
 | 
						|
#    a copy of the License at
 | 
						|
#
 | 
						|
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
#
 | 
						|
#    Unless required by applicable law or agreed to in writing, software
 | 
						|
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
						|
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
						|
#    License for the specific language governing permissions and limitations
 | 
						|
#    under the License.
 | 
						|
 | 
						|
import itertools
 | 
						|
import os
 | 
						|
 | 
						|
import six
 | 
						|
 | 
						|
 | 
						|
class PleasantTable(object):
 | 
						|
    """A tiny pretty printing table (like prettytable/tabulate but smaller).
 | 
						|
 | 
						|
    Creates simply formatted tables (with no special sauce)::
 | 
						|
 | 
						|
        >>> from taskflow.types import table
 | 
						|
        >>> tbl = table.PleasantTable(['Name', 'City', 'State', 'Country'])
 | 
						|
        >>> tbl.add_row(["Josh", "San Jose", "CA", "USA"])
 | 
						|
        >>> print(tbl.pformat())
 | 
						|
        +------+----------+-------+---------+
 | 
						|
          Name |   City   | State | Country
 | 
						|
        +------+----------+-------+---------+
 | 
						|
          Josh | San Jose |  CA   |   USA
 | 
						|
        +------+----------+-------+---------+
 | 
						|
    """
 | 
						|
 | 
						|
    # Constants used when pretty formatting the table.
 | 
						|
    COLUMN_STARTING_CHAR = ' '
 | 
						|
    COLUMN_ENDING_CHAR = ''
 | 
						|
    COLUMN_SEPARATOR_CHAR = '|'
 | 
						|
    HEADER_FOOTER_JOINING_CHAR = '+'
 | 
						|
    HEADER_FOOTER_CHAR = '-'
 | 
						|
    LINE_SEP = os.linesep
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _center_text(text, max_len, fill=' '):
 | 
						|
        return '{0:{fill}{align}{size}}'.format(text, fill=fill,
 | 
						|
                                                align="^", size=max_len)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def _size_selector(cls, possible_sizes):
 | 
						|
        """Select the maximum size, utility function for adding borders.
 | 
						|
 | 
						|
        The number two is used so that the edges of a column have spaces
 | 
						|
        around them (instead of being right next to a column separator).
 | 
						|
 | 
						|
        :param possible_sizes: possible sizes available
 | 
						|
        :returns: maximum size
 | 
						|
        :rtype: number
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            return max(x + 2 for x in possible_sizes)
 | 
						|
        except ValueError:
 | 
						|
            return 0
 | 
						|
 | 
						|
    def __init__(self, columns):
 | 
						|
        if len(columns) == 0:
 | 
						|
            raise ValueError("Column count must be greater than zero")
 | 
						|
        self._columns = [column.strip() for column in columns]
 | 
						|
        self._rows = []
 | 
						|
 | 
						|
    def add_row(self, row):
 | 
						|
        if len(row) != len(self._columns):
 | 
						|
            raise ValueError("Row must have %s columns instead of"
 | 
						|
                             " %s columns" % (len(self._columns), len(row)))
 | 
						|
        self._rows.append([six.text_type(column) for column in row])
 | 
						|
 | 
						|
    def pformat(self):
 | 
						|
        # Figure out the maximum column sizes...
 | 
						|
        column_count = len(self._columns)
 | 
						|
        column_sizes = [0] * column_count
 | 
						|
        headers = []
 | 
						|
        for i, column in enumerate(self._columns):
 | 
						|
            possible_sizes_iter = itertools.chain(
 | 
						|
                [len(column)], (len(row[i]) for row in self._rows))
 | 
						|
            column_sizes[i] = self._size_selector(possible_sizes_iter)
 | 
						|
            headers.append(self._center_text(column, column_sizes[i]))
 | 
						|
        # Build the header and footer prefix/postfix.
 | 
						|
        header_footer_buf = six.StringIO()
 | 
						|
        header_footer_buf.write(self.HEADER_FOOTER_JOINING_CHAR)
 | 
						|
        for i, header in enumerate(headers):
 | 
						|
            header_footer_buf.write(self.HEADER_FOOTER_CHAR * len(header))
 | 
						|
            if i + 1 != column_count:
 | 
						|
                header_footer_buf.write(self.HEADER_FOOTER_JOINING_CHAR)
 | 
						|
        header_footer_buf.write(self.HEADER_FOOTER_JOINING_CHAR)
 | 
						|
        # Build the main header.
 | 
						|
        content_buf = six.StringIO()
 | 
						|
        content_buf.write(header_footer_buf.getvalue())
 | 
						|
        content_buf.write(self.LINE_SEP)
 | 
						|
        content_buf.write(self.COLUMN_STARTING_CHAR)
 | 
						|
        for i, header in enumerate(headers):
 | 
						|
            if i + 1 == column_count:
 | 
						|
                if self.COLUMN_ENDING_CHAR:
 | 
						|
                    content_buf.write(headers[i])
 | 
						|
                    content_buf.write(self.COLUMN_ENDING_CHAR)
 | 
						|
                else:
 | 
						|
                    content_buf.write(headers[i].rstrip())
 | 
						|
            else:
 | 
						|
                content_buf.write(headers[i])
 | 
						|
                content_buf.write(self.COLUMN_SEPARATOR_CHAR)
 | 
						|
        content_buf.write(self.LINE_SEP)
 | 
						|
        content_buf.write(header_footer_buf.getvalue())
 | 
						|
        # Build the main content.
 | 
						|
        row_count = len(self._rows)
 | 
						|
        if row_count:
 | 
						|
            content_buf.write(self.LINE_SEP)
 | 
						|
            for i, row in enumerate(self._rows):
 | 
						|
                pieces = []
 | 
						|
                for j, column in enumerate(row):
 | 
						|
                    pieces.append(self._center_text(column, column_sizes[j]))
 | 
						|
                    if j + 1 != column_count:
 | 
						|
                        pieces.append(self.COLUMN_SEPARATOR_CHAR)
 | 
						|
                blob = ''.join(pieces)
 | 
						|
                if self.COLUMN_ENDING_CHAR:
 | 
						|
                    content_buf.write(self.COLUMN_STARTING_CHAR)
 | 
						|
                    content_buf.write(blob)
 | 
						|
                    content_buf.write(self.COLUMN_ENDING_CHAR)
 | 
						|
                else:
 | 
						|
                    blob = blob.rstrip()
 | 
						|
                    if blob:
 | 
						|
                        content_buf.write(self.COLUMN_STARTING_CHAR)
 | 
						|
                        content_buf.write(blob)
 | 
						|
                if i + 1 != row_count:
 | 
						|
                    content_buf.write(self.LINE_SEP)
 | 
						|
            content_buf.write(self.LINE_SEP)
 | 
						|
            content_buf.write(header_footer_buf.getvalue())
 | 
						|
        return content_buf.getvalue()
 |