class RootCollection(Collection):

    def __init__(self, **kwargs):
        return super(RootCollection, self).__init__('', **kwargs) class ShellCompleter(Completer):

    def __init__(self):
        self.context = Context().shell
        self.completers = {}
        for ext in extension.ExtensionManager(namespace="contrail_api_cli.completer",
                                              invoke_on_load=True,
                                              on_load_failure_callback=self._on_failure):
            self.completers[ext.name] = ext.obj

    def _on_failure(self, mgr, entrypoint, exc):
        printo('Cannot load completer %s: %s' % (entrypoint.name,
                                                 exc))

    def get_completions(self, document, complete_event):
        word_before_cursor = document.get_word_before_cursor(WORD=True)
        words = document.text_before_cursor.split()
        try:
            self.parser = CommandParser(document)
        except CommandNotFound:
            if 'commands' in self.completers:
                for c in self.completers['commands'].get_completions(word_before_cursor, self.context):
                    yield c
            raise StopIteration
        except CommandInvalid:
            raise StopIteration

        # still typing the command
        if word_before_cursor == self.parser.cmd_name:
            raise StopIteration

        # complete options for the current command
        if word_before_cursor.startswith('-'):
            for c in self.get_option_name_completion(word_before_cursor):
                yield c
            raise StopIteration

        # check we need a value for an option
        option = self.get_last_option(word_before_cursor, words)
        if option is not None and option.need_value:
            for c in self.get_option_value_completion(option, word_before_cursor):
                yield c
            raise StopIteration

        # check we need arguments values
        arg = self.get_last_arg(word_before_cursor, words)
        if arg is not None and arg.complete is not None:
            logger.debug('Complete using arg matcher %s' % arg.complete)
            completer_name = arg.complete.split(':')[0]
            if completer_name in self.completers:
                for c in self.completers[completer_name].get_completions(word_before_cursor, self.context, arg):
                    yield c
            else:
                logger.warning('No completer found for %s' % completer_name)
            raise StopIteration

    def get_option_name_completion(self, word_before_cursor):
        logger.debug('Complete option names')
        for option in self.parser.available_options:
            option_name = option.short_name or option.long_name
            if word_before_cursor.startswith('--'):
                option_name = option.long_name
            if option_name.startswith(word_before_cursor):
                yield Completion(option_name,
                                 -len(word_before_cursor),
                                 display_meta=option.help)

    def get_option_value_completion(self, option, word_before_cursor):
        logger.debug('Complete option value')
        # complete choices
        if option.kwargs.get('choices'):
            logger.debug('Complete using choices %s' % option.kwargs['choices'])
            for choice in option.kwargs['choices']:
                yield Completion(choice,
                                 -len(word_before_cursor))
        # complete resources
        elif option.complete is not None:
            logger.debug('Complete using option matcher %s' % option.complete)
            completer_name = option.complete.split(':')[0]
            if completer_name in self.completers:
                for c in self.completers[completer_name].get_completions(word_before_cursor, self.context, option):
                    yield c
            else:
                logger.warning('No completer found for %s' % completer_name)

    def get_last_option(self, word_before_cursor, words):
        # current option value
        if word_before_cursor:
            try:
                option = self.parser.get_option(words[-2])
            except IndexError:
                option = None
        # no value typed
        else:
            option = self.parser.get_option(words[-1])
        return option

    def get_last_arg(self, word_before_cursor, words):
        # current arg value
        if word_before_cursor and list(self.parser.available_args):
            # depending on the cursor position, use a relative
            # parser to get the current argument
            p = CommandParser(Document(text=" ".join(words)))
            arg = list(p.available_args)[0]
        # get the next arg in line
        else:
            arg = next(self.parser.available_args)
        return arg class CommandError(Exception):
    pass class CommandNotFound(Exception):
    pass class NotFound(GreenletExit):

    def __init__(self, resource=None, collection=None):
        super(GreenletExit, self).__init__()
        self.r = resource
        self.c = collection

    def __str__(self):
        return "No resource found" class Exists(GreenletExit):

    def __init__(self, resources=None):
        self.resources = []
        if resources is not None:
            self.resources = resources

    @property
    def _paths(self):
        return ", ".join([text_type(c.path) for c in self.resources])

    def __repr__(self):
        return "%s(%s)" % (self.__class__.__name__,
                           ", ".join([repr(r) for r in self.resources])) class Command(object):
    """Base class for commands
    """
    description = ""
    """Description of the command"""
    aliases = []
    """Command aliases"""
    _options = None
    _args = None

    def __init__(self, name):
        self.parser = ArgumentParser(prog=name, description=self.description)
        self.add_arguments_to_parser(self.parser)
        self._is_piped = False

    def current_path(self, resource):
        """Return current path for resource

        :param resource: resource or collection
        :type resource: Resource|Collection

        :rtype: str
        """
        return text_type(resource.path.relative_to(Context().shell.current_path))

    @property
    def is_piped(self):
        """Return True if the command result is beeing piped
        to another command.

        :rtype: bool
        """
        return not sys.stdout.isatty() or self._is_piped

    @is_piped.setter
    def is_piped(self, value):
        self._is_piped = value

    @classproperty
    def options(cls):
        if cls._options is not None:
            return cls._options
        cls._options = OrderedDict()
        for attr, option in sorted(
                inspect.getmembers(cls, lambda o: isinstance(o, Option)),
                key=lambda i: i[1]._creation_idx):
            option.attr = attr
            cls._options[text_type(attr)] = option
        return cls._options

    @classproperty
    def args(cls):
        if cls._args is not None:
            return cls._args
        cls._args = OrderedDict()
        for attr, arg in sorted(
                inspect.getmembers(cls, lambda o: isinstance(o, Arg)),
                key=lambda i: i[1]._creation_idx):
            arg.attr = attr
            cls._args[text_type(attr)] = arg
        return cls._args

    @classmethod
    def add_arguments_to_parser(cls, parser):
        for (arg_name, arg) in cls.args.items():
            parser.add_argument(arg_name, **arg.kwargs)
        for (option_name, option) in cls.options.items():
            parser.add_argument(*option.option_strings, **option.kwargs)

    def parse_and_call(self, *args):
        args = self.parser.parse_args(args=args)
        return self.__call__(**vars(args))

    @abc.abstractmethod
    def __call__(self, **kwargs):
        """Command must implement this method.

        The command must return an unicode string
        (unicode in python2 or str in python3)

        :param kwargs: options of the command

        :rtype: unicode | str
        """ class Arg(BaseOption):
    pass CONFIG_DIR = os.path.expanduser('~/.config/contrail-api-cli') def printo(msg, encoding=None, errors='replace', std_type='stdout'):
    """Write msg on stdout. If no encoding is specified
    the detected encoding of stdout is used. If the encoding
    can't encode some chars they are replaced by '?'

    :param msg: message
    :type msg: unicode on python2 | str on python3
    """
    std = getattr(sys, std_type, sys.stdout)
    if encoding is None:
        try:
            encoding = std.encoding
        except AttributeError:
            encoding = None
    # Fallback to ascii if no encoding is found
    if encoding is None:
        encoding = 'ascii'
    # https://docs.python.org/3/library/sys.html#sys.stdout
    # write in the binary buffer directly in python3
    if hasattr(std, 'buffer'):
        std = std.buffer
    std.write(msg.encode(encoding, errors=errors))
    std.write(b'\n')
    std.flush() def eventloop():
    # Allow to keep gevent greenlets running
    # while waiting for some input on the cli
    def inputhook(context):
        while not context.input_is_ready():
            gevent.sleep(0.1)

    return create_eventloop(inputhook=inputhook) def style_from_dict(d): class CommandManager(object):

    def __init__(self, load_default=True):
        """Load commands from namespace

        :param load_default: tell the manager to load the default
                             namespace for commands (contrail_api_cli.command).
                             (True by default)
        :type load_default: bool
        """
        self.mgrs = []
        if load_default is True:
            self.load_namespace('contrail_api_cli.command')

    def load_namespace(self, ns):
        """Load commands from namespace.

        :param ns: namespace name
        :type ns: str
        """
        mgr = extension.ExtensionManager(namespace=ns,
                                         verify_requirements=True,
                                         on_load_failure_callback=self._on_failure)
        # Load commands from ns
        for ext in mgr.extensions:
            try:
                obj = ext.plugin(ext.name)
            except Exception as err:
                self._on_failure(self, ext, err)
                obj = None
            ext.obj = obj

        self.mgrs.append(mgr)

    def unload_namespace(self, ns):
        self.mgrs = [mgr for mgr in self.mgrs if not mgr.namespace == ns]

    def _on_failure(self, mgr, entrypoint, exc):
        print('Cannot load command %s: %s' % (entrypoint.name,
                                              exc))

    def get(self, name):
        """Return command instance of loaded
        commands by name

        :param name: name of the command
        :type name: str
        """
        for cmd_name, cmd in self.list:
            if cmd_name == name:
                return cmd
        raise CommandNotFound('Command %s not found. Type help for all commands' % name)

    @property
    def extensions(self):
        return itertools.chain(*[mgr.extensions for mgr in self.mgrs])

    @property
    def list(self):
        """Generator of command instances

        :rtype: (name, Command)
        """
        for ext in self.extensions:
            # don't return ext that failed
            # to load earlier
            if ext.obj is None:
                continue
            yield (ext.name, ext.obj)

    def add(self, name, cmd):
        ext = extension.Extension(name, None, cmd.__class__, cmd)
        self.mgrs[0].extensions.append(ext)

    @classmethod
    def register_argparse_commands(cls, parser, argv):
        in_parser = argparse.ArgumentParser(add_help=False)
        for p in (parser, in_parser):
            p.add_argument('--ns',
                           help='Command namespaces to load (default: %(default)s)',
                           metavar='COMMAND_NAMESPACE',
                           action='append',
                           default=[])
        options, _ = in_parser.parse_known_args(argv)
        mgr = CommandManager()
        for ns in options.ns:
            mgr.load_namespace(ns)
        subparsers = parser.add_subparsers(dest='subcmd')
        for cmd_name, cmd in mgr.list:
            subparser = subparsers.add_parser(cmd_name, help=cmd.description)
            cmd.add_arguments_to_parser(subparser)
        return mgr

    def get_completions(self, word_before_cursor, context, option=None):
        for cmd_name, cmd in self.list:
            if cmd_name.startswith(word_before_cursor):
                yield Completion(cmd_name,
                                 -len(word_before_cursor),
                                 display_meta=cmd.description) class Context(object):
    _schema = None
    _shell = ShellContext
    _session = None

    @property
    def schema(self):
        if self._schema is None:
            raise SchemaNotInitialized("The schema must be first initialized")
        else:
            return self._schema

    @schema.setter
    def schema(self, schema):
        self._schema = schema

    @property
    def session(self):
        if self._session is None:
            raise SessionNotInitialized("The session must be first initialized")
        else:
            return self._session

    @session.setter
    def session(self, session):
        self._session = session

    @property
    def shell(self):
        return self._shell class SchemaError(Exception):
    pass
