Revision dfee2caf kamaki/cli/__init__.py

b/kamaki/cli/__init__.py
39 39
#Monkey-patch everything for gevent early on
40 40
gevent.monkey.patch_all()
41 41

  
42
import inspect
43 42
import logging
44
import sys
45 43

  
44
from inspect import getargspec
46 45
from argparse import ArgumentParser
47 46
from base64 import b64encode
48 47
from os.path import abspath, basename, exists
49
from sys import exit, stdout, stderr
48
from sys import exit, stdout, stderr, argv
50 49

  
51 50
try:
52 51
    from collections import OrderedDict
53 52
except ImportError:
54 53
    from ordereddict import OrderedDict
55 54

  
56
from colors import magenta, red, yellow, bold
57

  
58
from kamaki import clients
55
#from kamaki import clients
59 56
from .errors import CLIError
60
from .config import Config
57
from .config import Config #TO BE REMOVED
58
from .utils import magenta, red, yellow, CommandTree
59
from argument import _arguments, parse_known_args
61 60

  
62
_commands = OrderedDict()
61
_commands = CommandTree()
63 62

  
64
GROUPS = {}
63
GROUPS={}
65 64
CLI_LOCATIONS = ['kamaki.cli.commands', 'kamaki.commands', 'kamaki.cli', 'kamaki', '']
66 65

  
67
def command(group=None, name=None, syntax=None):
68
    """Class decorator that registers a class as a CLI command."""
66
def command():
67
    """Class decorator that registers a class as a CLI command"""
69 68

  
70 69
    def decorator(cls):
71
        grp, sep, cmd = cls.__name__.partition('_')
72
        if not sep:
73
            grp, cmd = None, cls.__name__
74

  
75
        #cls.api = api
76
        cls.group = group or grp
77
        cls.name = name or cmd
78

  
79
        short_description, sep, long_description = cls.__doc__.partition('\n')
80
        cls.description = short_description
81
        cls.long_description = long_description or short_description
82

  
83
        cls.syntax = syntax
84
        if cls.syntax is None:
85
            # Generate a syntax string based on main's arguments
86
            spec = inspect.getargspec(cls.main.im_func)
87
            args = spec.args[1:]
88
            n = len(args) - len(spec.defaults or ())
89
            required = ' '.join('<%s>' % x.replace('____', '[:').replace('___', ':').replace('__',']').replace('_', ' ') for x in args[:n])
90
            optional = ' '.join('[%s]' % x.replace('____', '[:').replace('___', ':').replace('__', ']').replace('_', ' ') for x in args[n:])
91
            cls.syntax = ' '.join(x for x in [required, optional] if x)
92
            if spec.varargs:
93
                cls.syntax += ' <%s ...>' % spec.varargs
94

  
95
        if cls.group not in _commands:
96
            _commands[cls.group] = OrderedDict()
97
        _commands[cls.group][cls.name] = cls
70
        """Any class with name of the form cmd1_cmd2_cmd3_... is accepted"""
71
        cls.description, sep, cls.long_description = cls.__doc__.partition('\n')
72

  
73
        # Generate a syntax string based on main's arguments
74
        spec = getargspec(cls.main.im_func)
75
        args = spec.args[1:]
76
        n = len(args) - len(spec.defaults or ())
77
        required = ' '.join('<%s>' % x.replace('____', '[:').replace('___', ':').replace('__',']').\
78
            replace('_', ' ') for x in args[:n])
79
        optional = ' '.join('[%s]' % x.replace('____', '[:').replace('___', ':').replace('__', ']').\
80
            replace('_', ' ') for x in args[n:])
81
        cls.syntax = ' '.join(x for x in [required, optional] if x)
82
        if spec.varargs:
83
            cls.syntax += ' <%s ...>' % spec.varargs
84

  
85
        _commands.add(cls.__name__, cls)
98 86
        return cls
99 87
    return decorator
