Fix deprecated terms in documentation
[kamaki] / kamaki / cli / __init__.py
index 818456d..20785d5 100644 (file)
@@ -1,19 +1,17 @@
-#!/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
 # conditions are met:
 #
 #   1. Redistributions of source code must retain the above
-#        copyright notice, this list of conditions and the following
-#        disclaimer.
+#      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.
+#      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
 # 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
-
-import gevent.monkey
-#Monkey-patch everything for gevent early on
-gevent.monkey.patch_all()
+# or implied, of GRNET S.A.command
 
 import logging
-
+from sys import argv, exit, stdout
+from os.path import basename, exists
 from inspect import getargspec
-from argparse import ArgumentParser, ArgumentError
-from os.path import basename
-from sys import exit, stdout, stderr, argv
-
-try:
-       from collections import OrderedDict
-except ImportError:
-       from ordereddict import OrderedDict
-
-#from kamaki import clients
-from .errors import CLIError, CLISyntaxError, CLICmdIncompleteError, CLICmdSpecError
-from .utils import bold, magenta, red, yellow, print_list, print_dict, remove_colors
-from .command_tree import CommandTree
-from .argument import _arguments, parse_known_args
-from .history import History
+
+from kamaki.cli.argument import ArgumentParseManager
+from kamaki.cli.history import History
+from kamaki.cli.utils import print_dict, red, magenta, yellow
+from kamaki.cli.errors import CLIError
+from kamaki.cli import logger
+
+_help = False
+_debug = False
+_include = False
+_verbose = False
+_colors = False
+kloger = None
+filelog = None
+
+#  command auxiliary methods
+
+_best_match = []
+
+
+def _arg2syntax(arg):
+    return arg.replace(
+        '____', '[:').replace(
+            '___', ':').replace(
+                '__', ']').replace(
+                    '_', ' ')
+
+
+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>' % _arg2syntax(x) for x in args[:n]])
+        optional = ' '.join(['[%s]' % _arg2syntax(x) 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 _num_of_matching_terms(basic_list, attack_list):
+    if not attack_list:
+        return len(basic_list)
+
+    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 not prefix:
+        _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
+        e.g. spec_cmd0_cmd1 will be command spec cmd0
+
+        :param 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
+
+        :returns: the specified class object
+    """
+
+    def wrap(cls):
+        global kloger
+        cls_name = cls.__name__
+
+        if not cmd_tree:
+            if _debug:
+                kloger.warning('command %s found but not loaded' % cls_name)
+            return cls
+
+        name_terms = cls_name.split('_')
+        if not _update_best_match(name_terms, prefix):
+            if _debug:
+                kloger.warning('%s failed to update_best_match' % cls_name)
+            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)
+            if _debug:
+                kloger.warning('%s failed max_len test' % cls_name)
+            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
+
 
 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"""
-
-               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
-               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
-
-               #store each term, one by one, first
-               _commands.add_command(cls.__name__, cls.description, cls)
-
-               return cls
-       return decorator
-
-def _update_parser(parser, arguments):
-       for name, argument in arguments.items():
-               try:
-                       argument.update_parser(parser, name)
-               except ArgumentError:
-                       pass
-
-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 _print_error_message(cli_err):
-       errmsg = unicode(cli_err) + (' (%s)'%cli_err.status if cli_err.status else ' ')
-       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)
-       if cli_err.details is not None and len(cli_err.details) > 0:
-               print(': %s'%cli_err.details)
-       else:
-               print()
-
-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
-
-def load_command(group, unparsed, reload_package=False):
-       global candidate_command_terms
-       candidate_command_terms = [group] + unparsed
-       pkg = load_group_package(group, reload_package)
-
-       #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
-
-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
-
-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 subcmd_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 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 _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'):
-                               print(unicode(err))
-                       help_method()
-               else:
-                       raise
-       except CLIError as err:
-               if instance.get_argument('debug'):
-                       raise
-               _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)
-               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()
-
-                       #Shuuuut, we now have to 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 CLIError as err:
-               if _debug:
-                       raise
-               _print_error_message(err)
-               exit(1)
-
-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():
-       _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
-       shell.run()
+    'kamaki.cli.commands',
+    'kamaki.commands',
+    'kamaki.cli',
+    'kamaki',
+    '']
+
+
+#  Generic init auxiliary functions
+
+
+def _setup_logging(silent=False, debug=False, verbose=False, include=False):
+    """handle logging for clients package"""
+
+    if silent:
+        logger.add_stream_logger(__name__, logging.CRITICAL)
+        return
+
+    sfmt, rfmt = '> %(message)s', '< %(message)s'
+    if debug:
+        print('Logging location: %s' % logger.get_log_filename())
+        logger.add_stream_logger('kamaki.clients.send', logging.DEBUG, sfmt)
+        logger.add_stream_logger('kamaki.clients.recv', logging.DEBUG, rfmt)
+        logger.add_stream_logger(__name__, logging.DEBUG)
+    elif verbose:
+        logger.add_stream_logger('kamaki.clients.send', logging.INFO, sfmt)
+        logger.add_stream_logger('kamaki.clients.recv', logging.INFO, rfmt)
+        logger.add_stream_logger(__name__, logging.INFO)
+    if include:
+        logger.add_stream_logger('kamaki.clients.send', logging.INFO, sfmt)
+        logger.add_stream_logger('kamaki.clients.recv', logging.INFO, rfmt)
+    logger.add_stream_logger(__name__, logging.WARNING)
+    global kloger
+    kloger = logger.get_logger(__name__)
+
+
+def _check_config_version(cnf):
+    guess = cnf.guess_version()
+    if exists(cnf.path) and guess < 0.9:
+        print('Config file format version >= 9.0 is required')
+        print('Configuration file: %s' % cnf.path)
+        print('Attempting to fix this:')
+        print('Calculating changes while preserving information')
+        lost_terms = cnf.rescue_old_file()
+        print('... DONE')
+        if lost_terms:
+            print 'The following information will NOT be preserved:'
+            print '\t', '\n\t'.join(lost_terms)
+        print('Kamaki is ready to convert the config file')
+        stdout.write('Create (overwrite) file %s ? [y/N] ' % cnf.path)
+        from sys import stdin
+        reply = stdin.readline()
+        if reply in ('Y\n', 'y\n'):
+            cnf.write()
+            print('... DONE')
+        else:
+            print('... ABORTING')
+            raise CLIError(
+                'Invalid format for config file %s' % cnf.path,
+                importance=3, details=[
+                    'Please, update config file',
+                    'For automatic conversion, rerun and say Y'])
+
+
+def _init_session(arguments, is_non_API=False):
+    """
+    :returns: (AuthCachedClient, str) authenticator and cloud name
+    """
+    global _help
+    _help = arguments['help'].value
+    global _debug
+    _debug = arguments['debug'].value
+    global _include
+    _include = arguments['include'].value
+    global _verbose
+    _verbose = arguments['verbose'].value
+    _cnf = arguments['config']
+
+    if _help or is_non_API:
+        return None, None
+
+    _check_config_version(_cnf.value)
+
+    global _colors
+    _colors = _cnf.value.get_global('colors')
+    if not (stdout.isatty() and _colors == 'on'):
+        from kamaki.cli.utils import remove_colors
+        remove_colors()
+    _silent = arguments['silent'].value
+    _setup_logging(_silent, _debug, _verbose, _include)
+
+    cloud = arguments['cloud'].value or _cnf.value.get(
+        'global', 'default_cloud')
+    if not cloud:
+        num_of_clouds = len(_cnf.value.keys('cloud'))
+        if num_of_clouds == 1:
+            cloud = _cnf.value.keys('cloud')[0]
+        elif num_of_clouds > 1:
+            raise CLIError(
+                'Found %s clouds but none of them is set as default' % (
+                    num_of_clouds),
+                importance=2, details=[
+                    'Please, choose one of the following cloud names:',
+                    ', '.join(_cnf.value.keys('cloud')),
+                    'To see all cloud settings:',
+                    '  kamaki config get cloud.<cloud name>',
+                    'To set a default cloud:',
+                    '  kamaki config set default_cloud <cloud name>',
+                    'To pick a cloud for the current session, use --cloud:',
+                    '  kamaki --cloud=<cloud name> ...'])
+    if not cloud in _cnf.value.keys('cloud'):
+        raise CLIError(
+            'No cloud%s is configured' % ((' "%s"' % cloud) if cloud else ''),
+            importance=3, details=[
+                'To configure a new cloud "%s", find and set the' % (
+                    cloud or '<cloud name>'),
+                'single authentication URL and token:',
+                '  kamaki config set cloud.%s.url <URL>' % (
+                    cloud or '<cloud name>'),
+                '  kamaki config set cloud.%s.token <t0k3n>' % (
+                    cloud or '<cloud name>')])
+    auth_args = dict()
+    for term in ('url', 'token'):
+        try:
+            auth_args[term] = _cnf.get_cloud(cloud, term)
+        except KeyError:
+            auth_args[term] = ''
+        if not auth_args[term]:
+            raise CLIError(
+                'No authentication %s provided for cloud "%s"' % (term, cloud),
+                importance=3, details=[
+                    'Set a %s for cloud %s:' % (term, cloud),
+                    '  kamaki config set cloud.%s.%s <%s>' % (
+                        cloud, term, term)])
+
+    from kamaki.clients.astakos import AstakosClient as AuthCachedClient
+    try:
+        return AuthCachedClient(auth_args['url'], auth_args['token']), cloud
+    except AssertionError as ae:
+        kloger.warning('WARNING: Failed to load authenticator [%s]' % ae)
+        return None, cloud
+
+
+def _load_spec_module(spec, arguments, module):
+    if not spec:
+        return None
+    pkg = None
+    for location in cmd_spec_locations:
+        location += spec if location == '' else '.%s' % spec
+        try:
+            pkg = __import__(location, fromlist=[module])
+            return pkg
+        except ImportError as ie:
+            continue
+    if not pkg:
+        kloger.debug('Loading cmd grp %s failed: %s' % (spec, ie))
+    return pkg
+
+
+def _groups_help(arguments):
+    global _debug
+    global kloger
+    descriptions = {}
+    for cmd_group, spec in arguments['config'].get_cli_specs():
+        pkg = _load_spec_module(spec, arguments, '_commands')
+        if pkg:
+            cmds = getattr(pkg, '_commands')
+            try:
+                for cmd in cmds:
+                    descriptions[cmd.name] = cmd.description
+            except TypeError:
+                if _debug:
+                    kloger.warning(
+                        'No cmd description for module %s' % cmd_group)
+        elif _debug:
+            kloger.warning('Loading of %s cmd spec failed' % cmd_group)
+    print('\nOptions:\n - - - -')
+    print_dict(descriptions)
+
+
+def _load_all_commands(cmd_tree, arguments):
+    _cnf = arguments['config']
+    for cmd_group, spec in _cnf.get_cli_specs():
+        try:
+            spec_module = _load_spec_module(spec, arguments, '_commands')
+            spec_commands = getattr(spec_module, '_commands')
+        except AttributeError:
+            if _debug:
+                global kloger
+                kloger.warning('No valid description for %s' % cmd_group)
+            continue
+        for spec_tree in spec_commands:
+            if spec_tree.name == cmd_group:
+                cmd_tree.add_tree(spec_tree)
+                break
+
+
+#  Methods to be used by CLI implementations
+
+
+def print_subcommands_help(cmd):
+    printout = {}
+    for subcmd in cmd.get_subcommands():
+        spec, sep, print_path = subcmd.path.partition('_')
+        printout[print_path.replace('_', ' ')] = subcmd.description
+    if printout:
+        print('\nOptions:\n - - - -')
+        print_dict(printout)
+
+
+def update_parser_help(parser, cmd):
+    global _best_match
+    parser.syntax = parser.syntax.split('<')[0]
+    parser.syntax += ' '.join(_best_match)
+
+    description = ''
+    if cmd.is_command:
+        cls = cmd.get_class()
+        parser.syntax += ' ' + cls.syntax
+        parser.update_arguments(cls().arguments)
+        description = getattr(cls, 'long_description', '')
+        description = description.strip()
+    else:
+        parser.syntax += ' <...>'
+    if cmd.has_description:
+        parser.parser.description = cmd.help + (
+            ('\n%s' % description) if description else '')
+    else:
+        parser.parser.description = description
+
+
+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)
+    for errmsg in cli_err.details:
+        print('|  %s' % errmsg)
+
+
+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
+    return 1
+
+
+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 set_command_params(parameters):
+    """Add a parameters list to a command
+
+    :param paramters: (list of str) a list of parameters
+    """
+    global command
+    def_params = list(command.func_defaults)
+    def_params[0] = parameters
+    command.func_defaults = tuple(def_params)
+
+
+#  CLI Choice:
+
+def run_one_cmd(exe_string, parser, auth_base, cloud):
+    global _history
+    _history = History(
+        parser.arguments['config'].get_global('history_file'))
+    _history.add(' '.join([exe_string] + argv[1:]))
+    from kamaki.cli import one_command
+    one_command.run(auth_base, cloud, parser, _help)
+
+
+def run_shell(exe_string, parser, auth_base, cloud):
+    from command_shell import _init_shell
+    shell = _init_shell(exe_string, parser)
+    _load_all_commands(shell.cmd_tree, parser.arguments)
+    shell.run(auth_base, cloud, parser)
+
+
+def is_non_API(parser):
+    nonAPIs = ('history', 'config')
+    for term in parser.unparsed:
+        if not term.startswith('-'):
+            if term in nonAPIs:
+                return True
+            return False
+    return False
 
