Nicer timeline output

This commit is contained in:
Ivan Habunek 2019-02-14 16:53:58 +01:00
parent 996228d224
commit e6d585ae5d
No known key found for this signature in database
GPG Key ID: CDBD63C43A30BB95
2 changed files with 97 additions and 70 deletions

View File

@ -128,7 +128,8 @@ def test_timeline(mock_get, monkeypatch, capsys):
'created_at': '2017-04-12T15:53:18.174Z',
'content': "<p>The computer can&apos;t tell you the emotional story. It can give you the exact mathematical design, but what's missing is the eyebrows.</p>",
'reblog': None,
'in_reply_to_id': None
'in_reply_to_id': None,
'media_attachments': [],
}])
console.run_command(app, user, 'timeline', ['--once'])
@ -136,16 +137,18 @@ def test_timeline(mock_get, monkeypatch, capsys):
mock_get.assert_called_once_with(app, user, '/api/v1/timelines/home?limit=10', None)
out, err = capsys.readouterr()
lines = out.split("\n")
assert "Frank Zappa 🎸" in lines[1]
assert "@fz" in lines[1]
assert "2017-04-12 15:53" in lines[1]
assert (
"The computer can't tell you the emotional story. It can give you the "
"exact mathematical design, but\nwhat's missing is the eyebrows." in out)
assert "111111111111111111" in lines[-3]
expected = (
"───────────────────────────────┬────────────────────────────────────────────────────────────────────────────────────────\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"
"───────────────────────────────┼────────────────────────────────────────────────────────────────────────────────────────\n"
)
assert out == expected
assert err == ""
@ -153,14 +156,21 @@ def test_timeline(mock_get, monkeypatch, capsys):
def test_timeline_with_re(mock_get, monkeypatch, capsys):
mock_get.return_value = MockResponse([{
'id': '111111111111111111',
'created_at': '2017-04-12T15:53:18.174Z',
'account': {
'display_name': 'Frank Zappa',
'username': 'fz'
},
'created_at': '2017-04-12T15:53:18.174Z',
'content': "<p>The computer can&apos;t tell you the emotional story. It can give you the exact mathematical design, but what's missing is the eyebrows.</p>",
'reblog': None,
'in_reply_to_id': '111111111111111110'
'reblog': {
'account': {
'display_name': 'Johnny Cash',
'username': 'jc'
},
'content': "<p>The computer can&apos;t tell you the emotional story. It can give you the exact mathematical design, but what's missing is the eyebrows.</p>",
'media_attachments': [],
},
'in_reply_to_id': '111111111111111110',
'media_attachments': [],
}])
console.run_command(app, user, 'timeline', ['--once'])
@ -168,12 +178,21 @@ def test_timeline_with_re(mock_get, monkeypatch, capsys):
mock_get.assert_called_once_with(app, user, '/api/v1/timelines/home?limit=10', None)
out, err = capsys.readouterr()
assert "The computer can't tell you the emotional story." in out
assert "but what's missing is the eyebrows." in out
assert "Frank Zappa" in out
assert "@fz" in out
assert "id: 111111111111111111" in out
assert "[RE]" in out
lines = out.split("\n")
assert "Frank Zappa" in lines[1]
assert "@fz" in lines[1]
assert "2017-04-12 15:53" in lines[1]
assert (
"The computer can't tell you the emotional story. It can give you the "
"exact mathematical design, but\nwhat's missing is the eyebrows." in out)
assert "111111111111111111" in lines[-3]
assert "↻ Reblogged \x1b[34m@jc\x1b[0m" in lines[-3]
assert err == ""
@mock.patch('toot.http.get')
def test_thread(mock_get, monkeypatch, capsys):
@ -187,7 +206,8 @@ def test_thread(mock_get, monkeypatch, capsys):
'created_at': '2017-04-12T15:53:18.174Z',
'content': "my response in the middle",
'reblog': None,
'in_reply_to_id': '111111111111111110'
'in_reply_to_id': '111111111111111110',
'media_attachments': [],
}),
MockResponse({
'ancestors': [{
@ -198,6 +218,7 @@ def test_thread(mock_get, monkeypatch, capsys):
},
'created_at': '2017-04-12T15:53:18.174Z',
'content': "original content",
'media_attachments': [],
'reblog': None,
'in_reply_to_id': None}],
'descendants': [{
@ -208,6 +229,7 @@ def test_thread(mock_get, monkeypatch, capsys):
},
'created_at': '2017-04-12T15:53:18.174Z',
'content': "response message",
'media_attachments': [],
'reblog': None,
'in_reply_to_id': '111111111111111111'}],
}),
@ -223,6 +245,8 @@ def test_thread(mock_get, monkeypatch, capsys):
out, err = capsys.readouterr()
assert not err
# Display order
assert out.index('original content') < out.index('my response in the middle')
assert out.index('my response in the middle') < out.index('response message')
@ -232,8 +256,8 @@ def test_thread(mock_get, monkeypatch, capsys):
assert "response message" in out
assert "Frank Zappa" in out
assert "@fz" in out
assert "id: 111111111111111111" in out
assert "[RE]" in out
assert "111111111111111111" in out
assert "In reply to" in out
@mock.patch('toot.http.post')
def test_upload(mock_post, capsys):