100 88

  
......
103 91
    Each CLI can set more than one api descriptions"""
104 92
    GROUPS[api] = description
105 93

  
106
def main():
107

  
108
    def print_groups():
109
        print('\nGroups:')
110
        for group in _commands:
111
            description = GROUPS.get(group, '')
112
            print(' ', group.ljust(12), description)
113

  
114
    def print_commands(group):
115
        description = GROUPS.get(group, '')
116
        if description:
117
            print('\n' + description)
118

  
119
        print('\nCommands:')
120
        for name, cls in _commands[group].items():
121
            print(' ', name.ljust(14), cls.description)
122

  
123
    def manage_logging_handlers(args):
124
        """This is mostly to handle logging for clients package"""
125

  
126
        def add_handler(name, level, prefix=''):
127
            h = logging.StreamHandler()
128
            fmt = logging.Formatter(prefix + '%(message)s')
129
            h.setFormatter(fmt)
130
            logger = logging.getLogger(name)
131
            logger.addHandler(h)
132
            logger.setLevel(level)
133

  
134
        if args.silent:
135
            add_handler('', logging.CRITICAL)
136
        elif args.debug:
137
            add_handler('requests', logging.INFO, prefix='* ')
138
            add_handler('clients.send', logging.DEBUG, prefix='> ')
139
            add_handler('clients.recv', logging.DEBUG, prefix='< ')
140
        elif args.verbose:
141
            add_handler('requests', logging.INFO, prefix='* ')
142
            add_handler('clients.send', logging.INFO, prefix='> ')
143
            add_handler('clients.recv', logging.INFO, prefix='< ')
144
        elif args.include:
145
            add_handler('clients.recv', logging.INFO)
146
        else:
147
            add_handler('', logging.WARNING)
148

  
149
    def load_groups(config):
150
        """load groups and import CLIs and Modules"""
151
        loaded_modules = {}
152
        for api in config.apis():
153
            api_cli = config.get(api, 'cli')
154
            if None == api_cli or len(api_cli)==0:
155
                print('Warnig: No Command Line Interface "%s" given for API "%s"'%(api_cli, api))
156
                print('\t(cli option in config file)')
157
                continue
158
            if not loaded_modules.has_key(api_cli):
159
                loaded_modules[api_cli] = False
160
                for location in CLI_LOCATIONS:
161
                    location += api_cli if location == '' else '.%s'%api_cli
162
                    try:
163
                        __import__(location)
164
                        loaded_modules[api_cli] = True
165
                        break
166
                    except ImportError:
167
                        pass
168
                if not loaded_modules[api_cli]:
169
                    print('Warning: failed to load Command Line Interface "%s" for API "%s"'%(api_cli, api))
170
                    print('\t(No suitable cli in known paths)')
171
                    continue
172
            if not GROUPS.has_key(api):
173
                GROUPS[api] = 'No description (interface: %s)'%api_cli
174

  
175
    def init_parser(exe):
176
        parser = ArgumentParser(add_help=False)
177
        parser.prog = '%s <group> <command>' % exe
178
        parser.add_argument('-h', '--help', dest='help', action='store_true',
179
                          default=False,
180
                          help="Show this help message and exit")
181
        parser.add_argument('--config', dest='config', metavar='PATH',
182
                          help="Specify the path to the configuration file")
183
        parser.add_argument('-d', '--debug', dest='debug', action='store_true',
184
                          default=False,
185
                          help="Include debug output")
186
        parser.add_argument('-i', '--include', dest='include', action='store_true',
187
                          default=False,
188
                          help="Include protocol headers in the output")
189
        parser.add_argument('-s', '--silent', dest='silent', action='store_true',
190
                          default=False,
191
                          help="Silent mode, don't output anything")
192
        parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
193
                          default=False,
194
                          help="Make the operation more talkative")
195
        parser.add_argument('-V', '--version', dest='version', action='store_true',
196
                          default=False,
197
                          help="Show version number and quit")
198
        parser.add_argument('-o', dest='options', action='append',
199
                          default=[], metavar="KEY=VAL",
200
                          help="Override a config value")
201
        return parser
202

  
203
    def find_term_in_args(arg_list, term_list):
204
        """find an arg_list term in term_list. All other terms up to found
205
        term are rearanged at the end of arg_list, preserving relative order
