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