Complete UI/cli interface refactoring, minor bugs
authorStavros Sachtouris <saxtouri@admin.grnet.gr>
Wed, 14 Nov 2012 11:38:03 +0000 (13:38 +0200)
committerStavros Sachtouris <saxtouri@admin.grnet.gr>
Wed, 14 Nov 2012 11:38:03 +0000 (13:38 +0200)
12 files changed:
kamaki/cli/__init__.py
kamaki/cli/argument.py
kamaki/cli/command_shell.py
kamaki/cli/commands/astakos_cli.py
kamaki/cli/commands/config_cli.py
kamaki/cli/commands/cyclades_cli.py
kamaki/cli/commands/history_cli.py
kamaki/cli/commands/image_cli.py
kamaki/cli/commands/pithos_cli.py
kamaki/cli/commands/test_cli.py
kamaki/cli/new.py [deleted file]
setup.py

index 3d48f93..51d1f68 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python
-# Copyright 2011-2012 GRNET S.A. All rights reserved.
+# Copyright 2012-2013 GRNET S.A. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or
 # without modification, are permitted provided that the following
 # The views and conclusions contained in the software and
 # documentation are those of the authors and should not be
 # interpreted as representing official policies, either expressed
-# or implied, of GRNET S.A.
-
-#from __future__ import print_function
+# or implied, of GRNET S.A.command
 
 import logging
-
-from inspect import getargspec
-from argparse import ArgumentParser, ArgumentError
+from sys import argv, exit, stdout
 from os.path import basename
-from sys import exit, stdout, argv
+from inspect import getargspec
 
-from kamaki.cli.errors import CLIError, CLICmdSpecError
-from kamaki.cli.utils import magenta, red, yellow, print_dict, print_list,\
-    remove_colors
-from kamaki.cli.command_tree import CommandTree
-from kamaki.cli.argument import _arguments, parse_known_args
+from kamaki.cli.argument import _arguments, parse_known_args, init_parser,\
+    update_arguments
 from kamaki.cli.history import History
+from kamaki.cli.utils import print_dict, print_list, red, magenta, yellow
+from kamaki.cli.errors import CLIError
 
-cmd_spec_locations = [
-    'kamaki.cli.commands',
-    'kamaki.commands',
-    'kamaki.cli',
-    'kamaki',
-    '']
-_commands = CommandTree(name='kamaki',
-    description='A command line tool for poking clouds')
-
-# If empty, all commands are loaded, if not empty, only commands in this list
-# e.g. [store, lele, list, lolo] is good to load store_list but not list_store
-# First arg should always refer to a group
-candidate_command_terms = []
-allow_no_commands = False
-allow_all_commands = False
-allow_subclass_signatures = False
-
-
-def _allow_class_in_cmd_tree(cls):
-    global allow_all_commands
-    if allow_all_commands:
-        return True
-    global allow_no_commands
-    if allow_no_commands:
-        return False
-
-    term_list = cls.__name__.split('_')
-    global candidate_command_terms
-    index = 0
-    for term in candidate_command_terms:
-        try:
-            index += 1 if term_list[index] == term else 0
-        except IndexError:  # Whole term list matched!
-            return True
-    if allow_subclass_signatures:
-        if index == len(candidate_command_terms) and len(term_list) > index:
-            try:  # is subterm already in _commands?
-                _commands.get_command('_'.join(term_list[:index + 1]))
-            except KeyError:  # No, so it must be placed there
-                return True
-        return False
-
-    return True if index == len(term_list) else False
-
-
-def command():
-    """Class decorator that registers a class as a CLI command"""
-
-    def decorator(cls):
-        """Any class with name of the form cmd1_cmd2_cmd3_... is accepted"""
+_help = False
+_debug = False
+_verbose = False
+_colors = False
 
-        if not _allow_class_in_cmd_tree(cls):
-            return cls
-
-        cls.description, sep, cls.long_description\
-            = cls.__doc__.partition('\n')
 
-        # Generate a syntax string based on main's arguments
+def _construct_command_syntax(cls):
         spec = getargspec(cls.main.im_func)
         args = spec.args[1:]
         n = len(args) - len(spec.defaults or ())
@@ -123,110 +66,102 @@ def command():
         if spec.varargs:
             cls.syntax += ' <%s ...>' % spec.varargs
 
-        # store each term, one by one, first
-        _commands.add_command(cls.__name__, cls.description, cls)
 
-        return cls
-    return decorator
+def _get_cmd_tree_from_spec(spec, cmd_tree_list):
+    for tree in cmd_tree_list:
+        if tree.name == spec:
+            return tree
+    return None
 
 
-def _update_parser(parser, arguments):
-    for name, argument in arguments.items():
-        try:
-            argument.update_parser(parser, name)
-        except ArgumentError:
-            pass
+_best_match = []
 
 
-def _init_parser(exe):
-    parser = ArgumentParser(add_help=False)
-    parser.prog = '%s <cmd_group> [<cmd_subbroup> ...] <cmd>' % exe
-    _update_parser(parser, _arguments)
-    return parser
+def _num_of_matching_terms(basic_list, attack_list):
+    if not attack_list:
+        return 1
 
+    matching_terms = 0
+    for i, term in enumerate(basic_list):
+        try:
+            if term != attack_list[i]:
+                break
+        except IndexError:
+            break
+        matching_terms += 1
+    return matching_terms
 
-def _print_error_message(cli_err):
-    errmsg = '%s' % cli_err
-    if cli_err.importance == 1:
-        errmsg = magenta(errmsg)
-    elif cli_err.importance == 2:
-        errmsg = yellow(errmsg)
-    elif cli_err.importance > 2:
-        errmsg = red(errmsg)
-    stdout.write(errmsg)
-    print_list(cli_err.details)
 
+def _update_best_match(name_terms, prefix=[]):
+    if prefix:
+        pref_list = prefix if isinstance(prefix, list) else prefix.split('_')
+    else:
+        pref_list = []
 
-def get_command_group(unparsed):
-    groups = _arguments['config'].get_groups()
-    for grp_candidate in unparsed:
-        if grp_candidate in groups:
-            unparsed.remove(grp_candidate)
-            return grp_candidate
-    return None
+    num_of_matching_terms = _num_of_matching_terms(name_terms, pref_list)
+    global _best_match
 
+    if num_of_matching_terms and len(_best_match) <= num_of_matching_terms:
+        if len(_best_match) < num_of_matching_terms:
+            _best_match = name_terms[:num_of_matching_terms]
+        return True
+    return False
+
+
+def command(cmd_tree, prefix='', descedants_depth=1):
+    """Load a class as a command
+        spec_cmd0_cmd1 will be command spec cmd0
+        @cmd_tree is initialized in cmd_spec file and is the structure
+            where commands are loaded. Var name should be _commands
+        @param prefix if given, load only commands prefixed with prefix,
+        @param descedants_depth is the depth of the tree descedants of the
+            prefix command. It is used ONLY if prefix and if prefix is not
+            a terminal command
+    """
+
+    def wrap(cls):
+        cls_name = cls.__name__
+
+        if not cmd_tree:
+            if _debug:
+                print('Warning: command %s found but not loaded' % cls_name)
+            return cls
 
-def load_command(group, unparsed, reload_package=False):
-    global candidate_command_terms
-    candidate_command_terms = [group] + unparsed
-    load_group_package(group, reload_package)
+        name_terms = cls_name.split('_')
+        if not _update_best_match(name_terms, prefix):
+            return None
 
