Toot-Mastodon-CLI-TUI-clien.../toot/output.py

270 lines
7.5 KiB
Python
Raw Normal View History

2017-04-19 14:47:30 +02:00
# -*- coding: utf-8 -*-
import os
import re
import sys
2022-11-22 21:27:04 +01:00
import textwrap
2017-04-19 14:47:30 +02:00
2019-02-14 16:53:58 +01:00
from textwrap import wrap
from toot.tui.utils import parse_datetime
2022-11-22 21:27:04 +01:00
from wcwidth import wcswidth
2022-11-22 21:27:04 +01:00
from toot.utils import get_text, parse_html
2019-02-14 16:53:58 +01:00
from toot.wcstring import wc_wrap
2017-12-29 14:26:40 +01:00
START_CODES = {
2017-12-30 13:14:37 +01:00
'red': '\033[31m',
'green': '\033[32m',
'yellow': '\033[33m',
'blue': '\033[34m',
'magenta': '\033[35m',
2017-12-30 13:14:37 +01:00
'cyan': '\033[36m',
}
2017-04-19 14:47:30 +02:00
END_CODE = '\033[0m'
2017-04-19 14:47:30 +02:00
START_PATTERN = "<(" + "|".join(START_CODES.keys()) + ")>"
2017-04-19 14:47:30 +02:00
END_PATTERN = "</(" + "|".join(START_CODES.keys()) + ")>"
2017-04-19 14:47:30 +02:00
def start_code(match):
name = match.group(1)
return START_CODES[name]
2017-04-19 14:47:30 +02:00
def colorize(text):
text = re.sub(START_PATTERN, start_code, text)
text = re.sub(END_PATTERN, END_CODE, text)
2017-04-19 14:47:30 +02:00
return text
2017-04-19 14:47:30 +02:00
def strip_tags(text):
text = re.sub(START_PATTERN, '', text)
text = re.sub(END_PATTERN, '', text)
2017-04-19 14:47:30 +02:00
return text
2017-04-19 14:47:30 +02:00
def use_ansi_color():
"""Returns True if ANSI color codes should be used."""
# Windows doesn't support color unless ansicon is installed
# See: http://adoxa.altervista.org/ansicon/
if sys.platform == 'win32' and 'ANSICON' not in os.environ:
return False
# Don't show color if stdout is not a tty, e.g. if output is piped on
if not sys.stdout.isatty():
return False
# Don't show color if explicitly specified in options
if "--no-color" in sys.argv:
return False
return True
USE_ANSI_COLOR = use_ansi_color()
QUIET = "--quiet" in sys.argv
2017-04-19 14:47:30 +02:00
def print_out(*args, **kwargs):
if not QUIET:
args = [colorize(a) if USE_ANSI_COLOR else strip_tags(a) for a in args]
print(*args, **kwargs)
def print_err(*args, **kwargs):
args = ["<red>{}</red>".format(a) for a in args]
args = [colorize(a) if USE_ANSI_COLOR else strip_tags(a) for a in args]
print(*args, file=sys.stderr, **kwargs)
2017-12-29 14:26:40 +01:00
def print_instance(instance):
print_out("<green>{}</green>".format(instance['title']))
print_out("<blue>{}</blue>".format(instance['uri']))
print_out("running Mastodon {}".format(instance['version']))
2022-11-22 21:27:04 +01:00
print_out()
description = instance.get("description")
if description:
for paragraph in re.split(r"[\r\n]+", description.strip()):
paragraph = get_text(paragraph)
print_out(textwrap.fill(paragraph, width=80))
print_out()
rules = instance.get("rules")
if rules:
print_out("Rules:")
for ordinal, rule in enumerate(rules):
ordinal = f"{ordinal + 1}."
lines = textwrap.wrap(rule["text"], 80 - len(ordinal))
first = True
for line in lines:
if first:
print_out(f"{ordinal} {line}")
first = False
else:
print_out(f"{' ' * len(ordinal)} {line}")
2017-12-29 14:42:51 +01:00
def print_account(account):
print_out("<green>@{}</green> {}".format(account['acct'], account['display_name']))
note = get_text(account['note'])
if note:
print_out("")
print_out("\n".join(wrap(note)))
print_out("")
print_out("ID: <green>{}</green>".format(account['id']))
print_out("Since: <green>{}</green>".format(account['created_at'][:19].replace('T', ' @ ')))
print_out("")
print_out("Followers: <yellow>{}</yellow>".format(account['followers_count']))
print_out("Following: <yellow>{}</yellow>".format(account['following_count']))
print_out("Statuses: <yellow>{}</yellow>".format(account['statuses_count']))
print_out("")
print_out(account['url'])
2019-02-14 16:53:58 +01:00
HASHTAG_PATTERN = re.compile(r'(?<!\w)(#\w+)\b')
def highlight_hashtags(line):
return re.sub(HASHTAG_PATTERN, '<cyan>\\1</cyan>', line)
2022-11-22 21:27:21 +01:00
def print_acct_list(accounts):
for account in accounts:
print_out("* <green>@{}</green> {}".format(
account['acct'],
account['display_name']
))
2019-02-14 16:53:58 +01:00
2022-11-22 21:27:21 +01:00
2017-12-29 14:42:51 +01:00
def print_search_results(results):
accounts = results['accounts']
hashtags = results['hashtags']
if accounts:
print_out("\nAccounts:")
print_acct_list(accounts)
2017-12-29 14:42:51 +01:00
if hashtags:
print_out("\nHashtags:")
2019-09-22 13:04:58 +02:00
print_out(", ".join(["<green>#{}</green>".format(t["name"]) for t in hashtags]))
2017-12-29 14:42:51 +01:00
if not accounts and not hashtags:
print_out("<yellow>Nothing found</yellow>")
2019-02-14 16:53:58 +01:00
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']
2022-11-29 10:08:21 +01:00
poll = reblog.get('poll') if reblog else status.get('poll')
time = parse_datetime(status['created_at'])
2022-11-06 05:31:49 +01:00
time = time.strftime('%Y-%m-%d %H:%M %Z')
2019-02-15 13:26:22 +01:00
username = "@" + status['account']['acct']
2019-02-14 16:53:58 +01:00
spacing = width - wcswidth(username) - wcswidth(time)
2019-02-14 16:53:58 +01:00
display_name = status['account']['display_name']
if display_name:
spacing -= wcswidth(display_name) + 1
2019-02-14 16:53:58 +01:00
print_out("{}{}{}{}".format(
"<green>{}</green> ".format(display_name) if display_name else "",
"<blue>{}</blue>".format(username),
" " * spacing,
"<yellow>{}</yellow>".format(time),
))
2019-02-14 16:53:58 +01:00
for paragraph in parse_html(content):
print_out("")
for line in paragraph:
for subline in wc_wrap(line, width):
print_out(highlight_hashtags(subline))
if media_attachments:
print_out("\nMedia:")
for attachment in media_attachments:
url = attachment["url"]
2019-02-14 16:53:58 +01:00
for line in wc_wrap(url, width):
print_out(line)
if poll:
2022-11-29 09:20:00 +01:00
print_poll(poll)
2022-11-29 09:20:00 +01:00
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']['acct']) if reblog else "",
))
2022-11-29 09:20:00 +01:00
def print_poll(poll):
print_out()
for idx, option in enumerate(poll["options"]):
perc = (round(100 * option["votes_count"] / poll["votes_count"])
if poll["votes_count"] else 0)
2022-11-29 09:20:00 +01:00
if poll["voted"] and poll["own_votes"] and idx in poll["own_votes"]:
voted_for = " <yellow>✓</yellow>"
else:
voted_for = ""
2022-11-29 09:20:00 +01:00
print_out(f'{option["title"]} - {perc}% {voted_for}')
2022-11-29 09:20:00 +01:00
poll_footer = f'Poll · {poll["votes_count"]} votes'
2022-11-29 09:20:00 +01:00
if poll["expired"]:
poll_footer += " · Closed"
2022-11-29 09:20:00 +01:00
if poll["expires_at"]:
expires_at = parse_datetime(poll["expires_at"]).strftime("%Y-%m-%d %H:%M")
poll_footer += f" · Closes on {expires_at}"
print_out("\n{}".format(poll_footer))
2019-02-14 16:53:58 +01:00
def print_timeline(items, width=100):
print_out("" * width)
for item in items:
2019-02-14 16:53:58 +01:00
print_status(item, width)
print_out("" * width)
notification_msgs = {
"follow": "{account} now follows you",
"mention": "{account} mentioned you in",
"reblog": "{account} reblogged your status",
"favourite": "{account} favourited your status",
}
def print_notification(notification, width=100):
account = "{display_name} @{acct}".format(**notification["account"])
msg = notification_msgs.get(notification["type"])
if msg is None:
return
print_out("" * width)
print_out(msg.format(account=account))
status = notification.get("status")
if status is not None:
print_status(status, width)
def print_notifications(notifications, width=100):
for notification in notifications:
print_notification(notification)
print_out("" * width)