merging v0.6.8

This commit is contained in:
Niru Maheswaranathan
2017-05-25 15:07:49 -07:00
12 changed files with 205 additions and 162 deletions

View File

@@ -3,6 +3,7 @@ python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
install:
- sudo apt-get update
- if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then

View File

@@ -14,6 +14,6 @@ test:
nosetests -v --with-coverage --cover-package=tableprint --logging-level=INFO
clean:
rm -rf tableprint.egg-info
rm -f *.pyc
rm -rf __pycache__
rm -R tableprint.egg-info
rm -f tableprint/*.pyc
rm -R tableprint/__pycache__/

View File

@@ -49,6 +49,7 @@ Hosted at Read The Docs: [tableprint.readthedocs.org](http://tableprint.readthed
- `six`
## Version
- 0.6.7 (May 25 2017) Fixes some bugs with ANSI escape sequences
- 0.5.0 (Sept 29 2016) Better handling of ANSI escape sequences in table rows
- 0.4.0 (May 3 2016) Adds a 'block' style
- 0.3.2 (May 3 2016) Adds a test suite

View File

@@ -1,29 +1,28 @@
import re
import os
from setuptools import setup
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
with open(os.path.join(__location__, 'tableprint/metadata.py'), 'r') as f:
metadata = dict(re.findall("__([a-z_]+)__\s*=\s*'([^']+)'", f.read()))
setup(
name='tableprint',
url=metadata['url'],
version=metadata['version'],
# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# https://packaging.python.org/en/latest/single_source_version.html
version='0.7.0',
author=metadata['author'],
author_email=metadata['author_email'],
description='Pretty console printing of tabular data',
license=metadata['license'],
description=metadata['description'],
long_description='''Formatted console printing of tabular data.
tableprint lets you easily print formatted tables of data.
Unlike other modules, you can print single rows of data at a time
(useful for printing ongoing computation results).''',
# The project's main homepage.
url='https://github.com/nirum/tableprint',
# Author details
author='Niru Maheswaranathan',
author_email='niru@fastmail.com',
# Choose your license
license='MIT',
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
# How mature is this project? Common values are
@@ -44,6 +43,7 @@ setup(
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],
# What does your project relate to?
@@ -51,8 +51,7 @@ setup(
# You can just specify the packages manually here if your project is
# simple. Or you can use find_packages().
packages=[],
py_modules=['tableprint'],
packages=['tableprint'],
# List run-time dependencies here. These will be installed by pip when your
# project is installed. For an analysis of "install_requires" vs pip's
@@ -67,5 +66,4 @@ setup(
'dev': [],
'test': ['pytest', 'coverage'],
},
)

8
tableprint/__init__.py Normal file
View File

@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
"""
Tableprint
"""
from .metadata import __author__, __version__
from .printer import *
from .style import *
from .utils import *

15
tableprint/metadata.py Normal file
View File

@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# Version info
__version__ = '0.6.8'
__license__ = 'MIT'
# Project description(s)
__description__ = 'Pretty console printing of tabular data'
# The project's main homepage.
__url__ = 'https://github.com/nirum/tableprint'
# Author details
__author__ = 'Niru Maheswaranathan'
__author_email__ = 'niru@fastmail.com'

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Tableprint
Table printing
A module to print and display formatted tables of data
@@ -11,58 +11,18 @@ Usage
>>> tableprint.table(data, headers)
"""
from __future__ import print_function, unicode_literals
from six import string_types
from collections import namedtuple
from numbers import Number
import sys
import re
from numbers import Number
import numpy as np
from six import string_types
__all__ = ('table', 'header', 'row', 'hr', 'top', 'bottom',
'banner', 'dataframe', 'humantime', 'styles')
__version__ = '0.7.0'
from .style import LineStyle, STYLES
from .utils import ansi_len, format_line
__all__ = ('table', 'header', 'row', 'hrule', 'top', 'bottom', 'banner', 'dataframe')
# set up table styles
LineStyle = namedtuple('LineStyle', ('begin', 'hline', 'sep', 'end'))
TableStyle = namedtuple('TableStyle', ('top', 'below_header', 'bottom', 'row'))
styles = {
'grid': TableStyle(
top=LineStyle('+', '-', '+', '+'),
below_header=LineStyle('+', '-', '+', '+'),
bottom=LineStyle('+', '-', '+', '+'),
row=LineStyle('|', '', '|', '|'),
),
'fancy_grid': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle('', '', '', ''),
bottom=LineStyle("", "", "", ""),
row=LineStyle('', '', '', ''),
),
'clean': TableStyle(
top=LineStyle(' ', '', ' ', ' '),
below_header=LineStyle(' ', '', ' ', ' '),
bottom=LineStyle(" ", "", " ", " "),
row=LineStyle(' ', '', ' ', ' '),
),
'round': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle('', '', '', ''),
bottom=LineStyle('', '', '', ''),
row=LineStyle('', '', '', ''),
),
'banner': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle("", "", "", ""),
bottom=LineStyle("", "", "", ""),
row=LineStyle('', '', '', ''),
),
'block': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle(' ', '', '', ' '),
bottom=LineStyle('', '', '', ''),
row=LineStyle(' ', '', ' ', ' '),
),
}
STYLE = 'round'
WIDTH = 11
FMT = '5g'
@@ -92,10 +52,10 @@ def table(data, headers=None, format_spec=FMT, width=WIDTH, style=STYLE, out=sys
A file handle or object that has write() and flush() methods (Default: sys.stdout)
"""
ncols = len(data[0]) if headers is None else len(headers)
tablestyle = styles[style]
tablestyle = STYLES[style]
# Initialize with a hr or the header
tablestr = [hr(ncols, width, tablestyle.top)] \
tablestr = [hrule(ncols, width, tablestyle.top)] \
if headers is None else [header(headers, width, style)]
# parse each row
@@ -103,7 +63,7 @@ def table(data, headers=None, format_spec=FMT, width=WIDTH, style=STYLE, out=sys
# only add the final border if there was data in the table
if len(data) > 0:
tablestr += [hr(ncols, width, tablestyle.bottom)]
tablestr += [hrule(ncols, width, tablestyle.bottom)]
# print the table
out.write('\n'.join(tablestr) + '\n')
@@ -122,14 +82,14 @@ def header(headers, width=WIDTH, style=STYLE, add_hr=True):
The width of each column (Default: 11)
style : string or tuple, optional
A formatting style (see styles)
A formatting style (see STYLES)
Returns
-------
headerstr : string
A string consisting of the full header row to print
"""
tablestyle = styles[style]
tablestyle = STYLES[style]
# parse width
if isinstance(width, int):
@@ -142,11 +102,11 @@ def header(headers, width=WIDTH, style=STYLE, add_hr=True):
data = map(lambda x: ('{:^%d}' % (x[0] + _ansi_len(x[1]))).format(x[1]), zip(widths, headers))
# build the formatted str
headerstr = _format_line(data, tablestyle.row)
headerstr = format_line(data, tablestyle.row)
if add_hr:
upper = hr(len(headers), widths, tablestyle.top)
lower = hr(len(headers), widths, tablestyle.below_header)
upper = hrule(len(headers), widths, tablestyle.top)
lower = hrule(len(headers), widths, tablestyle.below_header)
headerstr = '\n'.join([upper, headerstr, lower])
return headerstr
@@ -174,7 +134,7 @@ def row(values, width=WIDTH, format_spec=FMT, style=STYLE):
rowstr : string
A string consisting of the full row of data to print
"""
tablestyle = styles[style]
tablestyle = STYLES[style]
# parse width
if isinstance(width, int):
@@ -183,7 +143,7 @@ def row(values, width=WIDTH, format_spec=FMT, style=STYLE):
assert len(width) == len(values), "Width and values must have the same length"
widths = width
assert isinstance(format_spec, string_types) | (type(format_spec) is list), \
assert isinstance(format_spec, string_types) | isinstance(format_spec, list), \
"format_spec must be a string or list of strings"
if isinstance(format_spec, string_types):
@@ -196,7 +156,7 @@ def row(values, width=WIDTH, format_spec=FMT, style=STYLE):
width, datum, prec = val
if isinstance(datum, string_types):
return ('{:>%i}' % (width + _ansi_len(datum))).format(datum)
return ('{:>%i}' % (width + ansi_len(datum))).format(datum)
elif isinstance(datum, Number):
return ('{:>%i.%s}' % (width, prec)).format(datum)
@@ -208,10 +168,10 @@ def row(values, width=WIDTH, format_spec=FMT, style=STYLE):
data = map(mapdata, zip(widths, values, format_spec))
# build the row string
return _format_line(data, tablestyle.row)
return format_line(data, tablestyle.row)
def hr(n, width=WIDTH, linestyle=LineStyle('', '', '', '')):
def hrule(n=1, width=WIDTH, linestyle=LineStyle('', '', '', '')):
"""Returns a formatted string used as a border between table rows
Parameters
@@ -244,12 +204,12 @@ def hr(n, width=WIDTH, linestyle=LineStyle('', '─', '─', '')):
def top(n, width=WIDTH, style=STYLE):
"""Prints the top row of a table"""
return hr(n, width, linestyle=styles[style].top)
return hrule(n, width, linestyle=STYLES[style].top)
def bottom(n, width=WIDTH, style=STYLE):
"""Prints the top row of a table"""
return hr(n, width, linestyle=styles[style].bottom)
return hrule(n, width, linestyle=STYLES[style].bottom)
def banner(message, width=30, style='banner', out=sys.stdout):
@@ -282,70 +242,3 @@ def dataframe(df, **kwargs):
A pandas DataFrame with the table to print
"""
table(np.array(df), list(df.columns), **kwargs)
def humantime(t):
"""Converts a time in seconds to a reasonable human readable time
Parameters
----------
t : float
The number of seconds
Returns
-------
time : string
The human readable formatted value of the given time
"""
try:
t = float(t)
except (ValueError, TypeError):
raise ValueError("Input must be numeric")
# weeks
if t >= 7 * 60 * 60 * 24:
weeks = np.floor(t / (7 * 60 * 60 * 24))
timestr = "{:g} weeks, ".format(weeks) + humantime(t % (7 * 60 * 60 * 24))
# days
elif t >= 60 * 60 * 24:
days = np.floor(t / (60 * 60 * 24))
timestr = "{:g} days, ".format(days) + humantime(t % (60 * 60 * 24))
# hours
elif t >= 60 * 60:
hours = np.floor(t / (60 * 60))
timestr = "{:g} hours, ".format(hours) + humantime(t % (60 * 60))
# minutes
elif t >= 60:
minutes = np.floor(t / 60.)
timestr = "{:g} min., ".format(minutes) + humantime(t % 60)
# seconds
elif (t >= 1) | (t == 0):
timestr = "{:g} s".format(t)
# milliseconds
elif t >= 1e-3:
timestr = "{:g} ms".format(t * 1e3)
# microseconds
elif t >= 1e-6:
timestr = "{:g} \u03BCs".format(t * 1e6)
# nanoseconds or smaller
else:
timestr = "{:g} ns".format(t * 1e9)
return timestr
def _ansi_len(string):
"""Extra length due to any ANSI sequences in the string."""
return len(string) - len(re.compile(r'\x1b[^m]*m').sub('', string))
def _format_line(data, linestyle):
"""Formats a list of elements using the given line style"""
return linestyle.begin + linestyle.sep.join(data) + linestyle.end

48
tableprint/style.py Normal file
View File

@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
"""
Table styles
"""
from collections import namedtuple
__all__ = ('STYLES', 'LineStyle', 'TableStyle')
LineStyle = namedtuple('LineStyle', ('begin', 'hline', 'sep', 'end'))
TableStyle = namedtuple('TableStyle', ('top', 'below_header', 'bottom', 'row'))
STYLES = {
'grid': TableStyle(
top=LineStyle('+', '-', '+', '+'),
below_header=LineStyle('+', '-', '+', '+'),
bottom=LineStyle('+', '-', '+', '+'),
row=LineStyle('|', '', '|', '|'),
),
'fancy_grid': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle('', '', '', ''),
bottom=LineStyle("", "", "", ""),
row=LineStyle('', '', '', ''),
),
'clean': TableStyle(
top=LineStyle(' ', '', ' ', ' '),
below_header=LineStyle(' ', '', ' ', ' '),
bottom=LineStyle(" ", "", " ", " "),
row=LineStyle(' ', '', ' ', ' '),
),
'round': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle('', '', '', ''),
bottom=LineStyle('', '', '', ''),
row=LineStyle('', '', '', ''),
),
'banner': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle("", "", "", ""),
bottom=LineStyle("", "", "", ""),
row=LineStyle('', '', '', ''),
),
'block': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle(' ', '', '', ' '),
bottom=LineStyle('', '', '', ''),
row=LineStyle(' ', '', ' ', ' '),
),
}

75
tableprint/utils.py Normal file
View File

@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
"""
Tableprint utilities
"""
import re
import numpy as np
__all__ = ('humantime',)
def humantime(time):
"""Converts a time in seconds to a reasonable human readable time
Parameters
----------
t : float
The number of seconds
Returns
-------
time : string
The human readable formatted value of the given time
"""
try:
time = float(time)
except (ValueError, TypeError):
raise ValueError("Input must be numeric")
# weeks
if time >= 7 * 60 * 60 * 24:
weeks = np.floor(time / (7 * 60 * 60 * 24))
timestr = "{:g} weeks, ".format(weeks) + humantime(time % (7 * 60 * 60 * 24))
# days
elif time >= 60 * 60 * 24:
days = np.floor(time / (60 * 60 * 24))
timestr = "{:g} days, ".format(days) + humantime(time % (60 * 60 * 24))
# hours
elif time >= 60 * 60:
hours = np.floor(time / (60 * 60))
timestr = "{:g} hours, ".format(hours) + humantime(time % (60 * 60))
# minutes
elif time >= 60:
minutes = np.floor(time / 60.)
timestr = "{:g} min., ".format(minutes) + humantime(time % 60)
# seconds
elif (time >= 1) | (time == 0):
timestr = "{:g} s".format(time)
# milliseconds
elif time >= 1e-3:
timestr = "{:g} ms".format(time * 1e3)
# microseconds
elif time >= 1e-6:
timestr = "{:g} \u03BCs".format(time * 1e6)
# nanoseconds or smaller
else:
timestr = "{:g} ns".format(time * 1e9)
return timestr
def ansi_len(string):
"""Extra length due to any ANSI sequences in the string."""
return len(string) - len(re.compile(r'\x1b[^m]*m').sub('', string))
def format_line(data, linestyle):
"""Formats a list of elements using the given line style"""
return linestyle.begin + linestyle.sep.join(data) + linestyle.end

View File

@@ -5,6 +5,7 @@ import pytest
def test_borders():
"""Tests printing of the top and bottom borders"""
# top
assert top(5, width=2, style='round') == '╭──┬──┬──┬──┬──╮'
@@ -16,6 +17,7 @@ def test_borders():
def test_row():
"""Tests printing of a single row of data"""
# valid
assert row("abc", width=3, style='round') == '│ a│ b│ c│'

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from tableprint import table, banner, dataframe, hr
from tableprint import table, banner, dataframe, hrule
from io import StringIO
import numpy as np
@@ -23,6 +23,7 @@ def test_frame():
def __init__(self, data, headers):
self.data = data
self.columns = headers
def __array__(self):
return self.data
@@ -44,8 +45,8 @@ def test_banner():
assert output.getvalue() == '╒═╕\n│!│\n╘═╛\n'
def test_hr():
def test_hrule():
output = hr(1, width=11)
output = hrule(1, width=11)
assert len(output) == 11
assert '───────────'

View File

@@ -1,20 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from tableprint import humantime, _format_line, LineStyle
from tableprint import humantime, LineStyle
from tableprint.utils import format_line
import pytest
def test_format_line():
# using ASCII
assert _format_line(['foo', 'bar'], LineStyle('(', '_', '+', ')')) == '(foo+bar)'
assert _format_line("abc", LineStyle('[', '*', '.', ']')) == '[a.b.c]'
assert _format_line(["_"], LineStyle('o', '', '!', 'o')) == 'o_o'
assert _format_line([], LineStyle(':', '', '', ')')) == ':)'
assert format_line(['foo', 'bar'], LineStyle('(', '_', '+', ')')) == '(foo+bar)'
assert format_line("abc", LineStyle('[', '*', '.', ']')) == '[a.b.c]'
assert format_line(["_"], LineStyle('o', '', '!', 'o')) == 'o_o'
assert format_line([], LineStyle(':', '', '', ')')) == ':)'
# using unicode
assert _format_line(['.', '.', '.'], LineStyle('', '_', '', '')) == '★...☆'
assert _format_line("☚☛", LineStyle('', '*', '', '')) == '♪☚♩☛♫'
assert format_line(['.', '.', '.'], LineStyle('', '_', '', '')) == '★...☆'
assert format_line("☚☛", LineStyle('', '*', '', '')) == '♪☚♩☛♫'
def test_humantime():