diff --git a/README.md b/README.md index 9c1799d..2a7f5e7 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Many other modules for formatting tabular data require the developer to create a objects/data into a structure the formatter can consume. One relatively novel aspect of tableformatter is the ability to directly receive arbitrary Python objects. -[![Screenshot](tf.png)](https://github.com/python-tableformatter/tableformatter/blob/master/tf.png) +[![Screenshot](tablefmt.png)](https://github.com/python-tableformatter/tableformatter/blob/master/tablefmt.png) ## Main Features @@ -51,13 +51,13 @@ to this function is ``rows`` which is an Iterable of Iterables such as a list of a 2D [numpy](http://www.numpy.org) array. ``generate_table`` outputs a nicely formatted table: ```Python -import tableformatter as tf +import tableformatter as tablefmt rows = [('A1', 'A2', 'A3', 'A4'), ('B1', 'B2\nB2\nB2', 'B3', 'B4'), ('C1', 'C2', 'C3', 'C4'), ('D1', 'D2', 'D3', 'D4')] -print(tf.generate_table(rows)) +print(tablefmt.generate_table(rows)) ╔════╤════╤════╤════╗ ║ A1 │ A2 │ A3 │ A4 ║ ║ B1 │ B2 │ B3 │ B4 ║ @@ -93,7 +93,7 @@ The second argument to ``generate_table`` named ``columns`` is optional and defi ```Python cols = ['Col1', 'Col2', 'Col3', 'Col4'] -print(tf.generate_table(rows, cols)) +print(tablefmt.generate_table(rows, cols)) ╔══════╤══════╤══════╤══════╗ ║ Col1 │ Col2 │ Col3 │ Col4 ║ ╠══════╪══════╪══════╪══════╣ @@ -116,7 +116,7 @@ Supported grid sytles are: * **SparseGrid** - sparse grid with no lines at all to conserve both vertical and horizontal space ```Python -print(tf.generate_table(rows, grid_style=tf.FancyGrid())) +print(tablefmt.generate_table(rows, grid_style=tablefmt.FancyGrid())) ╔════╤════╤════╤════╗ ║ A1 │ A2 │ A3 │ A4 ║ ╟────┼────┼────┼────╢ @@ -137,7 +137,7 @@ this and print "rows" up-to-down and "columns" left-to-right then that is easily to ``generate_table``: ```Python -print(tf.generate_table(rows, cols, transpose=True)) +print(tablefmt.generate_table(rows, cols, transpose=True)) ╔══════╦════╤════╤════╤════╗ ║ Col1 ║ A1 │ B1 │ C1 │ D1 ║ ║ Col2 ║ A2 │ B2 │ C2 │ D2 ║ @@ -186,11 +186,11 @@ Possible alignments are all elements within the ``ColumnAlignment`` enum class: * AlignBottom ```Python -columns = (tf.Column('Col1', cell_halign=tf.ColumnAlignment.AlignLeft), - tf.Column('Col2', cell_halign=tf.ColumnAlignment.AlignRight), - tf.Column('Col3', cell_halign=tf.ColumnAlignment.AlignCenter), - tf.Column('Col4', cell_valign=tf.ColumnAlignment.AlignBottom)) -print(tf.generate_table(rows, columns)) +columns = (tablefmt.Column('Col1', cell_halign=tablefmt.ColumnAlignment.AlignLeft), + tablefmt.Column('Col2', cell_halign=tablefmt.ColumnAlignment.AlignRight), + tablefmt.Column('Col3', cell_halign=tablefmt.ColumnAlignment.AlignCenter), + tablefmt.Column('Col4', cell_valign=tablefmt.ColumnAlignment.AlignBottom)) +print(tablefmt.generate_table(rows, columns)) ╔══════╤══════╤══════╤══════╗ ║ Col1 │ Col2 │ Col3 │ col4 ║ ╠══════╪══════╪══════╪══════╣ @@ -212,6 +212,8 @@ formatting numbers: * FormatCommas - Formats a number with comma separators ```Python +import tableformatter.formatters + rows = [(None, None), ('123', '123'), (123, 123), @@ -219,19 +221,27 @@ rows = [(None, None), (12345678, 12345678), (1234567890, 1234567890), (1234567890123, 1234567890123)] -cols = (tf.Column('First', width=20, formatter=tf.FormatBytes(), cell_halign=tf.ColumnAlignment.AlignRight), - tf.Column('Second', formatter=tf.FormatCommas(), cell_halign=tf.ColumnAlignment.AlignRight)) -print(tf.generate_table(rows, cols)) +cols = (tablefmt.Column('First', width=20, formatter=tableformatter.formatters.FormatBytes(), + cell_halign=tablefmt.ColumnAlignment.AlignRight), + tablefmt.Column('Second', formatter=tableformatter.formatters.FormatCommas(), + cell_halign=tablefmt.ColumnAlignment.AlignRight)) +print(tablefmt.generate_table(rows, cols)) ╔═══════════╤═══════════════════╗ ║ First │ Second ║ ╠═══════════╪═══════════════════╣ ║ │ ║ -║ 123.00 B │ 123 ║ -║ 123.00 B │ 123 ║ -║ 12.06 KB │ 12,345 ║ -║ 11.77 MB │ 12,345,678 ║ -║ 1.15 GB │ 1,234,567,890 ║ -║ 1.12 TB │ 1,234,567,890,123 ║ +║ 123.00 +B │ 123 ║ +║ 123.00 +B │ 123 ║ +║ 12.06 +KB │ 12, 345 ║ +║ 11.77 +MB │ 12, 345, 678 ║ +║ 1.15 +GB │ 1, 234, 567, 890 ║ +║ 1.12 +TB │ 1, 234, 567, 890, 123 ║ ╚═══════════╧═══════════════════╝ ``` @@ -247,7 +257,7 @@ and background color of cell contents. It is trivial to alternate the background color of each row as follows: ```Python from colorama import Back -print(generate_table(rows, cols, grid_style=tf.AlternatingRowGrid(Back.GREEN, Back.BLUE))) +print(generate_table(rows, cols, grid_style=tablefmt.AlternatingRowGrid(Back.GREEN, Back.BLUE))) ``` See the [cmd2_tables.py](https://github.com/python-tableformatter/tableformatter/blob/master/examples/cmd2_tables.py) or [color.py](https://github.com/python-tableformatter/tableformatter/blob/master/examples/color.py) examples for more diff --git a/examples/cmd2_tables.py b/examples/cmd2_tables.py index 54da6ae..e2a64c2 100755 --- a/examples/cmd2_tables.py +++ b/examples/cmd2_tables.py @@ -26,9 +26,11 @@ from typing import Tuple import cmd2 -import tableformatter as tf +import tableformatter as tablefmt # Configure colors for when users chooses the "-c" flag to enable color in the table output +import tableformatter.formatters + try: from colored import bg @@ -73,15 +75,17 @@ def two_dec(num: float) -> str: row.append(row[-2] / row[-1]) # Column headers plus optional formatting info for each column -COLUMNS = [tf.Column('City', width=11, header_halign=tf.ColumnAlignment.AlignCenter), - tf.Column('Province', header_halign=tf.ColumnAlignment.AlignCenter), +COLUMNS = [tablefmt.Column('City', width=11, header_halign=tablefmt.ColumnAlignment.AlignCenter), + tablefmt.Column('Province', header_halign=tablefmt.ColumnAlignment.AlignCenter), 'Country', # NOTE: If you don't need any special effects, you can just pass a string - tf.Column('Continent', cell_halign=tf.ColumnAlignment.AlignCenter), - tf.Column('Population', cell_halign=tf.ColumnAlignment.AlignRight, formatter=tf.FormatCommas()), - tf.Column('Area (km²)', width=7, header_halign=tf.ColumnAlignment.AlignCenter, - cell_halign=tf.ColumnAlignment.AlignRight, formatter=two_dec), - tf.Column('Pop. Density (/km²)', width=12, header_halign=tf.ColumnAlignment.AlignCenter, - cell_halign=tf.ColumnAlignment.AlignRight, formatter=no_dec), + tablefmt.Column('Continent', cell_halign=tablefmt.ColumnAlignment.AlignCenter), + tablefmt.Column('Population', + cell_halign=tablefmt.ColumnAlignment.AlignRight, + formatter=tableformatter.formatters.FormatCommas()), + tablefmt.Column('Area (km²)', width=7, header_halign=tablefmt.ColumnAlignment.AlignCenter, + cell_halign=tablefmt.ColumnAlignment.AlignRight, formatter=two_dec), + tablefmt.Column('Pop. Density (/km²)', width=12, header_halign=tablefmt.ColumnAlignment.AlignCenter, + cell_halign=tablefmt.ColumnAlignment.AlignRight, formatter=no_dec), ] @@ -123,16 +127,19 @@ def pop_density(data: CityInfo) -> str: # If table entries are python objects, all columns must be defined with the object attribute to query for each field # - attributes can be fields or functions. If a function is provided, the formatter will automatically call # the function to retrieve the value -OBJ_COLS = [tf.Column('City', attrib='city', header_halign=tf.ColumnAlignment.AlignCenter), - tf.Column('Province', attrib='province', header_halign=tf.ColumnAlignment.AlignCenter), - tf.Column('Country', attrib='country'), - tf.Column('Continent', attrib='continent', cell_halign=tf.ColumnAlignment.AlignCenter), - tf.Column('Population', attrib='get_population', cell_halign=tf.ColumnAlignment.AlignRight, - formatter=tf.FormatCommas()), - tf.Column('Area (km²)', attrib='get_area', width=7, header_halign=tf.ColumnAlignment.AlignCenter, - cell_halign=tf.ColumnAlignment.AlignRight, formatter=two_dec), - tf.Column('Pop. Density (/km²)', width=12, header_halign=tf.ColumnAlignment.AlignCenter, - cell_halign=tf.ColumnAlignment.AlignRight, obj_formatter=pop_density), +OBJ_COLS = [tablefmt.Column('City', attrib='city', header_halign=tablefmt.ColumnAlignment.AlignCenter), + tablefmt.Column('Province', attrib='province', header_halign=tablefmt.ColumnAlignment.AlignCenter), + tablefmt.Column('Country', attrib='country'), + tablefmt.Column('Continent', attrib='continent', cell_halign=tablefmt.ColumnAlignment.AlignCenter), + tablefmt.Column('Population', attrib='get_population', cell_halign=tablefmt.ColumnAlignment.AlignRight, + formatter=tableformatter.formatters.FormatCommas()), + tablefmt.Column('Area (km²)', + attrib='get_area', + width=7, + header_halign=tablefmt.ColumnAlignment.AlignCenter, + cell_halign=tablefmt.ColumnAlignment.AlignRight, formatter=two_dec), + tablefmt.Column('Pop. Density (/km²)', width=12, header_halign=tablefmt.ColumnAlignment.AlignCenter, + cell_halign=tablefmt.ColumnAlignment.AlignRight, obj_formatter=pop_density), ] EXTREMELY_HIGH_POULATION_DENSITY = 25000 @@ -142,7 +149,7 @@ def high_density_tuples(row_tuple: Tuple) -> dict: """Color rows with extremely high population density red.""" opts = dict() if len(row_tuple) >= 7 and row_tuple[6] > EXTREMELY_HIGH_POULATION_DENSITY: - opts[tf.TableFormatter.ROW_OPT_TEXT_COLOR] = tf.TableColors.TEXT_COLOR_RED + opts[tablefmt.TableFormatter.ROW_OPT_TEXT_COLOR] = tablefmt.TableColors.TEXT_COLOR_RED return opts @@ -150,7 +157,7 @@ def high_density_objs(row_obj: CityInfo) -> dict: """Color rows with extremely high population density red.""" opts = dict() if float(pop_density(row_obj)) > EXTREMELY_HIGH_POULATION_DENSITY: - opts[tf.TableFormatter.ROW_OPT_TEXT_COLOR] = tf.TableColors.TEXT_COLOR_RED + opts[tablefmt.TableFormatter.ROW_OPT_TEXT_COLOR] = tablefmt.TableColors.TEXT_COLOR_RED return opts @@ -170,11 +177,11 @@ def ptable(self, rows, columns, grid_args, row_stylist): :param row_stylist: function to determine how each row gets styled """ if grid_args.color: - grid = tf.AlternatingRowGrid(BACK_PRI, BACK_ALT) + grid = tablefmt.AlternatingRowGrid(BACK_PRI, BACK_ALT) elif grid_args.fancy: - grid = tf.FancyGrid() + grid = tablefmt.FancyGrid() elif grid_args.sparse: - grid = tf.SparseGrid() + grid = tablefmt.SparseGrid() else: grid = None @@ -182,8 +189,8 @@ def ptable(self, rows, columns, grid_args, row_stylist): if grid_args.transpose: transpose = True - formatted_table = tf.generate_table(rows=rows, columns=columns, grid_style=grid, row_tagger=row_stylist, - transpose=transpose) + formatted_table = tablefmt.generate_table(rows=rows, columns=columns, grid_style=grid, row_tagger=row_stylist, + transpose=transpose) self.ppaged(formatted_table, chop=True) table_parser = argparse.ArgumentParser() diff --git a/examples/color.py b/examples/color.py index c514d16..37ab358 100755 --- a/examples/color.py +++ b/examples/color.py @@ -3,7 +3,7 @@ """ Simple demonstration of TableFormatter with some colored output. """ -import tableformatter as tf +import tableformatter as tablefmt from tableformatter import generate_table try: @@ -30,4 +30,4 @@ columns = ('Col1', 'Col2', 'Col3', 'Col4') print("Table with colorful alternating rows") -print(generate_table(rows, columns, grid_style=tf.AlternatingRowGrid(BACK_GREEN, BACK_BLUE))) +print(generate_table(rows, columns, grid_style=tablefmt.AlternatingRowGrid(BACK_GREEN, BACK_BLUE))) diff --git a/examples/columns.py b/examples/columns.py index 373e5eb..4a45416 100755 --- a/examples/columns.py +++ b/examples/columns.py @@ -3,7 +3,7 @@ """ Demonstration of all of the per-Column customizations that are available. """ -import tableformatter as tf +import tableformatter as tablefmt class MyRowObject(object): @@ -75,70 +75,70 @@ def int2word(num, separator="-"): MyRowObject('D1', 'D2', 7, 5)] -columns = (tf.Column('First', width=20, attrib='field1'), - tf.Column('Second', attrib='field2'), - tf.Column('Num 1', width=3, attrib='get_field3'), - tf.Column('Num 2', attrib='field4'), - tf.Column('Multiplied', obj_formatter=multiply)) +columns = (tablefmt.Column('First', width=20, attrib='field1'), + tablefmt.Column('Second', attrib='field2'), + tablefmt.Column('Num 1', width=3, attrib='get_field3'), + tablefmt.Column('Num 2', attrib='field4'), + tablefmt.Column('Multiplied', obj_formatter=multiply)) print("First: Wrapped\nMultiplied: object formatter") -print(tf.generate_table(rows, columns)) +print(tablefmt.generate_table(rows, columns)) -columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.WRAP_WITH_INDENT), - tf.Column('Second', attrib='field2'), - tf.Column('Num 1', width=3, attrib='get_field3', header_halign=tf.ColumnAlignment.AlignCenter), - tf.Column('Num 2', attrib='field4'), - tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) +columns = (tablefmt.Column('First', width=20, attrib='field1', wrap_mode=tablefmt.WrapMode.WRAP_WITH_INDENT), + tablefmt.Column('Second', attrib='field2'), + tablefmt.Column('Num 1', width=3, attrib='get_field3', header_halign=tablefmt.ColumnAlignment.AlignCenter), + tablefmt.Column('Num 2', attrib='field4'), + tablefmt.Column('Multiplied', attrib=None, obj_formatter=multiply)) print("First: Wrapped with indent\nNum 1: header align center") -print(tf.generate_table(rows, columns)) +print(tablefmt.generate_table(rows, columns)) -columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.WRAP_WITH_INDENT, +columns = (tablefmt.Column('First', width=20, attrib='field1', wrap_mode=tablefmt.WrapMode.WRAP_WITH_INDENT, wrap_prefix='>>> '), - tf.Column('Second', attrib='field2', cell_halign=tf.ColumnAlignment.AlignCenter), - tf.Column('Num 1', width=3, attrib='get_field3', header_halign=tf.ColumnAlignment.AlignRight), - tf.Column('Num 2', attrib='field4', header_valign=tf.ColumnAlignment.AlignTop), - tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) + tablefmt.Column('Second', attrib='field2', cell_halign=tablefmt.ColumnAlignment.AlignCenter), + tablefmt.Column('Num 1', width=3, attrib='get_field3', header_halign=tablefmt.ColumnAlignment.AlignRight), + tablefmt.Column('Num 2', attrib='field4', header_valign=tablefmt.ColumnAlignment.AlignTop), + tablefmt.Column('Multiplied', attrib=None, obj_formatter=multiply)) print("First: Wrapped with indent, custom wrap prefix\n" "Second: Header align center\n" "Num 1: header align right\n" "Num 2: header align top") -print(tf.generate_table(rows, columns)) +print(tablefmt.generate_table(rows, columns)) -columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_END), - tf.Column('Second', attrib='field2', cell_padding=3), - tf.Column('Num 1', width=3, attrib='get_field3'), - tf.Column('Num 2', attrib='field4'), - tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) +columns = (tablefmt.Column('First', width=20, attrib='field1', wrap_mode=tablefmt.WrapMode.TRUNCATE_END), + tablefmt.Column('Second', attrib='field2', cell_padding=3), + tablefmt.Column('Num 1', width=3, attrib='get_field3'), + tablefmt.Column('Num 2', attrib='field4'), + tablefmt.Column('Multiplied', attrib=None, obj_formatter=multiply)) print("First: Truncate end\n" "Second: cell padding 3 spaces") -print(tf.generate_table(rows, columns)) +print(tablefmt.generate_table(rows, columns)) -columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_FRONT), - tf.Column('Second', attrib='field2', cell_padding=5, cell_halign=tf.ColumnAlignment.AlignRight), - tf.Column('Num 1', attrib='get_field3'), - tf.Column('Num 2', attrib='field4'), - tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) +columns = (tablefmt.Column('First', width=20, attrib='field1', wrap_mode=tablefmt.WrapMode.TRUNCATE_FRONT), + tablefmt.Column('Second', attrib='field2', cell_padding=5, cell_halign=tablefmt.ColumnAlignment.AlignRight), + tablefmt.Column('Num 1', attrib='get_field3'), + tablefmt.Column('Num 2', attrib='field4'), + tablefmt.Column('Multiplied', attrib=None, obj_formatter=multiply)) print("First; Truncate Front\n" "Second: cell align right, cell padding=5") -print(tf.generate_table(rows, columns)) +print(tablefmt.generate_table(rows, columns)) -columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_MIDDLE), - tf.Column('Second', attrib='field2'), - tf.Column('Num 1', attrib='get_field3'), - tf.Column('Num 2', attrib='field4', cell_valign=tf.ColumnAlignment.AlignBottom), - tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) +columns = (tablefmt.Column('First', width=20, attrib='field1', wrap_mode=tablefmt.WrapMode.TRUNCATE_MIDDLE), + tablefmt.Column('Second', attrib='field2'), + tablefmt.Column('Num 1', attrib='get_field3'), + tablefmt.Column('Num 2', attrib='field4', cell_valign=tablefmt.ColumnAlignment.AlignBottom), + tablefmt.Column('Multiplied', attrib=None, obj_formatter=multiply)) print("First: Truncate Middle\nNum 2: cell align bottom") -print(tf.generate_table(rows, columns)) +print(tablefmt.generate_table(rows, columns)) -columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_HARD), - tf.Column('Second', attrib='field2'), - tf.Column('Num 1', attrib='get_field3'), - tf.Column('Num 2', attrib='field4', formatter=int2word), - tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) +columns = (tablefmt.Column('First', width=20, attrib='field1', wrap_mode=tablefmt.WrapMode.TRUNCATE_HARD), + tablefmt.Column('Second', attrib='field2'), + tablefmt.Column('Num 1', attrib='get_field3'), + tablefmt.Column('Num 2', attrib='field4', formatter=int2word), + tablefmt.Column('Multiplied', attrib=None, obj_formatter=multiply)) print("First: Truncate Hard\nNum 2: Field formatter") -print(tf.generate_table(rows, columns)) +print(tablefmt.generate_table(rows, columns)) diff --git a/examples/data_types.py b/examples/data_types.py index aae1c91..85afd35 100755 --- a/examples/data_types.py +++ b/examples/data_types.py @@ -15,33 +15,33 @@ from collections import OrderedDict import numpy as np import pandas as pd -import tableformatter as tf +import tableformatter as tablefmt iteralbe_of_iterables = [[1, 2, 3, 4], [5, 6, 7, 8]] print("Data type: iterable of iterables") print(iteralbe_of_iterables) -print(tf.generate_table(iteralbe_of_iterables)) +print(tablefmt.generate_table(iteralbe_of_iterables)) np_2d_array = np.array([[1, 2, 3, 4], [5, 6, 7, 8]]) print("Data type: NumPy 2D array") print(np_2d_array) -print(tf.generate_table(np_2d_array)) +print(tablefmt.generate_table(np_2d_array)) np_rec_array = np.rec.array([(1, 2., 'Hello'), (2, 3., "World")], dtype=[('foo', 'i4'), ('bar', 'f4'), ('baz', 'U10')]) print("Data type: Numpy record array") print(np_rec_array) -print(tf.generate_table(np_rec_array)) +print(tablefmt.generate_table(np_rec_array)) d = {'col1': [1, 5], 'col2': [2, 6], 'col3': [3, 7], 'col4': [4, 8]} od = OrderedDict(sorted(d.items(), key=lambda t: t[0])) pandas_dataframe = pd.DataFrame(data=od) print("Data type: Pandas DataFrame") print(pandas_dataframe) -print(tf.generate_table(pandas_dataframe)) +print(tablefmt.generate_table(pandas_dataframe)) d1 = {1: 'a', 2: 'b', 3: 'c', 4: 'd'} d2 = {5: 'e', 6: 'f', 7: 'g', 8: 'h'} @@ -49,12 +49,12 @@ OrderedDict(sorted(d2.items(), key=lambda t: t[0]))] print("Data type: iterable of dicts (dict keys iterated through as column values)") print(iterable_of_dicts) -print(tf.generate_table(iterable_of_dicts)) +print(tablefmt.generate_table(iterable_of_dicts)) dict_of_iterables = od print("Data type: dict of iterables (dict keys iterated through as rows where each key must be a hashable iterable)") print(dict_of_iterables) -print(tf.generate_table(dict_of_iterables)) +print(tablefmt.generate_table(dict_of_iterables)) class MyRowObject(object): @@ -72,10 +72,10 @@ def get_field3(self): rows = [MyRowObject(1, 2, 3, 4), MyRowObject(5, 6, 7, 8)] -columns = (tf.Column('Col1', attrib='field1'), - tf.Column('Col2', attrib='field2'), - tf.Column('Col3', attrib='get_field3'), - tf.Column('Col4', attrib='field4')) +columns = (tablefmt.Column('Col1', attrib='field1'), + tablefmt.Column('Col2', attrib='field2'), + tablefmt.Column('Col3', attrib='get_field3'), + tablefmt.Column('Col4', attrib='field4')) print("Data type: iterable of arbitrary non-iterable objects") print(rows) -print(tf.generate_table(rows, columns)) +print(tablefmt.generate_table(rows, columns)) diff --git a/examples/formatters.py b/examples/formatters.py index b96d833..f267f60 100755 --- a/examples/formatters.py +++ b/examples/formatters.py @@ -3,9 +3,11 @@ """ Demonstration of field and object formatters for both object and tuple based table entries """ -import tableformatter as tf +import tableformatter as tablefmt from typing import Tuple +import tableformatter.formatters + class MyRowObject(object): """Simple object to demonstrate using a list of objects with TableFormatter""" @@ -80,7 +82,7 @@ def tag_row_obj(row_obj: MyRowObject) -> dict: optional properties""" opts = dict() if row_obj.field4 % 4 == 0: - opts[tf.TableFormatter.ROW_OPT_TEXT_COLOR] = tf.TableColors.TEXT_COLOR_GREEN + opts[tablefmt.TableFormatter.ROW_OPT_TEXT_COLOR] = tablefmt.TableColors.TEXT_COLOR_GREEN return opts rows = [MyRowObject(None, None, 17, 4), @@ -91,16 +93,16 @@ def tag_row_obj(row_obj: MyRowObject) -> dict: MyRowObject(1234567890, 1234567890, 7, 5), MyRowObject(1234567890123, 1234567890123, 7, 5)] -columns = (tf.Column('First', width=20, attrib='field1', formatter=tf.FormatBytes(), - cell_halign=tf.ColumnAlignment.AlignRight), - tf.Column('Second', attrib='field2', formatter=tf.FormatCommas(), - cell_halign=tf.ColumnAlignment.AlignRight), - tf.Column('Num 1', width=3, attrib='get_field3'), - tf.Column('Num 2', attrib='field4'), - tf.Column('Multiplied', obj_formatter=multiply)) +columns = (tablefmt.Column('First', width=20, attrib='field1', formatter=tableformatter.formatters.FormatBytes(), + cell_halign=tablefmt.ColumnAlignment.AlignRight), + tablefmt.Column('Second', attrib='field2', formatter=tableformatter.formatters.FormatCommas(), + cell_halign=tablefmt.ColumnAlignment.AlignRight), + tablefmt.Column('Num 1', width=3, attrib='get_field3'), + tablefmt.Column('Num 2', attrib='field4'), + tablefmt.Column('Multiplied', obj_formatter=multiply)) print("Formatters on object-based row entries") -print(tf.generate_table(rows, columns, row_tagger=tag_row_obj)) +print(tablefmt.generate_table(rows, columns, row_tagger=tag_row_obj)) def tag_row_tuples(row_tuple: Tuple) -> dict: @@ -112,7 +114,7 @@ def tag_row_tuples(row_tuple: Tuple) -> dict: """ opts = dict() if len(row_tuple) >= 4 and row_tuple[3] % 4 == 0: - opts[tf.TableFormatter.ROW_OPT_TEXT_COLOR] = tf.TableColors.TEXT_COLOR_GREEN + opts[tablefmt.TableFormatter.ROW_OPT_TEXT_COLOR] = tablefmt.TableColors.TEXT_COLOR_GREEN return opts @@ -124,11 +126,11 @@ def tag_row_tuples(row_tuple: Tuple) -> dict: (1234567890, 1234567890, 7, 5, None), (1234567890123, 1234567890123, 7, 5, None)] -columns = (tf.Column('First', width=20, formatter=tf.FormatBytes(), cell_halign=tf.ColumnAlignment.AlignRight), - tf.Column('Second', formatter=tf.FormatCommas(), cell_halign=tf.ColumnAlignment.AlignRight), - tf.Column('Num 1'), - tf.Column('Num 2', formatter=int2word), - tf.Column('Multiplied', obj_formatter=multiply_tuple)) +columns = (tablefmt.Column('First', width=20, formatter=tableformatter.formatters.FormatBytes(), cell_halign=tablefmt.ColumnAlignment.AlignRight), + tablefmt.Column('Second', formatter=tableformatter.formatters.FormatCommas(), cell_halign=tablefmt.ColumnAlignment.AlignRight), + tablefmt.Column('Num 1'), + tablefmt.Column('Num 2', formatter=int2word), + tablefmt.Column('Multiplied', obj_formatter=multiply_tuple)) print("Formatters on tuple-based row entries") -print(tf.generate_table(rows, columns, row_tagger=tag_row_tuples)) +print(tablefmt.generate_table(rows, columns, row_tagger=tag_row_tuples)) diff --git a/examples/rows.py b/examples/rows.py index e63a3cc..58b30b8 100755 --- a/examples/rows.py +++ b/examples/rows.py @@ -4,7 +4,7 @@ Demonstration of per-row customizations (currently only text foreground color) Also demonstrates use of object formatter with both tuple-based and object-base row entries """ -import tableformatter as tf +import tableformatter as tablefmt class MyRowObject(object): @@ -75,37 +75,37 @@ def int2word(num, separator="-"): print("num out of range") -rows = [tf.Row(MyRowObject('Longer text that will trigger the column wrapping', 'A2', 5, 56), - text_color=tf.TableColors.TEXT_COLOR_RED), - tf.Row(MyRowObject('B1', 'B2\nB2\nB2', 23, 8), - text_color=tf.TableColors.TEXT_COLOR_GREEN), +rows = [tablefmt.Row(MyRowObject('Longer text that will trigger the column wrapping', 'A2', 5, 56), + text_color=tablefmt.TableColors.TEXT_COLOR_RED), + tablefmt.Row(MyRowObject('B1', 'B2\nB2\nB2', 23, 8), + text_color=tablefmt.TableColors.TEXT_COLOR_GREEN), MyRowObject('C1', 'C2', 4, 9), MyRowObject('D1', 'D2', 7, 5)] -columns = (tf.Column('First', width=20, attrib='field1'), - tf.Column('Second', attrib='field2'), - tf.Column('Num 1', width=3, attrib='get_field3'), - tf.Column('Num 2', attrib='field4'), - tf.Column('Multiplied', obj_formatter=multiply)) +columns = (tablefmt.Column('First', width=20, attrib='field1'), + tablefmt.Column('Second', attrib='field2'), + tablefmt.Column('Num 1', width=3, attrib='get_field3'), + tablefmt.Column('Num 2', attrib='field4'), + tablefmt.Column('Multiplied', obj_formatter=multiply)) print("Per-row customizations with object-based row entries") -print(tf.generate_table(rows, columns)) +print(tablefmt.generate_table(rows, columns)) -rows = [tf.Row('Longer text that will trigger the column wrapping', 'A2', 5, 56, None, - text_color=tf.TableColors.TEXT_COLOR_RED), - tf.Row('B1', 'B2\nB2\nB2', 23, 8, None, - text_color=tf.TableColors.TEXT_COLOR_GREEN), +rows = [tablefmt.Row('Longer text that will trigger the column wrapping', 'A2', 5, 56, None, + text_color=tablefmt.TableColors.TEXT_COLOR_RED), + tablefmt.Row('B1', 'B2\nB2\nB2', 23, 8, None, + text_color=tablefmt.TableColors.TEXT_COLOR_GREEN), ('C1', 'C2', 4, 9, None), ('D1', 'D2', 7, 5, None)] -columns = (tf.Column('First', width=20, wrap_mode=tf.WrapMode.TRUNCATE_HARD), - tf.Column('Second'), - tf.Column('Num 1'), - tf.Column('Num 2', formatter=int2word), - tf.Column('Multiplied', obj_formatter=multiply_tuple)) +columns = (tablefmt.Column('First', width=20, wrap_mode=tablefmt.WrapMode.TRUNCATE_HARD), + tablefmt.Column('Second'), + tablefmt.Column('Num 1'), + tablefmt.Column('Num 2', formatter=int2word), + tablefmt.Column('Multiplied', obj_formatter=multiply_tuple)) print("Per-row customizations with tuple-based row entries") -print(tf.generate_table(rows, columns)) +print(tablefmt.generate_table(rows, columns)) diff --git a/examples/simple_text.py b/examples/simple_text.py index df5dfb4..75dbab2 100755 --- a/examples/simple_text.py +++ b/examples/simple_text.py @@ -4,7 +4,7 @@ Simple demonstration of TableFormatter with a list of tuples as table entries. TableFormatter will automatically expand the row height to handle multi-line entries. """ -import tableformatter as tf +import tableformatter as tablefmt from tableformatter import generate_table rows = [('A1', 'A2', 'A3', 'A4'), @@ -23,10 +23,10 @@ print("Basic Table, FancyGrid:") -print(generate_table(rows, grid_style=tf.FancyGrid())) +print(generate_table(rows, grid_style=tablefmt.FancyGrid())) print("Basic Table, SparseGrid:") -print(generate_table(rows, grid_style=tf.SparseGrid())) +print(generate_table(rows, grid_style=tablefmt.SparseGrid())) print("Table with header, AlteratingRowGrid:") print(generate_table(rows, columns)) @@ -37,7 +37,7 @@ print("Table with header, transposed, FancyGrid:") -print(generate_table(rows, columns, grid_style=tf.FancyGrid(), transpose=True)) +print(generate_table(rows, columns, grid_style=tablefmt.FancyGrid(), transpose=True)) print("Table with header, transposed, SparseGrid:") -print(generate_table(rows, columns, grid_style=tf.SparseGrid(), transpose=True)) +print(generate_table(rows, columns, grid_style=tablefmt.SparseGrid(), transpose=True)) diff --git a/examples/write_table.py b/examples/write_table.py index 5111634..57c0a9e 100755 --- a/examples/write_table.py +++ b/examples/write_table.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # coding=utf-8 -import tableformatter as tf +import tableformatter as tablefmt from tableformatter import generate_table rows = [('A1', 'A2', 'A3', 'A4'), @@ -10,14 +10,14 @@ columns = ('Col1', 'Col2', 'Col3', 'Col4') -tf.TableColors.set_color_library('None') +tablefmt.TableColors.set_color_library('None') with open('table_none.txt', mode='w') as outfile: - print(generate_table(rows, columns, grid_style=tf.FancyGrid()), file=outfile) + print(generate_table(rows, columns, grid_style=tablefmt.FancyGrid()), file=outfile) -tf.TableColors.set_color_library('colorama') +tablefmt.TableColors.set_color_library('colorama') with open('table_colorama.txt', mode='w') as outfile: - print(generate_table(rows, columns, grid_style=tf.FancyGrid()), file=outfile) + print(generate_table(rows, columns, grid_style=tablefmt.FancyGrid()), file=outfile) -tf.TableColors.set_color_library('colored') +tablefmt.TableColors.set_color_library('colored') with open('table_colored.txt', mode='w') as outfile: - print(generate_table(rows, columns, grid_style=tf.FancyGrid()), file=outfile) + print(generate_table(rows, columns, grid_style=tablefmt.FancyGrid()), file=outfile) diff --git a/setup.py b/setup.py index b190025..3c50122 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ } PACKAGE_DATA = { - '': ['py.typed'] + 'tableformatter': ['py.typed'] } setup( diff --git a/tableformatter/__init__.py b/tableformatter/__init__.py new file mode 100644 index 0000000..fffdb4c --- /dev/null +++ b/tableformatter/__init__.py @@ -0,0 +1,35 @@ +from .formatters import ( + FormatBytes, + FormatCommas, +) +from .tableformatter import ( + AlternatingRowGrid, + Column, + ColumnAlignment, + FancyGrid, + Grid, + Row, + SparseGrid, + TableColors, + TableFormatter, + WrapMode, + generate_table, + set_default_grid, +) + +__all__: List[str] = [ + 'AlternatingRowGrid', + 'Column', + 'ColumnAlignment', + 'FancyGrid', + 'FormatBytes', + 'FormatCommas', + 'Grid', + 'Row', + 'SparseGrid', + 'TableColors', + 'TableFormatter', + 'WrapMode', + 'generate_table', + 'set_default_grid', +] diff --git a/tableformatter/formatters.py b/tableformatter/formatters.py new file mode 100644 index 0000000..df4dc6c --- /dev/null +++ b/tableformatter/formatters.py @@ -0,0 +1,44 @@ +class FormatBytes: + """Formats a value in bytes into a human readable string""" + KB = 1024 + MB = KB * 1024 + GB = MB * 1024 + TB = GB * 1024 + + def __call__(self, byte_size: int): + """ + Formats a value in bytes into a human readable string + :param byte_size: size in bytes + :return: human-readable string + """ + if byte_size is None: + return '' + if not isinstance(byte_size, int): + try: + byte_size = int(byte_size) + except ValueError: + return '' + + decimal_format = '{:.02f}' + if byte_size > FormatBytes.TB: + out = decimal_format.format(byte_size / FormatBytes.TB) + " TB" + elif byte_size > FormatBytes.GB: + out = decimal_format.format(byte_size / FormatBytes.GB) + " GB" + elif byte_size > FormatBytes.MB: + out = decimal_format.format(byte_size / FormatBytes.MB) + " MB" + elif byte_size > FormatBytes.KB: + out = decimal_format.format(byte_size / FormatBytes.KB) + " KB" + else: + out = decimal_format.format(byte_size) + " B" + + return out + + +class FormatCommas: + """Formats a number with comma separators""" + def __call__(self, number: Union[int, str]): + if number is None: + return '' + if isinstance(number, str): + number = int(number) + return format(number, ',d') diff --git a/py.typed b/tableformatter/py.typed similarity index 100% rename from py.typed rename to tableformatter/py.typed diff --git a/tableformatter.py b/tableformatter/tableformatter.py similarity index 97% rename from tableformatter.py rename to tableformatter/tableformatter.py index d226123..0044870 100755 --- a/tableformatter.py +++ b/tableformatter/tableformatter.py @@ -7,7 +7,7 @@ import itertools import re import textwrap as textw -from typing import List, Iterable, Optional, Tuple, Union, Callable, Sequence +from typing import Callable, Iterable, List, Optional, Sequence, Tuple, Union from wcwidth import wcswidth @@ -42,7 +42,7 @@ def __subclasshook__(cls, C): ELLIPSIS = '…' -def _text_wrap(text: str, width: int=70) -> List[str]: +def _text_wrap(text: str, width: int = 70) -> List[str]: """Wrap a single paragraph of text, returning a list of wrapped lines. Reformat the single paragraph in 'text' so it fits in lines of no @@ -287,7 +287,7 @@ def _printable_width(text: str) -> int: class TableColors(object): """Colors""" try: - from colored import fg, bg, attr + from colored import attr, bg, fg TEXT_COLOR_WHITE = fg('white') TEXT_COLOR_YELLOW = fg(226) @@ -300,7 +300,7 @@ class TableColors(object): RESET = attr('reset') except ImportError: try: - from colorama import Fore, Back, Style + from colorama import Back, Fore, Style TEXT_COLOR_WHITE = Fore.WHITE TEXT_COLOR_YELLOW = Fore.LIGHTYELLOW_EX @@ -326,7 +326,7 @@ class TableColors(object): def set_color_library(cls, library_name: str) -> None: """Manually override the color library being used.""" if library_name == 'colored': - from colored import fg, bg, attr + from colored import attr, bg, fg cls.TEXT_COLOR_WHITE = fg('white') cls.TEXT_COLOR_YELLOW = fg(226) @@ -338,7 +338,7 @@ def set_color_library(cls, library_name: str) -> None: cls.BOLD = attr('bold') cls.RESET = attr('reset') elif library_name == 'colorama': - from colorama import Fore, Back, Style + from colorama import Back, Fore, Style cls.TEXT_COLOR_WHITE = Fore.WHITE cls.TEXT_COLOR_YELLOW = Fore.LIGHTYELLOW_EX @@ -1428,47 +1428,3 @@ def _get_pad_string(self, column: Union[int, str]): return '' -class FormatBytes: - """Formats a value in bytes into a human readable string""" - KB = 1024 - MB = KB * 1024 - GB = MB * 1024 - TB = GB * 1024 - - def __call__(self, byte_size: int): - """ - Formats a value in bytes into a human readable string - :param byte_size: size in bytes - :return: human-readable string - """ - if byte_size is None: - return '' - if not isinstance(byte_size, int): - try: - byte_size = int(byte_size) - except ValueError: - return '' - - decimal_format = '{:.02f}' - if byte_size > FormatBytes.TB: - out = decimal_format.format(byte_size / FormatBytes.TB) + " TB" - elif byte_size > FormatBytes.GB: - out = decimal_format.format(byte_size / FormatBytes.GB) + " GB" - elif byte_size > FormatBytes.MB: - out = decimal_format.format(byte_size / FormatBytes.MB) + " MB" - elif byte_size > FormatBytes.KB: - out = decimal_format.format(byte_size / FormatBytes.KB) + " KB" - else: - out = decimal_format.format(byte_size) + " B" - - return out - - -class FormatCommas: - """Formats a number with comma separators""" - def __call__(self, number: Union[int, str]): - if number is None: - return '' - if isinstance(number, str): - number = int(number) - return format(number, ',d') diff --git a/tests/test_columns.py b/tests/test_columns.py index b40831d..46aa7cd 100644 --- a/tests/test_columns.py +++ b/tests/test_columns.py @@ -4,11 +4,11 @@ """ import pytest -import tableformatter as tf +import tableformatter as tablefmt # Make the test results reproducible regardless of what color libraries are installed -tf.TableColors.set_color_library('none') -tf.set_default_grid(tf.AlternatingRowGrid('', '')) +tablefmt.TableColors.set_color_library('none') +tablefmt.set_default_grid(tablefmt.AlternatingRowGrid('', '')) class MyRowObject(object): @@ -84,12 +84,12 @@ def obj_rows(): def test_wrapped_object_formatter(obj_rows): - columns = (tf.Column('First', width=20, attrib='field1'), - tf.Column('Second', attrib='field2'), - tf.Column('Num 1', width=3, attrib='get_field3'), - tf.Column('Num 2', attrib='field4'), - tf.Column('Multiplied', obj_formatter=multiply)) - table = tf.generate_table(obj_rows, columns) + columns = (tablefmt.Column('First', width=20, attrib='field1'), + tablefmt.Column('Second', attrib='field2'), + tablefmt.Column('Num 1', width=3, attrib='get_field3'), + tablefmt.Column('Num 2', attrib='field4'), + tablefmt.Column('Multiplied', obj_formatter=multiply)) + table = tablefmt.generate_table(obj_rows, columns) expected = ''' ╔══════════════════════╤════════╤═════╤═══════╤════════════╗ ║ │ │ Num │ │ ║ @@ -109,12 +109,12 @@ def test_wrapped_object_formatter(obj_rows): def test_wrapped_indent_center_header(obj_rows): - columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.WRAP_WITH_INDENT), - tf.Column('Second', attrib='field2'), - tf.Column('Num 1', width=3, attrib='get_field3', header_halign=tf.ColumnAlignment.AlignCenter), - tf.Column('Num 2', attrib='field4'), - tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) - table = tf.generate_table(obj_rows, columns) + columns = (tablefmt.Column('First', width=20, attrib='field1', wrap_mode=tablefmt.WrapMode.WRAP_WITH_INDENT), + tablefmt.Column('Second', attrib='field2'), + tablefmt.Column('Num 1', width=3, attrib='get_field3', header_halign=tablefmt.ColumnAlignment.AlignCenter), + tablefmt.Column('Num 2', attrib='field4'), + tablefmt.Column('Multiplied', attrib=None, obj_formatter=multiply)) + table = tablefmt.generate_table(obj_rows, columns) expected = ''' ╔══════════════════════╤════════╤═════╤═══════╤════════════╗ ║ │ │ Num │ │ ║ @@ -134,13 +134,13 @@ def test_wrapped_indent_center_header(obj_rows): def test_wrapped_custom_indent_header_right_header_top(obj_rows): - columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.WRAP_WITH_INDENT, + columns = (tablefmt.Column('First', width=20, attrib='field1', wrap_mode=tablefmt.WrapMode.WRAP_WITH_INDENT, wrap_prefix='>>> '), - tf.Column('Second', attrib='field2', cell_halign=tf.ColumnAlignment.AlignCenter), - tf.Column('Num 1', width=3, attrib='get_field3', header_halign=tf.ColumnAlignment.AlignRight), - tf.Column('Num 2', attrib='field4', header_valign=tf.ColumnAlignment.AlignTop), - tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) - table = tf.generate_table(obj_rows, columns) + tablefmt.Column('Second', attrib='field2', cell_halign=tablefmt.ColumnAlignment.AlignCenter), + tablefmt.Column('Num 1', width=3, attrib='get_field3', header_halign=tablefmt.ColumnAlignment.AlignRight), + tablefmt.Column('Num 2', attrib='field4', header_valign=tablefmt.ColumnAlignment.AlignTop), + tablefmt.Column('Multiplied', attrib=None, obj_formatter=multiply)) + table = tablefmt.generate_table(obj_rows, columns) expected = ''' ╔══════════════════════╤════════╤═════╤═══════╤════════════╗ ║ │ │ Num │ Num 2 │ ║ @@ -160,12 +160,12 @@ def test_wrapped_custom_indent_header_right_header_top(obj_rows): def test_truncate_end_custom_padding(obj_rows): - columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_END), - tf.Column('Second', attrib='field2', cell_padding=3), - tf.Column('Num 1', width=3, attrib='get_field3'), - tf.Column('Num 2', attrib='field4'), - tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) - table = tf.generate_table(obj_rows, columns) + columns = (tablefmt.Column('First', width=20, attrib='field1', wrap_mode=tablefmt.WrapMode.TRUNCATE_END), + tablefmt.Column('Second', attrib='field2', cell_padding=3), + tablefmt.Column('Num 1', width=3, attrib='get_field3'), + tablefmt.Column('Num 2', attrib='field4'), + tablefmt.Column('Multiplied', attrib=None, obj_formatter=multiply)) + table = tablefmt.generate_table(obj_rows, columns) expected = ''' ╔══════════════════════╤════════════╤═════╤═══════╤════════════╗ ║ │ │ Num │ │ ║ @@ -183,12 +183,12 @@ def test_truncate_end_custom_padding(obj_rows): def test_truncate_front_custom_padding_cell_align_right(obj_rows): - columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_FRONT), - tf.Column('Second', attrib='field2', cell_padding=5, cell_halign=tf.ColumnAlignment.AlignRight), - tf.Column('Num 1', attrib='get_field3'), - tf.Column('Num 2', attrib='field4'), - tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) - table = tf.generate_table(obj_rows, columns) + columns = (tablefmt.Column('First', width=20, attrib='field1', wrap_mode=tablefmt.WrapMode.TRUNCATE_FRONT), + tablefmt.Column('Second', attrib='field2', cell_padding=5, cell_halign=tablefmt.ColumnAlignment.AlignRight), + tablefmt.Column('Num 1', attrib='get_field3'), + tablefmt.Column('Num 2', attrib='field4'), + tablefmt.Column('Multiplied', attrib=None, obj_formatter=multiply)) + table = tablefmt.generate_table(obj_rows, columns) expected = ''' ╔══════════════════════╤════════════════╤═══════╤═══════╤════════════╗ ║ First │ Second │ Num 1 │ Num 2 │ Multiplied ║ @@ -205,12 +205,12 @@ def test_truncate_front_custom_padding_cell_align_right(obj_rows): def test_truncate_middle_cell_align_bottom(obj_rows): - columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_MIDDLE), - tf.Column('Second', attrib='field2'), - tf.Column('Num 1', attrib='get_field3'), - tf.Column('Num 2', attrib='field4', cell_valign=tf.ColumnAlignment.AlignBottom), - tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) - table = tf.generate_table(obj_rows, columns) + columns = (tablefmt.Column('First', width=20, attrib='field1', wrap_mode=tablefmt.WrapMode.TRUNCATE_MIDDLE), + tablefmt.Column('Second', attrib='field2'), + tablefmt.Column('Num 1', attrib='get_field3'), + tablefmt.Column('Num 2', attrib='field4', cell_valign=tablefmt.ColumnAlignment.AlignBottom), + tablefmt.Column('Multiplied', attrib=None, obj_formatter=multiply)) + table = tablefmt.generate_table(obj_rows, columns) expected = ''' ╔══════════════════════╤════════╤═══════╤═══════╤════════════╗ ║ First │ Second │ Num 1 │ Num 2 │ Multiplied ║ @@ -227,12 +227,12 @@ def test_truncate_middle_cell_align_bottom(obj_rows): def test_truncate_hard_field_formatter(obj_rows): - columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_HARD), - tf.Column('Second', attrib='field2'), - tf.Column('Num 1', attrib='get_field3'), - tf.Column('Num 2', attrib='field4', formatter=int2word), - tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) - table = tf.generate_table(obj_rows, columns) + columns = (tablefmt.Column('First', width=20, attrib='field1', wrap_mode=tablefmt.WrapMode.TRUNCATE_HARD), + tablefmt.Column('Second', attrib='field2'), + tablefmt.Column('Num 1', attrib='get_field3'), + tablefmt.Column('Num 2', attrib='field4', formatter=int2word), + tablefmt.Column('Multiplied', attrib=None, obj_formatter=multiply)) + table = tablefmt.generate_table(obj_rows, columns) expected = ''' ╔══════════════════════╤════════╤═══════╤═══════════╤════════════╗ ║ First │ Second │ Num 1 │ Num 2 │ Multiplied ║ diff --git a/tests/test_data_types.py b/tests/test_data_types.py index 11f4d3d..3e57776 100644 --- a/tests/test_data_types.py +++ b/tests/test_data_types.py @@ -5,7 +5,7 @@ from collections import OrderedDict from typing import Iterator -import tableformatter as tf +import tableformatter as tablefmt EXPECTED_BASIC = ''' @@ -31,7 +31,7 @@ def test_iterable_of_iterables(): - table = tf.generate_table(iteralbe_of_iterables) + table = tablefmt.generate_table(iteralbe_of_iterables) assert table == EXPECTED_BASIC @@ -40,12 +40,12 @@ def test_iterable_of_dicts(): d2 = {5: 'e', 6: 'f', 7: 'g', 8: 'h'} iterable_of_dicts = [OrderedDict(sorted(d1.items(), key=lambda t: t[0])), OrderedDict(sorted(d2.items(), key=lambda t: t[0]))] - table = tf.generate_table(iterable_of_dicts) + table = tablefmt.generate_table(iterable_of_dicts) assert table == EXPECTED_BASIC def test_dict_of_iterables(): - table = tf.generate_table(od) + table = tablefmt.generate_table(od) assert table == EXPECTED_WITH_HEADERS @@ -65,11 +65,11 @@ def get_field3(self): def test_iterable_of_non_iterable_objects(): rows = [MyRowObject(1, 2, 3, 4), MyRowObject(5, 6, 7, 8)] - columns = (tf.Column('col1', attrib='field1'), - tf.Column('col2', attrib='field2'), - tf.Column('col3', attrib='get_field3'), - tf.Column('col4', attrib='field4')) - table = tf.generate_table(rows, columns) + columns = (tablefmt.Column('col1', attrib='field1'), + tablefmt.Column('col2', attrib='field2'), + tablefmt.Column('col3', attrib='get_field3'), + tablefmt.Column('col4', attrib='field4')) + table = tablefmt.generate_table(rows, columns) assert table == EXPECTED_WITH_HEADERS @@ -106,11 +106,11 @@ def __iter__(self) -> Iterator[int]: def test_iterable_of_non_indexable_iterables(): rows = [NonIndexableRowObject(1, 2, 3, 4), NonIndexableRowObject(5, 6, 7, 8)] - columns = (tf.Column('col1', attrib='field1'), - tf.Column('col2', attrib='field2'), - tf.Column('col3', attrib='field3'), - tf.Column('col4', attrib='field4')) - table = tf.generate_table(rows, columns) + columns = (tablefmt.Column('col1', attrib='field1'), + tablefmt.Column('col2', attrib='field2'), + tablefmt.Column('col3', attrib='field3'), + tablefmt.Column('col4', attrib='field4')) + table = tablefmt.generate_table(rows, columns) assert table == EXPECTED_WITH_HEADERS @@ -122,14 +122,14 @@ def test_iterable_of_non_indexable_iterables(): np_2d_array = np.array(iteralbe_of_iterables) def test_numpy_2d_array(): - table = tf.generate_table(np_2d_array) + table = tablefmt.generate_table(np_2d_array) assert table == EXPECTED_BASIC def test_numpy_record_array(): np_rec_array = np.rec.array([(1, 2., 'Hello'), (2, 3., "World")], dtype=[('foo', 'i4'), ('bar', 'f4'), ('baz', 'U10')]) - table = tf.generate_table(np_rec_array) + table = tablefmt.generate_table(np_rec_array) expected = ''' ╔═════╤═════╤═══════╗ ║ foo │ bar │ baz ║ @@ -148,5 +148,5 @@ def test_numpy_record_array(): df = pd.DataFrame(data=od) def test_pandas_dataframe(): - table = tf.generate_table(df) + table = tablefmt.generate_table(df) assert table == EXPECTED_WITH_HEADERS diff --git a/tests/test_formatters.py b/tests/test_formatters.py index 984ffee..e1ba661 100644 --- a/tests/test_formatters.py +++ b/tests/test_formatters.py @@ -6,11 +6,13 @@ """ import pytest -import tableformatter as tf +import tableformatter as tablefmt # Make the test results reproducible regardless of what color libraries are installed -tf.TableColors.set_color_library('none') -tf.set_default_grid(tf.AlternatingRowGrid('', '', '')) +import tableformatter.formatters + +tablefmt.TableColors.set_color_library('none') +tablefmt.set_default_grid(tablefmt.AlternatingRowGrid('', '', '')) class MyRowObject(object): @@ -108,14 +110,14 @@ def test_fmt_obj_rows(): MyRowObject(1234567890, 1234567890, 7, 5), MyRowObject(1234567890123, 1234567890123, 7, 5)] - columns = (tf.Column('First', width=20, attrib='field1', formatter=tf.FormatBytes(), - cell_halign=tf.ColumnAlignment.AlignRight), - tf.Column('Second', attrib='field2', formatter=tf.FormatCommas(), - cell_halign=tf.ColumnAlignment.AlignRight), - tf.Column('Num 1', width=3, attrib='get_field3'), - tf.Column('Num 2', attrib='field4'), - tf.Column('Multiplied', obj_formatter=multiply)) - table = tf.generate_table(rows, columns) + columns = (tablefmt.Column('First', width=20, attrib='field1', formatter=tableformatter.formatters.FormatBytes(), + cell_halign=tablefmt.ColumnAlignment.AlignRight), + tablefmt.Column('Second', attrib='field2', formatter=tableformatter.formatters.FormatCommas(), + cell_halign=tablefmt.ColumnAlignment.AlignRight), + tablefmt.Column('Num 1', width=3, attrib='get_field3'), + tablefmt.Column('Num 2', attrib='field4'), + tablefmt.Column('Multiplied', obj_formatter=multiply)) + table = tablefmt.generate_table(rows, columns) assert table == expected @@ -142,12 +144,12 @@ def test_fmt_tuple_rows(): (1234567890, 1234567890, 7, 5, None), (1234567890123, 1234567890123, 7, 5, None)] - columns = (tf.Column('First', width=20, formatter=tf.FormatBytes(), cell_halign=tf.ColumnAlignment.AlignRight), - tf.Column('Second', formatter=tf.FormatCommas(), cell_halign=tf.ColumnAlignment.AlignRight), - tf.Column('Num 1'), - tf.Column('Num 2', formatter=int2word), - tf.Column('Multiplied', obj_formatter=multiply_tuple)) + columns = (tablefmt.Column('First', width=20, formatter=tableformatter.formatters.FormatBytes(), cell_halign=tablefmt.ColumnAlignment.AlignRight), + tablefmt.Column('Second', formatter=tableformatter.formatters.FormatCommas(), cell_halign=tablefmt.ColumnAlignment.AlignRight), + tablefmt.Column('Num 1'), + tablefmt.Column('Num 2', formatter=int2word), + tablefmt.Column('Multiplied', obj_formatter=multiply_tuple)) - table = tf.generate_table(rows, columns) + table = tablefmt.generate_table(rows, columns) assert table == expected diff --git a/tests/test_rows.py b/tests/test_rows.py index ef5c79a..8259d43 100644 --- a/tests/test_rows.py +++ b/tests/test_rows.py @@ -6,12 +6,12 @@ """ import pytest -import tableformatter as tf +import tableformatter as tablefmt from collections import namedtuple # Make the test results reproducible regardless of what color libraries are installed -tf.TableColors.set_color_library('none') -tf.set_default_grid(tf.AlternatingRowGrid('', '', '')) +tablefmt.TableColors.set_color_library('none') +tablefmt.set_default_grid(tablefmt.AlternatingRowGrid('', '', '')) class MyRowObject(object): @@ -110,19 +110,19 @@ def test_obj_rows(): ║ D1 │ D2 │ 7 │ 5 │ 35 ║ ╚══════════════════════╧════════╧═════╧═══════╧════════════╝ '''.lstrip('\n') - rows = [tf.Row(MyRowObject('Longer text that will trigger the column wrapping', 'A2', 5, 56), + rows = [tablefmt.Row(MyRowObject('Longer text that will trigger the column wrapping', 'A2', 5, 56), text_color='R'), - tf.Row(MyRowObject('B1', 'B2\nB2\nB2', 23, 8), + tablefmt.Row(MyRowObject('B1', 'B2\nB2\nB2', 23, 8), text_color='G'), MyRowObject('C1', 'C2', 4, 9), MyRowObject('D1', 'D2', 7, 5)] - columns = (tf.Column('First', width=20, attrib='field1'), - tf.Column('Second', attrib='field2'), - tf.Column('Num 1', width=3, attrib='get_field3'), - tf.Column('Num 2', attrib='field4'), - tf.Column('Multiplied', obj_formatter=multiply)) - table = tf.generate_table(rows, columns) + columns = (tablefmt.Column('First', width=20, attrib='field1'), + tablefmt.Column('Second', attrib='field2'), + tablefmt.Column('Num 1', width=3, attrib='get_field3'), + tablefmt.Column('Num 2', attrib='field4'), + tablefmt.Column('Multiplied', obj_formatter=multiply)) + table = tablefmt.generate_table(rows, columns) assert table == expected @@ -142,19 +142,19 @@ def test_namedtuple_rows(): ║ D1 │ D2 │ 7 │ 5 │ 35 ║ ╚══════════════════════╧════════╧═════╧═══════╧════════════╝ '''.lstrip('\n') - rows = [tf.Row(NamedTupleRow('Longer text that will trigger the column wrapping', 'A2', 5, 56), + rows = [tablefmt.Row(NamedTupleRow('Longer text that will trigger the column wrapping', 'A2', 5, 56), text_color='R'), - tf.Row(NamedTupleRow('B1', 'B2\nB2\nB2', 23, 8), + tablefmt.Row(NamedTupleRow('B1', 'B2\nB2\nB2', 23, 8), text_color='G'), NamedTupleRow('C1', 'C2', 4, 9), NamedTupleRow('D1', 'D2', 7, 5)] - columns = (tf.Column('First', width=20, attrib='field1'), - tf.Column('Second', attrib='field2'), - tf.Column('Num 1', width=3, attrib='field3'), - tf.Column('Num 2', attrib='field4'), - tf.Column('Multiplied', obj_formatter=multiply_named_tuple)) - table = tf.generate_table(rows, columns) + columns = (tablefmt.Column('First', width=20, attrib='field1'), + tablefmt.Column('Second', attrib='field2'), + tablefmt.Column('Num 1', width=3, attrib='field3'), + tablefmt.Column('Num 2', attrib='field4'), + tablefmt.Column('Multiplied', obj_formatter=multiply_named_tuple)) + table = tablefmt.generate_table(rows, columns) assert table == expected @@ -172,19 +172,19 @@ def test_tuple_rows(): ╚══════════════════════╧════════╧═══════╧═══════════╧════════════╝ '''.lstrip('\n') - rows = [tf.Row('Longer text that will trigger the column wrapping', 'A2', 5, 56, None, + rows = [tablefmt.Row('Longer text that will trigger the column wrapping', 'A2', 5, 56, None, text_color='R'), - tf.Row('B1', 'B2\nB2\nB2', 23, 8, None, + tablefmt.Row('B1', 'B2\nB2\nB2', 23, 8, None, text_color='G'), ('C1', 'C2', 4, 9, None), ('D1', 'D2', 7, 5, None)] - columns = (tf.Column('First', width=20, wrap_mode=tf.WrapMode.TRUNCATE_HARD), - tf.Column('Second'), - tf.Column('Num 1'), - tf.Column('Num 2', formatter=int2word), - tf.Column('Multiplied', obj_formatter=multiply_tuple)) + columns = (tablefmt.Column('First', width=20, wrap_mode=tablefmt.WrapMode.TRUNCATE_HARD), + tablefmt.Column('Second'), + tablefmt.Column('Num 1'), + tablefmt.Column('Num 2', formatter=int2word), + tablefmt.Column('Multiplied', obj_formatter=multiply_tuple)) - table = tf.generate_table(rows, columns) + table = tablefmt.generate_table(rows, columns) assert table == expected diff --git a/tests/test_simple.py b/tests/test_simple.py index 2e407f6..f237779 100644 --- a/tests/test_simple.py +++ b/tests/test_simple.py @@ -6,11 +6,11 @@ """ import pytest -import tableformatter as tf +import tableformatter as tablefmt # Make the test results reproducible regardless of what color libraries are installed -tf.TableColors.set_color_library('none') -tf.set_default_grid(tf.AlternatingRowGrid('', '')) +tablefmt.TableColors.set_color_library('none') +tablefmt.set_default_grid(tablefmt.AlternatingRowGrid('', '')) WITH_HEADER = ''' ╔══════╤══════╤══════╤══════╗ @@ -61,7 +61,7 @@ def test_basic_table(rows): ║ D1 │ D2 │ D3 │ D4 ║ ╚════╧════╧════╧════╝ '''.lstrip('\n') - table = tf.generate_table(rows) + table = tablefmt.generate_table(rows) assert table == expected def test_basic_transposed(rows): @@ -75,7 +75,7 @@ def test_basic_transposed(rows): ║ A4 │ B4 │ C4 │ D4 ║ ╚════╧════╧════╧════╝ '''.lstrip('\n') - table = tf.generate_table(rows, transpose=True) + table = tablefmt.generate_table(rows, transpose=True) assert table == expected def test_basic_fancy_grid(rows): @@ -92,7 +92,7 @@ def test_basic_fancy_grid(rows): ║ D1 │ D2 │ D3 │ D4 ║ ╚════╧════╧════╧════╝ '''.lstrip('\n') - table = tf.generate_table(rows, grid_style=tf.FancyGrid()) + table = tablefmt.generate_table(rows, grid_style=tablefmt.FancyGrid()) assert table == expected def test_basic_sparse_grid(rows): @@ -103,15 +103,15 @@ def test_basic_sparse_grid(rows): B2 C1 C2 C3 C4 D1 D2 D3 D4 \n \n'''.lstrip('\n') - table = tf.generate_table(rows, grid_style=tf.SparseGrid()) + table = tablefmt.generate_table(rows, grid_style=tablefmt.SparseGrid()) assert table == expected def test_table_with_header(rows, cols): - table = tf.generate_table(rows, cols) + table = tablefmt.generate_table(rows, cols) assert table == WITH_HEADER def test_table_with_header_transposed(rows, cols): - table = tf.generate_table(rows, cols, transpose=True) + table = tablefmt.generate_table(rows, cols, transpose=True) assert table == HEADER_TRANSPOSED def test_table_with_header_transposed_fancy(rows, cols): @@ -128,7 +128,7 @@ def test_table_with_header_transposed_fancy(rows, cols): ║ Col4 ║ A4 │ B4 │ C4 │ D4 ║ ╚══════╩════╧════╧════╧════╝ '''.lstrip('\n') - table = tf.generate_table(rows, cols, grid_style=tf.FancyGrid(), transpose=True) + table = tablefmt.generate_table(rows, cols, grid_style=tablefmt.FancyGrid(), transpose=True) assert table == expected def test_table_with_header_transposed_sparse(rows, cols): @@ -139,7 +139,7 @@ def test_table_with_header_transposed_sparse(rows, cols): B2 Col3 A3 B3 C3 D3 Col4 A4 B4 C4 D4 \n \n'''.lstrip('\n') - table = tf.generate_table(rows, cols, grid_style=tf.SparseGrid(), transpose=True) + table = tablefmt.generate_table(rows, cols, grid_style=tablefmt.SparseGrid(), transpose=True) assert table == expected @@ -166,18 +166,18 @@ def obj_rows(): @pytest.fixture def obj_cols(): - columns = (tf.Column('Col1', attrib='field1'), - tf.Column('Col2', attrib='field2'), - tf.Column('Col3', attrib='get_field3'), - tf.Column('Col4', attrib='field4')) + columns = (tablefmt.Column('Col1', attrib='field1'), + tablefmt.Column('Col2', attrib='field2'), + tablefmt.Column('Col3', attrib='get_field3'), + tablefmt.Column('Col4', attrib='field4')) return columns def test_object_table_with_header(obj_rows, obj_cols): - table = tf.generate_table(obj_rows, obj_cols) + table = tablefmt.generate_table(obj_rows, obj_cols) assert table == WITH_HEADER def test_object_table_transposed(obj_rows, obj_cols): - table = tf.generate_table(obj_rows, obj_cols, transpose=True) + table = tablefmt.generate_table(obj_rows, obj_cols, transpose=True) assert table == HEADER_TRANSPOSED def test_object_table_fancy_grid(obj_rows, obj_cols): @@ -196,7 +196,7 @@ def test_object_table_fancy_grid(obj_rows, obj_cols): ║ D1 │ D2 │ D3 │ D4 ║ ╚══════╧══════╧══════╧══════╝ '''.lstrip('\n') - table = tf.generate_table(obj_rows, obj_cols, grid_style=tf.FancyGrid()) + table = tablefmt.generate_table(obj_rows, obj_cols, grid_style=tablefmt.FancyGrid()) assert table == expected def test_object_table_sparse_grid(obj_rows, obj_cols): @@ -207,14 +207,14 @@ def test_object_table_sparse_grid(obj_rows, obj_cols): B2 C1 C2 C3 C4 D1 D2 D3 D4 \n \n'''.lstrip('\n') - table = tf.generate_table(obj_rows, obj_cols, grid_style=tf.SparseGrid()) + table = tablefmt.generate_table(obj_rows, obj_cols, grid_style=tablefmt.SparseGrid()) assert table == expected def test_object_table_columns_rearranged(obj_rows): - cols2 = (tf.Column('Col1', attrib='field3'), - tf.Column('Col2', attrib='field2'), - tf.Column('Col3', attrib='field1'), - tf.Column('Col4', attrib='field4')) + cols2 = (tablefmt.Column('Col1', attrib='field3'), + tablefmt.Column('Col2', attrib='field2'), + tablefmt.Column('Col3', attrib='field1'), + tablefmt.Column('Col4', attrib='field4')) expected = ''' ╔══════╤══════╤══════╤══════╗ ║ Col1 │ Col2 │ Col3 │ Col4 ║ @@ -227,7 +227,7 @@ def test_object_table_columns_rearranged(obj_rows): ║ │ D2 │ D1 │ D4 ║ ╚══════╧══════╧══════╧══════╝ '''.lstrip('\n') - table = tf.generate_table(obj_rows, cols2) + table = tablefmt.generate_table(obj_rows, cols2) assert table == expected diff --git a/tests/test_utils.py b/tests/test_utils.py index 713226f..e0a62e6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -4,7 +4,7 @@ """ import pytest -import tableformatter as tf +import tableformatter as tablefmt @pytest.fixture @@ -35,63 +35,63 @@ def whitespace_text(): # Test text wrapping def test_empty_wrap(empty_text): expected = [] - wrapped = tf._text_wrap(empty_text) + wrapped = tablefmt._text_wrap(empty_text) assert expected == wrapped def test_no_wrap(sample_text): expected = [sample_text] - wrapped = tf._text_wrap(sample_text) + wrapped = tablefmt._text_wrap(sample_text) assert expected == wrapped def test_simple_wrap(sample_text): expected = ['Foo', 'Bar'] - wrapped = tf._text_wrap(sample_text, width=3) + wrapped = tablefmt._text_wrap(sample_text, width=3) assert expected == wrapped def test_double_wrap(long_text): expected = ['Foo', 'Bar', 'Baz'] - wrapped = tf._text_wrap(long_text, width=3) + wrapped = tablefmt._text_wrap(long_text, width=3) assert expected == wrapped def test_multiline_no_wrap(multiline_text): expected = ['FooBar Baz'] - wrapped = tf._text_wrap(multiline_text) + wrapped = tablefmt._text_wrap(multiline_text) assert expected == wrapped def test_multiline_with_wrap(multiline_text): expected = ['Foo', 'Bar', 'Baz'] - wrapped = tf._text_wrap(multiline_text, width=3) + wrapped = tablefmt._text_wrap(multiline_text, width=3) assert expected == wrapped def test_trailing_whitespace_wrap(whitespace_text): expected = ['FooBar'] - wrapped = tf._text_wrap(whitespace_text) + wrapped = tablefmt._text_wrap(whitespace_text) assert expected == wrapped # Test tab translation and printable width calculation def test_empty_translation(empty_text): expected = empty_text - translated = tf._translate_tabs(empty_text) + translated = tablefmt._translate_tabs(empty_text) assert expected == translated def test_empty_width(empty_text): - assert tf._printable_width(empty_text) == len(empty_text) + assert tablefmt._printable_width(empty_text) == len(empty_text) def test_translation_no_tabs(sample_text): expected = sample_text - translated = tf._translate_tabs(sample_text) + translated = tablefmt._translate_tabs(sample_text) assert expected == translated def test_width_no_tabs(sample_text): - assert tf._printable_width(sample_text) == len(sample_text) + assert tablefmt._printable_width(sample_text) == len(sample_text) def test_translation_with_tabs(tabbed_text): expected = 'Foo Bar' - translated = tf._translate_tabs(tabbed_text) + translated = tablefmt._translate_tabs(tabbed_text) assert expected == translated assert expected != tabbed_text def test_width_with_tabs(tabbed_text): expected = 'Foo Bar' - assert tf._printable_width(tabbed_text) == len(expected) + assert tablefmt._printable_width(tabbed_text) == len(expected)