-    #From all possible parsed commands, chose the first match in user string
-    final_cmd = _commands.get_command(group)
-    for term in unparsed:
-        cmd = final_cmd.get_subcmd(term)
-        if cmd is not None:
-            final_cmd = cmd
-            unparsed.remove(cmd.name)
-    return final_cmd
+        global _best_match
+        max_len = len(_best_match) + descedants_depth
+        if len(name_terms) > max_len:
+            partial = '_'.join(name_terms[:max_len])
+            if not cmd_tree.has_command(partial):  # add partial path
+                cmd_tree.add_command(partial)
+            return None
 
+        cls.description, sep, cls.long_description\
+        = cls.__doc__.partition('\n')
+        _construct_command_syntax(cls)
 
-def shallow_load():
-    """Load only group names and descriptions"""
-    global allow_no_commands
-    allow_no_commands = True  # load only descriptions
-    for grp in _arguments['config'].get_groups():
-        load_group_package(grp)
-    allow_no_commands = False
+        cmd_tree.add_command(cls_name, cls.description, cls)
+        return cls
+    return wrap
 
 
-def load_group_package(group, reload_package=False):
-    spec_pkg = _arguments['config'].value.get(group, 'cli')
-    if spec_pkg is None:
-        return None
-    for location in cmd_spec_locations:
-        location += spec_pkg if location == '' else ('.' + spec_pkg)
-        try:
-            package = __import__(location, fromlist=['API_DESCRIPTION'])
-        except ImportError:
-            continue
-        if reload_package:
-            reload(package)
-        for grp, descr in package.API_DESCRIPTION.items():
-            _commands.add_command(grp, descr)
-        return package
-    raise CLICmdSpecError(details='Cmd Spec Package %s load failed' % spec_pkg)
-
-
-def print_commands(prefix=None, full_depth=False):
-    cmd_list = _commands.get_groups() if prefix is None\
-        else _commands.get_subcommands(prefix)
-    cmds = {}
-    for subcmd in cmd_list:
-        if subcmd.sublen() > 0:
-            sublen_str = '( %s more terms ... )' % subcmd.sublen()
-            cmds[subcmd.name] = [subcmd.help, sublen_str]\
-                if subcmd.has_description else sublen_str
-        else:
-            cmds[subcmd.name] = subcmd.help
-    if len(cmds) > 0:
-        print('\nOptions:')
-        print_dict(cmds, ident=12)
-    if full_depth:
-        _commands.pretty_print()
+def get_cmd_terms():
+    global command
+    return [term for term in command.func_defaults[0]\
+        if not term.startswith('-')]
+
+cmd_spec_locations = [
+    'kamaki.cli.commands',
+    'kamaki.commands',
+    'kamaki.cli',
+    'kamaki',
+    '']
 
 
-def setup_logging(silent=False, debug=False, verbose=False, include=False):
+def _setup_logging(silent=False, debug=False, verbose=False, include=False):
     """handle logging for clients package"""
 
     def add_handler(name, level, prefix=''):
@@ -253,108 +188,197 @@ def setup_logging(silent=False, debug=False, verbose=False, include=False):
         add_handler('', logging.WARNING)
 
 
+def _init_session(arguments):
+    global _help
+    _help = arguments['help'].value
+    global _debug
+    _debug = arguments['debug'].value
+    global _verbose
+    _verbose = arguments['verbose'].value
+    global _colors
+    _colors = arguments['config'].get('global', 'colors')
+    _silent = arguments['silent'].value
+    _include = arguments['include'].value
+    _setup_logging(_silent, _debug, _verbose, _include)
+
+
+def get_command_group(unparsed, arguments):
+    groups = arguments['config'].get_groups()
+    for term in unparsed:
+        if term.startswith('-'):
+            continue
+        if term in groups:
+            unparsed.remove(term)
+            return term
+        return None
+    return None
+
+
+def _load_spec_module(spec, arguments, module):
+    spec_name = arguments['config'].get(spec, 'cli')
+    if spec_name is None:
+        return None
+    pkg = None
+    for location in cmd_spec_locations:
+        location += spec_name if location == '' else '.%s' % spec_name
+        try:
+            pkg = __import__(location, fromlist=[module])
+            return pkg
+        except ImportError:
+            continue
+    return pkg
+
+
+def _groups_help(arguments):
+    global _debug
+    descriptions = {}
+    for spec in arguments['config'].get_groups():
+        pkg = _load_spec_module(spec, arguments, '_commands')
+        if pkg:
+            cmds = None
+            try:
+                cmds = [
+                    cmd for cmd in getattr(pkg, '_commands')\
+                    if arguments['config'].get(cmd.name, 'cli')
+                ]
+            except AttributeError:
+                if _debug:
+                    print('Warning: No description for %s' % spec)
+            try:
+                for cmd in cmds:
+                    descriptions[cmd.name] = cmd.description
+            except TypeError:
+                if _debug:
+                    print('Warning: no cmd specs in module %s' % spec)
+        elif _debug:
+            print('Warning: Loading of %s cmd spec failed' % spec)
+    print('\nOptions:\n - - - -')
+    print_dict(descriptions)
+
+
+def _print_subcommands_help(cmd):
+    printout = {}
+    for subcmd in cmd.get_subcommands():
+        printout[subcmd.path.replace('_', ' ')] = subcmd.description
+    if printout:
+        print('\nOptions:\n - - - -')
+        print_dict(printout)
+
+
+def _update_parser_help(parser, cmd):
+    global _best_match
+    parser.prog = parser.prog.split('<')[0]
+    parser.prog += ' '.join(_best_match)
+
+    if cmd.is_command:
+        cls = cmd.get_class()
+        parser.prog += ' ' + cls.syntax
+        arguments = cls().arguments
+        update_arguments(parser, arguments)
+    else:
+        parser.prog += ' <...>'
+    if cmd.has_description:
+        parser.description = cmd.help
+
+
+def _print_error_message(cli_err):
+    errmsg = '%s' % cli_err
+    if cli_err.importance == 1:
+        errmsg = magenta(errmsg)
+    elif cli_err.importance == 2:
+        errmsg = yellow(errmsg)
+    elif cli_err.importance > 2:
+        errmsg = red(errmsg)
+    stdout.write(errmsg)
+    print_list(cli_err.details)
+
+
+def _get_best_match_from_cmd_tree(cmd_tree, unparsed):
+    matched = [term for term in unparsed if not term.startswith('-')]
+    while matched:
+        try:
+            return cmd_tree.get_command('_'.join(matched))
+        except KeyError:
+            matched = matched[:-1]
+    return None
+
+
 def _exec_cmd(instance, cmd_args, help_method):
     try:
         return instance.main(*cmd_args)
     except TypeError as err:
         if err.args and err.args[0].startswith('main()'):
             print(magenta('Syntax error'))
-            if instance.get_argument('verbose'):
+            if _debug:
+                raise err
+            if _verbose:
                 print(unicode(err))
             help_method()
         else:
             raise
     except CLIError as err:
-        if instance.get_argument('debug'):
-            raise
+        if _debug:
+            raise err
         _print_error_message(err)
     return 1
 
 
