X-Git-Url: https://code.grnet.gr/git/kamaki/blobdiff_plain/524dc2f88ed5b95c5264486cc2e7ba5807ab5c7d..feature-input-output-encoding:/kamaki/cli/command_shell.py diff --git a/kamaki/cli/command_shell.py b/kamaki/cli/command_shell.py index 57d8b11..b9391bc 100644 --- a/kamaki/cli/command_shell.py +++ b/kamaki/cli/command_shell.py @@ -1,4 +1,4 @@ -# Copyright 2012 GRNET S.A. All rights reserved. +# Copyright 2012-2014 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following @@ -34,23 +34,24 @@ from cmd import Cmd from os import popen from sys import stdout -from argparse import ArgumentParser -from kamaki.cli import _exec_cmd, _print_error_message -from kamaki.cli.argument import _arguments, update_arguments -from kamaki.cli.utils import print_dict +from kamaki.cli import exec_cmd, print_error_message, print_subcommands_help +from kamaki.cli.argument import ArgumentParseManager +from kamaki.cli.utils import print_dict, split_input, pref_enc from kamaki.cli.history import History from kamaki.cli.errors import CLIError +from kamaki.clients import ClientError +from kamaki.cli.logger import add_file_logger +log = add_file_logger(__name__) -def _init_shell(exe_string, arguments): - arguments.pop('version', None) - arguments.pop('options', None) - arguments.pop('history', None) + +def _init_shell(exe_string, parser, username='', userid=''): + parser.arguments.pop('version', None) shell = Shell() shell.set_prompt(exe_string) from kamaki import __version__ as version - shell.greet(version) + shell.greet(version, username, userid) shell.do_EOF = shell.do_exit from kamaki.cli.command_tree import CommandTree shell.cmd_tree = CommandTree( @@ -61,40 +62,73 @@ def _init_shell(exe_string, arguments): class Shell(Cmd): """Kamaki interactive shell""" _prefix = '[' - _suffix = ']:' + _suffix = ']: ' cmd_tree = None _history = None - _arguments = None _context_stack = [] _prompt_stack = [] + _parser = None + auth_base = None + cloud = None undoc_header = 'interactive shell commands:' + def emptyline(self): + self.lastcmd = '' + + def postcmd(self, post, line): + if self._context_stack: + self._roll_command() + self._restore(self._context_stack.pop()) + self.set_prompt( + self._prompt_stack.pop()[len(self._prefix):-len(self._suffix)]) + + return Cmd.postcmd(self, post, line) + def precmd(self, line): if line.startswith('/'): - cur_cmd_path = self.prompt.replace(' ', '_')[1:-2] + start, end = len(self._prefix), -len(self._suffix) + cur_cmd_path = self.prompt.replace(' ', '_')[start:end] if cur_cmd_path != self.cmd_tree.name: cur_cmd = self.cmd_tree.get_command(cur_cmd_path) - self._context_stack.append(self._backup()) self._prompt_stack.append(self.prompt) new_context = self - new_context._roll_command(cur_cmd_path) + self._roll_command(cur_cmd.path) new_context.set_prompt(self.cmd_tree.name) for grp_cmd in self.cmd_tree.get_subcommands(): self._register_command(grp_cmd.path) return line[1:] return line - def greet(self, version): - print('kamaki v%s - Interactive Shell\n\t(exit or ^D to exit)\n'\ - % version) + def greet(self, version, username='', userid=''): + print('kamaki v%s - Interactive Shell\n' % version) + print('\t/exit \tterminate kamaki') + print('\texit or ^D\texit context') + print('\t? or help \tavailable commands') + print('\t?command \thelp on command') + print('\t!\texecute OS shell command') + print('') + if username or userid: + print('Session user is %s (uuid: %s)' % (username, userid)) def set_prompt(self, new_prompt): - self.prompt = '[%s]:' % new_prompt + self.prompt = '%s%s%s' % (self._prefix, new_prompt, self._suffix) + + def cmdloop(self): + while True: + try: + Cmd.cmdloop(self) + except KeyboardInterrupt: + print(' - interrupted') + continue + break def do_exit(self, line): print('') + start, end = len(self._prefix), -len(self._suffix) + if self.prompt[start:end] == self.cmd_tree.name: + exit(0) return True def do_shell(self, line): @@ -118,13 +152,12 @@ class Shell(Cmd): except KeyError: pass - def _roll_command(self, cmd_path): - for subname in self.cmd_tree.get_subnames(cmd_path): + def _roll_command(self, cmd_path=None): + for subname in self.cmd_tree.subnames(cmd_path): self._unregister_method('do_%s' % subname) self._unregister_method('complete_%s' % subname) self._unregister_method('help_%s' % subname) - @classmethod def _backup(self): return dict(self.__dict__) @@ -133,9 +166,23 @@ class Shell(Cmd): def _restore(self, oldcontext): self.__dict__ = oldcontext + @staticmethod + def _create_help_method(cmd_name, args, required, descr, syntax): + tmp_args = dict(args) + #tmp_args.pop('options', None) + tmp_args.pop('cloud', None) + tmp_args.pop('debug', None) + tmp_args.pop('verbose', None) + tmp_args.pop('silent', None) + tmp_args.pop('config', None) + help_parser = ArgumentParseManager( + cmd_name, tmp_args, required, + syntax=syntax, description=descr, check_required=False) + return help_parser.print_help + def _register_command(self, cmd_path): cmd = self.cmd_tree.get_command(cmd_path) - arguments = self._arguments + arguments = self._parser.arguments def do_method(new_context, line): """ Template for all cmd.Cmd methods of the form do_ @@ -143,44 +190,64 @@ class Shell(Cmd): is always parsed to most specific even if cmd_term_term is not a terminal path """ - subcmd, cmd_args = cmd.parse_out(line.split()) - if self._history: - self._history.add(' '.join([cmd.path.replace('_', ' '), line])) - cmd_parser = ArgumentParser(cmd.name, add_help=False) - cmd_parser.description = subcmd.help + line = line.decode(pref_enc) + subcmd, cmd_args = cmd.parse_out(split_input(line)) + self._history.add(' '.join([cmd.path.replace('_', ' '), line])) + cmd_parser = ArgumentParseManager( + cmd.name, dict(self._parser.arguments)) + cmd_parser.parser.description = subcmd.help # exec command or change context if subcmd.is_command: # exec command - cls = subcmd.get_class() - instance = cls(dict(arguments)) - cmd_parser.prog = '%s %s' % (cmd_parser.prog.replace('_', ' '), - cls.syntax) - update_arguments(cmd_parser, instance.arguments) - if '-h' in cmd_args or '--help' in cmd_args: - cmd_parser.print_help() - return - parsed, unparsed = cmd_parser.parse_known_args(cmd_args) - - for name, arg in instance.arguments.items(): - arg.value = getattr(parsed, name, arg.default) try: - _exec_cmd(instance, unparsed, cmd_parser.print_help) - except CLIError as err: - _print_error_message(err) - elif ('-h' in cmd_args or '--help' in cmd_args) \ - or len(cmd_args): # print options - print('%s: %s' % (cmd.name, subcmd.help)) - options = {} - for sub in subcmd.get_subcommands(): - options[sub.name] = sub.help - print_dict(options) + cls = subcmd.cmd_class + cmd_parser.required = getattr(cls, 'required', None) + ldescr = getattr(cls, 'long_description', '') + if subcmd.path == 'history_run': + instance = cls( + dict(cmd_parser.arguments), self.auth_base, + cmd_tree=self.cmd_tree) + else: + instance = cls( + dict(cmd_parser.arguments), + self.auth_base, self.cloud) + cmd_parser.update_arguments(instance.arguments) + cmd_parser.arguments = instance.arguments + subpath = subcmd.path.split('_')[ + (len(cmd.path.split('_')) - 1):] + cmd_parser.syntax = '%s %s' % ( + ' '.join(subpath), instance.syntax) + help_method = self._create_help_method( + cmd.name, cmd_parser.arguments, + cmd_parser.required, subcmd.help, cmd_parser.syntax) + if '-h' in cmd_args or '--help' in cmd_args: + help_method() + if ldescr.strip(): + print('\nDetails:') + print('%s' % ldescr) + return + cmd_parser.parse(cmd_args) + + for name, arg in instance.arguments.items(): + arg.value = getattr( + cmd_parser.parsed, name, arg.default) + + exec_cmd(instance, cmd_parser.unparsed, help_method) + #[term for term in cmd_parser.unparsed\ + # if not term.startswith('-')], + except (ClientError, CLIError) as err: + print_error_message(err) + elif ('-h' in cmd_args or '--help' in cmd_args) or len(cmd_args): + # print options + print('%s' % cmd.help) + print_subcommands_help(cmd) else: # change context #new_context = this backup_context = self._backup() old_prompt = self.prompt new_context._roll_command(cmd.parent_path) new_context.set_prompt(subcmd.path.replace('_', ' ')) - newcmds = [subcmd for subcmd in subcmd.get_subcommands()] + newcmds = [subcmd for subcmd in subcmd.subcommands.values()] for subcmd in newcmds: new_context._register_command(subcmd.path) new_context.cmdloop() @@ -191,23 +258,43 @@ class Shell(Cmd): def help_method(self): print('%s (%s -h for more options)' % (cmd.help, cmd.name)) + if cmd.is_command: + cls = cmd.cmd_class + ldescr = getattr(cls, 'long_description', '') + #_construct_command_syntax(cls) + plist = self.prompt[len(self._prefix):-len(self._suffix)] + plist = plist.split(' ') + clist = cmd.path.split('_') + upto = 0 + if ldescr: + print('%s' % ldescr) + for i, term in enumerate(plist): + try: + if clist[i] == term: + upto += 1 + except IndexError: + break + print('Syntax: %s %s' % (' '.join(clist[upto:]), cls.syntax)) + if cmd.subcommands: + print_subcommands_help(cmd) + self._register_method(help_method, 'help_%s' % cmd.name) def complete_method(self, text, line, begidx, endidx): - subcmd, cmd_args = cmd.parse_out(line.split()[1:]) + subcmd, cmd_args = cmd.parse_out(split_input(line)[1:]) if subcmd.is_command: - cls = subcmd.get_class() + cls = subcmd.cmd_class instance = cls(dict(arguments)) empty, sep, subname = subcmd.path.partition(cmd.path) cmd_name = '%s %s' % (cmd.name, subname.replace('_', ' ')) - print('\n%s\nSyntax:\t%s %s'\ - % (cls.description, cmd_name, cls.syntax)) + print('\n%s\nSyntax:\t%s %s' % ( + cls.help, cmd_name, cls.syntax)) cmd_args = {} for arg in instance.arguments.values(): cmd_args[','.join(arg.parsed_name)] = arg.help - print_dict(cmd_args, ident=2) + print_dict(cmd_args, indent=2) stdout.write('%s %s' % (self.prompt, line)) - return subcmd.get_subnames() + return subcmd.subnames() self._register_method(complete_method, 'complete_%s' % cmd.name) @property @@ -217,18 +304,31 @@ class Shell(Cmd): hdr = tmp_partition[0].strip() return '%s commands:' % hdr - def run(self, arguments, path=''): - self._history = History(arguments['config'].get('history', 'file')) - self._arguments = arguments + def run(self, auth_base, cloud, parser, path=''): + self.auth_base = auth_base + self.cloud = cloud + self._parser = parser + cnf = parser.arguments['config'] + self._history = History(cnf.get('global', 'history_file')) + self._history.limit = cnf.get('global', 'history_limit') if path: cmd = self.cmd_tree.get_command(path) intro = cmd.path.replace('_', ' ') else: intro = self.cmd_tree.name + acceptable = parser.arguments['config'].groups + total = self.cmd_tree.groups.keys() + self.cmd_tree.exclude(set(total).difference(acceptable)) + for subcmd in self.cmd_tree.get_subcommands(path): self._register_command(subcmd.path) self.set_prompt(intro) - self.cmdloop() + try: + self.cmdloop() + except Exception as e: + print('(%s)' % e) + from traceback import print_stack + print_stack()