Skip to content
This repository has been archived by the owner on Dec 7, 2021. It is now read-only.

Commit

Permalink
Migrated to granular character control.
Browse files Browse the repository at this point in the history
More CHAR class variables so that in the future heading/footing borders
can be distinct.

Fixes #26
  • Loading branch information
Robpol86 committed May 30, 2016
1 parent 68c416b commit a283661
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 111 deletions.
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -176,13 +176,17 @@ Added
* Support for https://pypi.python.org/pypi/termcolor
* Support for RTL characters (Arabic and Hebrew).

Changed
* Refactored again, but this time entire project including tests.

Removed
* ``padded_table_data`` property and ``join_row()``. Moving away from repeated string joining/splitting.

Fixed
* ``set_terminal_title()`` Unicode handling on Windows.
* https://github.com/Robpol86/terminaltables/issues/20
* https://github.com/Robpol86/terminaltables/issues/23
* https://github.com/Robpol86/terminaltables/issues/26

2.1.0 - 2015-11-02
------------------
Expand Down
112 changes: 79 additions & 33 deletions terminaltables/base_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,35 @@ class BaseTable(object):
:ivar int padding_right: Number of spaces to pad on the right side of every cell.
"""

CHAR_CORNER_LOWER_LEFT = '+'
CHAR_CORNER_LOWER_RIGHT = '+'
CHAR_CORNER_UPPER_LEFT = '+'
CHAR_CORNER_UPPER_RIGHT = '+'
CHAR_HORIZONTAL = '-'
CHAR_INTERSECT_BOTTOM = '+'
CHAR_INTERSECT_CENTER = '+'
CHAR_INTERSECT_LEFT = '+'
CHAR_INTERSECT_RIGHT = '+'
CHAR_INTERSECT_TOP = '+'
CHAR_VERTICAL = '|'
CHAR_F_INNER_HORIZONTAL = '-'
CHAR_F_INNER_INTERSECT = '+'
CHAR_F_INNER_VERTICAL = '|'
CHAR_F_OUTER_LEFT_INTERSECT = '+'
CHAR_F_OUTER_LEFT_VERTICAL = '|'
CHAR_F_OUTER_RIGHT_INTERSECT = '+'
CHAR_F_OUTER_RIGHT_VERTICAL = '|'
CHAR_H_INNER_HORIZONTAL = '-'
CHAR_H_INNER_INTERSECT = '+'
CHAR_H_INNER_VERTICAL = '|'
CHAR_H_OUTER_LEFT_INTERSECT = '+'
CHAR_H_OUTER_LEFT_VERTICAL = '|'
CHAR_H_OUTER_RIGHT_INTERSECT = '+'
CHAR_H_OUTER_RIGHT_VERTICAL = '|'
CHAR_INNER_HORIZONTAL = '-'
CHAR_INNER_INTERSECT = '+'
CHAR_INNER_VERTICAL = '|'
CHAR_OUTER_BOTTOM_HORIZONTAL = '-'
CHAR_OUTER_BOTTOM_INTERSECT = '+'
CHAR_OUTER_BOTTOM_LEFT = '+'
CHAR_OUTER_BOTTOM_RIGHT = '+'
CHAR_OUTER_LEFT_INTERSECT = '+'
CHAR_OUTER_LEFT_VERTICAL = '|'
CHAR_OUTER_RIGHT_INTERSECT = '+'
CHAR_OUTER_RIGHT_VERTICAL = '|'
CHAR_OUTER_TOP_HORIZONTAL = '-'
CHAR_OUTER_TOP_INTERSECT = '+'
CHAR_OUTER_TOP_LEFT = '+'
CHAR_OUTER_TOP_RIGHT = '+'

def __init__(self, table_data, title=None):
"""Constructor.
Expand Down Expand Up @@ -60,23 +78,38 @@ def horizontal_border(self, style, outer_widths):
:rtype: tuple
"""
if style == 'top':
left = self.CHAR_CORNER_UPPER_LEFT
center = self.CHAR_INTERSECT_TOP if self.inner_column_border else ''
right = self.CHAR_CORNER_UPPER_RIGHT
horizontal = self.CHAR_OUTER_TOP_HORIZONTAL
left = self.CHAR_OUTER_TOP_LEFT
intersect = self.CHAR_OUTER_TOP_INTERSECT if self.inner_column_border else ''
right = self.CHAR_OUTER_TOP_RIGHT
title = self.title
elif style == 'bottom':
left = self.CHAR_CORNER_LOWER_LEFT
center = self.CHAR_INTERSECT_BOTTOM if self.inner_column_border else ''
right = self.CHAR_CORNER_LOWER_RIGHT
horizontal = self.CHAR_OUTER_BOTTOM_HORIZONTAL
left = self.CHAR_OUTER_BOTTOM_LEFT
intersect = self.CHAR_OUTER_BOTTOM_INTERSECT if self.inner_column_border else ''
right = self.CHAR_OUTER_BOTTOM_RIGHT
title = None
elif style == 'heading':
horizontal = self.CHAR_H_INNER_HORIZONTAL
left = self.CHAR_H_OUTER_LEFT_INTERSECT if self.outer_border else ''
intersect = self.CHAR_H_INNER_INTERSECT if self.inner_column_border else ''
right = self.CHAR_H_OUTER_RIGHT_INTERSECT if self.outer_border else ''
title = None
elif style == 'footing':
horizontal = self.CHAR_F_INNER_HORIZONTAL
left = self.CHAR_F_OUTER_LEFT_INTERSECT if self.outer_border else ''
intersect = self.CHAR_F_INNER_INTERSECT if self.inner_column_border else ''
right = self.CHAR_F_OUTER_RIGHT_INTERSECT if self.outer_border else ''
title = None
else:
left = self.CHAR_INTERSECT_LEFT if self.outer_border else ''
center = self.CHAR_INTERSECT_CENTER if self.inner_column_border else ''
right = self.CHAR_INTERSECT_RIGHT if self.outer_border else ''
horizontal = self.CHAR_INNER_HORIZONTAL
left = self.CHAR_OUTER_LEFT_INTERSECT if self.outer_border else ''
intersect = self.CHAR_INNER_INTERSECT if self.inner_column_border else ''
right = self.CHAR_OUTER_RIGHT_INTERSECT if self.outer_border else ''
title = None
return build_border(outer_widths, self.CHAR_HORIZONTAL, left, center, right, title)
return build_border(outer_widths, horizontal, left, intersect, right, title)

def gen_row_lines(self, row, inner_widths, height):
def gen_row_lines(self, row, style, inner_widths, height):
r"""Combine cells in row and group them into lines with vertical borders.
Caller is expected to pass yielded lines to ''.join() to combine them into a printable line. Caller must append
Expand All @@ -98,6 +131,7 @@ def gen_row_lines(self, row, inner_widths, height):
]
:param iter row: One row in the table. List of cells.
:param str style: Type of border characters to use.
:param iter inner_widths: List of widths (no padding) for each column.
:param int height: Inner height (no padding) (number of lines) to expand row to.
Expand All @@ -116,16 +150,22 @@ def gen_row_lines(self, row, inner_widths, height):
padding = (self.padding_left, self.padding_right, 0, 0)
cells_in_row.append(align_and_pad_cell(cell, align, inner_dimensions, padding))