-def one_command():
-    _debug = False
-    _help = False
-    _verbose = False
-    try:
-        exe = basename(argv[0])
-        parser = _init_parser(exe)
-        parsed, unparsed = parse_known_args(parser, _arguments)
-        _colors = _arguments['config'].get('global', 'colors')
-        if _colors != 'on':
-            remove_colors()
-        _history = History(_arguments['config'].get('history', 'file'))
-        _history.add(' '.join([exe] + argv[1:]))
-        _debug = _arguments['debug'].value
-        _help = _arguments['help'].value
-        _verbose = _arguments['verbose'].value
-        if _arguments['version'].value:
-            exit(0)
-
-        group = get_command_group(unparsed)
-        if group is None:
-            parser.print_help()
-            shallow_load()
-            print_commands(full_depth=_debug)
-            exit(0)
-
-        cmd = load_command(group, unparsed)
-        # Find the most specific subcommand
-        for term in list(unparsed):
-            if cmd.is_command:
-                break
-            if cmd.contains(term):
-                cmd = cmd.get_subcmd(term)
-                unparsed.remove(term)
-
-        if _help or not cmd.is_command:
-            if cmd.has_description:
-                parser.description = cmd.help
-            else:
-                try:
-                    parser.description =\
-                        _commands.get_closest_ancestor_command(cmd.path).help
-                except KeyError:
-                    parser.description = ' '
-            parser.prog = '%s %s ' % (exe, cmd.path.replace('_', ' '))
-            if cmd.is_command:
-                cli = cmd.get_class()
-                parser.prog += cli.syntax
-                _update_parser(parser, cli().arguments)
-            else:
-                parser.prog += '[...]'
-            parser.print_help()
-
-            # load one more level just to see what is missing
-            global allow_subclass_signatures
-            allow_subclass_signatures = True
-            load_command(group, cmd.path.split('_')[1:], reload_package=True)
-
-            print_commands(cmd.path, full_depth=_debug)
-            exit(0)
-
-        setup_logging(silent=_arguments['silent'].value,
-            debug=_debug,
-            verbose=_verbose,
-            include=_arguments['include'].value)
-        cli = cmd.get_class()
-        executable = cli(_arguments)
-        _update_parser(parser, executable.arguments)
-        parser.prog = '%s %s %s'\
-            % (exe, cmd.path.replace('_', ' '), cli.syntax)
-        parsed, new_unparsed = parse_known_args(parser, _arguments)
-        unparsed = [term for term in unparsed if term in new_unparsed]
-        ret = _exec_cmd(executable, unparsed, parser.print_help)
-        exit(ret)
-    except Exception as err:
-        if _debug:
-            from traceback import print_stack
-            print_stack()
-            raise
-        err = err if isinstance(err, CLIError)\
-            else CLIError('Unexpected Error (%s): %s' % (type(err), err))
-        _print_error_message(err)
+def set_command_param(param, value):
+    if param == 'prefix':
+        pos = 0
+    elif param == 'descedants_depth':
+        pos = 1
+    else:
+        return
+    global command
+    def_params = list(command.func_defaults)
+    def_params[pos] = value
+    command.func_defaults = tuple(def_params)
+
+
+def one_cmd(parser, unparsed, arguments):
+    group = get_command_group(list(unparsed), arguments)
+    if not group:
+        parser.print_help()
+        _groups_help(arguments)
+        exit(0)
+
+    set_command_param(
+        'prefix',
+        [term for term in unparsed if not term.startswith('-')]
+    )
+    global _best_match
+    _best_match = []
+
+    spec_module = _load_spec_module(group, arguments, '_commands')
+
+    cmd_tree = _get_cmd_tree_from_spec(group, spec_module._commands)
+
+    if _best_match:
+        cmd = cmd_tree.get_command('_'.join(_best_match))
+    else:
+        cmd = _get_best_match_from_cmd_tree(cmd_tree, unparsed)
+        _best_match = cmd.path.split('_')
+    if cmd is None:
+        if _debug or _verbose:
+            print('Unexpected error: failed to load command')
         exit(1)
 
+    _update_parser_help(parser, cmd)
+
+    if _help or not cmd.is_command:
+        parser.print_help()
+        _print_subcommands_help(cmd)
+        exit(0)
+
+    cls = cmd.get_class()
+    executable = cls(arguments)
+    parsed, unparsed = parse_known_args(parser, executable.arguments)
+    for term in _best_match:
+        unparsed.remove(term)
+    _exec_cmd(executable, unparsed, parser.print_help)
+
+
 from command_shell import _fix_arguments, Shell
 
 
@@ -367,26 +391,45 @@ def _start_shell():
     return shell
 
 
-def run_shell():
+def run_shell(arguments):
     _fix_arguments()
     shell = _start_shell()
     _config = _arguments['config']
-    _config.value = None
-    for grp in _config.get_groups():
-        global allow_all_commands
-        allow_all_commands = True
-        load_group_package(grp)
-    setup_logging(silent=_arguments['silent'].value,
-        debug=_arguments['debug'].value,
-        verbose=_arguments['verbose'].value,
-        include=_arguments['include'].value)
-    shell.cmd_tree = _commands
+    from kamaki.cli.command_tree import CommandTree
+    shell.cmd_tree = CommandTree(
+        'kamaki', 'A command line tool for poking clouds')
+    for spec in [spec for spec in _config.get_groups()\
+            if arguments['config'].get(spec, 'cli')]:
+        try:
+            spec_module = _load_spec_module(spec, arguments, '_commands')
+            spec_commands = getattr(spec_module, '_commands')
+        except AttributeError:
+            if _debug:
+                print('Warning: No valid description for %s' % spec)
+            continue
+        for spec_tree in spec_commands:
+            if spec_tree.name == spec:
+                shell.cmd_tree.add_tree(spec_tree)
+                break
     shell.run()
 
 
 def main():
+    exe = basename(argv[0])
+    parser = init_parser(exe, _arguments)
+    parsed, unparsed = parse_known_args(parser, _arguments)
+
+    if _arguments['version'].value:
+        exit(0)
 
-    if len(argv) <= 1:
-        run_shell()
+    _init_session(_arguments)
+
+    if unparsed:
+        _history = History(_arguments['config'].get('history', 'file'))
+        _history.add(' '.join([exe] + argv[1:]))
+        one_cmd(parser, unparsed, _arguments)
+    elif _help:
+        parser.print_help()
+        _groups_help(_arguments)
     else:
-        one_command()
+        run_shell(_arguments)
index 76bf589..3e6c0d8 100644 (file)
@@ -118,6 +118,7 @@ class Argument(object):
 class ConfigArgument(Argument):
     @property
     def value(self):
+        super(self.__class__, self).value
         return super(self.__class__, self).value
 
     @value.setter
index af97f55..d45e98d 100644 (file)
 
 from cmd import Cmd
 from os import popen
-from argparse import ArgumentParser
-from kamaki.cli import _update_parser, _exec_cmd
-from .argument import _arguments
-from .utils import print_dict
 from sys import stdout
-from .history import History
+from argparse import ArgumentParser
+
+from kamaki.cli import _exec_cmd
+from kamaki.cli.argument import _arguments, update_arguments
+from kamaki.cli.utils import print_dict
+from kamaki.cli.history import History
 
 
 def _fix_arguments():
@@ -127,7 +128,8 @@ class Shell(Cmd):
                 instance = cls(dict(_arguments))
                 cmd_parser.prog = '%s %s' % (cmd_parser.prog.replace('_', ' '),
                     cls.syntax)
-                _update_parser(cmd_parser, instance.arguments)
+                update_arguments(cmd_parser, instance.arguments)
+                #_update_parser(cmd_parser, instance.arguments)
                 if '-h' in cmd_args or '--help' in cmd_args:
                     cmd_parser.print_help()
                     return
index 077adca..d48649e 100644 (file)
@@ -38,8 +38,8 @@ from kamaki.cli.errors import raiseCLIError
 from kamaki.cli.commands import _command_init
 from kamaki.cli.command_tree import CommandTree
 
-astakos_commands = CommandTree('astakos', 'Astakos API commands')
-_commands = [astakos_commands]
+astakos_cmds = CommandTree('astakos', 'Astakos API commands')
+_commands = [astakos_cmds]
 
 
 class _astakos_init(_command_init):
@@ -53,12 +53,12 @@ class _astakos_init(_command_init):
         self.client = AstakosClient(base_url=base_url, token=token)
 
 