206
        """
207
        arg_tail = []
208
        while len(arg_list) > 0:
209
            group = arg_list.pop(0)
210
            if group not in term_list:
211
                arg_tail.append(group)
212
            else:
213
                arg_list += arg_tail
214
                return group
215
        return None
216

  
217
    """Main Code"""
218
    exe = basename(sys.argv[0])
219
    parser = init_parser(exe)
220
    args, argv = parser.parse_known_args()
221

  
222
    #print version
223
    if args.version:
224
        import kamaki
225
        print("kamaki %s" % kamaki.__version__)
226
        exit(0)
227

  
228
    config = Config(args.config) if args.config else Config()
229

  
230
    #load config options from command line
231
    for option in args.options:
232
        keypath, sep, val = option.partition('=')
233
        if not sep:
234
            print("Invalid option '%s'" % option)
235
            exit(1)
236
        section, sep, key = keypath.partition('.')
237
        if not sep:
238
            print("Invalid option '%s'" % option)
239
            exit(1)
240
        config.override(section.strip(), key.strip(), val.strip())
241

  
242
    load_groups(config)
243
    group = find_term_in_args(argv, _commands)
244
    if not group:
245
        parser.print_help()
246
        print_groups()
247
        exit(0)
248

  
249
    parser.prog = '%s %s <command>' % (exe, group)
250
    command = find_term_in_args(argv, _commands[group])
251

  
252
    if not command:
253
        parser.print_help()
254
        print_commands(group)
255
        exit(0)
256

  
257
    cmd = _commands[group][command]()
258

  
259
    parser.prog = '%s %s %s' % (exe, group, command)
260
    if cmd.syntax:
261
        parser.prog += '  %s' % cmd.syntax
262
    parser.description = cmd.description
263
    parser.epilog = ''
264
    if hasattr(cmd, 'update_parser'):
265
        cmd.update_parser(parser)
266

  
267
    #check other args
268
    args, argv = parser.parse_known_args()
269
    if group != argv[0]:
270
        errmsg = red('Invalid command group '+argv[0])
271
        print(errmsg, file=stderr)
272
        exit(1)
273
    if command != argv[1]:
274
        errmsg = red('Invalid command "%s" in group "%s"'%(argv[1], argv[0]))
275
        print(errmsg, file=stderr)
276
        exit(1)
277

  
278
    if args.help:
279
        parser.print_help()
280
        exit(0)
281

  
282
    manage_logging_handlers(args)
283
    cmd.args = args
284
    cmd.config = config
94
def _init_parser(exe):
95
    parser = ArgumentParser(add_help=True)
96
    parser.prog='%s <cmd_group> [<cmd_subbroup> ...] <cmd>'%exe
97
    for name, argument in _arguments.items():
98
        argument.update_parser(parser, name)
99
    return parser
100

  
101
def _print_error_message(cli_err):
102
    errmsg = '%s'%unicode(cli_err) +' (%s)'%cli_err.status if cli_err.status else ' '
103
    if cli_err.importance == 1:
104
        errmsg = magenta(errmsg)
105
    elif cli_err.importance == 2:
106
        errmsg = yellow(errmsg)
107
    elif cli_err.importance > 2:
108
        errmsg = red(errmsg)
109
    stdout.write(errmsg)
110
    if cli_err.details is not None and len(cli_err.details) > 0:
111
        print(': %s'%cli_err.details)
112
    else:
113
        print
114

  
115
def one_command():
285 116
    try:
286
        ret = cmd.main(*argv[2:])
287
        exit(ret)
288
    except TypeError as e:
289
        if e.args and e.args[0].startswith('main()'):
290
            parser.print_help()
291
            exit(1)
292
        else:
293
            raise
117
        exe = basename(argv[0])
118
        parser = _init_parser(exe)
119
        parsed, unparsed = parse_known_args(parser)
120
        if _arguments['version'].value:
121
            exit(0)
294 122
    except CLIError as err:
295
        errmsg = 'CLI Error '
296
        errmsg += '(%s): '%err.status if err.status else ': '
297
        errmsg += unicode(err.message) if err.message else ''
298
        if err.importance == 1:
299
            errmsg = yellow(errmsg)
300
        elif err.importance == 2:
301
            errmsg = magenta(errmsg)
302
        elif err.importance > 2:
303
            errmsg = red(errmsg)
304
        print(errmsg, file=stderr)
123
        _print_error_message(err)
305 124
        exit(1)
306

  
307
if __name__ == '__main__':
308
    main()

Also available in: Unified diff