# Combine cells and borders.
lines = build_row(
cells_in_row,
self.CHAR_VERTICAL if self.outer_border else '',
self.CHAR_VERTICAL if self.inner_column_border else '',
self.CHAR_VERTICAL if self.outer_border else ''
)
# Determine border characters.
if style == 'heading':
left = self.CHAR_H_OUTER_LEFT_VERTICAL if self.outer_border else ''
center = self.CHAR_H_INNER_VERTICAL if self.inner_column_border else ''
right = self.CHAR_H_OUTER_RIGHT_VERTICAL if self.outer_border else ''
elif style == 'footing':
left = self.CHAR_F_OUTER_LEFT_VERTICAL if self.outer_border else ''
center = self.CHAR_F_INNER_VERTICAL if self.inner_column_border else ''
right = self.CHAR_F_OUTER_RIGHT_VERTICAL if self.outer_border else ''
else:
left = self.CHAR_OUTER_LEFT_VERTICAL if self.outer_border else ''
center = self.CHAR_INNER_VERTICAL if self.inner_column_border else ''
right = self.CHAR_OUTER_RIGHT_VERTICAL if self.outer_border else ''

# Yield each line.
for line in lines:
for line in build_row(cells_in_row, left, center, right):
yield line

def gen_table(self, inner_widths, inner_heights, outer_widths):
Expand All @@ -145,15 +185,21 @@ def gen_table(self, inner_widths, inner_heights, outer_widths):
last_row_index, before_last_row_index = row_count - 1, row_count - 2
for i, row in enumerate(self.table_data):
# Yield the row line by line (e.g. multi-line rows).
for line in self.gen_row_lines(row, inner_widths, inner_heights[i]):
if self.inner_heading_row_border and i == 0:
style = 'heading'
elif self.inner_footing_row_border and i == last_row_index:
style = 'footing'
else:
style = 'row'
for line in self.gen_row_lines(row, style, inner_widths, inner_heights[i]):
yield line
# If this is the last row then break. No separator needed.
if i == last_row_index:
break
# Yield header separator.
# Yield heading separator.
if self.inner_heading_row_border and i == 0:
yield self.horizontal_border('heading', outer_widths)
# Yield footer separator.
# Yield footing separator.
elif self.inner_footing_row_border and i == before_last_row_index:
yield self.horizontal_border('footing', outer_widths)
# Yield row separator.
Expand Down
26 changes: 16 additions & 10 deletions terminaltables/github_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ class GithubFlavoredMarkdownTable(AsciiTable):
"""Github flavored markdown table.
https://help.github.com/articles/github-flavored-markdown/#tables
"""
CHAR_HORIZONTAL = '-'
:ivar iter table_data: List (empty or list of lists of strings) representing the table.
:ivar dict justify_columns: Horizontal justification. Keys are column indexes (int). Values are right/left/center.
"""

def __init__(self, table_data):
"""Constructor.
Expand All @@ -21,7 +22,7 @@ def __init__(self, table_data):
super(GithubFlavoredMarkdownTable, self).__init__(table_data)