-@command(_commands)
+@command(astakos_cmds)
 class astakos_authenticate(_astakos_init):
     """Authenticate a user"""
 
     def main(self, custom_token=None):
-        super(astakos_authenticate, self).main()
+        super(self.__class__, self).main()
         try:
             reply = self.client.authenticate(custom_token)
         except ClientError as err:
index d1f1a7d..e76bda9 100644 (file)
 from kamaki.cli import command
 from kamaki.cli.argument import FlagArgument
 from kamaki.cli.commands import _command_init
+from kamaki.cli.command_tree import CommandTree
 
+config_cmds = CommandTree('config', 'Configuration commands')
+_command = [config_cmds]
 
-API_DESCRIPTION = {'config': 'Configuration commands'}
 
-
-@command()
+@command(config_cmds)
 class config_list(_command_init):
     """List configuration options"""
 
@@ -55,7 +56,7 @@ class config_list(_command_init):
                 print('%s.%s = %s' % (section, key, val))
 
 
-@command()
+@command(config_cmds)
 class config_get(_command_init):
     """Show a configuration option"""
 
@@ -67,7 +68,7 @@ class config_get(_command_init):
             print(value)
 
 
-@command()
+@command(config_cmds)
 class config_set(_command_init):
     """Set a configuration option"""
 
@@ -78,7 +79,7 @@ class config_set(_command_init):
         self.config.write()
 
 
-@command()
+@command(config_cmds)
 class config_delete(_command_init):
     """Delete a configuration option (and use the default value)"""
 
index 4bccf95..376fd24 100644 (file)
@@ -32,6 +32,7 @@
 # or implied, of GRNET S.A.
 
 from kamaki.cli import command
+from kamaki.cli.command_tree import CommandTree
 from kamaki.cli.utils import print_dict, print_items, print_list, bold
 from kamaki.cli.errors import CLIError, raiseCLIError, CLISyntaxError
 from kamaki.clients.cyclades import CycladesClient, ClientError
@@ -43,10 +44,15 @@ from base64 import b64encode
 from os.path import exists
 
 
-API_DESCRIPTION = {'server': 'Compute/Cyclades API server commands',
-    'flavor': 'Compute/Cyclades API flavor commands',
-    'image': 'Compute/Cyclades or Glance API image commands',
-    'network': 'Compute/Cyclades API network commands'}
+server_cmds = CommandTree('server',
+    'Compute/Cyclades API server commands')
+flavor_cmds = CommandTree('flavor',
+    'Compute/Cyclades API flavor commands')
+image_cmds = CommandTree('image',
+    'Compute/Cyclades or Glance API image commands')
+network_cmds = CommandTree('network',
+    'Compute/Cyclades API network commands')
+_commands = [server_cmds, flavor_cmds, image_cmds, network_cmds]
 
 
 class _init_cyclades(_command_init):
@@ -58,7 +64,7 @@ class _init_cyclades(_command_init):
         self.client = CycladesClient(base_url=base_url, token=token)
 
 
-@command()
+@command(server_cmds)
 class server_list(_init_cyclades):
     """List servers"""
 
@@ -85,7 +91,7 @@ class server_list(_init_cyclades):
             raiseCLIError(err)
 
 
-@command()
+@command(server_cmds)
 class server_info(_init_cyclades):
     """Get server details"""
 
@@ -145,7 +151,7 @@ class PersonalityArgument(ValueArgument):
             pass
 
 
-@command()
+@command(server_cmds)
 class server_create(_init_cyclades):
     """Create a server"""
 
@@ -179,7 +185,7 @@ class server_create(_init_cyclades):
         print_dict(reply)
 
 
-@command()
+@command(server_cmds)
 class server_rename(_init_cyclades):
     """Update a server's name"""
 
@@ -195,7 +201,7 @@ class server_rename(_init_cyclades):
                 importance=1)
 
 
-@command()
+@command(server_cmds)
 class server_delete(_init_cyclades):
     """Delete a server"""
 
@@ -210,7 +216,7 @@ class server_delete(_init_cyclades):
                 importance=1)
 
 
-@command()
+@command(server_cmds)
 class server_reboot(_init_cyclades):
     """Reboot a server"""
 
@@ -230,7 +236,7 @@ class server_reboot(_init_cyclades):
                 importance=1)
 
 
-@command()
+@command(server_cmds)
 class server_start(_init_cyclades):
     """Start a server"""
 
@@ -245,7 +251,7 @@ class server_start(_init_cyclades):
                 importance=1)
 
 
-@command()
+@command(server_cmds)
 class server_shutdown(_init_cyclades):
     """Shutdown a server"""
 
@@ -260,7 +266,7 @@ class server_shutdown(_init_cyclades):
                 importance=1)
 
 
-@command()
+@command(server_cmds)
 class server_console(_init_cyclades):
     """Get a VNC console"""
 
@@ -276,7 +282,7 @@ class server_console(_init_cyclades):
         print_dict(reply)
 
 
-@command()
+@command(server_cmds)
 class server_firewall(_init_cyclades):
     """Set the server's firewall profile"""
 
@@ -291,7 +297,7 @@ class server_firewall(_init_cyclades):
                 importance=1)
 
 
-@command()
+@command(server_cmds)
 class server_addr(_init_cyclades):
     """List a server's nic address"""
 
@@ -307,7 +313,7 @@ class server_addr(_init_cyclades):
         print_list(reply)
 
 
-@command()
+@command(server_cmds)
 class server_meta(_init_cyclades):
     """Get a server's metadata"""
 
@@ -323,7 +329,7 @@ class server_meta(_init_cyclades):
         print_dict(reply)
 
 
-@command()
+@command(server_cmds)
 class server_addmeta(_init_cyclades):
     """Add server metadata"""
 
@@ -340,7 +346,7 @@ class server_addmeta(_init_cyclades):
         print_dict(reply)
 
 
-@command()
+@command(server_cmds)
 class server_setmeta(_init_cyclades):
     """Update server's metadata"""
 
@@ -358,7 +364,7 @@ class server_setmeta(_init_cyclades):
         print_dict(reply)
 
 
-@command()
+@command(server_cmds)
 class server_delmeta(_init_cyclades):
     """Delete server metadata"""
 
@@ -373,7 +379,7 @@ class server_delmeta(_init_cyclades):
                 importance=1)
 
 
-@command()
+@command(server_cmds)
 class server_stats(_init_cyclades):
     """Get server statistics"""
 
@@ -389,7 +395,7 @@ class server_stats(_init_cyclades):
         print_dict(reply, exclude=('serverRef',))
 
 
-@command()
+@command(server_cmds)
 class server_wait(_init_cyclades):
     """Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]"""
 
@@ -421,7 +427,7 @@ class server_wait(_init_cyclades):
             print('\nTime out')
 
 
-@command()
+@command(flavor_cmds)
 class flavor_list(_init_cyclades):
     """List flavors"""
 
@@ -438,7 +444,7 @@ class flavor_list(_init_cyclades):
         print_items(flavors)
 
 
-@command()
+@command(flavor_cmds)
 class flavor_info(_init_cyclades):
     """Get flavor details"""
 
@@ -454,7 +460,7 @@ class flavor_info(_init_cyclades):
         print_dict(flavor)
 
 
-@command()
+@command(network_cmds)
 class network_list(_init_cyclades):
     """List networks"""
 
@@ -479,7 +485,7 @@ class network_list(_init_cyclades):
         self.print_networks(networks)
 
 
-@command()
+@command(network_cmds)
 class network_create(_init_cyclades):
     """Create a network"""
 
@@ -507,7 +513,7 @@ class network_create(_init_cyclades):
         print_dict(reply)
 
 
-@command()
+@command(network_cmds)
 class network_info(_init_cyclades):
     """Get network details"""
 
