Toot-Mastodon-CLI-TUI-clien.../toot/cli/__init__.py

183 lines
5.3 KiB
Python

import click
import logging
import os
import sys
import typing as t
from click.shell_completion import CompletionItem
from click.types import StringParamType
from functools import wraps
from toot import App, User, config, __version__
from toot.output import print_warning
from toot.settings import get_settings
if t.TYPE_CHECKING:
import typing_extensions as te
P = te.ParamSpec("P")
R = t.TypeVar("R")
T = t.TypeVar("T")
PRIVACY_CHOICES = ["public", "unlisted", "private"]
VISIBILITY_CHOICES = ["public", "unlisted", "private", "direct"]
TUI_COLORS = {
"1": 1,
"16": 16,
"88": 88,
"256": 256,
"16777216": 16777216,
"24bit": 16777216,
}
TUI_COLORS_CHOICES = list(TUI_COLORS.keys())
TUI_COLORS_VALUES = list(TUI_COLORS.values())
DURATION_EXAMPLES = """e.g. "1 day", "2 hours 30 minutes", "5 minutes 30
seconds" or any combination of above. Shorthand: "1d", "2h30m", "5m30s\""""
def get_default_visibility() -> str:
return os.getenv("TOOT_POST_VISIBILITY", "public")
def get_default_map():
settings = get_settings()
common = settings.get("common", {})
commands = settings.get("commands", {})
# TODO: remove in version 1.0
tui_old = settings.get("tui", {}).copy()
if "palette" in tui_old:
del tui_old["palette"]
if tui_old:
# TODO: don't show the warning for [toot.palette]
print_warning("Settings section [tui] has been deprecated in favour of [commands.tui].")
tui_new = commands.get("tui", {})
commands["tui"] = {**tui_old, **tui_new}
return {**common, **commands}
# Tweak the Click context
# https://click.palletsprojects.com/en/8.1.x/api/#context
CONTEXT = dict(
# Enable using environment variables to set options
auto_envvar_prefix="TOOT",
# Add shorthand -h for invoking help
help_option_names=["-h", "--help"],
# Always show default values for options
show_default=True,
# Load command defaults from settings
default_map=get_default_map(),
)
class Context(t.NamedTuple):
app: t.Optional[App]
user: t.Optional[User] = None
color: bool = False
debug: bool = False
class TootObj(t.NamedTuple):
"""Data to add to Click context"""
color: bool = True
debug: bool = False
as_user: t.Optional[str] = None
# Pass a context for testing purposes
test_ctx: t.Optional[Context] = None
class AccountParamType(StringParamType):
"""Custom type to add shell completion for account names"""
name = "account"
def shell_complete(self, ctx, param, incomplete: str):
users = config.load_config()["users"].keys()
return [
CompletionItem(u)
for u in users
if u.lower().startswith(incomplete.lower())
]
class InstanceParamType(StringParamType):
"""Custom type to add shell completion for instance domains"""
name = "instance"
def shell_complete(self, ctx, param, incomplete: str):
apps = config.load_config()["apps"]
return [
CompletionItem(i)
for i in apps.keys()
if i.lower().startswith(incomplete.lower())
]
def pass_context(f: "t.Callable[te.Concatenate[Context, P], R]") -> "t.Callable[P, R]":
"""Pass the toot Context as first argument."""
@wraps(f)
def wrapped(*args: "P.args", **kwargs: "P.kwargs") -> R:
return f(get_context(), *args, **kwargs)
return wrapped
def get_context() -> Context:
click_context = click.get_current_context()
obj: TootObj = click_context.obj
# This is used to pass a context for testing, not used in normal usage
if obj.test_ctx:
return obj.test_ctx
if obj.as_user:
user, app = config.get_user_app(obj.as_user)
if not user or not app:
raise click.ClickException(f"Account '{obj.as_user}' not found. Run `toot auth` to see available accounts.")
else:
user, app = config.get_active_user_app()
if not user or not app:
raise click.ClickException("This command requires you to be logged in.")
return Context(app, user, obj.color, obj.debug)
json_option = click.option(
"--json",
is_flag=True,
default=False,
help="Print data as JSON rather than human readable text"
)
@click.group(context_settings=CONTEXT)
@click.option("-w", "--max-width", type=int, default=80, help="Maximum width for content rendered by toot")
@click.option("--debug/--no-debug", default=False, help="Log debug info to stderr")
@click.option("--color/--no-color", default=sys.stdout.isatty(), help="Use ANSI color in output")
@click.option("--as", "as_user", type=AccountParamType(), help="The account to use, overrides the active account.")
@click.version_option(__version__, message="%(prog)s v%(version)s")
@click.pass_context
def cli(ctx: click.Context, max_width: int, color: bool, debug: bool, as_user: str):
"""Toot is a Mastodon CLI"""
ctx.obj = TootObj(color, debug, as_user)
ctx.color = color
ctx.max_content_width = max_width
if debug:
logging.basicConfig(level=logging.DEBUG)
from toot.cli import accounts # noqa
from toot.cli import auth # noqa
from toot.cli import lists # noqa
from toot.cli import post # noqa
from toot.cli import read # noqa
from toot.cli import statuses # noqa
from toot.cli import tags # noqa
from toot.cli import timelines # noqa
from toot.cli import tui # noqa