imaginAIry/imaginairy/cli/clickshell_mod.py

105 lines
3.3 KiB
Python
Raw Normal View History

"""Most of these modifications are just so we get full stack traces in the shell."""
import logging
import shlex
import traceback
from functools import update_wrapper
from typing import ClassVar
import click
from click_help_colors import HelpColorsCommand, HelpColorsMixin
from click_shell import Shell
from click_shell._compat import get_method_type
from click_shell.core import ClickShell, get_complete, get_help
logger = logging.getLogger(__name__)
def mod_get_invoke(command):
"""
Get the Cmd main method from the click command
:param command: The click Command object
:return: the do_* method for Cmd
:rtype: function.
"""
assert isinstance(command, click.Command)
def invoke_(self, arg): # pylint: disable=unused-argument
try:
command.main(
args=shlex.split(arg),
prog_name=command.name,
standalone_mode=False,
parent=self.ctx,
)
except click.ClickException as e:
# Show the error message
e.show()
except click.Abort:
# We got an EOF or Keyboard interrupt. Just silence it
pass
except SystemExit:
# Catch this an return the code instead. All of click's help commands do a sys.exit(),
# and that's not ideal when running in a shell.
pass
except Exception as e: # noqa
traceback.print_exception(e)
# logger.warning(traceback.format_exc())
# Always return False so the shell doesn't exit
return False
invoke_ = update_wrapper(invoke_, command.callback)
invoke_.__name__ = "do_%s" % command.name
return invoke_
class ModClickShell(ClickShell):
def add_command(self, cmd, name):
# Use the MethodType to add these as bound methods to our current instance
setattr(self, "do_%s" % name, get_method_type(mod_get_invoke(cmd), self))
setattr(self, "help_%s" % name, get_method_type(get_help(cmd), self))
setattr(self, "complete_%s" % name, get_method_type(get_complete(cmd), self))
class ModShell(Shell):
def __init__(
self, prompt=None, intro=None, hist_file=None, on_finished=None, **attrs
):
attrs["invoke_without_command"] = True
super(Shell, self).__init__(**attrs)
# Make our shell
self.shell = ModClickShell(hist_file=hist_file, on_finished=on_finished)
if prompt:
self.shell.prompt = prompt
self.shell.intro = intro
class ColorShell(HelpColorsMixin, ModShell):
pass
class ImagineColorsCommand(HelpColorsCommand):
_option_order: ClassVar = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.help_headers_color = "yellow"
self.help_options_color = "green"
def parse_args(self, ctx, args):
# run the parser for ourselves to preserve the passed order
parser = self.make_parser(ctx)
opts, _, param_order = parser.parse_args(args=list(args))
type(self)._option_order = []
for param in param_order:
# Type check
option = opts[param.name]
if isinstance(option, list):
type(self)._option_order.append((param, option.pop(0)))
# return "normal" parse results
return super().parse_args(ctx, args)