Source code for taskflow.types.table

# -*- 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


[docs]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()

Navigation