@@ -527,7 +533,7 @@ class network_info(_init_cyclades):
         network_info.print_network(network)
 
 
-@command()
+@command(network_cmds)
 class network_rename(_init_cyclades):
     """Update network name"""
 
@@ -539,7 +545,7 @@ class network_rename(_init_cyclades):
             raiseCLIError(err)
 
 
-@command()
+@command(network_cmds)
 class network_delete(_init_cyclades):
     """Delete a network"""
 
@@ -551,7 +557,7 @@ class network_delete(_init_cyclades):
             raiseCLIError(err)
 
 
-@command()
+@command(network_cmds)
 class network_connect(_init_cyclades):
     """Connect a server to a network"""
 
@@ -563,7 +569,7 @@ class network_connect(_init_cyclades):
             raiseCLIError(err)
 
 
-@command()
+@command(network_cmds)
 class network_disconnect(_init_cyclades):
     """Disconnect a nic that connects a server to a network"""
 
index aee76b4..5111452 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
+from kamaki.cli.command_tree import CommandTree
 from kamaki.cli.argument import IntArgument, ValueArgument
 from kamaki.cli.history import History
 from kamaki.cli import command
 from kamaki.cli.commands import _command_init
 
 
-API_DESCRIPTION = {'history': 'Command history'}
+history_cmds = CommandTree('history', 'Command history')
 
 
 class _init_history(_command_init):
@@ -47,7 +48,7 @@ class _init_history(_command_init):
         self.history = History(self.config.get('history', 'file'))
 
 
-@command()
+@command(history_cmds)
 class history(_init_history):
     """Show history [containing terms...]"""
 
@@ -65,7 +66,7 @@ class history(_init_history):
         print(''.join(ret))
 
 
-@command()
+@command(history_cmds)
 class history_clean(_init_history):
     """Clean up history"""
 
index f84555d..9c23e1e 100644 (file)
@@ -32,6 +32,7 @@
 # or implied, of GRNET S.A.command
 
 from kamaki.cli import command
+from kamaki.cli.command_tree import CommandTree
 from kamaki.cli.errors import raiseCLIError
 from kamaki.cli.utils import print_dict, print_items, bold
 from kamaki.clients.image import ImageClient, ClientError
@@ -41,7 +42,9 @@ from kamaki.cli.commands.cyclades_cli import _init_cyclades
 from kamaki.cli.commands import _command_init
 
 
-API_DESCRIPTION = {'image': 'Compute/Cyclades or Glance API image commands'}
+image_cmds = CommandTree('image',
+    'Compute/Cyclades or Glance API image commands')
+_commands = [image_cmds]
 
 
 class _init_image(_command_init):
@@ -58,7 +61,7 @@ class _init_image(_command_init):
             raiseCLIError(err)
 
 
-@command()
+@command(image_cmds)
 class image_public(_init_image):
     """List public images"""
 
@@ -102,7 +105,7 @@ class image_public(_init_image):
         print_items(images, title=('name',))
 
 
-@command()
+@command(image_cmds)
 class image_meta(_init_image):
     """Get image metadata"""
 
@@ -115,7 +118,7 @@ class image_meta(_init_image):
         print_dict(image)
 
 
-@command()
+@command(image_cmds)
 class image_test(_init_image):
     """Test stuff"""
 
@@ -128,7 +131,7 @@ class image_test(_init_image):
         print('OK...')
 
 
-@command()
+@command(image_cmds)
 class image_register(_init_image):
     """Register an image"""
 
@@ -184,7 +187,7 @@ class image_register(_init_image):
             raiseCLIError(err)
 
 
-@command()
+@command(image_cmds)
 class image_members(_init_image):
     """Get image members"""
 
@@ -198,7 +201,7 @@ class image_members(_init_image):
             print(member['member_id'])
 
 
-@command()
+@command(image_cmds)
 class image_shared(_init_image):
     """List shared images"""
 
@@ -212,7 +215,7 @@ class image_shared(_init_image):
             print(image['image_id'])
 
 
-@command()
+@command(image_cmds)
 class image_addmember(_init_image):
     """Add a member to an image"""
 
@@ -224,7 +227,7 @@ class image_addmember(_init_image):
             raiseCLIError(err)
 
 
-@command()
+@command(image_cmds)
 class image_delmember(_init_image):
     """Remove a member from an image"""
 
@@ -236,7 +239,7 @@ class image_delmember(_init_image):
             raiseCLIError(err)
 
 
-@command()
+@command(image_cmds)
 class image_setmembers(_init_image):
     """Set the members of an image"""
 
@@ -248,7 +251,7 @@ class image_setmembers(_init_image):
             raiseCLIError(err)
 
 
-@command()
+@command(image_cmds)
 class image_list(_init_cyclades):
     """List images"""
 
@@ -273,7 +276,7 @@ class image_list(_init_cyclades):
         self._print(images)
 
 
-@command()
+@command(image_cmds)
 class image_info(_init_cyclades):
     """Get image details"""
 
@@ -292,7 +295,7 @@ class image_info(_init_cyclades):
         self._print(image)
 
 
-@command()
+@command(image_cmds)
 class image_delete(_init_cyclades):
     """Delete image"""
 
@@ -304,7 +307,7 @@ class image_delete(_init_cyclades):
             raiseCLIError(err)
 
 
-@command()
+@command(image_cmds)
 class image_properties(_init_cyclades):
     """Get image properties"""
 
@@ -317,7 +320,7 @@ class image_properties(_init_cyclades):
         print_dict(reply)
 
 
-@command()
+@command(image_cmds)
 class image_addproperty(_init_cyclades):
     """Add an image property"""
 
@@ -330,7 +333,7 @@ class image_addproperty(_init_cyclades):
         print_dict(reply)
 
 
-@command()
+@command(image_cmds)
 class image_setproperty(_init_cyclades):
     """Update an image property"""
 
@@ -344,7 +347,7 @@ class image_setproperty(_init_cyclades):
         print_dict(reply)
 
 
-@command()
+@command(image_cmds)
 class image_delproperty(_init_cyclades):
     """Delete an image property"""
 
index 6131ee8..50b6581 100644 (file)
@@ -32,6 +32,7 @@
 # or implied, of GRNET S.A.command
 
 from kamaki.cli import command
+from kamaki.cli.command_tree import CommandTree
 from kamaki.cli.errors import CLIError, raiseCLIError
 from kamaki.cli.utils import format_size, print_dict, pretty_keys
 from kamaki.cli.argument import FlagArgument, ValueArgument, IntArgument
@@ -44,7 +45,8 @@ from time import localtime, strftime
 from datetime import datetime as dtm
 
 
-API_DESCRIPTION = dict(store='Pithos+ storage commands')
+pithos_cmds = CommandTree('store', 'Pithos+ storage commands')
+_commands = [pithos_cmds]
 
 
 # Argument functionality
@@ -244,7 +246,7 @@ class _store_container_command(_store_account_command):
         self.container = self.client.container
 
 
-@command()
+@command(pithos_cmds)
 class store_list(_store_container_command):
     """List containers, object trees or objects in a directory
     """