def horizontal_border(self, _, outer_widths):
"""Handle the GitHub header border.
"""Handle the GitHub heading border.
E.g.:
|:---|:---:|---:|----|
Expand All @@ -32,20 +33,25 @@ def horizontal_border(self, _, outer_widths):
:return: Prepared border strings in a generator.
:rtype: iter
"""
horizontal = str(self.CHAR_INNER_HORIZONTAL)
left = self.CHAR_OUTER_LEFT_VERTICAL
intersect = self.CHAR_INNER_VERTICAL
right = self.CHAR_OUTER_RIGHT_VERTICAL

columns = list()
for i, width in enumerate(outer_widths):
justify = self.justify_columns.get(i)
width = max(3, width) # Width should be at least 3 so justification can be applied.
if justify == 'left':
columns.append(':' + self.CHAR_HORIZONTAL * (width - 1))
columns.append(':' + horizontal * (width - 1))
elif justify == 'right':
columns.append(self.CHAR_HORIZONTAL * (width - 1) + ':')
columns.append(horizontal * (width - 1) + ':')
elif justify == 'center':
columns.append(':' + self.CHAR_HORIZONTAL * (width - 2) + ':')
columns.append(':' + horizontal * (width - 2) + ':')
else:
columns.append(self.CHAR_HORIZONTAL * width)
columns.append(horizontal * width)

return combine(columns, self.CHAR_VERTICAL, self.CHAR_VERTICAL, self.CHAR_VERTICAL)
return combine(columns, left, intersect, right)

def gen_table(self, inner_widths, inner_heights, outer_widths):
"""Combine everything and yield every line of the entire table with borders.
Expand All @@ -57,8 +63,8 @@ def gen_table(self, inner_widths, inner_heights, outer_widths):
"""
for i, row in enumerate(self.table_data):
# Yield the row line by line (e.g. multi-line rows).
for line in self.gen_row_lines(row, inner_widths, inner_heights[i]):
for line in self.gen_row_lines(row, 'row', inner_widths, inner_heights[i]):
yield line
# Yield header separator.
# Yield heading separator.
if i == 0:
yield self.horizontal_border(None, outer_widths)
120 changes: 87 additions & 33 deletions terminaltables/other_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,35 @@ class UnixTable(AsciiTable):
Similar to the tables shown on PC BIOS boot messages, but not double-lined.
"""

