#from kamaki import clients
from .errors import CLIError, CLISyntaxError, CLICmdIncompleteError, CLICmdSpecError
from .config import Config #TO BE REMOVED
-from .utils import bold, magenta, red, yellow, CommandTree, print_list, print_dict
+from .utils import bold, magenta, red, yellow, print_list, print_dict
+from .command import CommandTree
from argument import _arguments, parse_known_args
cmd_spec_locations = [
'kamaki.cli',
'kamaki',
'']
-_commands = CommandTree(description='A command line tool for poking clouds')
+_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 = []
-do_no_load_commands = False
-put_subclass_signatures_in_commands = False
-
-def _put_subclass_signatures_in_commands(cls):
- global candidate_command_terms
-
- part_name = '_'.join(candidate_command_terms)
- try:
- empty, same, rest = cls.__name__.partition(part_name)
- except ValueError:
+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
- if len(empty) != 0:
- return False
- if len(rest) == 0:
- _commands.add_path(cls.__name__, (cls.__doc__.partition('\n'))[0])
- else:
- rest_terms = rest[1:].split('_')
- new_name = part_name+'_'+rest_terms[0]
- desc = cls.__doc__.partition('\n')[0] if new_name == cls.__name__ else ''
- _commands.add_path(new_name, desc)
- return True
-
-def _put_class_path_in_commands(cls):
- #Maybe I should apologise for the globals, but they are used in a smart way, so...
- global candidate_command_terms
term_list = cls.__name__.split('_')
-
- tmp_tree = _commands
- if len(candidate_command_terms) > 0:
- #This is the case of a one-command execution: discard if not requested
- if term_list[0] != candidate_command_terms[0]:
+ global candidate_command_terms
+ index = 0
+ for term in candidate_command_terms:
+ try:
+ index += 1 if term_list[index] == term else 0
+ except IndexError:
return False
- i = 0
- for term in term_list:
- #check if the term is requested by user
- if term not in candidate_command_terms[i:]:
- return False
- i = 1+candidate_command_terms.index(term)
- #now, put the term in the tree
- if term not in tmp_tree.get_command_names():
- tmp_tree.add_command(term)
- tmp_tree = tmp_tree.get_command(term)
- else:
- #Just insert everything in the tree
- for term in term_list:
- if term not in tmp_tree.get_command_names():
- tmp_tree.add_command(term)
- tmp_tree = tmp_tree.get_command()
- 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"""
- global do_no_load_commands
- if do_no_load_commands:
- return cls
-
- global put_subclass_signatures_in_commands
- if put_subclass_signatures_in_commands:
- _put_subclass_signatures_in_commands(cls)
- return cls
- if not _put_class_path_in_commands(cls):
+ if not _allow_class_in_cmd_tree(cls):
return cls
cls.description, sep, cls.long_description = cls.__doc__.partition('\n')
else:
print()
-def _expand_cmd(cmd_prefix, unparsed):
- if len(unparsed) == 0:
- return None
- prefix = (cmd_prefix+'_') if len(cmd_prefix) > 0 else ''
- for term in _commands.list(cmd_prefix):
- try:
- unparsed.remove(term)
- except ValueError:
- continue
- return prefix+term
- return None
-
-def _retrieve_cmd(unparsed):
- cmd_str = None
- cur_cmd = _expand_cmd('', unparsed)
- while cur_cmd is not None:
- cmd_str = cur_cmd
- cur_cmd = _expand_cmd(cur_cmd, unparsed)
- if cmd_str is None:
- print(bold('Command groups:'))
- print_list(_commands.get_groups(), ident=14)
- print
- return None
- try:
- return _commands.get_class(cmd_str)
- except CLICmdIncompleteError:
- print(bold('%s:'%cmd_str))
- print_list(_commands.list(cmd_str))
- return None
-
def get_command_group(unparsed):
groups = _arguments['config'].get_groups()
for grp_candidate in unparsed:
return grp_candidate
return None
-def _order_in_list(list1, list2):
- order = 0
- for i,term in enumerate(list1):
- order += len(list2)*i*list2.index(term)
- return order
-
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 one
- final_cmd = group
- next_names = [None]
- next_names = _commands.get_command_names(final_cmd)
- while len(next_names) > 0:
- if len(next_names) == 1:
- final_cmd+='_'+next_names[0]
- else:#choose the first in user string
- try:
- pos = unparsed.index(next_names[0])
- except ValueError:
- return final_cmd
- choice = 0
- for i, name in enumerate(next_names[1:]):
- tmp_index = unparsed.index(name)
- if tmp_index < pos:
- pos = tmp_index
- choice = i+1
- final_cmd+='_'+next_names[choice]
- next_names = _commands.get_command_names(final_cmd)
+ #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 do_no_load_commands
- do_no_load_commands = True#load only descriptions
+ global allow_no_commands
+ allow_no_commands = True#load only descriptions
for grp in _arguments['config'].get_groups():
load_group_package(grp)
- do_no_load_commands = False
+ allow_no_commands = False
def load_group_package(group, reload_package=False):
spec_pkg = _arguments['config'].value.get(group, 'cli')
location += spec_pkg if location == '' else ('.'+spec_pkg)
try:
package = __import__(location, fromlist=['API_DESCRIPTION'])
- if reload_package:
- reload(package)
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=[], full_tree=False):
- cmd = _commands.get_command(prefix)
- grps = {' . ':cmd.description} if cmd.is_command else {}
- for grp in cmd.get_command_names():
- grps[grp] = cmd.get_description(grp)
- print('\nOptions:')
- print_dict(grps, ident=12)
- if full_tree:
- _commands.print_tree(level=-1)
+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 one_command():
_debug = False
_help = False
+ _verbose = False
try:
exe = basename(argv[0])
parser = _init_parser(exe)
parsed, unparsed = parse_known_args(parser)
_debug = _arguments['debug'].value
_help = _arguments['help'].value
+ _verbose = _arguments['verbose'].value
if _arguments['version'].value:
exit(0)
if group is None:
parser.print_help()
shallow_load()
- print_commands(full_tree=_arguments['verbose'].value)
- print()
+ print_commands(full_depth=_verbose)
exit(0)
- command_path = load_command(group, unparsed)
- cli = _commands.get_class(command_path)
- if cli is None or _help: #Not a complete command or help
- parser.description = _commands.closest_description(command_path)
- parser.prog = '%s %s '%(exe, command_path.replace('_', ' '))
- if cli is None:
- parser.prog += '<...>'
+ 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 put_subclass_signatures_in_commands
- put_subclass_signatures_in_commands = True
- load_command(group, command_path.split('_')[1:], reload_package=True)
+ global allow_subclass_signatures
+ allow_subclass_signatures = True
+ load_command(group, cmd.path.split('_')[1:], reload_package=True)
- print_commands(command_path, full_tree=_arguments['verbose'].value)
+ print_commands(cmd.path, full_depth=_verbose)
exit(0)
- #Now, load the cmd
- cmd = cli(_arguments)
- _update_parser(parser, cmd.arguments)
- parser.prog = '%s %s %s'%(exe, command_path.replace('_', ' '), cli.syntax)
- parsed, unparsed = parse_known_args(parser)
- for term in command_path.split('_'):
- unparsed.remove(term)
+ cli = cmd.get_class()
+ executable = cli(_arguments)
+ _update_parser(parser, executable.arguments)
+ parser.prog = '%s %s %s'%(exe, cmd.path.replace('_', ' '), cli.syntax)
+ parse_known_args(parser)
try:
- ret = cmd.main(*unparsed)
+ ret = executable.main(*unparsed)
exit(ret)
except TypeError as e:
if e.args and e.args[0].startswith('main()'):
# or implied, of GRNET S.A.
import cmd
+#from .errors import CLIUnknownCommand, CLICmdIncompleteError, CLICmdSpecError, CLIError
class Command(object):
"""Store a command and the next-level commands as well - no deep tree here"""
"""Check if a name is a direct child of self"""
return self.subcommands.has_key(name)
+ @property
def is_command(self):
return self.cmd_class is not None
+ @property
+ def has_description(self):
+ return len(self.help.strip()) > 0
+ @property
+ def description(self):
+ return self.help
def set_class(self, cmd_class):
self.cmd_class = cmd_class
def pretty_print(self, recursive=False):
print('Path: %s (Name: %s) is_cmd: %s\n\thelp: %s'%(self.path, self.name,
- self.is_command(), self.help))
+ self.is_command, self.help))
for cmd in self.get_subcommands():
cmd.pretty_print(recursive)
tmp.add_subcmd(Command('store_list_one', 'List just one stuff'))
cmd.pretty_print(True)
+class CommandTree(object):
+
+ groups = {}
+ _all_commands = {}
+ name = None
+ description = None
+
+ def __init__(self, name, description=''):
+ self.name = name
+ self.description = description
+
+ def add_command(self, command_path, description=None, cmd_class=None):
+ terms = command_path.split('_')
+ try:
+ cmd = self.groups[terms[0]]
+ except KeyError:
+ cmd = Command(terms[0])
+ self.groups[terms[0]] = cmd
+ self._all_commands[terms[0]] = cmd
+ path = terms[0]
+ for term in terms[1:]:
+ path += '_'+term
+ try:
+ cmd = cmd.subcommands[term]
+ except KeyError:
+ new_cmd = Command(path)
+ self._all_commands[path] = new_cmd
+ cmd.add_subcmd(new_cmd)
+ cmd = new_cmd
+ if cmd_class is not None:
+ cmd.set_class(cmd_class)
+ if description is not None:
+ cmd.help = description
+ def get_command(self, path):
+ return self._all_commands[path]
+ def get_groups(self):
+ return self.groups.values()
+ def get_group_names(self):
+ return self.groups.keys()
+
+ def set_description(self, path, description):
+ self._all_commands[path].help = description
+ def get_descitpion(self, path):
+ return self._all_commands[path].help
+ def set_class(self, path, cmd_class):
+ self._all_commands[path].set_class(cmd_class)
+ def get_class(self, path):
+ return self._all_commands[path].get_class()
+
+ def get_subnames(self, path):
+ return self._all_commands[path].get_subnames()
+ def get_subcommands(self, path):
+ return self._all_commands[path].get_subcommands()
+ def get_parent(self, path):
+ if '_' not in path:
+ return None
+ terms = path.split('_')
+ parent_path = '_'.join(terms[:-1])
+ return self._all_commands[parent_path]
+ def get_closest_ancestor_command(self, path):
+ path, sep, name = path.rpartition('_')
+ while len(path) > 0:
+ cmd = self._all_commands[path]
+ if cmd.is_command:
+ return cmd
+ path, sep, name = path.rpartition('_')
+ return None
+
+ if '_' not in path:
+ return None
+ terms = terms[:-1]
+ while len(terms) > 0:
+ tmp_path = '_'.join(terms)
+ cmd = self._all_commands[tmp_path]
+ if cmd.is_command:
+ return cmd
+ terms = terms[:-1]
+ raise KeyError('No ancestor commands')
+
+ def pretty_print(self, group=None):
+ if group is None:
+ for group in self.groups:
+ self.pretty_print(group)
+ else:
+ self.groups[group].pretty_print(recursive=True)
+
+def test_CommandTree():
+ tree = CommandTree('kamaki', 'the kamaki tools')
+ tree.add_command('store', 'A storage thingy')
+ tree.add_command('server_list_lala', description='A testing server list', cmd_class=Shell)
+ tree.add_command('store_list_all', 'List all things', cmd_class=Command)
+ tree.add_command('store_list', 'List smthing pls', cmd_class=Shell)
+ tree.add_command('server_list', description='A server list subgrp')
+ tree.add_command('server', description='A server is a SERVER', cmd_class=CommandTree)
+ tree.set_class('server', None)
+ tree.set_description('server_list', '')
+ if tree.get_class('server_list_lala') is Shell:
+ print('server_list_lala is Shell')
+ else:
+ print('server_list_lala is not Shell')
+ tree.pretty_print()
+ print('store_list_all closest parent command is %s'%tree.get_closest_ancestor_command('store_list_all').path)
+ tree.set_class('store', tree.get_command('store_list').get_class())
+ tree.set_class('store_list', None)
+ print('store_list_all closest parent command is %s'%tree.get_closest_ancestor_command('store_list_all').path)
+ try:
+ print('nonexisting_list_command closest parent is %s'%tree.get_closest_ancestor_command('nonexisting_list_command').path)
+ except KeyError:
+ print('Aparrently nonexisting_list_command is nonexisting ')
+
class Shell(cmd.Cmd):
"""Simple command processor example."""
#import sys
#sh.onecmd(' '.join(sys.argv[1:]))
-test_Command()
\ No newline at end of file
+#test_CommandTree()
\ No newline at end of file
return val
red = yellow = magenta = bold
-from .errors import CLIUnknownCommand, CLICmdIncompleteError, CLICmdSpecError, CLIError
-class CommandTree(object):
- """A tree of command terms usefull for fast commands checking
- """
-
- def __init__(self, run_class=None, description='', commands={}):
- self.run_class = run_class
- self.description = description
- self.commands = commands
-
- def get_command_names(self, prefix=[]):
- cmd = self.get_command(prefix)
- return cmd.commands.keys()
-
- def get_terminal_commands(self, prefix=''):
- cmd = self.get_command(prefix)
- terminal_cmds = [prefix] if cmd.is_command() else []
- prefix = '' if len(prefix) == 0 else '%s_'%prefix
- for term, tree in cmd.commands.items():
- xtra = self.get_terminal_commands(prefix+term)
- terminal_cmds.append(*xtra)
- return terminal_cmds
-
- def add_path(self, command, description):
- path = get_pathlist_from_prefix(command)
- tmp = self
- for term in path:
- try:
- tmp = tmp.get_command(term)
- except CLIUnknownCommand:
- tmp.add_command(term)
- tmp = tmp.get_command(term)
- tmp.description = description
-
- def add_command(self, new_command, new_descr='', new_class=None):
- cmd_list = new_command.split('_')
- cmd = self.get_command(cmd_list[:-1])
- try:
- existing = cmd.get_command(cmd_list[-1])
- if new_class is not None:
- existing.run_class = new_class
- if new_descr not in (None, ''):
- existing.description = new_descr
- except CLIUnknownCommand:
- cmd.commands[new_command] = CommandTree(new_class,new_descr,{})
-
- def is_command(self, command=''):
- if self.get_command(command).run_class is None:
- return False
- return True
-
- def get_class(self, command=''):
- cmd = self.get_command(command)
- return cmd.run_class
- def set_class(self, command, new_class):
- cmd = self.get_command(command)
- cmd.run_class = new_class
-
- def get_description(self, command):
- cmd = self.get_command(command)
- return cmd.description
- def set_description(self, command, new_descr):
- cmd = self.get_command(command)
- cmd.description = new_descr
-
- def closest_complete_command(self, command):
- path = get_pathlist_from_prefix(command)
- tmp = self
- choice = self
- for term in path:
- tmp = tmp.get_command(term)
- if tmp.is_command():
- choice = tmp
- return choice
-
- def closest_description(self, command):
- path = get_pathlist_from_prefix(command)
- desc = self.description
- tmp = self
- for term in path:
- tmp = tmp.get_command(term)
- if tmp.description not in [None, '']:
- desc = tmp.description
- return desc
-
- def copy_command(self, prefix=''):
- cmd = self.get_command(prefix)
- from copy import deepcopy
- return deepcopy(cmd)
-
- def get_command(self, command):
- """
- @return a tuple of the form (cls_object, 'description text', {term1':(...), 'term2':(...)})
- """
- path = get_pathlist_from_prefix(command)
- cmd = self
- try:
- for term in path:
- cmd = cmd.commands[term]
- except KeyError:
- error_index = path.index(term)
- details='Command term %s not in path %s'%(unicode(term), path[:error_index])
- raise CLIUnknownCommand('Unknown command', details=details)
- return cmd
-
- def print_tree(self, command=[], level = 0, tabs=0):
- cmd = self.get_command(command)
- command_str = '_'.join(command) if isinstance(command, list) else command
- print(' '*tabs+command_str+': '+cmd.description)
- if level != 0:
- for name in cmd.get_command_names():
- new_level = level if level < 0 else (level-1)
- cmd.print_tree(name, new_level, tabs+1)
-
-def get_pathlist_from_prefix(prefix):
- if isinstance(prefix, list):
- return prefix
- if len(prefix) == 0:
- return []
- return unicode(prefix).split('_')
def pretty_keys(d, delim='_', recurcive=False):
"""Transform keys of a dict from the form