1 # Licensed under the Apache License, Version 2.0 (the "License"); you may
2 # not use this file except in compliance with the License. You may obtain
3 # a copy of the License at
5 # http://www.apache.org/licenses/LICENSE-2.0
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 # License for the specific language governing permissions and limitations
13 """Discover and lookup command plugins.
22 LOG = logging.getLogger(__name__)
25 class EntryPointWrapper(object):
26 """Wrap up a command class already imported to make it look like a plugin.
29 def __init__(self, name, command_class):
31 self.command_class = command_class
33 def load(self, require=False):
34 return self.command_class
37 class CommandManager(object):
38 """Discovers commands and handles lookup based on argv data.
40 :param namespace: String containing the setuptools entrypoint namespace
41 for the plugins to be loaded. For example,
42 ``'cliff.formatter.list'``.
43 :param convert_underscores: Whether cliff should convert underscores to
44 spaces in entry_point commands.
46 def __init__(self, namespace, convert_underscores=True):
48 self.namespace = namespace
49 self.convert_underscores = convert_underscores
52 def _load_commands(self):
53 # NOTE(jamielennox): kept for compatibility.
54 self.load_commands(self.namespace)
56 def load_commands(self, namespace):
57 """Load all the commands from an entrypoint"""
58 for ep in pkg_resources.iter_entry_points(namespace):
59 LOG.debug('found command %r', ep.name)
60 cmd_name = (ep.name.replace('_', ' ')
61 if self.convert_underscores
63 self.commands[cmd_name] = ep
67 return iter(self.commands.items())
69 def add_command(self, name, command_class):
70 self.commands[name] = EntryPointWrapper(name, command_class)
72 def find_command(self, argv):
73 """Given an argument list, find a command and
74 return the processor and any remaining arguments.
76 start = self._get_last_possible_command_index(argv)
77 for i in range(start, 0, -1):
78 name = ' '.join(argv[:i])
79 search_args = argv[i:]
80 if name in self.commands:
81 cmd_ep = self.commands[name]
82 if hasattr(cmd_ep, 'resolve'):
83 cmd_factory = cmd_ep.resolve()
85 # NOTE(dhellmann): Some fake classes don't take
86 # require as an argument. Yay?
87 arg_spec = inspect.getargspec(cmd_ep.load)
88 if 'require' in arg_spec[0]:
89 cmd_factory = cmd_ep.load(require=False)
91 cmd_factory = cmd_ep.load()
92 return (cmd_factory, name, search_args)
94 raise ValueError('Unknown command %r' %
97 def _get_last_possible_command_index(self, argv):
98 """Returns the index after the last argument
99 in argv that can be a command word
101 for i, arg in enumerate(argv):
102 if arg.startswith('-'):