CHAR_CORNER_LOWER_LEFT = '\033(0\x6d\033(B'
CHAR_CORNER_LOWER_RIGHT = '\033(0\x6a\033(B'
CHAR_CORNER_UPPER_LEFT = '\033(0\x6c\033(B'
CHAR_CORNER_UPPER_RIGHT = '\033(0\x6b\033(B'
CHAR_HORIZONTAL = '\033(0\x71\033(B'
CHAR_INTERSECT_BOTTOM = '\033(0\x76\033(B'
CHAR_INTERSECT_CENTER = '\033(0\x6e\033(B'
CHAR_INTERSECT_LEFT = '\033(0\x74\033(B'
CHAR_INTERSECT_RIGHT = '\033(0\x75\033(B'
CHAR_INTERSECT_TOP = '\033(0\x77\033(B'
CHAR_VERTICAL = '\033(0\x78\033(B'
CHAR_F_INNER_HORIZONTAL = '\033(0\x71\033(B'
CHAR_F_INNER_INTERSECT = '\033(0\x6e\033(B'
CHAR_F_INNER_VERTICAL = '\033(0\x78\033(B'
CHAR_F_OUTER_LEFT_INTERSECT = '\033(0\x74\033(B'
CHAR_F_OUTER_LEFT_VERTICAL = '\033(0\x78\033(B'
CHAR_F_OUTER_RIGHT_INTERSECT = '\033(0\x75\033(B'
CHAR_F_OUTER_RIGHT_VERTICAL = '\033(0\x78\033(B'
CHAR_H_INNER_HORIZONTAL = '\033(0\x71\033(B'
CHAR_H_INNER_INTERSECT = '\033(0\x6e\033(B'
CHAR_H_INNER_VERTICAL = '\033(0\x78\033(B'
CHAR_H_OUTER_LEFT_INTERSECT = '\033(0\x74\033(B'
CHAR_H_OUTER_LEFT_VERTICAL = '\033(0\x78\033(B'
CHAR_H_OUTER_RIGHT_INTERSECT = '\033(0\x75\033(B'
CHAR_H_OUTER_RIGHT_VERTICAL = '\033(0\x78\033(B'
CHAR_INNER_HORIZONTAL = '\033(0\x71\033(B'
CHAR_INNER_INTERSECT = '\033(0\x6e\033(B'
CHAR_INNER_VERTICAL = '\033(0\x78\033(B'
CHAR_OUTER_BOTTOM_HORIZONTAL = '\033(0\x71\033(B'
CHAR_OUTER_BOTTOM_INTERSECT = '\033(0\x76\033(B'
CHAR_OUTER_BOTTOM_LEFT = '\033(0\x6d\033(B'
CHAR_OUTER_BOTTOM_RIGHT = '\033(0\x6a\033(B'
CHAR_OUTER_LEFT_INTERSECT = '\033(0\x74\033(B'
CHAR_OUTER_LEFT_VERTICAL = '\033(0\x78\033(B'
CHAR_OUTER_RIGHT_INTERSECT = '\033(0\x75\033(B'
CHAR_OUTER_RIGHT_VERTICAL = '\033(0\x78\033(B'
CHAR_OUTER_TOP_HORIZONTAL = '\033(0\x71\033(B'
CHAR_OUTER_TOP_INTERSECT = '\033(0\x77\033(B'
CHAR_OUTER_TOP_LEFT = '\033(0\x6c\033(B'
CHAR_OUTER_TOP_RIGHT = '\033(0\x6b\033(B'

