diff --git a/silkaj/tui.py b/silkaj/tui.py index 99a3dec81b8303e4d8101d30974b60e20b96df0d..b5d757765f24166e368b10ce3bb85473968b7283 100644 --- a/silkaj/tui.py +++ b/silkaj/tui.py @@ -13,15 +13,21 @@ # You should have received a copy of the GNU Affero General Public License # along with Silkaj. If not, see <https://www.gnu.org/licenses/>. +import shutil import sys +from collections import OrderedDict from typing import List, Optional import click +from texttable import Texttable from silkaj import constants from silkaj import crypto_tools as ct from silkaj import wot_tools +HORIZ_TABLE_CHARS = ["─", "│", "─", "â•"] +VERT_TABLE_CHARS = ["─", "│", "│", "â•"] + def display_amount( tx: List, message: str, amount: float, ud_value: float, currency_symbol: str @@ -66,3 +72,74 @@ def gen_pubkey_checksum( def send_doc_confirmation(document_name: str) -> None: if not click.confirm(f"Do you confirm sending this {document_name}?"): sys.exit(constants.SUCCESS_EXIT_STATUS) + + +class Table(Texttable): + def __init__( + self, + style="default", + ): + Texttable.__init__(self, max_width=shutil.get_terminal_size()[0]) + + if style == "columns": + self.set_deco(self.HEADER | self.VLINES | self.BORDER) + self.set_chars(VERT_TABLE_CHARS) + + def fill_rows(self, rows: list, headers=None): + """ + Fills a table from headers and rows list. + `rows` is a list of lists representing each row content. + each element of `rows` and headers must be of same length. + """ + if headers: + if len(rows) == 0: + rows.append([""] * len(headers)) + assert len(headers) == len(rows[0]) + self.header(headers) + for line in rows: + assert len(line) == len(rows[0]) + self.add_row(line) + return self + + def fill_from_dict(self, _dict: OrderedDict): + """ + Given an OrderedDict where each value represents a column, + fill a table where labels are dict keys and columns are dict values. + This function stops on the first line with only empty cells. + """ + labels = list(_dict.keys()) + content = [] + + n = 0 + while True: + line = [] + empty_cells_number = 0 + + for label in labels: + try: + line.append(_dict[label][n]) + except IndexError: + line.append("") + empty_cells_number += 1 + # break on first empty line + if empty_cells_number == len(labels): + break + content.append(line) + n += 1 + + return self.fill_rows(content, labels) + + def fill_from_dict_list(self, dict_list: list): + """ + Given a list of dict with same keys, + fills the table with keys as headers. + """ + headers = list(dict_list[0].keys()) + content = [] + for _dict in dict_list: + assert list(_dict.keys()) == headers + line = [] + for head in headers: + line.append(_dict[head]) + content.append(line) + return self.fill_rows(content, headers) diff --git a/tests/test_tui.py b/tests/test_tui.py index e488e3090f01af9659158f729b70129228288856..72924fc86ab629cfbec547f5ba889a4f09efca64 100644 --- a/tests/test_tui.py +++ b/tests/test_tui.py @@ -13,13 +13,16 @@ # You should have received a copy of the GNU Affero General Public License # along with Silkaj. If not, see <https://www.gnu.org/licenses/>. +import shutil +from collections import OrderedDict + import pytest +from texttable import Texttable from patched.test_constants import mock_ud_value from patched.wot import patched_is_member -from silkaj import wot_tools +from silkaj import tui, wot_tools from silkaj.constants import G1_SYMBOL, SHORT_PUBKEY_SIZE -from silkaj.tui import display_amount, display_pubkey, gen_pubkey_checksum # display_amount() @@ -35,7 +38,7 @@ def test_display_amount(message, amount, currency_symbol): ] ] tx = [] - display_amount(tx, message, amount, mock_ud_value, currency_symbol) + tui.display_amount(tx, message, amount, mock_ud_value, currency_symbol) assert tx == expected @@ -50,11 +53,11 @@ def test_display_amount(message, amount, currency_symbol): def test_display_pubkey(message, pubkey, uid, monkeypatch): monkeypatch.setattr(wot_tools, "is_member", patched_is_member) - expected = [[f"{message} (pubkey:checksum)", gen_pubkey_checksum(pubkey)]] + expected = [[f"{message} (pubkey:checksum)", tui.gen_pubkey_checksum(pubkey)]] if uid: expected.append([f"{message} (id)", uid]) tx = [] - display_pubkey(tx, message, pubkey) + tui.display_pubkey(tx, message, pubkey) assert tx == expected @@ -66,10 +69,138 @@ def test_display_pubkey(message, pubkey, uid, monkeypatch): ], ) def test_gen_pubkey_checksum(pubkey, checksum): - assert pubkey + ":" + checksum == gen_pubkey_checksum(pubkey) - assert pubkey[:SHORT_PUBKEY_SIZE] + "…:" + checksum == gen_pubkey_checksum( + assert f"{pubkey}:{checksum}" == tui.gen_pubkey_checksum(pubkey) + assert f"{pubkey[:SHORT_PUBKEY_SIZE]}…:{checksum}" == tui.gen_pubkey_checksum( pubkey, short=True ) - assert pubkey[:14] + "…:" + checksum == gen_pubkey_checksum( + assert f"{pubkey[:14]}…:{checksum}" == tui.gen_pubkey_checksum( pubkey, short=True, length=14 ) + + +def test_create_table(): + + expected = Texttable(max_width=shutil.get_terminal_size()[0]) + expected.add_rows([["one", "two"], ["three", "four"]]) + expected.set_chars(tui.VERT_TABLE_CHARS) + + test_table = tui.Table().add_rows([["one", "two"], ["three", "four"]]) + assert expected.draw() == test_table.draw() + + expected.set_deco(expected.HEADER | expected.VLINES | expected.BORDER) + test_table = tui.Table("columns").add_rows([["one", "two"], ["three", "four"]]) + assert expected.draw() == test_table.draw() + + +@pytest.mark.parametrize( + "rows, headers, expected", + [ + ( + [ + ["three", "o'clock"], + ["four", "o'clock"], + ["rock", "rock around the clock"], + ], + ["one", "two"], + "│───────│───────────────────────│\n\ +│ one │ two │\n\ +│â•â•â•â•â•â•â•â”‚â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â”‚\n\ +│ three │ o'clock │\n\ +│───────│───────────────────────│\n\ +│ four │ o'clock │\n\ +│───────│───────────────────────│\n\ +│ rock │ rock around the clock │\n\ +│───────│───────────────────────│", + ), + ( + [ + ["three", "o'clock"], + ["four", "o'clock"], + ["rock", "rock around the clock"], + ], + None, + "│───────│───────────────────────│\n\ +│ three │ o'clock │\n\ +│───────│───────────────────────│\n\ +│ four │ o'clock │\n\ +│───────│───────────────────────│\n\ +│ rock │ rock around the clock │\n\ +│───────│───────────────────────│", + ), + ( + [ + ["three", "o'clock"], + ["four", "o'clock"], + ["rock"], + ], + ["one", "two"], + False, + ), + ], +) +def test_fill_rows(rows, headers, expected): + table = tui.Table() + if not expected: + with pytest.raises(AssertionError) as pytest_error: + table.fill_rows(rows, headers) + assert pytest_error.type == AssertionError # is it useful ? + else: + table.fill_rows(rows, headers) + assert table.draw() == expected + + +@pytest.mark.parametrize( + "dict_, expected", + [ + ( + OrderedDict( + [ + ("one", ["three", "four", "rock"]), + ("two", ["o'clock", "o'clock", "rock around the clock"]), + ] + ), + "│───────│───────────────────────│\n\ +│ one │ two │\n\ +│â•â•â•â•â•â•â•â”‚â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â”‚\n\ +│ three │ o'clock │\n\ +│───────│───────────────────────│\n\ +│ four │ o'clock │\n\ +│───────│───────────────────────│\n\ +│ rock │ rock around the clock │\n\ +│───────│───────────────────────│", + ), + ], +) +def test_fill_from_dict(dict_, expected): + table = tui.Table() + table.fill_from_dict(dict_) + assert table.draw() == expected + + +@pytest.mark.parametrize( + "dict_list, expected", + [ + ( + ( + [ + {"one": "three", "two": "o'clock"}, + {"one": "four", "two": "o'clock"}, + {"one": "rock", "two": "rock around the clock"}, + ] + ), + "│───────│───────────────────────│\n\ +│ one │ two │\n\ +│â•â•â•â•â•â•â•â”‚â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â”‚\n\ +│ three │ o'clock │\n\ +│───────│───────────────────────│\n\ +│ four │ o'clock │\n\ +│───────│───────────────────────│\n\ +│ rock │ rock around the clock │\n\ +│───────│───────────────────────│", + ), + ], +) +def test_fill_from_dict_list(dict_list, expected): + table = tui.Table() + table.fill_from_dict_list(dict_list) + assert table.draw() == expected