@@ -371,7 +373,7 @@ class store_list(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_mkdir(_store_container_command):
     """Create a directory"""
 
@@ -384,7 +386,7 @@ class store_mkdir(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_create(_store_container_command):
     """Create a container or a directory object"""
 
@@ -411,7 +413,7 @@ class store_create(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_copy(_store_container_command):
     """Copy an object"""
 
@@ -448,7 +450,7 @@ class store_copy(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_move(_store_container_command):
     """Copy an object"""
 
@@ -486,7 +488,7 @@ class store_move(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_append(_store_container_command):
     """Append local file to (existing) remote object"""
 
@@ -512,7 +514,7 @@ class store_append(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_truncate(_store_container_command):
     """Truncate remote file up to a size"""
 
@@ -525,7 +527,7 @@ class store_truncate(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_overwrite(_store_container_command):
     """Overwrite part (from start to end) of a remote file"""
 
@@ -553,7 +555,7 @@ class store_overwrite(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_manifest(_store_container_command):
     """Create a remote file with uploaded parts by manifestation"""
 
@@ -587,7 +589,7 @@ class store_manifest(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_upload(_store_container_command):
     """Upload a file"""
 
@@ -651,7 +653,7 @@ class store_upload(_store_container_command):
         print 'Upload completed'
 
 
-@command()
+@command(pithos_cmds)
 class store_cat(_store_container_command):
     """Print a file to console"""
 
@@ -687,7 +689,7 @@ class store_cat(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_download(_store_container_command):
     """Download a file"""
 
@@ -763,7 +765,7 @@ class store_download(_store_container_command):
         print
 
 
-@command()
+@command(pithos_cmds)
 class store_hashmap(_store_container_command):
     """Get the hashmap of an object"""
 
@@ -797,7 +799,7 @@ class store_hashmap(_store_container_command):
         print_dict(data)
 
 
-@command()
+@command(pithos_cmds)
 class store_delete(_store_container_command):
     """Delete a container [or an object]"""
 
@@ -827,7 +829,7 @@ class store_delete(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_purge(_store_container_command):
     """Purge a container"""
 
@@ -839,7 +841,7 @@ class store_purge(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_publish(_store_container_command):
     """Publish an object"""
 
@@ -852,7 +854,7 @@ class store_publish(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_unpublish(_store_container_command):
     """Unpublish an object"""
 
@@ -865,7 +867,7 @@ class store_unpublish(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_permissions(_store_container_command):
     """Get object read/write permissions """
 
@@ -879,7 +881,7 @@ class store_permissions(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_setpermissions(_store_container_command):
     """Set sharing permissions """
 
@@ -913,7 +915,7 @@ class store_setpermissions(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_delpermissions(_store_container_command):
     """Delete all sharing permissions"""
 
@@ -926,7 +928,7 @@ class store_delpermissions(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_info(_store_container_command):
     """Get information for account [, container [or object]]"""
 
@@ -951,7 +953,7 @@ class store_info(_store_container_command):
         print_dict(reply)
 
 
-@command()
+@command(pithos_cmds)
 class store_meta(_store_container_command):
     """Get custom meta-content for account [, container [or object]]"""
 
@@ -970,16 +972,16 @@ class store_meta(_store_container_command):
 
         detail = self.get_argument('detail')
         try:
+            until = self.get_argument('until')
             if self.container is None:
                 print(bold(self.client.account))
-                until = self.get_argument('until')
                 if detail:
                     reply = self.client.get_account_info(until=until)
                 else:
                     reply = self.client.get_account_meta(until=until)
                     reply = pretty_keys(reply, '-')
             elif self.path is None:
-                print(bold('%s: %s' % self.client.account, self.container))
+                print(bold('%s: %s' % (self.client.account, self.container)))
                 if detail:
                     reply = self.client.get_container_info(until=until)
                 else:
@@ -988,8 +990,8 @@ class store_meta(_store_container_command):
                     reply = {'container-meta': pretty_keys(cmeta, '-'),
                         'object-meta': pretty_keys(ometa, '-')}
             else:
-                print('%s: %s:%s'\
-                % (bold(self.client.account, self.container, self.path)))
+                print(bold('%s: %s:%s'\
+                    % (self.client.account, self.container, self.path)))
                 version = self.get_argument('object_version')
                 if detail:
                     reply = self.client.get_object_info(self.path,
@@ -1003,7 +1005,7 @@ class store_meta(_store_container_command):
         print_dict(reply)
 
 
-@command()
+@command(pithos_cmds)
 class store_setmeta(_store_container_command):
     """Set a new metadatum for account [, container [or object]]"""
 
@@ -1024,7 +1026,7 @@ class store_setmeta(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_delmeta(_store_container_command):
     """Delete an existing metadatum of account [, container [or object]]"""
 
@@ -1041,7 +1043,7 @@ class store_delmeta(_store_container_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_quota(_store_account_command):
     """Get  quota for account [or container]"""
 
@@ -1057,7 +1059,7 @@ class store_quota(_store_account_command):
         print_dict(reply)
 
 
-@command()
+@command(pithos_cmds)
 class store_setquota(_store_account_command):
     """Set new quota (in KB) for account [or container]"""
 
@@ -1073,7 +1075,7 @@ class store_setquota(_store_account_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_versioning(_store_account_command):
     """Get  versioning for account [or container ]"""
 
@@ -1089,7 +1091,7 @@ class store_versioning(_store_account_command):
         print_dict(reply)
 
 
-@command()
+@command(pithos_cmds)
 class store_setversioning(_store_account_command):
     """Set new versioning (auto, none) for account [or container]"""
 
@@ -1105,7 +1107,7 @@ class store_setversioning(_store_account_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_group(_store_account_command):
     """Get user groups details for account"""
 
@@ -1118,7 +1120,7 @@ class store_group(_store_account_command):
         print_dict(reply)
 
 
-@command()
+@command(pithos_cmds)
 class store_setgroup(_store_account_command):
     """Create/update a new user group on account"""
 
@@ -1130,7 +1132,7 @@ class store_setgroup(_store_account_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_delgroup(_store_account_command):
     """Delete a user group on an account"""
 
@@ -1142,7 +1144,7 @@ class store_delgroup(_store_account_command):
             raiseCLIError(err)
 
 
-@command()
+@command(pithos_cmds)
 class store_sharers(_store_account_command):
     """List the accounts that share objects with default account"""
 
@@ -1171,7 +1173,7 @@ class store_sharers(_store_account_command):
             print
 
 
-@command()
+@command(pithos_cmds)
 class store_versions(_store_container_command):
     """Get the version list of an object"""
 
index e7c252c..4e38b59 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.command
 
-from kamaki.cli.new import get_cmd_terms  # , command
+from kamaki.cli import get_cmd_terms, command
 from kamaki.cli.commands import _command_init
 from kamaki.cli.command_tree import CommandTree
 from kamaki.cli.argument import FlagArgument
 
-#API_DESCRIPTION = dict(test='Test sample')
-
-_commands = [
-    CommandTree('sample', 'Sample commands for developing your own'),
-    CommandTree('test', 'Test commands for testing clients')
-]
+sample_cmds = CommandTree(
+    'sample',
+    'Sample commands for developing your own')
+test_cmds = CommandTree(
+    'test',
+    'Test commands for testing clients')
+_commands = [sample_cmds, test_cmds]
 
 
 print('Command Terms: ', get_cmd_terms())
 
 
-def command(cmd_tree_list, prefix='', descedants_depth=1):
-    def wrap(cls):
-        cls_name = cls.__name__
-
-        cmd_tree = _commands[0] if cls_name.startswith('sample')\
-            else _commands[1]
-        if not cmd_tree:
-            return cls
-
-        cls.description, sep, cls.long_description\
-        = cls.__doc__.partition('\n')
-        from kamaki.cli.new import _construct_command_syntax
-        _construct_command_syntax(cls)
-
-        cmd_tree.add_command(cls_name, cls.description, cls)
-        return cls
-    return wrap
-
-
 class _test_init(_command_init):
 
     def main(self, *args, **kwargs):
@@ -76,7 +58,7 @@ class _test_init(_command_init):
             print('\t\tkwarg: %s: %s' % (k, v))
 
 
-@command(cmd_tree_list=_commands)
+@command(sample_cmds)
 class sample_cmd0(_test_init):
     """ test cmd
     This is the zero command test and this is the long description of it
@@ -86,7 +68,7 @@ class sample_cmd0(_test_init):
         super(self.__class__, self).main(mant)
 
 
-@command(cmd_tree_list=_commands)
+@command(sample_cmds)
 class sample_cmd_all(_test_init):
     """test cmd all"""
 
@@ -94,7 +76,7 @@ class sample_cmd_all(_test_init):
         super(self.__class__, self).main()
 
 
-@command(cmd_tree_list=_commands)
+@command(sample_cmds)
 class sample_cmd_some(_test_init):
     """test_cmd_some"""
 
@@ -102,7 +84,7 @@ class sample_cmd_some(_test_init):
         super(self.__class__, self).main(opt=opt)
 
 
-@command(cmd_tree_list=_commands)
+@command(test_cmds)
 class test_cmd0(_test_init):
     """ test cmd"""
 
@@ -110,7 +92,7 @@ class test_cmd0(_test_init):
         super(self.__class__, self).main(mant)
 
 
-@command(cmd_tree_list=_commands)
+@command(test_cmds)
 class test_cmd_all(_test_init):
     """test cmd all"""
 
@@ -122,7 +104,7 @@ class test_cmd_all(_test_init):
         super(self.__class__, self).main()
 
 
-@command(cmd_tree_list=_commands)
+@command(test_cmds)
 class test_cmdion(_test_init):
     """test_cmd_some"""
 
@@ -130,7 +112,7 @@ class test_cmdion(_test_init):
         super(self.__class__, self).main(opt=opt)
 
 
-@command(cmd_tree_list=_commands)
+@command(test_cmds)
 class test_cmd_cmdion_comedian(_test_init):
     """test_cmd_some"""
 
diff --git a/kamaki/cli/new.py b/kamaki/cli/new.py
deleted file mode 100644 (file)
index 21ad9dc..0000000
+++ /dev/null
@@ -1,444 +0,0 @@
-# Copyright 2012-2013 GRNET S.A. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or
-# without modification, are permitted provided that the following
-# conditions are met:
-#
-#   1. Redistributions of source code must retain the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer.
-#
-#   2. Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer in the documentation and/or other materials
-#      provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
-# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
-# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and
-# documentation are those of the authors and should not be
-# interpreted as representing official policies, either expressed
-# or implied, of GRNET S.A.command
-
-import logging
-from sys import argv, exit, stdout
-from os.path import basename
-from inspect import getargspec
-
-from kamaki.cli.argument import _arguments, parse_known_args, init_parser,\
-    update_arguments
-from kamaki.cli.history import History
-from kamaki.cli.utils import print_dict, print_list, red, magenta, yellow
-from kamaki.cli.errors import CLIError
-
-_help = False
-_debug = False
-_verbose = False
-_colors = False
-
-
-def _construct_command_syntax(cls):
-        spec = getargspec(cls.main.im_func)
-        args = spec.args[1:]
-        n = len(args) - len(spec.defaults or ())
-        required = ' '.join('<%s>' % x\
-            .replace('____', '[:')\
-            .replace('___', ':')\
-            .replace('__', ']').\
-            replace('_', ' ') for x in args[:n])
-        optional = ' '.join('[%s]' % x\
-            .replace('____', '[:')\
-            .replace('___', ':')\
-            .replace('__', ']').\
-            replace('_', ' ') for x in args[n:])
-        cls.syntax = ' '.join(x for x in [required, optional] if x)
-        if spec.varargs:
-            cls.syntax += ' <%s ...>' % spec.varargs
-
-
-def _get_cmd_tree_from_spec(spec, cmd_tree_list):
-    for tree in cmd_tree_list:
-        if tree.name == spec:
-            return tree
-    return None
-
-
-_best_match = []
-
-
-def _num_of_matching_terms(basic_list, attack_list):
-    if not attack_list:
-        return 1
-
-    matching_terms = 0
-    for i, term in enumerate(basic_list):
-        try:
-            if term != attack_list[i]:
-                break
-        except IndexError:
-            break
-        matching_terms += 1
-    return matching_terms
-
-
-def _update_best_match(name_terms, prefix=[]):
-    if prefix:
-        pref_list = prefix if isinstance(prefix, list) else prefix.split('_')
-    else:
-        pref_list = []
-
-    num_of_matching_terms = _num_of_matching_terms(name_terms, pref_list)
-    global _best_match
-
-    if num_of_matching_terms and len(_best_match) <= num_of_matching_terms:
-        if len(_best_match) < num_of_matching_terms:
-            _best_match = name_terms[:num_of_matching_terms]
-        return True
-    return False
-
-
-def command(cmd_tree_list, prefix='', descedants_depth=1):
-    """Load a class as a command
-        spec_cmd0_cmd1 will be command spec cmd0
-        @cmd_tree_list is initialized in cmd_spec file and is the structure
-            where commands are loaded. Var name should be _commands
-        @param prefix if given, load only commands prefixed with prefix,
-        @param descedants_depth is the depth of the tree descedants of the
-            prefix command. It is used ONLY if prefix and if prefix is not
-            a terminal command
-    """
-
-    def wrap(cls):
-        cls_name = cls.__name__
-
-        spec = cls_name.split('_')[0]
-        cmd_tree = _get_cmd_tree_from_spec(spec, cmd_tree_list)
-        if not cmd_tree:
-            if _debug:
-                print('Warning: command %s found but not loaded' % cls_name)
-            return cls
-
-        name_terms = cls_name.split('_')
-        if not _update_best_match(name_terms, prefix):
-            return None
-
-        global _best_match
-        max_len = len(_best_match) + descedants_depth
-        if len(name_terms) > max_len:
-            partial = '_'.join(name_terms[:max_len])
-            if not cmd_tree.has_command(partial):  # add partial path
-                cmd_tree.add_command(partial)
-            return None
-
-        cls.description, sep, cls.long_description\
-        = cls.__doc__.partition('\n')
-        _construct_command_syntax(cls)
-
-        cmd_tree.add_command(cls_name, cls.description, cls)
-        return cls
-    return wrap
-
-
-def get_cmd_terms():
-    global command
-    return [term for term in command.func_defaults[0]\
-        if not term.startswith('-')]
-
-cmd_spec_locations = [
-    'kamaki.cli.commands',
-    'kamaki.commands',
-    'kamaki.cli',
-    'kamaki',
-    '']
-
-
-def _setup_logging(silent=False, debug=False, verbose=False, include=False):
-    """handle logging for clients package"""
-
-    def add_handler(name, level, prefix=''):
-        h = logging.StreamHandler()
-        fmt = logging.Formatter(prefix + '%(message)s')
-        h.setFormatter(fmt)
-        logger = logging.getLogger(name)
-        logger.addHandler(h)
-        logger.setLevel(level)
-
-    if silent:
-        add_handler('', logging.CRITICAL)
-    elif debug:
-        add_handler('requests', logging.INFO, prefix='* ')
-        add_handler('clients.send', logging.DEBUG, prefix='> ')
-        add_handler('clients.recv', logging.DEBUG, prefix='< ')
-    elif verbose:
-        add_handler('requests', logging.INFO, prefix='* ')
-        add_handler('clients.send', logging.INFO, prefix='> ')
-        add_handler('clients.recv', logging.INFO, prefix='< ')
-    elif include:
-        add_handler('clients.recv', logging.INFO)
-    else:
-        add_handler('', logging.WARNING)
-
-
-def _init_session(arguments):
-    global _help
-    _help = arguments['help'].value
-    global _debug
-    _debug = arguments['debug'].value
-    global _verbose
-    _verbose = arguments['verbose'].value
-    global _colors
-    _colors = arguments['config'].get('global', 'colors')
-    _silent = arguments['silent'].value
-    _include = arguments['include'].value
-    _setup_logging(_silent, _debug, _verbose, _include)
-
-
-def get_command_group(unparsed, arguments):
-    groups = arguments['config'].get_groups()
-    for term in unparsed:
-        if term.startswith('-'):
-            continue
-        if term in groups:
-            unparsed.remove(term)
-            return term
-        return None
-    return None
-
-
-def _load_spec_module(spec, arguments, module):
-    spec_name = arguments['config'].get(spec, 'cli')
-    if spec_name is None:
-        return None
-    pkg = None
-    for location in cmd_spec_locations:
-        location += spec_name if location == '' else '.%s' % spec_name
-        try:
-            print('\t-- %s' % location)
-            pkg = __import__(location, fromlist=[module])
-            print('SO COOL')
-            return pkg
-        except ImportError:
-            continue
-    return pkg
-
-
-def _groups_help(arguments):
-    global _debug
-    descriptions = {}
-    for spec in arguments['config'].get_groups():
-        pkg = _load_spec_module(spec, arguments, '_commands')
-        if pkg:
-            cmds = None
-            try:
-                cmds = [
-                    cmd for cmd in getattr(pkg, '_commands')\
-                    if arguments['config'].get(cmd.name, 'cli')
-                ]
-            except AttributeError:
-                if _debug:
-                    print('Warning: No description for %s' % spec)
-            try:
-                for cmd in cmds:
-                    descriptions[cmd.name] = cmd.description
-            except TypeError:
-                if _debug:
-                    print('Warning: no cmd specs in module %s' % spec)
-        elif _debug:
-            print('Warning: Loading of %s cmd spec failed' % spec)
-    print('\nOptions:\n - - - -')
-    print_dict(descriptions)
-
-
-def _print_subcommands_help(cmd):
-    printout = {}
-    for subcmd in cmd.get_subcommands():
-        printout[subcmd.path.replace('_', ' ')] = subcmd.description
-    if printout:
-        print('\nOptions:\n - - - -')
-        print_dict(printout)
-
-
-def _update_parser_help(parser, cmd):
-    global _best_match
-    parser.prog = parser.prog.split('<')[0]
-    parser.prog += ' '.join(_best_match)
-
-    if cmd.is_command:
-        cls = cmd.get_class()
-        parser.prog += ' ' + cls.syntax
-        arguments = cls().arguments
-        update_arguments(parser, arguments)
-    else:
-        parser.prog += ' <...>'
-    if cmd.has_description:
-        parser.description = cmd.help
-
-
-def _print_error_message(cli_err):
-    errmsg = '%s' % cli_err
-    if cli_err.importance == 1:
-        errmsg = magenta(errmsg)
-    elif cli_err.importance == 2:
-        errmsg = yellow(errmsg)
-    elif cli_err.importance > 2:
-        errmsg = red(errmsg)
-    stdout.write(errmsg)
-    print_list(cli_err.details)
-
-
-def _get_best_match_from_cmd_tree(cmd_tree, unparsed):
-    matched = [term for term in unparsed if not term.startswith('-')]
-    while matched:
-        try:
-            return cmd_tree.get_command('_'.join(matched))
-        except KeyError:
-            matched = matched[:-1]
-    return None
-
-
-def _exec_cmd(instance, cmd_args, help_method):
-    try:
-        return instance.main(*cmd_args)
-    except TypeError as err:
-        if err.args and err.args[0].startswith('main()'):
-            print(magenta('Syntax error'))
-            if _debug:
-                raise err
-            if _verbose:
-                print(unicode(err))
-            help_method()
-        else:
-            raise
-    except CLIError as err:
-        if _debug:
-            raise err
-        _print_error_message(err)
-    return 1
-
-
-def set_command_param(param, value):
-    if param == 'prefix':
-        pos = 0
-    elif param == 'descedants_depth':
-        pos = 1
-    else:
-        return
-    global command
-    def_params = list(command.func_defaults)
-    def_params[pos] = value
-    command.func_defaults = tuple(def_params)
-
-
-def one_cmd(parser, unparsed, arguments):
-    group = get_command_group(list(unparsed), arguments)
-    if not group:
-        parser.print_help()
-        _groups_help(arguments)
-        exit(0)
-
-    set_command_param(
-        'prefix',
-        [term for term in unparsed if not term.startswith('-')]
-    )
-    global _best_match
-    _best_match = []
-
-    spec_module = _load_spec_module(group, arguments, '_commands')
-
-    cmd_tree = _get_cmd_tree_from_spec(group, spec_module._commands)
-
-    if _best_match:
-        cmd = cmd_tree.get_command('_'.join(_best_match))
-    else:
-        cmd = _get_best_match_from_cmd_tree(cmd_tree, unparsed)
-        _best_match = cmd.path.split('_')
-    if cmd is None:
-        if _debug or _verbose:
-            print('Unexpected error: failed to load command')
-        exit(1)
-
-    _update_parser_help(parser, cmd)
-
-    if _help or not cmd.is_command:
-        parser.print_help()
-        _print_subcommands_help(cmd)
-        exit(0)
-
-    cls = cmd.get_class()
-    executable = cls(arguments)
-    parsed, unparsed = parse_known_args(parser, executable.arguments)
-    for term in _best_match:
-        unparsed.remove(term)
-    _exec_cmd(executable, unparsed, parser.print_help)
-
-
-from command_shell import _fix_arguments, Shell
-
-
-def _start_shell():
-    shell = Shell()
-    shell.set_prompt(basename(argv[0]))
-    from kamaki import __version__ as version
-    shell.greet(version)
-    shell.do_EOF = shell.do_exit
-    return shell
-
-
-def run_shell(arguments):
-    _fix_arguments()
-    shell = _start_shell()
-    _config = _arguments['config']
-    from kamaki.cli.command_tree import CommandTree
-    shell.cmd_tree = CommandTree(
-        'kamaki', 'A command line tool for poking clouds')
-    for spec in [spec for spec in _config.get_groups()\
-            if arguments['config'].get(spec, 'cli')]:
-        print('SPEC NAME: %s' % spec)
-        try:
-            print('\t1')
-            spec_module = _load_spec_module(spec, arguments, '_commands')
-            print('\t2 %s' % spec_module)
-            spec_commands = getattr(spec_module, '_commands')
-            print('\t3')
-        except AttributeError:
-            if _debug:
-                print('Warning: No valid description for %s' % spec)
-            continue
-        for spec_tree in spec_commands:
-            if spec_tree.name == spec:
-                shell.cmd_tree.add_tree(spec_tree)
-                break
-    shell.run()
-
-
-def main():
-    exe = basename(argv[0])
-    parser = init_parser(exe, _arguments)
-    parsed, unparsed = parse_known_args(parser, _arguments)
-
-    if _arguments['version'].value:
-        exit(0)
-
-    _init_session(_arguments)
-    print(_arguments['config'].value.sections())
-
-    if unparsed:
-        _history = History(_arguments['config'].get('history', 'file'))
-        _history.add(' '.join([exe] + argv[1:]))
-        one_cmd(parser, unparsed, _arguments)
-    elif _help:
-        parser.print_help()
-        _groups_help(_arguments)
-    else:
-        run_shell(_arguments)
index 89eb4ec..b7a9ef5 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -52,7 +52,7 @@ setup(
     packages=['kamaki', 'kamaki.clients', 'kamaki.clients.connection', 'kamaki.cli', 'kamaki.cli.commands'],
     include_package_data=True,
     entry_points={
-        'console_scripts': ['kamaki = kamaki.cli:main', 'newmaki = kamaki.cli.new:main']
+        'console_scripts': ['kamaki = kamaki.cli:main']
     },
     install_requires=required
 )