View File

@ -3,14 +3,12 @@
import sys
import re
from bs4 import BeautifulSoup
from datetime import datetime
from itertools import chain
from itertools import zip_longest
from textwrap import wrap, TextWrapper
from textwrap import wrap
from wcwidth import wcswidth
from toot.utils import format_content, get_text
from toot.wcstring import pad
from toot.utils import format_content, get_text, parse_html
from toot.wcstring import wc_wrap
START_CODES = {
@ -101,6 +99,13 @@ def print_account(account):
print_out(account['url'])
HASHTAG_PATTERN = re.compile(r'(?<!\w)(#\w+)\b')
def highlight_hashtags(line):
return re.sub(HASHTAG_PATTERN, '<cyan>\\1</cyan>', line)
def print_search_results(results):
accounts = results['accounts']
hashtags = results['hashtags']
@ -121,54 +126,52 @@ def print_search_results(results):
print_out("<yellow>Nothing found</yellow>")
def print_timeline(items):
def _print_item(item):
def wrap_text(text, width):
wrapper = TextWrapper(width=width, break_long_words=False, break_on_hyphens=False)
return chain(*[wrapper.wrap(l) for l in text.split("\n")])
def print_status(status, width):
reblog = status['reblog']
content = reblog['content'] if reblog else status['content']
media_attachments = reblog['media_attachments'] if reblog else status['media_attachments']
in_reply_to = status['in_reply_to_id']
def timeline_rows(item):
display_name = item['account']['display_name']
username = "@" + item['account']['username']
time = item['time'].strftime('%Y-%m-%d %H:%M%Z')
time = status['created_at']
time = datetime.strptime(time, "%Y-%m-%dT%H:%M:%S.%fZ")
time = time.strftime('%Y-%m-%d %H:%M%z')
left_column = [display_name]
if display_name != username:
left_column.append(username)
left_column.append(time)
if item['reblogged']:
left_column.append("Reblogged @{}".format(item['reblogged']))
username = "@" + status['account']['username']
spacing = width - wcswidth(username) - wcswidth(time)
if item['reply_to_toot'] is not None:
left_column.append('[RE]')
display_name = status['account']['display_name']
if display_name:
spacing -= wcswidth(display_name) + 1
left_column.append("id: {}".format(item['id']))
print_out("{}{}{}{}".format(
"<green>{}</green> ".format(display_name) if display_name else "",
"<blue>{}</blue>".format(username),
" " * spacing,
"<yellow>{}</yellow>".format(time),
))
right_column = wrap_text(item['text'], 80)
for paragraph in parse_html(content):
print_out("")
for line in paragraph:
for subline in wc_wrap(line, width):
print_out(highlight_hashtags(subline))
return zip_longest(left_column, right_column, fillvalue="")
if media_attachments:
print_out("\nMedia:")
for attachment in media_attachments:
url = attachment['text_url'] or attachment['url']
for line in wc_wrap(url, width):
print_out(line)
for left, right in timeline_rows(item):
print_out("{}{}".format(pad(left, 30), right))
print_out("\n{}{}{}".format(
"ID <yellow>{}</yellow> ".format(status['id']),
"↲ In reply to <yellow>{}</yellow> ".format(in_reply_to) if in_reply_to else "",
"↻ Reblogged <blue>@{}</blue> ".format(reblog['account']['username']) if reblog else "",
))
def _parse_item(item):
content = item['reblog']['content'] if item['reblog'] else item['content']
reblogged = item['reblog']['account']['username'] if item['reblog'] else None
soup = BeautifulSoup(content.replace('&apos;', "'"), "html.parser")
text = soup.get_text()
time = datetime.strptime(item['created_at'], "%Y-%m-%dT%H:%M:%S.%fZ")
return {
"id": item['id'],
"account": item['account'],
"text": text,
"time": time,
"reblogged": reblogged,
"reply_to_toot": item['in_reply_to_id']
}
print_out("" * 31 + "" + "" * 88)
def print_timeline(items, width=100):
print_out("" * width)
for item in items:
_print_item(_parse_item(item))
print_out("" * 31 + "" + "" * 88)
print_status(item, width)
print_out("" * width)