Introduction
Introduction Statistics Contact Development Disclaimer Help
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)
You are viewing proxied material from vernunftzentrum.de. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.