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', '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>", '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, 'reblog': None,
'in_reply_to_id': None 'in_reply_to_id': None,
'media_attachments': [],
}]) }])
console.run_command(app, user, 'timeline', ['--once']) 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) mock_get.assert_called_once_with(app, user, '/api/v1/timelines/home?limit=10', None)
out, err = capsys.readouterr() 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 == "" assert err == ""
@ -153,14 +156,21 @@ def test_timeline(mock_get, monkeypatch, capsys):
def test_timeline_with_re(mock_get, monkeypatch, capsys): def test_timeline_with_re(mock_get, monkeypatch, capsys):
mock_get.return_value = MockResponse([{ mock_get.return_value = MockResponse([{
'id': '111111111111111111', 'id': '111111111111111111',
'created_at': '2017-04-12T15:53:18.174Z',
'account': { 'account': {
'display_name': 'Frank Zappa', 'display_name': 'Frank Zappa',
'username': 'fz' 'username': 'fz'
}, },
'created_at': '2017-04-12T15:53:18.174Z', 'reblog': {
'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>", 'account': {
'reblog': None, 'display_name': 'Johnny Cash',
'in_reply_to_id': '111111111111111110' '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']) 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) mock_get.assert_called_once_with(app, user, '/api/v1/timelines/home?limit=10', None)
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert "The computer can't tell you the emotional story." in out lines = out.split("\n")
assert "but what's missing is the eyebrows." in out
assert "Frank Zappa" in out assert "Frank Zappa" in lines[1]
assert "@fz" in out assert "@fz" in lines[1]
assert "id: 111111111111111111" in out assert "2017-04-12 15:53" in lines[1]
assert "[RE]" in out
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') @mock.patch('toot.http.get')
def test_thread(mock_get, monkeypatch, capsys): 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', 'created_at': '2017-04-12T15:53:18.174Z',
'content': "my response in the middle", 'content': "my response in the middle",
'reblog': None, 'reblog': None,
'in_reply_to_id': '111111111111111110' 'in_reply_to_id': '111111111111111110',
'media_attachments': [],
}), }),
MockResponse({ MockResponse({
'ancestors': [{ 'ancestors': [{
@ -198,6 +218,7 @@ def test_thread(mock_get, monkeypatch, capsys):
}, },
'created_at': '2017-04-12T15:53:18.174Z', 'created_at': '2017-04-12T15:53:18.174Z',
'content': "original content", 'content': "original content",
'media_attachments': [],
'reblog': None, 'reblog': None,
'in_reply_to_id': None}], 'in_reply_to_id': None}],
'descendants': [{ 'descendants': [{
@ -208,6 +229,7 @@ def test_thread(mock_get, monkeypatch, capsys):
}, },
'created_at': '2017-04-12T15:53:18.174Z', 'created_at': '2017-04-12T15:53:18.174Z',
'content': "response message", 'content': "response message",
'media_attachments': [],
'reblog': None, 'reblog': None,
'in_reply_to_id': '111111111111111111'}], 'in_reply_to_id': '111111111111111111'}],
}), }),
@ -223,6 +245,8 @@ def test_thread(mock_get, monkeypatch, capsys):
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert not err
# Display order # Display order
assert out.index('original content') < out.index('my response in the middle') assert out.index('original content') < out.index('my response in the middle')
assert out.index('my response in the middle') < out.index('response message') 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 "response message" in out
assert "Frank Zappa" in out assert "Frank Zappa" in out
assert "@fz" in out assert "@fz" in out
assert "id: 111111111111111111" in out assert "111111111111111111" in out
assert "[RE]" in out assert "In reply to" in out
@mock.patch('toot.http.post') @mock.patch('toot.http.post')
def test_upload(mock_post, capsys): def test_upload(mock_post, capsys):

View File

@ -3,14 +3,12 @@
import sys import sys
import re import re
from bs4 import BeautifulSoup
from datetime import datetime from datetime import datetime
from itertools import chain from textwrap import wrap
from itertools import zip_longest from wcwidth import wcswidth
from textwrap import wrap, TextWrapper
from toot.utils import format_content, get_text from toot.utils import format_content, get_text, parse_html
from toot.wcstring import pad from toot.wcstring import wc_wrap
START_CODES = { START_CODES = {
@ -101,6 +99,13 @@ def print_account(account):
print_out(account['url']) 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): def print_search_results(results):
accounts = results['accounts'] accounts = results['accounts']
hashtags = results['hashtags'] hashtags = results['hashtags']
@ -121,54 +126,52 @@ def print_search_results(results):
print_out("<yellow>Nothing found</yellow>") print_out("<yellow>Nothing found</yellow>")
def print_timeline(items): def print_status(status, width):
def _print_item(item): reblog = status['reblog']
def wrap_text(text, width): content = reblog['content'] if reblog else status['content']
wrapper = TextWrapper(width=width, break_long_words=False, break_on_hyphens=False) media_attachments = reblog['media_attachments'] if reblog else status['media_attachments']
return chain(*[wrapper.wrap(l) for l in text.split("\n")]) in_reply_to = status['in_reply_to_id']
def timeline_rows(item): time = status['created_at']
display_name = item['account']['display_name'] time = datetime.strptime(time, "%Y-%m-%dT%H:%M:%S.%fZ")
username = "@" + item['account']['username'] time = time.strftime('%Y-%m-%d %H:%M%z')
time = item['time'].strftime('%Y-%m-%d %H:%M%Z')
left_column = [display_name] username = "@" + status['account']['username']
if display_name != username: spacing = width - wcswidth(username) - wcswidth(time)
left_column.append(username)
left_column.append(time)
if item['reblogged']:
left_column.append("Reblogged @{}".format(item['reblogged']))
if item['reply_to_toot'] is not None: display_name = status['account']['display_name']
left_column.append('[RE]') 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("\n{}{}{}".format(
print_out("{}{}".format(pad(left, 30), right)) "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") def print_timeline(items, width=100):
text = soup.get_text() print_out("" * width)
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)
for item in items: for item in items:
_print_item(_parse_item(item)) print_status(item, width)
print_out("" * 31 + "" + "" * 88) print_out("" * width)