-def main():
 
-       if len(argv) <= 1:
-               run_shell()
-       else:
-               one_command()
\ No newline at end of file
+def main():
+    try:
+        exe = basename(argv[0])
+        parser = ArgumentParseManager(exe)
+
+        if parser.arguments['version'].value:
+            exit(0)
+
+        log_file = parser.arguments['config'].get_global('log_file')
+        if log_file:
+            logger.set_log_filename(log_file)
+        global filelog
+        filelog = logger.add_file_logger(__name__.split('.')[0])
+        filelog.info('* Initial Call *\n%s\n- - -' % ' '.join(argv))
+
+        auth_base, cloud = _init_session(parser.arguments, is_non_API(parser))
+
+        from kamaki.cli.utils import suggest_missing
+        global _colors
+        exclude = ['ansicolors'] if not _colors == 'on' else []
+        suggest_missing(exclude=exclude)
+
+        if parser.unparsed:
+            run_one_cmd(exe, parser, auth_base, cloud)
+        elif _help:
+            parser.parser.print_help()
+            _groups_help(parser.arguments)
+        else:
+            run_shell(exe, parser, auth_base, cloud)
+    except CLIError as err:
+        print_error_message(err)
+        if _debug:
+            raise err
+        exit(1)
+    except Exception as er:
+        print('Unknown Error: %s' % er)
+        if _debug:
+            raise
+        exit(1)