[commands] Add Context.reinvoke and Command.root_parent

Context.reinvoke would be the new way to bypass checks and cooldowns.
However, with its addition comes a change in the invocation order of
checks, callbacks, and cooldowns. While previously cooldowns would
trigger after command argument parsing, the new behaviour parses
cooldowns before command argument parsing.

The implication of this change is that Context.args and Context.kwargs
will no longer be filled properly.
This commit is contained in:
Rapptz
2017-05-19 21:33:39 -04:00
parent c3e39cd722
commit b81fbb5a7f
2 changed files with 128 additions and 2 deletions

View File

@@ -282,6 +282,24 @@ class Command:
return ' '.join(reversed(entries))
@property
def root_parent(self):
"""Retrieves the root parent of this command.
If the command has no parents then it returns ``None``.
For example in commands ``?a b c test``, the root parent is
``a``.
"""
entries = []
command = self
while command.parent is not None:
command = command.parent
entries.append(command)
entries.append(None)
entries.reverse()
return entries[-1]
@property
def qualified_name(self):
"""Retrieves the fully qualified command name.
@@ -350,7 +368,6 @@ class Command:
if not view.eof:
raise TooManyArguments('Too many arguments passed to ' + self.qualified_name)
@asyncio.coroutine
def _verify_checks(self, ctx):
if not self.enabled:
@@ -407,7 +424,6 @@ class Command:
def prepare(self, ctx):
ctx.command = self
yield from self._verify_checks(ctx)
yield from self._parse_arguments(ctx)
if self._buckets.valid:
bucket = self._buckets.get_bucket(ctx)
@@ -415,6 +431,7 @@ class Command:
if retry_after:
raise CommandOnCooldown(bucket, retry_after)
yield from self._parse_arguments(ctx)
yield from self.call_before_hooks(ctx)
def reset_cooldown(self, ctx):
@@ -440,6 +457,24 @@ class Command:
injected = hooked_wrapped_callback(self, ctx, self.callback)
yield from injected(*ctx.args, **ctx.kwargs)
@asyncio.coroutine
def reinvoke(self, ctx, *, call_hooks=False):
ctx.command = self
yield from self._parse_arguments(ctx)
if call_hooks:
yield from self.call_before_hooks(ctx)
ctx.invoked_subcommand = None
try:
yield from self.callback(*ctx.args, **ctx.kwargs)
except:
ctx.command_failed = True
raise
finally:
if call_hooks:
yield from self.call_after_hooks(ctx)
def error(self, coro):
"""A decorator that registers a coroutine as a local error handler.
@@ -821,6 +856,44 @@ class Group(GroupMixin, Command):
view.previous = previous
yield from super().invoke(ctx)
@asyncio.coroutine
def reinvoke(self, ctx, *, call_hooks=False):
early_invoke = not self.invoke_without_command
if early_invoke:
ctx.command = self
yield from self._parse_arguments(ctx)
if call_hooks:
yield from self.call_before_hooks(ctx)
view = ctx.view
previous = view.index
view.skip_ws()
trigger = view.get_word()
if trigger:
ctx.subcommand_passed = trigger
ctx.invoked_subcommand = self.all_commands.get(trigger, None)
if early_invoke:
try:
yield from self.callback(*ctx.args, **ctx.kwargs)
except:
ctx.command_failed = True
raise
finally:
if call_hooks:
yield from self.call_after_hooks(ctx)
if trigger and ctx.invoked_subcommand:
ctx.invoked_with = trigger
yield from ctx.invoked_subcommand.reinvoke(ctx, call_hooks=call_hooks)
elif not early_invoke:
# undo the trigger parsing
view.index = previous
view.previous = previous
yield from super().reinvoke(ctx, call_hooks=call_hooks)
# Decorators
def command(name=None, cls=None, **attrs):