Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add subcommand tab completion for data plugin commands #1035

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions aiida/cmdline/baseclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# For further information on the license, see the LICENSE.txt file #
# For further information please visit http://www.aiida.net #
###########################################################################

import click


class VerdiCommand(object):
Expand Down Expand Up @@ -137,6 +137,25 @@ def run(self, *args):
function_to_call(*args[1:])

def complete(self, subargs_idx, subargs):
"""
Relay tab-completion requests to subcommand complete functions.

Click-implemented commands (filtered by isinstance check) pose a special case.
Here we use aiida.cmdline.commands.click_subcmd_complete to create a completion function
on the fly, leveraging click functionality. Since VerdiCommands with click subcommands must actually rout to the root
(aiida.cmdline.commands.verdi) for each subcommand, we have only access to the root as a python object through routed_subcommands,
Not to the active subcommand.
This means we must follow the chain up through cli parameters. Since this function is only passed subargs, we need to use sys.argv.

sys.argv in this case follows the signature of ``verdi completion``::

verdi completion <num_subargs> verdi [<subgroup>, ...] <subcommand> ([<subcmd param>, ...] | '')

Therefore we can disregard the first four and the last element of argv and loop through sys.argv[4:len(sys.argv)-1], for each
determine wether it's a subcommand, if so, retrieve it's python object until we find the last subcommand, for which we then build
the completion function. We can safely disregard the last one, because it is never a (complete) subcommand and it will be passed
to the completion function we build in any case (as part of ``subargs``).
"""
if subargs_idx == 0:
print "\n".join(self.routed_subcommands.keys())
elif subargs_idx >= 1:
Expand All @@ -146,8 +165,16 @@ def complete(self, subargs_idx, subargs):
first_subarg = ''

try:
complete_function = self.routed_subcommands[
first_subarg]().complete
cmd_or_class = self.routed_subcommands[first_subarg]
if isinstance(cmd_or_class, (click.Command, click.MultiCommand)):
import sys
from aiida.cmdline.commands import click_subcmd_complete

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment explaining why you take out the first 3 args and what is the content of sys.argv[4:len(sys.argv)-1]?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requested info is in the docstring of the function now.

for i in sys.argv[4:len(sys.argv)-1]:
cmd_or_class = cmd_or_class.commands.get(i, cmd_or_class)
complete_function = click_subcmd_complete(cmd_or_class)
else:
complete_function = cmd_or_class().complete
except KeyError:
print ""
return
Expand Down
35 changes: 35 additions & 0 deletions aiida/cmdline/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,46 @@
from click_plugins import with_plugins


def click_subcmd_complete(cmd_group):
"""Create a subcommand completion function for a click command group."""
def complete(subargs_idx, subargs):
"""List valid subcommands for a command group that start with the last subarg."""
if subargs_idx >= 1:
return None
incomplete = subargs[-1]
print '\n'.join(cmd_group.list_commands({'parameters': [incomplete]}))
return complete


@click.group()
@click.option('--profile', '-p')
def verdi(profile):
"""
Toplevel command for click-implemented verdi commands.

Might eventually replace ``execute_from_cmdline``, however, there is no way to directly call this command from the commandline
currently. Instead, it is used for subcommand routing of commands written in click, see aiida/cmdline/commands/work.py for an
example. In short it exists, so the name by which the subcommand is called ('verdi something something') matches it's command
group hierarchy (group ``verdi``, subgroup ``something``, command ``something``).

"""
pass


@verdi.command()
@click.argument('completion_args', nargs=-1, type=click.UNPROCESSED)
def completion(completion_args):
"""
Completion command alias for click-implemented verdi commands.

Due to the roundabout process by which click-implemented verdi commands are called, pressing <Tab><Tab> on one of them tries to
call aiida.cmdline.commands.verdi with subcommand completion. Therefore in order to enable the same behaviour as on older commands,
such a subcommand with the same signature must exist and must run the same code with the same arguments.
"""
from aiida.cmdline.verdilib import Completion
Completion().run(*completion_args)


@verdi.group()
def export():
pass
Expand Down