Rework how colors are shown in the commandline - toot - Unnamed repository; edi… | |
Log | |
Files | |
Refs | |
LICENSE | |
--- | |
commit 39c2cc661d73c6c6c9a455579691f9895e2c7947 | |
parent edf657ba5b885a99ca532b0cf72c3f038909b067 | |
Author: Ivan Habunek <[email protected]> | |
Date: Mon, 8 May 2017 09:09:20 +0200 | |
Rework how colors are shown in the commandline | |
Add an option to disable colors. | |
fixes #15 | |
Diffstat: | |
toot/commands.py | 124 ++++++++++++++++--------------- | |
toot/console.py | 42 ++++++++++++++++++------------- | |
toot/output.py | 50 +++++++++++++++++++++---------- | |
3 files changed, 123 insertions(+), 93 deletions(-) | |
--- | |
diff --git 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 <green>{}</green>".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_secr… | |
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 | |
def create_app_interactive(): | |
- instance = input("Choose an instance [%s]: " % green(DEFAULT_INSTANCE)) | |
+ print_out("Choose an instance [<green>{}</green>]: ".format(DEFAULT_INSTAN… | |
+ | |
+ 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 <green>{}</green>".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: <green>{}</green>".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: <green>{}</green>".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, visi… | |
- print("Toot posted: " + green(response.get('url'))) | |
+ print_out("Toot posted: <green>{}</green>".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.instan… | |
+ print_out("You are logged in to <yellow>{}</yellow> as <yellow>{}</yel… | |
+ app.instance, user.username)) | |
+ print_out("User data: <green>{}</green>".format(config.get_user_config… | |
+ print_out("App data: <green>{}</green>".format(config.get_instance_co… | |
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("<green>✓ Successfully logged in.</green>") | |
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("<yellow>Two factor authentication is experimental.</yellow>") | |
+ print_out("<yellow>If you have problems logging in, please open an issue:<… | |
+ print_out("<yellow>https://github.com/ihabunek/toot/issues</yellow>") | |
+ print_out() | |
app = create_app_interactive() | |
two_factor_login_interactive(app) | |
- print() | |
- print(green("✓ Successfully logged in.")) | |
+ print_out() | |
+ print_out("<green>✓ Successfully logged in.</green>") | |
def logout(app, user, args): | |
config.delete_user() | |
- print(green("✓ You are now logged out")) | |
+ print_out("<green>✓ You are now logged out.</green>") | |
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 <yellow>{}</yellow>, type '<yell… | |
+ response['id'], response['type'])) | |
+ print_out("Original URL: <green>{}</green>".format(response['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): | |
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("* <green>@{}</green> {}".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(["<green>#{}</green>".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: <green>{}</green>".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("<green>@{}</green> {}".format(account['acct'], account['display… | |
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: <green>{}</green>".format(account['id'])) | |
+ print_out("Since: <green>{}</green>".format(account['created_at'][:19].rep… | |
+ 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']) | |
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("<green>✓ You are now following {}</green>".format(args.accoun… | |
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("<green>✓ You are no longer following {}</green>".format(args.… | |
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("<green>✓ You have muted {}</green>".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("<green>✓ {} is no longer muted</green>".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("<green>✓ You are now blocking {}</green>".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("<green>✓ {} is no longer blocked</green>".format(args.account… | |
def whoami(app, user, args): | |
diff --git 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_WEBS… | |
-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", "argum… | |
-# 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 '[email protected]'", | |
}) | |
@@ -202,20 +209,21 @@ def print_usage(): | |
("Accounts", ACCOUNTS_COMMANDS), | |
] | |
- print(CLIENT_NAME) | |
+ print_out("<green>{}</green>".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(" <yellow>toot {}</yellow> {}".format(cmd_name, cmd.des… | |
- print("") | |
- print("To get help for each command run:") | |
- print(" toot <command> --help") | |
- print("") | |
- print(CLIENT_WEBSITE) | |
+ print_out("") | |
+ print_out("To get help for each command run:") | |
+ print_out(" <yellow>toot <command> --help</yellow>") | |
+ print_out("") | |
+ print_out("<green>{}</green>".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 | |
@@ -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' | |
-def red(text): | |
- return _color(text, 1) | |
+START_PATTERN = "<(" + "|".join(START_CODES.keys()) + ")>" | |
+END_PATTERN = "</(" + "|".join(START_CODES.keys()) + ")>" | |
-def green(text): | |
- return _color(text, 2) | |
+def start_code(match): | |
+ name = match.group(1) | |
+ return START_CODES[name] | |
-def yellow(text): | |
- return _color(text, 3) | |
+def colorize(text): | |
+ text = re.sub(START_PATTERN, start_code, text) | |
+ text = re.sub(END_PATTERN, END_CODE, text) | |
-def blue(text): | |
- return _color(text, 4) | |
+ return text | |
-def magenta(text): | |
- return _color(text, 5) | |
+def strip_tags(text): | |
+ text = re.sub(START_PATTERN, '', text) | |
+ text = re.sub(END_PATTERN, '', text) | |
+ return text | |
-def cyan(text): | |
- return _color(text, 6) | |
+USE_ANSI_COLOR = "--no-color" not in sys.argv | |
-def print_error(text): | |
- print(red(text), file=sys.stderr) | |
+ | |
+def print_out(*args, **kwargs): | |
+ 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) |