import os.path
import re
import pipes
import tempfile
import shlex
from six import text_type
from keystoneauth1.exceptions.http import HttpError, HTTPClientError
from pygments.token import Token
from prompt_toolkit import prompt
from prompt_toolkit.history import FileHistory
from prompt_toolkit.key_binding.manager import KeyBindingManager
from ..resource import RootCollection
from ..completer import ShellCompleter
from ..exceptions import CommandError, CommandNotFound, \
    NotFound, Exists
from ..command import Command, Arg
from ..utils import CONFIG_DIR, printo, eventloop
from ..style import default as default_style
from ..manager import CommandManager
from ..context import Context
from ..schema import SchemaError
# -*- coding: utf-8 -*-
from __future__ import unicode_literals






class ShellAliases(object):

    def __init__(self):
        self._aliases = {}

    def set(self, alias):
        if '=' not in alias:
            raise CommandError('Alias %s is incorrect' % alias)
        alias, cmd = alias.split('=')
        self._aliases[alias.strip()] = cmd.strip()

    def get(self, alias):
        return self._aliases.get(alias, alias)

    def apply(self, cmd):
        cmd = re.split(r'(\s+)', cmd)
        cmd = [self.get(c) for c in cmd]
        return ''.join(cmd)


class Shell(Command):
    description = "Run an interactive shell"

    def __call__(self):

        def get_prompt_tokens(cli):
            return [
                (Token.Username, Context().session.user or ''),
                (Token.At, '@' if Context().session.user else ''),
                (Token.Host, Context().session.host),
                (Token.Colon, ':'),
                (Token.Path, text_type(Context().shell.current_path)),
                (Token.Pound, '> ')
            ]

        key_bindings_registry = KeyBindingManager.for_prompt().registry
        manager = CommandManager()
        manager.load_namespace('contrail_api_cli.shell_command')
        completer = ShellCompleter()
        history = FileHistory(os.path.join(CONFIG_DIR, 'history'))
        cmd_aliases = ShellAliases()
        for cmd_name, cmd in manager.list:
            map(cmd_aliases.set, cmd.aliases)
        # load home resources to have them in cache
        # also build shortcut list for resource types
        # typing vmi/ will be expanded to virtual-machine-interface/
        # automagically
        res_aliases = ShellAliases()
        try:
            for c in RootCollection(fetch=True):
                short_name = "".join([p if p == "ip" else p[0].lower()
                                      for p in c.type.split('-')])
                res_aliases.set("%s = %s" % (short_name, c.type))
        except HTTPClientError as e:
            return text_type(e)

        def _(event, aliases, char):
            b = event.cli.current_buffer
            w = b.document.get_word_before_cursor()
            if w is not None:
                if not w == aliases.get(w):
                    b.delete_before_cursor(count=len(w))
                    b.insert_text(aliases.get(w))
            b.insert_text(char)

        @key_bindings_registry.add_binding(' ')
        def _sa(event):
            _(event, cmd_aliases, ' ')

        @key_bindings_registry.add_binding('/')
        def _ra(event):
            _(event, res_aliases, '/')

        while True:
            try:
                action = prompt(get_prompt_tokens=get_prompt_tokens,
                                history=history,
                                completer=completer,
                                style=default_style,

                                eventloop=eventloop(),