f10685e5f9037e1588f098a3d6ac816921e7145f
[sdc/sdc-distribution-client.git] /
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
4 #
5 #       http://www.apache.org/licenses/LICENSE-2.0
6 #
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
11 #  under the License.
12
13 """Discover and lookup command plugins.
14 """
15
16 import inspect
17 import logging
18
19 import pkg_resources
20
21
22 LOG = logging.getLogger(__name__)
23
24
25 class EntryPointWrapper(object):
26     """Wrap up a command class already imported to make it look like a plugin.
27     """
28
29     def __init__(self, name, command_class):
30         self.name = name
31         self.command_class = command_class
32
33     def load(self, require=False):
34         return self.command_class
35
36
37 class CommandManager(object):
38     """Discovers commands and handles lookup based on argv data.
39
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.
45     """
46     def __init__(self, namespace, convert_underscores=True):
47         self.commands = {}
48         self.namespace = namespace
49         self.convert_underscores = convert_underscores
50         self._load_commands()
51
52     def _load_commands(self):
53         # NOTE(jamielennox): kept for compatibility.
54         self.load_commands(self.namespace)
55
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
62                         else ep.name)
63             self.commands[cmd_name] = ep
64         return
65
66     def __iter__(self):
67         return iter(self.commands.items())
68
69     def add_command(self, name, command_class):
70         self.commands[name] = EntryPointWrapper(name, command_class)
71
72     def find_command(self, argv):
73         """Given an argument list, find a command and
74         return the processor and any remaining arguments.
75         """
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()
84                 else:
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)
90                     else:
91                         cmd_factory = cmd_ep.load()
92                 return (cmd_factory, name, search_args)
93         else:
94             raise ValueError('Unknown command %r' %
95                              (argv,))
96
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
100         """
101         for i, arg in enumerate(argv):
102             if arg.startswith('-'):
103                 return i
104         return len(argv)