diff --git a/requirements.txt b/requirements.txt index b2b6258..a18e432 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ requests>=2.13,<3.0 beautifulsoup4>=4.5.0,<5.0 +wcwidth>=0.1.7,<2.0 diff --git a/setup.py b/setup.py index bf274f1..6d73704 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,7 @@ setup( install_requires=[ "requests>=2.13,<3.0", "beautifulsoup4>=4.5.0,<5.0", + "wcwidth>=0.1.7,<2.0", ], entry_points={ 'console_scripts': [ diff --git a/tests/test_console.py b/tests/test_console.py index 41b6efe..7d46e38 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -122,7 +122,7 @@ def test_timeline(mock_get, monkeypatch, capsys): mock_get.return_value = MockResponse([{ 'id': '111111111111111111', 'account': { - 'display_name': 'Frank Zappa', + 'display_name': 'Frank Zappa 🎸', 'username': 'fz' }, 'created_at': '2017-04-12T15:53:18.174Z', @@ -139,7 +139,7 @@ def test_timeline(mock_get, monkeypatch, capsys): expected = ( "───────────────────────────────┬────────────────────────────────────────────────────────────────────────────────────────\n" - "Frank Zappa │ The computer can't tell you the emotional story. It can give you the exact\n" + "Frank Zappa 🎸 │ The computer can't tell you the emotional story. It can give you the exact\n" "@fz │ mathematical design, but what's missing is the eyebrows.\n" "2017-04-12 15:53 │ \n" "id: 111111111111111111 │ \n" diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..ea42624 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,14 @@ +from toot import utils + + +def test_pad(): + text = 'Frank Zappa 🎸' + padded = utils.pad(text, 14) + assert padded == 'Frank Zappa 🎸' + # guitar symbol will occupy two cells, so padded text should be 1 + # character shorter + assert len(padded) == 13 + # when truncated, … occupies one cell, so we get full length + padded = utils.pad(text, 13) + assert padded == 'Frank Zappa …' + assert len(padded) == 13 diff --git a/toot/output.py b/toot/output.py index 60c034d..bfdd7e2 100644 --- a/toot/output.py +++ b/toot/output.py @@ -9,7 +9,7 @@ from itertools import chain from itertools import zip_longest from textwrap import wrap, TextWrapper -from toot.utils import format_content, get_text, trunc +from toot.utils import format_content, get_text, pad START_CODES = { 'red': '\033[31m', @@ -147,7 +147,7 @@ def print_timeline(items): return zip_longest(left_column, right_column, fillvalue="") for left, right in timeline_rows(item): - print_out("{:30} │ {}".format(trunc(left, 30), right)) + print_out("{} │ {}".format(pad(left, 30), right)) def _parse_item(item): content = item['reblog']['content'] if item['reblog'] else item['content'] diff --git a/toot/utils.py b/toot/utils.py index 2508e5a..8167889 100644 --- a/toot/utils.py +++ b/toot/utils.py @@ -7,6 +7,7 @@ import unicodedata import warnings from bs4 import BeautifulSoup +from wcwidth import wcswidth from toot.exceptions import ConsoleError @@ -75,14 +76,23 @@ def assert_domain_exists(domain): raise ConsoleError("Domain {} not found".format(domain)) -def trunc(text, length): +def trunc(text, length, text_length=None): """Trims text to given length, if trimmed appends ellipsis.""" - if len(text) <= length: + if text_length is None: + text_length = len(text) + if text_length <= length: return text return text[:length - 1] + '…' +def pad(text, length, fill=' '): + text_length = wcswidth(text) + text = trunc(text, length, text_length) + assert len(text) <= length + return text + fill * (length - text_length) + + EOF_KEY = "Ctrl-Z" if os.name == 'nt' else "Ctrl-D"