diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index e878b7b..fea6477 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -23,8 +23,7 @@ import uuid from click.testing import CliRunner, Result from pathlib import Path from toot import api, App, User -from toot.cli import Context -from toot.cli.base import TootObj +from toot.cli import Context, TootObj def pytest_configure(config): diff --git a/tests/integration/test_auth.py b/tests/integration/test_auth.py index 1cc0672..858d6fb 100644 --- a/tests/integration/test_auth.py +++ b/tests/integration/test_auth.py @@ -3,7 +3,7 @@ from unittest import mock from unittest.mock import MagicMock from toot import User, cli -from toot.cli.base import Run +from toot.cli import Run # TODO: figure out how to test login diff --git a/toot/cli/__init__.py b/toot/cli/__init__.py index 6f81aa0..d75f267 100644 --- a/toot/cli/__init__.py +++ b/toot/cli/__init__.py @@ -1,5 +1,134 @@ # flake8: noqa -from toot.cli.base import cli, Context +import click +import logging +import os +import sys +import typing as t + +from click.testing import Result +from functools import wraps +from toot import App, User, config, __version__ +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\"""" + + +# Type alias for run commands +Run = t.Callable[..., Result] + + +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", {}) + 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 + quiet: bool = False + + +class TootObj(t.NamedTuple): + """Data to add to Click context""" + color: bool = True + debug: bool = False + quiet: bool = False + # Pass a context for testing purposes + test_ctx: t.Optional[Context] = None + + +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 + + 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, obj.quiet) + + +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("--quiet/--no-quiet", default=False, help="Don't print anything to stdout") +@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, quiet: bool): + """Toot is a Mastodon CLI""" + ctx.obj = TootObj(color, debug, quiet) + ctx.color = color + ctx.max_content_width = max_width + + if debug: + logging.basicConfig(level=logging.DEBUG) from toot.cli import accounts from toot.cli import auth diff --git a/toot/cli/accounts.py b/toot/cli/accounts.py index 5cb66b9..01c499d 100644 --- a/toot/cli/accounts.py +++ b/toot/cli/accounts.py @@ -4,7 +4,7 @@ import json as pyjson from typing import BinaryIO, Optional from toot import api -from toot.cli.base import PRIVACY_CHOICES, cli, json_option, Context, pass_context +from toot.cli import PRIVACY_CHOICES, cli, json_option, Context, pass_context from toot.cli.validators import validate_language from toot.output import print_acct_list diff --git a/toot/cli/auth.py b/toot/cli/auth.py index e5a7c8e..c72f0c4 100644 --- a/toot/cli/auth.py +++ b/toot/cli/auth.py @@ -8,7 +8,7 @@ from click.types import StringParamType from toot import api, config, __version__ from toot.auth import get_or_create_app, login_auth_code, login_username_password -from toot.cli.base import cli +from toot.cli import cli from toot.cli.validators import validate_instance diff --git a/toot/cli/base.py b/toot/cli/base.py deleted file mode 100644 index 71df90f..0000000 --- a/toot/cli/base.py +++ /dev/null @@ -1,130 +0,0 @@ -import click -import logging -import os -import sys -import typing as t - -from click.testing import Result -from functools import wraps -from toot import App, User, config, __version__ -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\"""" - - -# Type alias for run commands -Run = t.Callable[..., Result] - - -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", {}) - 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 - quiet: bool = False - - -class TootObj(t.NamedTuple): - """Data to add to Click context""" - color: bool = True - debug: bool = False - quiet: bool = False - # Pass a context for testing purposes - test_ctx: t.Optional[Context] = None - - -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 - - 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, obj.quiet) - - -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("--quiet/--no-quiet", default=False, help="Don't print anything to stdout") -@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, quiet: bool): - """Toot is a Mastodon CLI""" - ctx.obj = TootObj(color, debug, quiet) - ctx.color = color - ctx.max_content_width = max_width - - if debug: - logging.basicConfig(level=logging.DEBUG) diff --git a/toot/cli/lists.py b/toot/cli/lists.py index 7fe30df..a7a8fcc 100644 --- a/toot/cli/lists.py +++ b/toot/cli/lists.py @@ -1,7 +1,7 @@ import click from toot import api, config -from toot.cli.base import Context, cli, pass_context +from toot.cli import Context, cli, pass_context from toot.output import print_list_accounts, print_lists, print_warning diff --git a/toot/cli/post.py b/toot/cli/post.py index fa5b578..af0fa60 100644 --- a/toot/cli/post.py +++ b/toot/cli/post.py @@ -7,8 +7,8 @@ from time import sleep, time from typing import BinaryIO, Optional, Tuple from toot import api -from toot.cli.base import cli, json_option, pass_context, Context -from toot.cli.base import DURATION_EXAMPLES, VISIBILITY_CHOICES +from toot.cli import cli, json_option, pass_context, Context +from toot.cli import DURATION_EXAMPLES, VISIBILITY_CHOICES from toot.cli.validators import validate_duration, validate_language from toot.entities import MediaAttachment, from_dict from toot.utils import EOF_KEY, delete_tmp_status_file, editor_input, multiline_input diff --git a/toot/cli/read.py b/toot/cli/read.py index 4d5e2d0..341fb8e 100644 --- a/toot/cli/read.py +++ b/toot/cli/read.py @@ -9,7 +9,7 @@ from toot.cli.validators import validate_instance from toot.entities import Instance, Status, from_dict, Account from toot.exceptions import ApiError, ConsoleError from toot.output import print_account, print_instance, print_search_results, print_status, print_timeline -from toot.cli.base import cli, json_option, pass_context, Context +from toot.cli import cli, json_option, pass_context, Context @cli.command() diff --git a/toot/cli/statuses.py b/toot/cli/statuses.py index 9e3ecee..0d088b2 100644 --- a/toot/cli/statuses.py +++ b/toot/cli/statuses.py @@ -1,8 +1,8 @@ import click from toot import api -from toot.cli.base import cli, json_option, Context, pass_context -from toot.cli.base import VISIBILITY_CHOICES +from toot.cli import cli, json_option, Context, pass_context +from toot.cli import VISIBILITY_CHOICES from toot.output import print_table diff --git a/toot/cli/tags.py b/toot/cli/tags.py index 2e8d40a..b79fdf0 100644 --- a/toot/cli/tags.py +++ b/toot/cli/tags.py @@ -2,7 +2,7 @@ import click import json as pyjson from toot import api -from toot.cli.base import cli, pass_context, json_option, Context +from toot.cli import cli, pass_context, json_option, Context from toot.entities import Tag, from_dict from toot.output import print_tag_list, print_warning diff --git a/toot/cli/timelines.py b/toot/cli/timelines.py index ffb382d..fc2be24 100644 --- a/toot/cli/timelines.py +++ b/toot/cli/timelines.py @@ -2,7 +2,7 @@ import sys import click from toot import api -from toot.cli.base import cli, pass_context, Context +from toot.cli import cli, pass_context, Context from typing import Optional from toot.cli.validators import validate_instance diff --git a/toot/cli/tui.py b/toot/cli/tui.py index a0c0f0f..1cedd75 100644 --- a/toot/cli/tui.py +++ b/toot/cli/tui.py @@ -1,7 +1,7 @@ import click from typing import Optional -from toot.cli.base import TUI_COLORS, Context, cli, pass_context +from toot.cli import TUI_COLORS, Context, cli, pass_context from toot.cli.validators import validate_tui_colors from toot.tui.app import TUI, TuiOptions diff --git a/toot/cli/validators.py b/toot/cli/validators.py index 819fdf9..6b7c8fe 100644 --- a/toot/cli/validators.py +++ b/toot/cli/validators.py @@ -4,7 +4,7 @@ import re from click import Context from typing import Optional -from toot.cli.base import TUI_COLORS +from toot.cli import TUI_COLORS def validate_language(ctx: Context, param: str, value: Optional[str]): diff --git a/toot/tui/app.py b/toot/tui/app.py index bc712cd..82747a2 100644 --- a/toot/tui/app.py +++ b/toot/tui/app.py @@ -7,7 +7,7 @@ from typing import NamedTuple, Optional from toot import api, config, __version__, settings from toot import App, User -from toot.cli.base import get_default_visibility +from toot.cli import get_default_visibility from toot.exceptions import ApiError from .compose import StatusComposer diff --git a/toot/tui/compose.py b/toot/tui/compose.py index 4d4b77a..c4a038a 100644 --- a/toot/tui/compose.py +++ b/toot/tui/compose.py @@ -1,7 +1,7 @@ import urwid import logging -from toot.cli.base import get_default_visibility +from toot.cli import get_default_visibility from .constants import VISIBILITY_OPTIONS from .widgets import Button, EditBox