diff --git a/kiwi_scp/commands/cli.py b/kiwi_scp/commands/cli.py index 3c3feef..a699763 100644 --- a/kiwi_scp/commands/cli.py +++ b/kiwi_scp/commands/cli.py @@ -1,7 +1,8 @@ +import importlib import os import sys from enum import Enum, auto -from typing import List, Tuple, Iterable, Any, Type +from typing import List, Tuple, Iterable, Type, Optional, TypeVar import click @@ -11,42 +12,50 @@ from ..instance import Instance, Project class KiwiCLI(click.MultiCommand): """Command Line Interface spread over multiple files in this directory""" - def list_commands(self, ctx): + def list_commands(self, ctx: click.Context) -> List[str]: """list all the commands defined by cmd_*.py files in this directory""" - return ( + return [ filename[4:-3] for filename in os.listdir(os.path.abspath(os.path.dirname(__file__))) if filename.startswith("cmd_") and filename.endswith(".py") - ) + ] - def get_command(self, ctx, name): + def get_command(self, ctx: click.Context, cmd_name: str) -> Optional[click.Command]: """import and return a specific command""" try: - mod = __import__(f"kiwi_scp.commands.cmd_{name}", None, None, ["CMD"]) + cmd_module = importlib.import_module(f"kiwi_scp.commands.cmd_{cmd_name}") + except ImportError: return - return mod.CMD + + for cmd_name in dir(cmd_module): + member = getattr(cmd_module, cmd_name) + if isinstance(member, click.Command): + return member + + +T = TypeVar("T") class KiwiCommand: @staticmethod - def print_multi_color(*content: Tuple[str, str]): + def print_multi_color(*content: Tuple[str, str]) -> None: for message, color in content: click.secho(message, fg=color, nl=False) click.echo() @staticmethod - def print_header(header: str): + def print_header(header: str) -> None: click.secho(header, fg="green", bold=True) @staticmethod - def print_error(error: str): + def print_error(error: str) -> None: click.secho(error, file=sys.stderr, fg="red", bold=True) @staticmethod - def print_list(content: Iterable[str]): + def print_list(content: Iterable[str]) -> None: for item in content: KiwiCommand.print_multi_color( (" - ", "green"), @@ -54,7 +63,7 @@ class KiwiCommand: ) @staticmethod - def user_query(description: str, default: Any, cast_to: Type[Any] = str): + def user_query(description: str, default: T, cast_to: Type[T] = str) -> T: # prompt user as per argument while True: try: diff --git a/kiwi_scp/commands/cmd_cmd.py b/kiwi_scp/commands/cmd_cmd.py index 211cf74..d0bc116 100644 --- a/kiwi_scp/commands/cmd_cmd.py +++ b/kiwi_scp/commands/cmd_cmd.py @@ -15,17 +15,16 @@ from ..instance import Instance, Project ) @click.argument( "compose_cmd", - metavar="CMD", + metavar="COMMAND", ) @kiwi_command( - "cmd", - KiwiCommandType.PROJECT, + cmd_type=KiwiCommandType.PROJECT, short_help="Run docker-compose command", # ignore arguments looking like options # just pass everything down to docker-compose context_settings={"ignore_unknown_options": True}, ) -class CMD(KiwiCommand): +class CmdCommand(KiwiCommand): """Run raw docker-compose command in a project""" @classmethod diff --git a/kiwi_scp/commands/cmd_init.py b/kiwi_scp/commands/cmd_init.py index 1823206..dc4b8f5 100644 --- a/kiwi_scp/commands/cmd_init.py +++ b/kiwi_scp/commands/cmd_init.py @@ -30,11 +30,10 @@ _logger = logging.getLogger(__name__) help=f"use default values even if {KIWI_CONF_NAME} is present", ) @kiwi_command( - "init", - KiwiCommandType.INSTANCE, + cmd_type=KiwiCommandType.INSTANCE, short_help="Initializes kiwi-scp", ) -class CMD(KiwiCommand): +class InitCommand(KiwiCommand): """Initialize or reconfigure a kiwi-scp instance""" @classmethod diff --git a/kiwi_scp/commands/cmd_list.py b/kiwi_scp/commands/cmd_list.py index ffd3e20..cb8f2e6 100644 --- a/kiwi_scp/commands/cmd_list.py +++ b/kiwi_scp/commands/cmd_list.py @@ -13,11 +13,10 @@ from ..instance import Instance, Project help=f"show actual config contents instead", ) @kiwi_command( - "list", - KiwiCommandType.SERVICE, + cmd_type=KiwiCommandType.SERVICE, short_help="Inspect a kiwi-scp instance", ) -class CMD(KiwiCommand): +class ListCommand(KiwiCommand): """List projects in this instance, services inside a project or service(s) inside a project""" @classmethod diff --git a/kiwi_scp/commands/decorators.py b/kiwi_scp/commands/decorators.py index c432779..76b5238 100644 --- a/kiwi_scp/commands/decorators.py +++ b/kiwi_scp/commands/decorators.py @@ -29,14 +29,12 @@ _logger = logging.getLogger(__name__) def kiwi_command( - name: str, - command_type: KiwiCommandType, + cmd_type: KiwiCommandType = KiwiCommandType.SERVICE, **decorator_kwargs, ) -> Callable: def decorator(command_cls: Type[KiwiCommand]) -> Callable: @click.command( - name, help=command_cls.__doc__, **decorator_kwargs, ) @@ -71,10 +69,10 @@ def kiwi_command( else: KiwiCommand.print_error(f"Project '{project_name}' not in kiwi-scp instance at '{ctx.directory}'!") - if command_type is KiwiCommandType.PROJECT: + if cmd_type is KiwiCommandType.PROJECT: cmd = _project_arg(cmd) - elif command_type is KiwiCommandType.SERVICE: + elif cmd_type is KiwiCommandType.SERVICE: cmd = _project_arg(cmd) cmd = _services_arg(cmd)