mirror of
https://github.com/ihabunek/toot.git
synced 2024-09-01 04:04:14 -04:00
Rework how colors are shown in the commandline
Add an option to disable colors. fixes #15
This commit is contained in:
parent
edf657ba5b
commit
39c2cc661d
124
toot/commands.py
124
toot/commands.py
@ -14,12 +14,12 @@ from itertools import chain
|
|||||||
from textwrap import TextWrapper, wrap
|
from textwrap import TextWrapper, wrap
|
||||||
|
|
||||||
from toot import api, config, DEFAULT_INSTANCE, User, App, ConsoleError
|
from toot import api, config, DEFAULT_INSTANCE, User, App, ConsoleError
|
||||||
from toot.output import green, yellow, print_error
|
from toot.output import print_out
|
||||||
from toot.app import TimelineApp
|
from toot.app import TimelineApp
|
||||||
|
|
||||||
|
|
||||||
def register_app(instance):
|
def register_app(instance):
|
||||||
print("Registering application with %s" % green(instance))
|
print_out("Registering application with <green>{}</green>".format(instance))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = api.create_app(instance)
|
response = api.create_app(instance)
|
||||||
@ -30,13 +30,15 @@ def register_app(instance):
|
|||||||
|
|
||||||
app = App(instance, base_url, response['client_id'], response['client_secret'])
|
app = App(instance, base_url, response['client_id'], response['client_secret'])
|
||||||
path = config.save_app(app)
|
path = config.save_app(app)
|
||||||
print("Application tokens saved to: {}\n".format(green(path)))
|
print_out("Application tokens saved to: <green>{}</green>\n".format(path))
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
def create_app_interactive():
|
def create_app_interactive():
|
||||||
instance = input("Choose an instance [%s]: " % green(DEFAULT_INSTANCE))
|
print_out("Choose an instance [<green>{}</green>]: ".format(DEFAULT_INSTANCE), end="")
|
||||||
|
|
||||||
|
instance = input()
|
||||||
if not instance:
|
if not instance:
|
||||||
instance = DEFAULT_INSTANCE
|
instance = DEFAULT_INSTANCE
|
||||||
|
|
||||||
@ -44,7 +46,8 @@ def create_app_interactive():
|
|||||||
|
|
||||||
|
|
||||||
def login_interactive(app):
|
def login_interactive(app):
|
||||||
print("\nLog in to " + green(app.instance))
|
print_out("\nLog in to <green>{}</green>".format(app.instance))
|
||||||
|
|
||||||
email = input('Email: ')
|
email = input('Email: ')
|
||||||
password = getpass('Password: ')
|
password = getpass('Password: ')
|
||||||
|
|
||||||
@ -52,14 +55,15 @@ def login_interactive(app):
|
|||||||
raise ConsoleError("Email and password cannot be empty.")
|
raise ConsoleError("Email and password cannot be empty.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print("Authenticating...")
|
print_out("Authenticating...")
|
||||||
response = api.login(app, email, password)
|
response = api.login(app, email, password)
|
||||||
except api.ApiError:
|
except api.ApiError:
|
||||||
raise ConsoleError("Login failed")
|
raise ConsoleError("Login failed")
|
||||||
|
|
||||||
user = User(app.instance, email, response['access_token'])
|
user = User(app.instance, email, response['access_token'])
|
||||||
path = config.save_user(user)
|
path = config.save_user(user)
|
||||||
print("Access token saved to: " + green(path))
|
|
||||||
|
print_out("Access token saved to: <green>{}</green>".format(path))
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
@ -67,7 +71,7 @@ def login_interactive(app):
|
|||||||
def two_factor_login_interactive(app):
|
def two_factor_login_interactive(app):
|
||||||
"""Hacky implementation of two factor authentication"""
|
"""Hacky implementation of two factor authentication"""
|
||||||
|
|
||||||
print("Log in to " + green(app.instance))
|
print_out("Log in to {}".format(app.instance))
|
||||||
email = input('Email: ')
|
email = input('Email: ')
|
||||||
password = getpass('Password: ')
|
password = getpass('Password: ')
|
||||||
|
|
||||||
@ -114,7 +118,7 @@ def two_factor_login_interactive(app):
|
|||||||
|
|
||||||
user = User(app.instance, email, access_token)
|
user = User(app.instance, email, access_token)
|
||||||
path = config.save_user(user)
|
path = config.save_user(user)
|
||||||
print("Access token saved to: " + green(path))
|
print_out("Access token saved to: <green>{}</green>".format(path))
|
||||||
|
|
||||||
|
|
||||||
def _print_timeline(item):
|
def _print_timeline(item):
|
||||||
@ -137,7 +141,7 @@ def _print_timeline(item):
|
|||||||
return zip_longest(left_column, right_column, fillvalue="")
|
return zip_longest(left_column, right_column, fillvalue="")
|
||||||
|
|
||||||
for left, right in timeline_rows(item):
|
for left, right in timeline_rows(item):
|
||||||
print("{:30} │ {}".format(left, right))
|
print_out("{:30} │ {}".format(left, right))
|
||||||
|
|
||||||
|
|
||||||
def _parse_timeline(item):
|
def _parse_timeline(item):
|
||||||
@ -161,10 +165,10 @@ def timeline(app, user, args):
|
|||||||
items = api.timeline_home(app, user)
|
items = api.timeline_home(app, user)
|
||||||
parsed_items = [_parse_timeline(t) for t in items]
|
parsed_items = [_parse_timeline(t) for t in items]
|
||||||
|
|
||||||
print("─" * 31 + "┬" + "─" * 88)
|
print_out("─" * 31 + "┬" + "─" * 88)
|
||||||
for item in parsed_items:
|
for item in parsed_items:
|
||||||
_print_timeline(item)
|
_print_timeline(item)
|
||||||
print("─" * 31 + "┼" + "─" * 88)
|
print_out("─" * 31 + "┼" + "─" * 88)
|
||||||
|
|
||||||
|
|
||||||
def curses(app, user, args):
|
def curses(app, user, args):
|
||||||
@ -181,76 +185,76 @@ def post(app, user, args):
|
|||||||
|
|
||||||
response = api.post_status(app, user, args.text, media_ids=media_ids, visibility=args.visibility)
|
response = api.post_status(app, user, args.text, media_ids=media_ids, visibility=args.visibility)
|
||||||
|
|
||||||
print("Toot posted: " + green(response.get('url')))
|
print_out("Toot posted: <green>{}</green>".format(response.get('url')))
|
||||||
|
|
||||||
|
|
||||||
def auth(app, user, args):
|
def auth(app, user, args):
|
||||||
if app and user:
|
if app and user:
|
||||||
print("You are logged in to {} as {}\n".format(
|
print_out("You are logged in to <yellow>{}</yellow> as <yellow>{}</yellow>\n".format(
|
||||||
yellow(app.instance),
|
app.instance, user.username))
|
||||||
yellow(user.username)
|
print_out("User data: <green>{}</green>".format(config.get_user_config_path()))
|
||||||
))
|
print_out("App data: <green>{}</green>".format(config.get_instance_config_path(app.instance)))
|
||||||
print("User data: " + green(config.get_user_config_path()))
|
|
||||||
print("App data: " + green(config.get_instance_config_path(app.instance)))
|
|
||||||
else:
|
else:
|
||||||
print("You are not logged in")
|
print_out("You are not logged in")
|
||||||
|
|
||||||
|
|
||||||
def login(app, user, args):
|
def login(app, user, args):
|
||||||
app = create_app_interactive()
|
app = create_app_interactive()
|
||||||
login_interactive(app)
|
login_interactive(app)
|
||||||
|
|
||||||
print()
|
print_out()
|
||||||
print(green("✓ Successfully logged in."))
|
print_out("<green>✓ Successfully logged in.</green>")
|
||||||
|
|
||||||
|
|
||||||
def login_2fa(app, user, args):
|
def login_2fa(app, user, args):
|
||||||
print()
|
print_out()
|
||||||
print(yellow("Two factor authentication is experimental."))
|
print_out("<yellow>Two factor authentication is experimental.</yellow>")
|
||||||
print(yellow("If you have problems logging in, please open an issue:"))
|
print_out("<yellow>If you have problems logging in, please open an issue:</yellow>")
|
||||||
print(yellow("https://github.com/ihabunek/toot/issues"))
|
print_out("<yellow>https://github.com/ihabunek/toot/issues</yellow>")
|
||||||
print()
|
print_out()
|
||||||
|
|
||||||
app = create_app_interactive()
|
app = create_app_interactive()
|
||||||
two_factor_login_interactive(app)
|
two_factor_login_interactive(app)
|
||||||
|
|
||||||
print()
|
print_out()
|
||||||
print(green("✓ Successfully logged in."))
|
print_out("<green>✓ Successfully logged in.</green>")
|
||||||
|
|
||||||
|
|
||||||
def logout(app, user, args):
|
def logout(app, user, args):
|
||||||
config.delete_user()
|
config.delete_user()
|
||||||
|
|
||||||
print(green("✓ You are now logged out"))
|
print_out("<green>✓ You are now logged out.</green>")
|
||||||
|
|
||||||
|
|
||||||
def upload(app, user, args):
|
def upload(app, user, args):
|
||||||
response = _do_upload(app, user, args.file)
|
response = _do_upload(app, user, args.file)
|
||||||
|
|
||||||
print("\nSuccessfully uploaded media ID {}, type '{}'".format(
|
print_out()
|
||||||
yellow(response['id']), yellow(response['type'])))
|
print_out("Successfully uploaded media ID <yellow>{}</yellow>, type '<yellow>{}</yellow>'".format(
|
||||||
print("Original URL: " + green(response['url']))
|
response['id'], response['type']))
|
||||||
print("Preview URL: " + green(response['preview_url']))
|
print_out("Original URL: <green>{}</green>".format(response['url']))
|
||||||
print("Text URL: " + green(response['text_url']))
|
print_out("Preview URL: <green>{}</green>".format(response['preview_url']))
|
||||||
|
print_out("Text URL: <green>{}</green>".format(response['text_url']))
|
||||||
|
|
||||||
|
|
||||||
def _print_accounts(accounts):
|
def _print_accounts(accounts):
|
||||||
if not accounts:
|
if not accounts:
|
||||||
return
|
return
|
||||||
|
|
||||||
print("\nAccounts:")
|
print_out("\nAccounts:")
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
acct = green("@{}".format(account['acct']))
|
print_out("* <green>@{}</green> {}".format(
|
||||||
display_name = account['display_name']
|
account['acct'],
|
||||||
print("* {} {}".format(acct, display_name))
|
account['display_name']
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
def _print_hashtags(hashtags):
|
def _print_hashtags(hashtags):
|
||||||
if not hashtags:
|
if not hashtags:
|
||||||
return
|
return
|
||||||
|
|
||||||
print("\nHashtags:")
|
print_out("\nHashtags:")
|
||||||
print(", ".join([green("#" + t) for t in hashtags]))
|
print_out(", ".join(["<green>#{}</green>".format(t) for t in hashtags]))
|
||||||
|
|
||||||
|
|
||||||
def search(app, user, args):
|
def search(app, user, args):
|
||||||
@ -261,7 +265,7 @@ def search(app, user, args):
|
|||||||
|
|
||||||
|
|
||||||
def _do_upload(app, user, file):
|
def _do_upload(app, user, file):
|
||||||
print("Uploading media: {}".format(green(file.name)))
|
print_out("Uploading media: <green>{}</green>".format(file.name))
|
||||||
return api.upload_media(app, user, file)
|
return api.upload_media(app, user, file)
|
||||||
|
|
||||||
|
|
||||||
@ -286,59 +290,59 @@ def _find_account(app, user, account_name):
|
|||||||
|
|
||||||
|
|
||||||
def _print_account(account):
|
def _print_account(account):
|
||||||
print("{} {}".format(green("@" + account['acct']), account['display_name']))
|
print_out("<green>@{}</green> {}".format(account['acct'], account['display_name']))
|
||||||
|
|
||||||
note = BeautifulSoup(account['note'], "html.parser").get_text()
|
note = BeautifulSoup(account['note'], "html.parser").get_text()
|
||||||
|
|
||||||
if note:
|
if note:
|
||||||
print("")
|
print_out("")
|
||||||
print("\n".join(wrap(note)))
|
print_out("\n".join(wrap(note)))
|
||||||
|
|
||||||
print("")
|
print_out("")
|
||||||
print("ID: " + green(account['id']))
|
print_out("ID: <green>{}</green>".format(account['id']))
|
||||||
print("Since: " + green(account['created_at'][:19].replace('T', ' @ ')))
|
print_out("Since: <green>{}</green>".format(account['created_at'][:19].replace('T', ' @ ')))
|
||||||
print("")
|
print_out("")
|
||||||
print("Followers: " + yellow(account['followers_count']))
|
print_out("Followers: <yellow>{}</yellow>".format(account['followers_count']))
|
||||||
print("Following: " + yellow(account['following_count']))
|
print_out("Following: <yellow>{}</yellow>".format(account['following_count']))
|
||||||
print("Statuses: " + yellow(account['statuses_count']))
|
print_out("Statuses: <yellow>{}</yellow>".format(account['statuses_count']))
|
||||||
print("")
|
print_out("")
|
||||||
print(account['url'])
|
print_out(account['url'])
|
||||||
|
|
||||||
|
|
||||||
def follow(app, user, args):
|
def follow(app, user, args):
|
||||||
account = _find_account(app, user, args.account)
|
account = _find_account(app, user, args.account)
|
||||||
api.follow(app, user, account['id'])
|
api.follow(app, user, account['id'])
|
||||||
print(green("✓ You are now following %s" % args.account))
|
print_out("<green>✓ You are now following {}</green>".format(args.account))
|
||||||
|
|
||||||
|
|
||||||
def unfollow(app, user, args):
|
def unfollow(app, user, args):
|
||||||
account = _find_account(app, user, args.account)
|
account = _find_account(app, user, args.account)
|
||||||
api.unfollow(app, user, account['id'])
|
api.unfollow(app, user, account['id'])
|
||||||
print(green("✓ You are no longer following %s" % args.account))
|
print_out("<green>✓ You are no longer following {}</green>".format(args.account))
|
||||||
|
|
||||||
|
|
||||||
def mute(app, user, args):
|
def mute(app, user, args):
|
||||||
account = _find_account(app, user, args.account)
|
account = _find_account(app, user, args.account)
|
||||||
api.mute(app, user, account['id'])
|
api.mute(app, user, account['id'])
|
||||||
print(green("✓ You have muted %s" % args.account))
|
print_out("<green>✓ You have muted {}</green>".format(args.account))
|
||||||
|
|
||||||
|
|
||||||
def unmute(app, user, args):
|
def unmute(app, user, args):
|
||||||
account = _find_account(app, user, args.account)
|
account = _find_account(app, user, args.account)
|
||||||
api.unmute(app, user, account['id'])
|
api.unmute(app, user, account['id'])
|
||||||
print(green("✓ %s is no longer muted" % args.account))
|
print_out("<green>✓ {} is no longer muted</green>".format(args.account))
|
||||||
|
|
||||||
|
|
||||||
def block(app, user, args):
|
def block(app, user, args):
|
||||||
account = _find_account(app, user, args.account)
|
account = _find_account(app, user, args.account)
|
||||||
api.block(app, user, account['id'])
|
api.block(app, user, account['id'])
|
||||||
print(green("✓ You are now blocking %s" % args.account))
|
print_out("<green>✓ You are now blocking {}</green>".format(args.account))
|
||||||
|
|
||||||
|
|
||||||
def unblock(app, user, args):
|
def unblock(app, user, args):
|
||||||
account = _find_account(app, user, args.account)
|
account = _find_account(app, user, args.account)
|
||||||
api.unblock(app, user, account['id'])
|
api.unblock(app, user, account['id'])
|
||||||
print(green("✓ %s is no longer blocked" % args.account))
|
print_out("<green>✓ {} is no longer blocked</green>".format(args.account))
|
||||||
|
|
||||||
|
|
||||||
def whoami(app, user, args):
|
def whoami(app, user, args):
|
||||||
|
@ -9,7 +9,7 @@ import logging
|
|||||||
from argparse import ArgumentParser, FileType
|
from argparse import ArgumentParser, FileType
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from toot import config, api, commands, ConsoleError, CLIENT_NAME, CLIENT_WEBSITE
|
from toot import config, api, commands, ConsoleError, CLIENT_NAME, CLIENT_WEBSITE
|
||||||
from toot.output import print_error
|
from toot.output import print_out, print_err
|
||||||
|
|
||||||
|
|
||||||
VISIBILITY_CHOICES = ['public', 'unlisted', 'private', 'direct']
|
VISIBILITY_CHOICES = ['public', 'unlisted', 'private', 'direct']
|
||||||
@ -26,7 +26,14 @@ def visibility(value):
|
|||||||
Command = namedtuple("Command", ["name", "description", "require_auth", "arguments"])
|
Command = namedtuple("Command", ["name", "description", "require_auth", "arguments"])
|
||||||
|
|
||||||
|
|
||||||
# Some common arguments:
|
common_args = [
|
||||||
|
(["--no-color"], {
|
||||||
|
"help": "don't use ANSI colors in output",
|
||||||
|
"action": 'store_true',
|
||||||
|
"default": False,
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
account_arg = (["account"], {
|
account_arg = (["account"], {
|
||||||
"help": "account name, e.g. 'Gargron' or 'polymerwitch@toot.cat'",
|
"help": "account name, e.g. 'Gargron' or 'polymerwitch@toot.cat'",
|
||||||
})
|
})
|
||||||
@ -202,20 +209,21 @@ def print_usage():
|
|||||||
("Accounts", ACCOUNTS_COMMANDS),
|
("Accounts", ACCOUNTS_COMMANDS),
|
||||||
]
|
]
|
||||||
|
|
||||||
print(CLIENT_NAME)
|
print_out("<green>{}</green>".format(CLIENT_NAME))
|
||||||
|
|
||||||
for name, cmds in groups:
|
for name, cmds in groups:
|
||||||
print("")
|
print_out("")
|
||||||
print(name + ":")
|
print_out(name + ":")
|
||||||
|
|
||||||
for cmd in cmds:
|
for cmd in cmds:
|
||||||
print(" toot", cmd.name.ljust(max_name_len + 2), cmd.description)
|
cmd_name = cmd.name.ljust(max_name_len + 2)
|
||||||
|
print_out(" <yellow>toot {}</yellow> {}".format(cmd_name, cmd.description))
|
||||||
|
|
||||||
print("")
|
print_out("")
|
||||||
print("To get help for each command run:")
|
print_out("To get help for each command run:")
|
||||||
print(" toot <command> --help")
|
print_out(" <yellow>toot <command> --help</yellow>")
|
||||||
print("")
|
print_out("")
|
||||||
print(CLIENT_WEBSITE)
|
print_out("<green>{}</green>".format(CLIENT_WEBSITE))
|
||||||
|
|
||||||
|
|
||||||
def get_argument_parser(name, command):
|
def get_argument_parser(name, command):
|
||||||
@ -224,7 +232,7 @@ def get_argument_parser(name, command):
|
|||||||
description=command.description,
|
description=command.description,
|
||||||
epilog=CLIENT_WEBSITE)
|
epilog=CLIENT_WEBSITE)
|
||||||
|
|
||||||
for args, kwargs in command.arguments:
|
for args, kwargs in command.arguments + common_args:
|
||||||
parser.add_argument(*args, **kwargs)
|
parser.add_argument(*args, **kwargs)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
@ -234,7 +242,7 @@ def run_command(app, user, name, args):
|
|||||||
command = next((c for c in COMMANDS if c.name == name), None)
|
command = next((c for c in COMMANDS if c.name == name), None)
|
||||||
|
|
||||||
if not command:
|
if not command:
|
||||||
print_error("Unknown command '{}'\n".format(name))
|
print_err("Unknown command '{}'\n".format(name))
|
||||||
print_usage()
|
print_usage()
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -242,8 +250,8 @@ def run_command(app, user, name, args):
|
|||||||
parsed_args = parser.parse_args(args)
|
parsed_args = parser.parse_args(args)
|
||||||
|
|
||||||
if command.require_auth and (not user or not app):
|
if command.require_auth and (not user or not app):
|
||||||
print_error("This command requires that you are logged in.")
|
print_err("This command requires that you are logged in.")
|
||||||
print_error("Please run `toot login` first.")
|
print_err("Please run `toot login` first.")
|
||||||
return
|
return
|
||||||
|
|
||||||
fn = commands.__dict__.get(name)
|
fn = commands.__dict__.get(name)
|
||||||
@ -276,6 +284,6 @@ def main():
|
|||||||
try:
|
try:
|
||||||
run_command(app, user, command_name, args)
|
run_command(app, user, command_name, args)
|
||||||
except ConsoleError as e:
|
except ConsoleError as e:
|
||||||
print_error(str(e))
|
print_err(str(e))
|
||||||
except api.ApiError as e:
|
except api.ApiError as e:
|
||||||
print_error(str(e))
|
print_err(str(e))
|
||||||
|
@ -3,35 +3,53 @@ from __future__ import unicode_literals
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
def _color(text, color):
|
START_CODES = {
|
||||||
return "\033[3{}m{}\033[0m".format(color, text)
|
'red': '\033[31m',
|
||||||
|
'green': '\033[32m',
|
||||||
|
'yellow': '\033[33m',
|
||||||
|
'blue': '\033[34m',
|
||||||
|
'magenta': '\033[35m',
|
||||||
|
'cyan': '\033[36m',
|
||||||
|
}
|
||||||
|
|
||||||
|
END_CODE = '\033[0m'
|
||||||
|
|
||||||
|
START_PATTERN = "<(" + "|".join(START_CODES.keys()) + ")>"
|
||||||
|
|
||||||
|
END_PATTERN = "</(" + "|".join(START_CODES.keys()) + ")>"
|
||||||
|
|
||||||
|
|
||||||
def red(text):
|
def start_code(match):
|
||||||
return _color(text, 1)
|
name = match.group(1)
|
||||||
|
return START_CODES[name]
|
||||||
|
|
||||||
|
|
||||||
def green(text):
|
def colorize(text):
|
||||||
return _color(text, 2)
|
text = re.sub(START_PATTERN, start_code, text)
|
||||||
|
text = re.sub(END_PATTERN, END_CODE, text)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
def yellow(text):
|
def strip_tags(text):
|
||||||
return _color(text, 3)
|
text = re.sub(START_PATTERN, '', text)
|
||||||
|
text = re.sub(END_PATTERN, '', text)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
def blue(text):
|
USE_ANSI_COLOR = "--no-color" not in sys.argv
|
||||||
return _color(text, 4)
|
|
||||||
|
|
||||||
|
|
||||||
def magenta(text):
|
def print_out(*args, **kwargs):
|
||||||
return _color(text, 5)
|
args = [colorize(a) if USE_ANSI_COLOR else strip_tags(a) for a in args]
|
||||||
|
print(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def cyan(text):
|
def print_err(*args, **kwargs):
|
||||||
return _color(text, 6)
|
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)
|
||||||
def print_error(text):
|
|
||||||
print(red(text), file=sys.stderr)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user