When there are two commands: one 'foo', that expects a positional argument, and a second command 'foo bar'. Trying to enter the command 'foo bar' will invoke the command 'foo' with position argument 'bar', and not the second command 'foo bar'. This is due to the fact that the find command method first checks for the first word as a command. By trying first to match all arguments this problem is solved. Closes-Bug: #1618673 Change-Id: I24ea39813ad30ec095ea18baad3a7b088fa8108f
93 lines
3.1 KiB
Python
93 lines
3.1 KiB
Python
"""Discover and lookup command plugins.
|
|
"""
|
|
|
|
import inspect
|
|
import logging
|
|
|
|
import pkg_resources
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class EntryPointWrapper(object):
|
|
"""Wrap up a command class already imported to make it look like a plugin.
|
|
"""
|
|
|
|
def __init__(self, name, command_class):
|
|
self.name = name
|
|
self.command_class = command_class
|
|
|
|
def load(self, require=False):
|
|
return self.command_class
|
|
|
|
|
|
class CommandManager(object):
|
|
"""Discovers commands and handles lookup based on argv data.
|
|
|
|
:param namespace: String containing the setuptools entrypoint namespace
|
|
for the plugins to be loaded. For example,
|
|
``'cliff.formatter.list'``.
|
|
:param convert_underscores: Whether cliff should convert underscores to
|
|
spaces in entry_point commands.
|
|
"""
|
|
def __init__(self, namespace, convert_underscores=True):
|
|
self.commands = {}
|
|
self.namespace = namespace
|
|
self.convert_underscores = convert_underscores
|
|
self._load_commands()
|
|
|
|
def _load_commands(self):
|
|
# NOTE(jamielennox): kept for compatability.
|
|
self.load_commands(self.namespace)
|
|
|
|
def load_commands(self, namespace):
|
|
"""Load all the commands from an entrypoint"""
|
|
for ep in pkg_resources.iter_entry_points(namespace):
|
|
LOG.debug('found command %r', ep.name)
|
|
cmd_name = (ep.name.replace('_', ' ')
|
|
if self.convert_underscores
|
|
else ep.name)
|
|
self.commands[cmd_name] = ep
|
|
return
|
|
|
|
def __iter__(self):
|
|
return iter(self.commands.items())
|
|
|
|
def add_command(self, name, command_class):
|
|
self.commands[name] = EntryPointWrapper(name, command_class)
|
|
|
|
def find_command(self, argv):
|
|
"""Given an argument list, find a command and
|
|
return the processor and any remaining arguments.
|
|
"""
|
|
start = self._get_last_possible_command_index(argv)
|
|
for i in range(start, 0, -1):
|
|
name = ' '.join(argv[:i])
|
|
search_args = argv[i:]
|
|
if name in self.commands:
|
|
cmd_ep = self.commands[name]
|
|
if hasattr(cmd_ep, 'resolve'):
|
|
cmd_factory = cmd_ep.resolve()
|
|
else:
|
|
# NOTE(dhellmann): Some fake classes don't take
|
|
# require as an argument. Yay?
|
|
arg_spec = inspect.getargspec(cmd_ep.load)
|
|
if 'require' in arg_spec[0]:
|
|
cmd_factory = cmd_ep.load(require=False)
|
|
else:
|
|
cmd_factory = cmd_ep.load()
|
|
return (cmd_factory, name, search_args)
|
|
else:
|
|
raise ValueError('Unknown command %r' %
|
|
(argv,))
|
|
|
|
def _get_last_possible_command_index(self, argv):
|
|
"""Returns the index after the last argument
|
|
in argv that can be a command word
|
|
"""
|
|
for i, arg in enumerate(argv):
|
|
if arg.startswith('-'):
|
|
return i
|
|
return len(argv)
|