@property
def table(self):
Expand All @@ -36,33 +54,69 @@ class WindowsTable(AsciiTable):
From: http://en.wikipedia.org/wiki/Code_page_437#Characters
"""

CHAR_CORNER_LOWER_LEFT = b'\xc0'.decode('ibm437')
CHAR_CORNER_LOWER_RIGHT = b'\xd9'.decode('ibm437')
CHAR_CORNER_UPPER_LEFT = b'\xda'.decode('ibm437')
CHAR_CORNER_UPPER_RIGHT = b'\xbf'.decode('ibm437')
CHAR_HORIZONTAL = b'\xc4'.decode('ibm437')
CHAR_INTERSECT_BOTTOM = b'\xc1'.decode('ibm437')
CHAR_INTERSECT_CENTER = b'\xc5'.decode('ibm437')
CHAR_INTERSECT_LEFT = b'\xc3'.decode('ibm437')
CHAR_INTERSECT_RIGHT = b'\xb4'.decode('ibm437')
CHAR_INTERSECT_TOP = b'\xc2'.decode('ibm437')
CHAR_VERTICAL = b'\xb3'.decode('ibm437')
CHAR_F_INNER_HORIZONTAL = b'\xc4'.decode('ibm437')
CHAR_F_INNER_INTERSECT = b'\xc5'.decode('ibm437')
CHAR_F_INNER_VERTICAL = b'\xb3'.decode('ibm437')
CHAR_F_OUTER_LEFT_INTERSECT = b'\xc3'.decode('ibm437')
CHAR_F_OUTER_LEFT_VERTICAL = b'\xb3'.decode('ibm437')
CHAR_F_OUTER_RIGHT_INTERSECT = b'\xb4'.decode('ibm437')
CHAR_F_OUTER_RIGHT_VERTICAL = b'\xb3'.decode('ibm437')
CHAR_H_INNER_HORIZONTAL = b'\xc4'.decode('ibm437')
CHAR_H_INNER_INTERSECT = b'\xc5'.decode('ibm437')
CHAR_H_INNER_VERTICAL = b'\xb3'.decode('ibm437')
CHAR_H_OUTER_LEFT_INTERSECT = b'\xc3'.decode('ibm437')
CHAR_H_OUTER_LEFT_VERTICAL = b'\xb3'.decode('ibm437')
CHAR_H_OUTER_RIGHT_INTERSECT = b'\xb4'.decode('ibm437')
CHAR_H_OUTER_RIGHT_VERTICAL = b'\xb3'.decode('ibm437')
CHAR_INNER_HORIZONTAL = b'\xc4'.decode('ibm437')
CHAR_INNER_INTERSECT = b'\xc5'.decode('ibm437')
CHAR_INNER_VERTICAL = b'\xb3'.decode('ibm437')
CHAR_OUTER_BOTTOM_HORIZONTAL = b'\xc4'.decode('ibm437')
CHAR_OUTER_BOTTOM_INTERSECT = b'\xc1'.decode('ibm437')
CHAR_OUTER_BOTTOM_LEFT = b'\xc0'.decode('ibm437')
CHAR_OUTER_BOTTOM_RIGHT = b'\xd9'.decode('ibm437')
CHAR_OUTER_LEFT_INTERSECT = b'\xc3'.decode('ibm437')
CHAR_OUTER_LEFT_VERTICAL = b'\xb3'.decode('ibm437')
CHAR_OUTER_RIGHT_INTERSECT = b'\xb4'.decode('ibm437')
CHAR_OUTER_RIGHT_VERTICAL = b'\xb3'.decode('ibm437')
CHAR_OUTER_TOP_HORIZONTAL = b'\xc4'.decode('ibm437')
CHAR_OUTER_TOP_INTERSECT = b'\xc2'.decode('ibm437')
CHAR_OUTER_TOP_LEFT = b'\xda'.decode('ibm437')
CHAR_OUTER_TOP_RIGHT = b'\xbf'.decode('ibm437')


class WindowsTableDouble(AsciiTable):
"""Draw a table using box-drawing characters on Windows platforms. This uses Code Page 437. Double-line borders."""

CHAR_CORNER_LOWER_LEFT = b'\xc8'.decode('ibm437')
CHAR_CORNER_LOWER_RIGHT = b'\xbc'.decode('ibm437')
CHAR_CORNER_UPPER_LEFT = b'\xc9'.decode('ibm437')
CHAR_CORNER_UPPER_RIGHT = b'\xbb'.decode('ibm437')
CHAR_HORIZONTAL = b'\xcd'.decode('ibm437')
CHAR_INTERSECT_BOTTOM = b'\xca'.decode('ibm437')
CHAR_INTERSECT_CENTER = b'\xce'.decode('ibm437')
CHAR_INTERSECT_LEFT = b'\xcc'.decode('ibm437')
CHAR_INTERSECT_RIGHT = b'\xb9'.decode('ibm437')
CHAR_INTERSECT_TOP = b'\xcb'.decode('ibm437')
CHAR_VERTICAL = b'\xba'.decode('ibm437')
CHAR_F_INNER_HORIZONTAL = b'\xcd'.decode('ibm437')
CHAR_F_INNER_INTERSECT = b'\xce'.decode('ibm437')
CHAR_F_INNER_VERTICAL = b'\xba'.decode('ibm437')
CHAR_F_OUTER_LEFT_INTERSECT = b'\xcc'.decode('ibm437')
CHAR_F_OUTER_LEFT_VERTICAL = b'\xba'.decode('ibm437')
CHAR_F_OUTER_RIGHT_INTERSECT = b'\xb9'.decode('ibm437')
CHAR_F_OUTER_RIGHT_VERTICAL = b'\xba'.decode('ibm437')
CHAR_H_INNER_HORIZONTAL = b'\xcd'.decode('ibm437')
CHAR_H_INNER_INTERSECT = b'\xce'.decode('ibm437')
CHAR_H_INNER_VERTICAL = b'\xba'.decode('ibm437')
CHAR_H_OUTER_LEFT_INTERSECT = b'\xcc'.decode('ibm437')
CHAR_H_OUTER_LEFT_VERTICAL = b'\xba'.decode('ibm437')
CHAR_H_OUTER_RIGHT_INTERSECT = b'\xb9'.decode('ibm437')
CHAR_H_OUTER_RIGHT_VERTICAL = b'\xba'.decode('ibm437')
CHAR_INNER_HORIZONTAL = b'\xcd'.decode('ibm437')
CHAR_INNER_INTERSECT = b'\xce'.decode('ibm437')
CHAR_INNER_VERTICAL = b'\xba'.decode('ibm437')
CHAR_OUTER_BOTTOM_HORIZONTAL = b'\xcd'.decode('ibm437')
CHAR_OUTER_BOTTOM_INTERSECT = b'\xca'.decode('ibm437')
CHAR_OUTER_BOTTOM_LEFT = b'\xc8'.decode('ibm437')
CHAR_OUTER_BOTTOM_RIGHT = b'\xbc'.decode('ibm437')
CHAR_OUTER_LEFT_INTERSECT = b'\xcc'.decode('ibm437')
CHAR_OUTER_LEFT_VERTICAL = b'\xba'.decode('ibm437')
CHAR_OUTER_RIGHT_INTERSECT = b'\xb9'.decode('ibm437')
CHAR_OUTER_RIGHT_VERTICAL = b'\xba'.decode('ibm437')
CHAR_OUTER_TOP_HORIZONTAL = b'\xcd'.decode('ibm437')
CHAR_OUTER_TOP_INTERSECT = b'\xcb'.decode('ibm437')
CHAR_OUTER_TOP_LEFT = b'\xc9'.decode('ibm437')
CHAR_OUTER_TOP_RIGHT = b'\xbb'.decode('ibm437')


class SingleTable(WindowsTable if IS_WINDOWS else UnixTable):
Expand Down

0 comments on commit a283661

Please sign in to comment.