def test_word_completer_ignore_case():
    completer = WordCompleter(['abc', 'def', 'aaa'], ignore_case=True)
    completions = completer.get_completions(Document('a'), CompleteEvent())
    assert [c.text for c in completions] == ['abc', 'aaa']

    completions = completer.get_completions(Document('A'), CompleteEvent())
    assert [c.text for c in completions] == ['abc', 'aaa']
Esempio n. 2
0
class CommandCompleter(Completer):
    """
    Completer for command names.
    """
    def __init__(self):
        # Completer for full command names.
        self._command_completer = WordCompleter(
            sorted(COMMANDS_TO_HANDLERS.keys()),
            ignore_case=True, WORD=True, match_middle=True)

        # Completer for aliases.
        self._aliases_completer = WordCompleter(
            sorted(ALIASES.keys()),
            ignore_case=True, WORD=True, match_middle=True)

    def get_completions(self, document, complete_event):
        # First, complete on full command names.
        found = False

        for c in self._command_completer.get_completions(document, complete_event):
            found = True
            yield c

        # When no matches are found, complete aliases instead.
        # The completion however, inserts the full name.
        if not found:
            for c in self._aliases_completer.get_completions(document, complete_event):
                full_name = ALIASES.get(c.display)

                yield Completion(full_name,
                                 start_position=c.start_position,
                                 display='%s (%s)' % (c.display, full_name))
def test_word_completer_sentence():
    # With sentence=True
    completer = WordCompleter(['hello world', 'www', 'hello www', 'hello there'], sentence=True)
    completions = completer.get_completions(Document('hello w'), CompleteEvent())
    assert [c.text for c in completions] == ['hello world', 'hello www']

    # With sentence=False
    completer = WordCompleter(['hello world', 'www', 'hello www', 'hello there'], sentence=False)
    completions = completer.get_completions(Document('hello w'), CompleteEvent())
    assert [c.text for c in completions] == ['www']
def test_word_completer_pattern():
    # With a pattern which support '.'
    completer = WordCompleter(['abc', 'a.b.c', 'a.b', 'xyz'],
                              pattern=re.compile(r'^([a-zA-Z0-9_.]+|[^a-zA-Z0-9_.\s]+)'))
    completions = completer.get_completions(Document('a.'), CompleteEvent())
    assert [c.text for c in completions] == ['a.b.c', 'a.b']

    # Without pattern
    completer = WordCompleter(['abc', 'a.b.c', 'a.b', 'xyz'])
    completions = completer.get_completions(Document('a.'), CompleteEvent())
    assert [c.text for c in completions] == []
def test_word_completer_static_word_list():
    completer = WordCompleter(['abc', 'def', 'aaa'])

    # Static list on empty input.
    completions = completer.get_completions(Document(''), CompleteEvent())
    assert [c.text for c in completions] == ['abc', 'def', 'aaa']

    # Static list on non-empty input.
    completions = completer.get_completions(Document('a'), CompleteEvent())
    assert [c.text for c in completions] == ['abc', 'aaa']

    completions = completer.get_completions(Document('A'), CompleteEvent())
    assert [c.text for c in completions] == []

    # Multiple words. (Check last only.)
    completions = completer.get_completions(Document('test a'), CompleteEvent())
    assert [c.text for c in completions] == ['abc', 'aaa']
def test_word_completer_dynamic_word_list():
    called = [0]
    def get_words():
        called[0] += 1
        return ['abc', 'def', 'aaa']

    completer = WordCompleter(get_words)

    # Dynamic list on empty input.
    completions = completer.get_completions(Document(''), CompleteEvent())
    assert [c.text for c in completions] == ['abc', 'def', 'aaa']
    assert called[0] == 1

    # Static list on non-empty input.
    completions = completer.get_completions(Document('a'), CompleteEvent())
    assert [c.text for c in completions] == ['abc', 'aaa']
    assert called[0] == 2
Esempio n. 7
0
def get_completions_for_parts(parts, last_part, complete_event, pymux):
    completer = None

    # Resolve aliases.
    if len(parts) > 0:
        parts = [ALIASES.get(parts[0], parts[0])] + parts[1:]

    if len(parts) == 0:
        # New command.
        completer = _command_completer

    elif len(parts) >= 1 and last_part.startswith('-'):
        flags = get_option_flags_for_command(parts[0])
        completer = WordCompleter(sorted(flags), WORD=True)

    elif len(parts) == 1 and parts[0] in ('set-option', 'set-window-option'):
        options = pymux.options if parts[0] == 'set-option' else pymux.window_options

        completer = WordCompleter(sorted(options.keys()), sentence=True)

    elif len(parts) == 2 and parts[0] in ('set-option', 'set-window-option'):
        options = pymux.options if parts[0] == 'set-option' else pymux.window_options

        option = options.get(parts[1])
        if option:
            completer = WordCompleter(sorted(option.get_all_values(pymux)), sentence=True)

    elif len(parts) == 1 and parts[0] == 'select-layout':
        completer = _layout_type_completer

    elif len(parts) == 1 and parts[0] == 'send-keys':
        completer = _keys_completer

    elif parts[0] == 'bind-key':
        if len(parts) == 1:
            completer = _keys_completer

        elif len(parts) == 2:
            completer = _command_completer

    # Recursive, for bind-key options.
    if parts and parts[0] == 'bind-key' and len(parts) > 2:
        for c in get_completions_for_parts(parts[2:], last_part, complete_event, pymux):
            yield c

    if completer:
        for c in completer.get_completions(Document(last_part), complete_event):
            yield c
Esempio n. 8
0
def test_word_completer_match_middle():
    completer = WordCompleter(['abc', 'def', 'abca'], match_middle=True)
    completions = completer.get_completions(Document('bc'), CompleteEvent())
    assert [c.text for c in completions] == ['abc', 'abca']
Esempio n. 9
0
class CommandsRegistry(object):
    """
    A registry that holds all commands implementations and creates a quick
    access point for resolving a command string into the corresponding handling
    object
    """
    def __init__(self, parser, listeners):
        self._completer = WordCompleter([], ignore_case=True, sentence=True)
        # maps a command to Command Instance
        self._cmd_instance_map = {}
        # objects interested in receiving messages
        self._listeners = []
        # argparser so each command can add its options
        self._parser = parser

        for lst in listeners:
            self.register_listener(lst(self))

    def _make_human_suggestion(self, suggestions):
        if len(suggestions) == 1:
            return suggestions[0]
        human_string = ", ".join(suggestions[:-1])
        return human_string + " or {}".format(suggestions[-1])

    def register_command(self, cmd_instance, override=False):
        if not isinstance(cmd_instance, Command):
            raise TypeError("Invalid command instance, must be an instance of "
                            "subclass of Command")

        cmd_instance.set_command_registry(self)
        cmd_keys = cmd_instance.get_command_names()
        for cmd in cmd_keys:
            if not cmd_instance.get_help(cmd):
                cprint(
                    ("[WARNING] The command {} will not be loaded. "
                     "Please provide a help message by either defining a "
                     "docstring or filling the help argument in the "
                     "@command annotation").format(cmd_keys[0]),
                    "red",
                )
                return None

        cmd_instance.add_arguments(self._parser)

        if not override:
            conflicts = [
                cmd for cmd in cmd_keys if cmd in self._cmd_instance_map
            ]
            if conflicts:
                raise ValueError("Some other command instance has registered "
                                 "the name(s) {}".format(conflicts))

        if isinstance(cmd_instance, Listener):
            self._listeners.append(cmd_instance)

        for cmd in cmd_keys:
            self._cmd_instance_map[cmd.lower()] = cmd_instance
            if cmd not in self._completer.words:
                self._completer.words.append(cmd)
                self._completer.meta_dict[cmd] = cmd_instance.get_help(cmd)

        aliases = cmd_instance.get_cli_aliases()
        for alias in aliases:
            self._cmd_instance_map[alias.lower()] = cmd_instance

    def register_priority_listener(self, instance):
        """
        Registers a listener that get the top priority in callbacks
        """
        if not isinstance(instance, Listener):
            raise TypeError("Only Listeners can be registered")
        self._listeners.insert(0, instance)

    def register_listener(self, instance):
        if not isinstance(instance, Listener):
            raise TypeError("Only Listeners can be registered")
        self._listeners.append(instance)

    def __contains__(self, cmd):
        return cmd.lower() in self._cmd_instance_map

    def get_completer(self):
        return self._completer

    def get_all_commands(self):
        return set(self._cmd_instance_map.values())

    def find_command(self, cmd):
        return self._cmd_instance_map.get(cmd.lower())

    def find_approx(self, cmd):
        """Finds the closest command to the passed cmd, this is used in case we
        cannot find an exact match for the cmd
        """
        suggestions = []
        for c in self._cmd_instance_map.keys():
            dist = Levenshtein.distance(str(cmd), str(c))
            if dist <= 2:
                suggestions.append(c)
        if not suggestions:
            return ""
        return " Did you mean `{}`?".format(
            self._make_human_suggestion(suggestions))

    def get_completions(self, document, complete_event):
        return self._completer.get_completions(document, complete_event)

    def dispatch_message(self, msg, *args, **kwargs):
        for mod in self._listeners:
            mod.react(msg, *args, **kwargs)

    def set_cli_args(self, args):
        self._args = args

    def get_cli_arg(self, arg):
        return getattr(self._args, arg, None)
Esempio n. 10
0
class HummingbotCompleter(Completer):
    def __init__(self, hummingbot_application):
        super(HummingbotCompleter, self).__init__()
        self.hummingbot_application = hummingbot_application
        self._path_completer = WordCompleter(
            file_name_list(CONF_FILE_PATH, "yml"))
        self._command_completer = WordCompleter(self.parser.commands,
                                                ignore_case=True)
        self._exchange_completer = WordCompleter(sorted(
            CONNECTOR_SETTINGS.keys()),
                                                 ignore_case=True)
        self._spot_completer = WordCompleter(sorted(
            EXCHANGES.union(SPOT_PROTOCOL_CONNECTOR)),
                                             ignore_case=True)
        self._spot_exchange_completer = WordCompleter(sorted(EXCHANGES),
                                                      ignore_case=True)
        self._derivative_completer = WordCompleter(DERIVATIVES,
                                                   ignore_case=True)
        self._derivative_exchange_completer = WordCompleter(
            DERIVATIVES.difference(DERIVATIVE_PROTOCOL_CONNECTOR),
            ignore_case=True)
        self._connect_option_completer = WordCompleter(CONNECT_OPTIONS,
                                                       ignore_case=True)
        self._export_completer = WordCompleter(["keys", "trades"],
                                               ignore_case=True)
        self._balance_completer = WordCompleter(["limit", "paper"],
                                                ignore_case=True)
        self._history_completer = WordCompleter(
            ["--days", "--verbose", "--precision"], ignore_case=True)
        self._strategy_completer = WordCompleter(STRATEGIES, ignore_case=True)
        self._py_file_completer = WordCompleter(
            file_name_list(SCRIPTS_PATH, "py"))
        self._rate_oracle_completer = WordCompleter(
            [r.name for r in RateOracleSource], ignore_case=True)

    @property
    def prompt_text(self) -> str:
        return self.hummingbot_application.app.prompt_text

    @property
    def parser(self) -> ThrowingArgumentParser:
        return self.hummingbot_application.parser

    def get_subcommand_completer(self, first_word: str) -> Completer:
        subcommands: List[str] = self.parser.subcommands_from(first_word)
        return WordCompleter(subcommands, ignore_case=True)

    @property
    def _trading_pair_completer(self) -> Completer:
        trading_pair_fetcher = TradingPairFetcher.get_instance()
        market = ""
        for exchange in sorted(list(CONNECTOR_SETTINGS.keys()),
                               key=len,
                               reverse=True):
            if exchange in self.prompt_text:
                market = exchange
                break
        trading_pairs = trading_pair_fetcher.trading_pairs.get(
            market, []) if trading_pair_fetcher.ready and market else []
        return WordCompleter(trading_pairs, ignore_case=True, sentence=True)

    @property
    def _wallet_address_completer(self):
        return WordCompleter(list_wallets(), ignore_case=True)

    @property
    def _option_completer(self):
        outer = re.compile(r"\((.+)\)")
        inner_str = outer.search(self.prompt_text).group(1)
        options = inner_str.split("/") if "/" in inner_str else []
        return WordCompleter(options, ignore_case=True)

    @property
    def _config_completer(self):
        config_keys = self.hummingbot_application.config_able_keys()
        return WordCompleter(config_keys, ignore_case=True)

    def _complete_strategies(self, document: Document) -> bool:
        return "strategy" in self.prompt_text and "strategy file" not in self.prompt_text

    def _complete_script_files(self, document: Document) -> bool:
        return "script file" in self.prompt_text

    def _complete_configs(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return "config" in text_before_cursor

    def _complete_options(self, document: Document) -> bool:
        return "(" in self.prompt_text and ")" in self.prompt_text and "/" in self.prompt_text

    def _complete_exchanges(self, document: Document) -> bool:
        return any(x for x in ("exchange name", "name of exchange",
                               "name of the exchange")
                   if x in self.prompt_text.lower())

    def _complete_derivatives(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return "perpetual" in text_before_cursor or \
               any(x for x in ("derivative connector", "derivative name", "name of derivative", "name of the derivative")
                   if x in self.prompt_text.lower())

    def _complete_connect_options(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return text_before_cursor.startswith("connect ")

    def _complete_spot_connectors(self, document: Document) -> bool:
        return "spot" in self.prompt_text

    def _complete_export_options(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return "export" in text_before_cursor

    def _complete_balance_options(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return text_before_cursor.startswith("balance ")

    def _complete_history_arguments(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return text_before_cursor.startswith("history ")

    def _complete_trading_pairs(self, document: Document) -> bool:
        return "trading pair" in self.prompt_text

    def _complete_paths(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return (("path" in self.prompt_text and "file" in self.prompt_text)
                or "import" in text_before_cursor)

    def _complete_wallet_addresses(self, document: Document) -> bool:
        return "Which wallet" in self.prompt_text

    def _complete_command(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return " " not in text_before_cursor and len(
            self.prompt_text.replace(">>> ", "")) == 0

    def _complete_subcommand(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        index: int = text_before_cursor.index(' ')
        return text_before_cursor[0:index] in self.parser.commands

    def _complete_balance_limit_exchanges(self, document: Document):
        text_before_cursor: str = document.text_before_cursor
        command_args = text_before_cursor.split(" ")
        return len(command_args) == 3 and command_args[
            0] == "balance" and command_args[1] == "limit"

    def _complete_rate_oracle_source(self, document: Document):
        return all(x in self.prompt_text for x in ("source", "rate oracle"))

    def get_completions(self, document: Document,
                        complete_event: CompleteEvent):
        """
        Get completions for the current scope. This is the defining function for the completer
        :param document:
        :param complete_event:
        """
        if self._complete_script_files(document):
            for c in self._py_file_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_paths(document):
            for c in self._path_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_strategies(document):
            for c in self._strategy_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_wallet_addresses(document):
            for c in self._wallet_address_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_spot_connectors(document):
            if "(Exchange/AMM)" in self.prompt_text:
                for c in self._spot_completer.get_completions(
                        document, complete_event):
                    yield c
            else:
                for c in self._spot_exchange_completer.get_completions(
                        document, complete_event):
                    yield c

        elif self._complete_connect_options(document):
            for c in self._connect_option_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_export_options(document):
            for c in self._export_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_balance_limit_exchanges(document):
            for c in self._connect_option_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_balance_options(document):
            for c in self._balance_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_history_arguments(document):
            for c in self._history_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_derivatives(document):
            if "(Exchange/AMM)" in self.prompt_text:
                for c in self._derivative_completer.get_completions(
                        document, complete_event):
                    yield c
            else:
                for c in self._derivative_exchange_completer.get_completions(
                        document, complete_event):
                    yield c

        elif self._complete_exchanges(document):
            for c in self._exchange_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_trading_pairs(document):
            for c in self._trading_pair_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_command(document):
            for c in self._command_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_configs(document):
            for c in self._config_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_options(document):
            for c in self._option_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_rate_oracle_source(document):
            for c in self._rate_oracle_completer.get_completions(
                    document, complete_event):
                yield c

        else:
            text_before_cursor: str = document.text_before_cursor
            try:
                first_word: str = text_before_cursor[0:text_before_cursor.
                                                     index(' ')]
            except ValueError:
                return
            subcommand_completer: Completer = self.get_subcommand_completer(
                first_word)
            if complete_event.completion_requested or self._complete_subcommand(
                    document):
                for c in subcommand_completer.get_completions(
                        document, complete_event):
                    yield c
Esempio n. 11
0
class HummingbotCompleter(Completer):
    def __init__(self, hummingbot_application):
        super(HummingbotCompleter, self).__init__()
        self.hummingbot_application = hummingbot_application

        # static completers
        self._path_completer = PathCompleter(get_paths=lambda: [f"./{CONF_FILE_PATH}"],
                                             file_filter=lambda fname: fname.endswith(".yml"))
        self._command_completer = WordCompleter(self.parser.commands, ignore_case=True)
        self._exchange_completer = WordCompleter(EXCHANGES, ignore_case=True)
        self._strategy_completer = WordCompleter(STRATEGIES, ignore_case=True)

    @property
    def prompt_text(self) -> str:
        return self.hummingbot_application.app.prompt_text

    @property
    def parser(self) -> ThrowingArgumentParser:
        return self.hummingbot_application.parser

    def get_subcommand_completer(self, first_word: str) -> Completer:
        subcommands: List[str] = self.parser.subcommands_from(first_word)
        return WordCompleter(subcommands, ignore_case=True)

    @property
    def _trading_pair_completer(self) -> Completer:
        trading_pair_fetcher = TradingPairFetcher.get_instance()
        market = None
        for exchange in EXCHANGES:
            if exchange in self.prompt_text:
                market = exchange
                break
        trading_pairs = trading_pair_fetcher.trading_pairs.get(market, []) if trading_pair_fetcher.ready else []
        return WordCompleter(trading_pairs, ignore_case=True)

    @property
    def _wallet_address_completer(self):
        return WordCompleter(list_wallets(), ignore_case=True)

    @property
    def _option_completer(self):
        outer = re.compile(r"\((.+)\)")
        inner_str = outer.search(self.prompt_text).group(1)
        options = inner_str.split("/") if "/" in inner_str else []
        return WordCompleter(options, ignore_case=True)

    @property
    def _config_completer(self):
        return WordCompleter(self.hummingbot_application.get_all_available_config_keys(), ignore_case=True)

    def _complete_strategies(self, document: Document) -> bool:
        return "strategy" in self.prompt_text

    def _complete_configs(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return "config" in text_before_cursor

    def _complete_options(self, document: Document) -> bool:
        return "(" in self.prompt_text and ")" in self.prompt_text and "/" in self.prompt_text

    def _complete_exchanges(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return "-e" in text_before_cursor or \
               "--exchange" in text_before_cursor or \
               "exchange" in self.prompt_text

    def _complete_trading_pairs(self, document: Document) -> bool:
        return "trading_pair" in self.prompt_text

    def _complete_paths(self, document: Document) -> bool:
        return "path" in self.prompt_text and "file" in self.prompt_text

    def _complete_wallet_addresses(self, document: Document) -> bool:
        return "Which wallet" in self.prompt_text

    def _complete_command(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return " " not in text_before_cursor and len(self.prompt_text.replace(">>> ", "")) == 0

    def _complete_subcommand(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        index: int = text_before_cursor.index(' ')
        return text_before_cursor[0:index] in self.parser.commands

    def get_completions(self, document: Document, complete_event: CompleteEvent):
        """
        Get completions for the current scope. This is the defining function for the completer
        :param document:
        :param complete_event:
        """
        if self._complete_paths(document):
            for c in self._path_completer.get_completions(document, complete_event):
                yield c
            return

        if self._complete_strategies(document):
            for c in self._strategy_completer.get_completions(document, complete_event):
                yield c

        if self._complete_wallet_addresses(document):
            for c in self._wallet_address_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_exchanges(document):
            for c in self._exchange_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_trading_pairs(document):
            for c in self._trading_pair_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_command(document):
            for c in self._command_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_configs(document):
            for c in self._config_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_options(document):
            for c in self._option_completer.get_completions(document, complete_event):
                yield c

        else:
            text_before_cursor: str = document.text_before_cursor
            first_word: str = text_before_cursor[0:text_before_cursor.index(' ')]
            subcommand_completer: Completer = self.get_subcommand_completer(first_word)
            if complete_event.completion_requested or self._complete_subcommand(document):
                for c in subcommand_completer.get_completions(document, complete_event):
                    yield c
Esempio n. 12
0
def test_word_completer_match_middle():
    completer = WordCompleter(["abc", "def", "abca"], match_middle=True)
    completions = completer.get_completions(Document("bc"), CompleteEvent())
    assert [c.text for c in completions] == ["abc", "abca"]
Esempio n. 13
0
class HummingbotCompleter(Completer):
    def __init__(self, hummingbot_application):
        super(HummingbotCompleter, self).__init__()
        self.hummingbot_application = hummingbot_application
        self._symbols: Dict[str, List[str]] = {}

        # static completers
        self._path_completer = PathCompleter()
        self._command_completer = WordCompleter(self.parser.commands,
                                                ignore_case=True)
        self._exchange_completer = WordCompleter(EXCHANGES, ignore_case=True)
        self._strategy_completer = WordCompleter(STRATEGIES, ignore_case=True)

        asyncio.ensure_future(self._fetch_symbols())

    async def _fetch_symbols(self):
        self._symbols: Dict[str, List[str]] = await fetch_all()

    @property
    def prompt_text(self) -> str:
        return self.hummingbot_application.app.prompt_text

    @property
    def parser(self) -> ThrowingArgumentParser:
        return self.hummingbot_application.parser

    def get_subcommand_completer(self, first_word: str) -> Completer:
        subcommands: List[str] = self.parser.subcommands_from(first_word)
        return WordCompleter(subcommands, ignore_case=True)

    @property
    def _symbol_completer(self) -> Completer:
        market = None
        for exchange in EXCHANGES:
            if exchange in self.prompt_text:
                market = exchange
                break
        return WordCompleter(self._symbols.get(market) or [], ignore_case=True)

    def _complete_strategies(self, document: Document) -> bool:
        return "strategy" in self.prompt_text

    def _complete_exchanges(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return "-e" in text_before_cursor or \
               "--exchange" in text_before_cursor or \
               "exchange" in self.prompt_text

    def _complete_symbols(self, document: Document) -> bool:
        return "symbol" in self.prompt_text

    def _complete_paths(self, document: Document) -> bool:
        return "path" in self.prompt_text and "file" in self.prompt_text

    def _complete_command(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return " " not in text_before_cursor and len(
            self.prompt_text.replace(">>> ", "")) == 0

    def _complete_subcommand(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        index: int = text_before_cursor.index(' ')
        return text_before_cursor[0:index] in self.parser.commands

    def get_completions(self, document: Document,
                        complete_event: CompleteEvent):
        """
        Get completions for the current scope. This is the defining function for the completer
        :param document:
        :param complete_event:
        """
        if self._complete_paths(document):
            for c in self._path_completer.get_completions(
                    document, complete_event):
                yield c

        if self._complete_strategies(document):
            for c in self._strategy_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_exchanges(document):
            for c in self._exchange_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_symbols(document):
            for c in self._symbol_completer.get_completions(
                    document, complete_event):
                yield c

        elif self._complete_command(document):
            for c in self._command_completer.get_completions(
                    document, complete_event):
                yield c

        else:
            text_before_cursor: str = document.text_before_cursor
            first_word: str = text_before_cursor[0:text_before_cursor.index(' '
                                                                            )]
            subcommand_completer: Completer = self.get_subcommand_completer(
                first_word)
            if complete_event.completion_requested or self._complete_subcommand(
                    document):
                for c in subcommand_completer.get_completions(
                        document, complete_event):
                    yield c
Esempio n. 14
0
    def get_completions(self, document: Document,
                        complete_event: CompleteEvent) -> Iterable[Completion]:
        """ Get a list of completions for the given document """

        text = document.text_before_cursor.lstrip()
        try:
            args = shlex.split(text)
        except ValueError:
            try:
                args = shlex.split(text + '"')
            except ValueError:
                args = shlex.split(text + "'")

        # We haven't finished typing the command. Use our word completer for
        # commands
        if text == "" or (len(args) == 1 and not text.endswith(" ")):
            yield from self.completer.get_completions(document, complete_event)
            return

        # Not in a known command, can't autocomplete
        if args[0] not in self.layers:
            return

        command = self.layers[args[0]]
        args = args[1:]
        next_completer = command[0]
        this_completer = command[0]
        positional = 0
        # state = "options", completing options next
        # state = "arguments", completing arguments to options next
        state = "options"

        for arg in args:
            if state == "options":
                # Flag options
                if arg.startswith("-"):
                    # Exact match, with a sub-completer
                    if arg in command[2] and command[2][arg] is not None:
                        # Completer for next argument
                        next_completer = command[2][arg]
                        state = "arguments"
                    # Exact match, with no arguments
                    elif arg in command[2]:
                        # Command has no argument, next completer is options
                        # completer
                        next_completer = command[0]
                        state = "options"
                        this_completer = command[0]
                    # Non-exact match
                    else:
                        next_completer = command[0]
                        this_completer = command[0]
                        state = "options"
                # Appears to be a positional argument, grab next positional
                # completer and increment positional count
                else:
                    if positional < len(command[1]):
                        this_completer = command[1][positional]
                        next_completer = command[0]
                        state = "options"
                        positional += 1
                    else:
                        this_completer = command[0]
                        next_completer = command[0]
                        state = "options"
            else:
                # Completing an argument to a option/switch. We can't verify
                # it's legitimacy, so we assume it's right, and reset to a
                # default state.
                state = "options"
                this_completer = next_completer
                next_completer = command[0]

        if isinstance(this_completer,
                      tuple) and this_completer[0] == "choices":
            this_completer = WordCompleter(this_completer[1])
        if isinstance(next_completer,
                      tuple) and next_completer[0] == "choices":
            next_completer = WordCompleter(next_completer[1])

        if text.endswith(" "):
            yield from next_completer.get_completions(document, complete_event)
        else:
            yield from this_completer.get_completions(document, complete_event)
Esempio n. 15
0
class CommandCompleter(Completer):
    """ Complete commands from a given list of commands """
    def __init__(self, commands: List["CommandDefinition"]):
        """ Construct a new command completer """

        self.layers = {}
        local_file_completer = LocalPathCompleter()
        remote_file_completer = RemotePathCompleter()

        for command in commands:
            self.layers[command.PROG] = [None, [], {}]
            option_names = []
            if command.ARGS is not None:
                for name_list, param in command.ARGS.items():
                    name_list = name_list.split(",")
                    if param.complete == Complete.CHOICES:
                        completer = ("choices", param.kwargs["choices"])
                    elif param.complete == Complete.LOCAL_FILE:
                        completer = local_file_completer
                    elif param.complete == Complete.REMOTE_FILE:
                        completer = remote_file_completer
                    elif param.complete == Complete.NONE:
                        completer = None
                    if len(name_list
                           ) == 1 and not name_list[0].startswith("-"):
                        self.layers[command.PROG][1].append(completer)
                    else:
                        for name in name_list:
                            self.layers[command.PROG][2][name] = completer
                            option_names.append(name)
                self.layers[command.PROG][0] = WordCompleter(option_names +
                                                             ["--help", "-h"])

        self.completer = WordCompleter(list(self.layers))

    def get_completions(self, document: Document,
                        complete_event: CompleteEvent) -> Iterable[Completion]:
        """ Get a list of completions for the given document """

        text = document.text_before_cursor.lstrip()
        try:
            args = shlex.split(text)
        except ValueError:
            try:
                args = shlex.split(text + '"')
            except ValueError:
                args = shlex.split(text + "'")

        # We haven't finished typing the command. Use our word completer for
        # commands
        if text == "" or (len(args) == 1 and not text.endswith(" ")):
            yield from self.completer.get_completions(document, complete_event)
            return

        # Not in a known command, can't autocomplete
        if args[0] not in self.layers:
            return

        command = self.layers[args[0]]
        args = args[1:]
        next_completer = command[0]
        this_completer = command[0]
        positional = 0
        # state = "options", completing options next
        # state = "arguments", completing arguments to options next
        state = "options"

        for arg in args:
            if state == "options":
                # Flag options
                if arg.startswith("-"):
                    # Exact match, with a sub-completer
                    if arg in command[2] and command[2][arg] is not None:
                        # Completer for next argument
                        next_completer = command[2][arg]
                        state = "arguments"
                    # Exact match, with no arguments
                    elif arg in command[2]:
                        # Command has no argument, next completer is options
                        # completer
                        next_completer = command[0]
                        state = "options"
                        this_completer = command[0]
                    # Non-exact match
                    else:
                        next_completer = command[0]
                        this_completer = command[0]
                        state = "options"
                # Appears to be a positional argument, grab next positional
                # completer and increment positional count
                else:
                    if positional < len(command[1]):
                        this_completer = command[1][positional]
                        next_completer = command[0]
                        state = "options"
                        positional += 1
                    else:
                        this_completer = command[0]
                        next_completer = command[0]
                        state = "options"
            else:
                # Completing an argument to a option/switch. We can't verify
                # it's legitimacy, so we assume it's right, and reset to a
                # default state.
                state = "options"
                this_completer = next_completer
                next_completer = command[0]

        if isinstance(this_completer,
                      tuple) and this_completer[0] == "choices":
            this_completer = WordCompleter(this_completer[1])
        if isinstance(next_completer,
                      tuple) and next_completer[0] == "choices":
            next_completer = WordCompleter(next_completer[1])

        if text.endswith(" "):
            yield from next_completer.get_completions(document, complete_event)
        else:
            yield from this_completer.get_completions(document, complete_event)
Esempio n. 16
0
class HummingbotCompleter(Completer):
    def __init__(self, hummingbot_application):
        super(HummingbotCompleter, self).__init__()
        self.hummingbot_application = hummingbot_application
        self._path_completer = WordCompleter(file_name_list(str(STRATEGIES_CONF_DIR_PATH), "yml"))
        self._command_completer = WordCompleter(self.parser.commands, ignore_case=True)
        self._exchange_completer = WordCompleter(sorted(AllConnectorSettings.get_connector_settings().keys()), ignore_case=True)
        self._spot_exchange_completer = WordCompleter(sorted(AllConnectorSettings.get_exchange_names()), ignore_case=True)
        self._exchange_amm_completer = WordCompleter(sorted(AllConnectorSettings.get_exchange_names().union(AllConnectorSettings.get_gateway_evm_amm_connector_names())), ignore_case=True)
        self._evm_amm_lp_completer = WordCompleter(sorted(AllConnectorSettings.get_gateway_evm_amm_lp_connector_names()), ignore_case=True)
        self._trading_timeframe_completer = WordCompleter(["infinite", "from_date_to_date", "daily_between_times"], ignore_case=True)
        self._derivative_completer = WordCompleter(AllConnectorSettings.get_derivative_names(), ignore_case=True)
        self._derivative_exchange_completer = WordCompleter(AllConnectorSettings.get_derivative_names().difference(AllConnectorSettings.get_derivative_dex_names()), ignore_case=True)
        self._connect_option_completer = WordCompleter(CONNECT_OPTIONS, ignore_case=True)
        self._export_completer = WordCompleter(["keys", "trades"], ignore_case=True)
        self._balance_completer = WordCompleter(["limit", "paper"], ignore_case=True)
        self._history_completer = WordCompleter(["--days", "--verbose", "--precision"], ignore_case=True)
        self._gateway_completer = WordCompleter(["create", "config", "connect", "connector-tokens", "generate-certs", "status", "test-connection", "start", "stop"], ignore_case=True)
        self._gateway_connect_completer = WordCompleter(GATEWAY_CONNECTORS, ignore_case=True)
        self._gateway_connector_tokens_completer = WordCompleter(sorted(AllConnectorSettings.get_gateway_evm_amm_connector_names()), ignore_case=True)
        self._gateway_config_completer = WordCompleter(hummingbot_application.gateway_config_keys, ignore_case=True)
        self._strategy_completer = WordCompleter(STRATEGIES, ignore_case=True)
        self._py_file_completer = WordCompleter(file_name_list(str(PMM_SCRIPTS_PATH), "py"))
        self._script_strategy_completer = WordCompleter(file_name_list(str(SCRIPT_STRATEGIES_PATH), "py"))
        self._rate_oracle_completer = WordCompleter([r.name for r in RateOracleSource], ignore_case=True)
        self._gateway_networks = []
        self._list_gateway_wallets_parameters = {"wallets": [], "chain": ""}

    def set_gateway_networks(self, gateway_networks):
        self._gateway_networks = gateway_networks

    def set_list_gateway_wallets_parameters(self, wallets, chain):
        self._list_gateway_wallets_parameters = {"wallets": wallets, "chain": chain}

    @property
    def prompt_text(self) -> str:
        return self.hummingbot_application.app.prompt_text

    @property
    def parser(self) -> ThrowingArgumentParser:
        return self.hummingbot_application.parser

    def get_subcommand_completer(self, first_word: str) -> Completer:
        subcommands: List[str] = self.parser.subcommands_from(first_word)
        return WordCompleter(subcommands, ignore_case=True)

    @property
    def _trading_pair_completer(self) -> Completer:
        trading_pair_fetcher = TradingPairFetcher.get_instance()
        market = ""
        for exchange in sorted(list(AllConnectorSettings.get_connector_settings().keys()), key=len, reverse=True):
            if exchange in self.prompt_text:
                market = exchange
                break
        trading_pairs = trading_pair_fetcher.trading_pairs.get(market, []) if trading_pair_fetcher.ready and market else []
        return WordCompleter(trading_pairs, ignore_case=True, sentence=True)

    @property
    def _gateway_network_completer(self):
        return WordCompleter(self._gateway_networks, ignore_case=True)

    @property
    def _gateway_wallet_address_completer(self):
        return WordCompleter(list_gateway_wallets(self._list_gateway_wallets_parameters["wallets"], self._list_gateway_wallets_parameters["chain"]), ignore_case=True)

    @property
    def _option_completer(self):
        outer = re.compile(r"\((.+)\)")
        inner_str = outer.search(self.prompt_text).group(1)
        options = inner_str.split("/") if "/" in inner_str else []
        return WordCompleter(options, ignore_case=True)

    @property
    def _config_completer(self):
        config_keys = self.hummingbot_application.configurable_keys()
        return WordCompleter(config_keys, ignore_case=True)

    def _complete_strategies(self, document: Document) -> bool:
        return "strategy" in self.prompt_text and "strategy file" not in self.prompt_text

    def _complete_pmm_script_files(self, document: Document) -> bool:
        return "PMM script file" in self.prompt_text

    def _complete_configs(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return "config" in text_before_cursor

    def _complete_options(self, document: Document) -> bool:
        return "(" in self.prompt_text and ")" in self.prompt_text and "/" in self.prompt_text

    def _complete_exchanges(self, document: Document) -> bool:
        return any(x for x in ("exchange name", "name of exchange", "name of the exchange")
                   if x in self.prompt_text.lower())

    def _complete_derivatives(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return "perpetual" in text_before_cursor or \
               any(x for x in ("derivative connector", "derivative name", "name of derivative", "name of the derivative")
                   if x in self.prompt_text.lower())

    def _complete_connect_options(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return text_before_cursor.startswith("connect ")

    def _complete_exchange_amm_connectors(self, document: Document) -> bool:
        return "(Exchange/AMM)" in self.prompt_text

    def _complete_spot_exchanges(self, document: Document) -> bool:
        return "spot" in self.prompt_text

    def _complete_lp_connector(self, document: Document) -> bool:
        return "LP" in self.prompt_text

    def _complete_trading_timeframe(self, document: Document) -> bool:
        return any(x for x in ("trading timeframe", "execution timeframe")
                   if x in self.prompt_text.lower())

    def _complete_export_options(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return "export" in text_before_cursor

    def _complete_balance_options(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return text_before_cursor.startswith("balance ")

    def _complete_history_arguments(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return text_before_cursor.startswith("history ")

    def _complete_gateway_connect_arguments(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return text_before_cursor.startswith("gateway connect ")

    def _complete_gateway_connector_tokens_arguments(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return text_before_cursor.startswith("gateway connector-tokens ")

    def _complete_gateway_arguments(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return text_before_cursor.startswith("gateway ") and not text_before_cursor.startswith("gateway config ")

    def _complete_gateway_config_arguments(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return text_before_cursor.startswith("gateway config ")

    def _complete_script_strategy_files(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return text_before_cursor.startswith("start --script ")

    def _complete_trading_pairs(self, document: Document) -> bool:
        return "trading pair" in self.prompt_text

    def _complete_paths(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return (("path" in self.prompt_text and "file" in self.prompt_text) or
                "import" in text_before_cursor)

    def _complete_gateway_network(self, document: Document) -> bool:
        return "Which network do you want" in self.prompt_text

    def _complete_gateway_wallet_addresses(self, document: Document) -> bool:
        return "Select a gateway wallet" in self.prompt_text

    def _complete_command(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        return " " not in text_before_cursor and len(self.prompt_text.replace(">>> ", "")) == 0

    def _complete_subcommand(self, document: Document) -> bool:
        text_before_cursor: str = document.text_before_cursor
        index: int = text_before_cursor.index(' ')
        return text_before_cursor[0:index] in self.parser.commands

    def _complete_balance_limit_exchanges(self, document: Document):
        text_before_cursor: str = document.text_before_cursor
        command_args = text_before_cursor.split(" ")
        return len(command_args) == 3 and command_args[0] == "balance" and command_args[1] == "limit"

    def _complete_rate_oracle_source(self, document: Document):
        return all(x in self.prompt_text for x in ("source", "rate oracle"))

    def get_completions(self, document: Document, complete_event: CompleteEvent):
        """
        Get completions for the current scope. This is the defining function for the completer
        :param document:
        :param complete_event:
        """
        if self._complete_pmm_script_files(document):
            for c in self._py_file_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_script_strategy_files(document):
            for c in self._script_strategy_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_paths(document):
            for c in self._path_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_strategies(document):
            for c in self._strategy_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_gateway_network(document):
            for c in self._gateway_network_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_gateway_wallet_addresses(document):
            for c in self._gateway_wallet_address_completer.get_completions(document, complete_event):
                yield c

        if self._complete_lp_connector(document):
            for c in self._evm_amm_lp_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_exchange_amm_connectors(document):
            if self._complete_spot_exchanges(document):
                for c in self._spot_exchange_completer.get_completions(document, complete_event):
                    yield c
            elif self._complete_derivatives(document):
                for c in self._derivative_exchange_completer.get_completions(document, complete_event):
                    yield c
            else:
                for c in self._exchange_amm_completer.get_completions(document, complete_event):
                    yield c

        elif self._complete_spot_exchanges(document):
            for c in self._spot_exchange_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_derivatives(document):
            for c in self._derivative_exchange_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_trading_timeframe(document):
            for c in self._trading_timeframe_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_connect_options(document):
            for c in self._connect_option_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_export_options(document):
            for c in self._export_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_balance_limit_exchanges(document):
            for c in self._connect_option_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_balance_options(document):
            for c in self._balance_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_history_arguments(document):
            for c in self._history_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_gateway_connect_arguments(document):
            for c in self._gateway_connect_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_gateway_connector_tokens_arguments(document):
            for c in self._gateway_connector_tokens_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_gateway_arguments(document):
            for c in self._gateway_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_gateway_config_arguments(document):
            for c in self._gateway_config_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_derivatives(document):
            if "(Exchange/AMM)" in self.prompt_text:
                for c in self._derivative_completer.get_completions(document, complete_event):
                    yield c
            else:
                for c in self._derivative_exchange_completer.get_completions(document, complete_event):
                    yield c

        elif self._complete_exchanges(document):
            for c in self._exchange_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_trading_pairs(document):
            for c in self._trading_pair_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_command(document):
            for c in self._command_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_configs(document):
            for c in self._config_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_options(document):
            for c in self._option_completer.get_completions(document, complete_event):
                yield c

        elif self._complete_rate_oracle_source(document):
            for c in self._rate_oracle_completer.get_completions(document, complete_event):
                yield c

        else:
            text_before_cursor: str = document.text_before_cursor
            try:
                first_word: str = text_before_cursor[0:text_before_cursor.index(' ')]
            except ValueError:
                return
            subcommand_completer: Completer = self.get_subcommand_completer(first_word)
            if complete_event.completion_requested or self._complete_subcommand(document):
                for c in subcommand_completer.get_completions(document, complete_event):
                    yield c
Esempio n. 17
0
class AutoCommand(Command):
    def __init__(self, fn):
        self._built_in = False
        self._fn = fn

        if not callable(fn):
            raise ValueError("fn argument must be a callable")

        self._obj_metadata = inspect_object(fn)
        self._is_super_command = len(self.metadata.subcommands) > 0
        self._subcommand_names = []

        # We never expect a function to be passed here that has a self argument
        # In that case, we should get a bound method
        if "self" in self.metadata.arguments and not inspect.ismethod(self._fn):
            raise ValueError(
                "Expecting either a function (eg. bar) or "
                "a bound method (eg. Foo().bar). "
                "You passed what appears to be an unbound method "
                "(eg. Foo.bar) it has a 'self' argument: %s" % function_to_str(fn)
            )

        if not self.metadata.command:
            raise ValueError(
                "function or class {} needs to be annotated with "
                "@command".format(function_to_str(fn))
            )
        # If this is a super command, we need a completer for sub-commands
        if self.super_command:
            self._commands_completer = WordCompleter(
                [], ignore_case=True, sentence=True
            )
            for _, inspection in self.metadata.subcommands:
                _sub_name = inspection.command.name
                self._commands_completer.words.append(_sub_name)
                self._commands_completer.meta_dict[_sub_name] = dedent(
                    inspection.command.help
                ).strip()
                self._subcommand_names.append(_sub_name)

    @property
    def metadata(self) -> FunctionInspection:
        """
        The Inspection object of this command. This object contains all the
        information required by AutoCommand to understand the command arguments
        type information, help messages, aliases, and attributes.
        """
        return self._obj_metadata

    def _create_subcommand_obj(self, key_values):
        """
        Instantiates an object of the super command class, passes the right
        arguments and returns a dict with the remaining unused arguments
        """
        kwargs = {
            k: v
            for k, v in get_arguments_for_inspection(self.metadata, key_values).items()
            if v is not None
        }
        remaining = {k: v for k, v in key_values.items() if k not in kwargs.keys()}
        return self._fn(**kwargs), remaining

    def run_interactive(self, cmd, args, raw):
        try:
            args_metadata = self.metadata.arguments
            parsed = parser.parse(args, expect_subcommand=self.super_command)

            # prepare args dict
            parsed_dict = parsed.asDict()
            args_dict = parsed.kv.asDict()
            key_values = parsed.kv.asDict()
            command_name = cmd
            # if this is a super command, we need first to create an instance of
            # the class (fn) and pass the right arguments
            if self.super_command:
                subcommand = parsed_dict.get("__subcommand__")
                if not subcommand:
                    cprint(
                        "A sub-command must be supplied, valid values: "
                        "{}".format(", ".join(self._get_subcommands())),
                        "red",
                    )
                    return 2
                sub_inspection = self.subcommand_metadata(subcommand)
                if not sub_inspection:
                    cprint(
                        "Invalid sub-command '{}', valid values: "
                        "{}".format(subcommand, ", ".join(self._get_subcommands())),
                        "red",
                    )
                    return 2
                instance, remaining_args = self._create_subcommand_obj(args_dict)
                assert instance
                args_dict = remaining_args
                key_values = copy.copy(args_dict)
                args_metadata = sub_inspection.arguments
                attrname = self._find_subcommand_attr(subcommand)
                command_name = subcommand
                assert attrname is not None
                fn = getattr(instance, attrname)
            else:
                # not a super-command, use use the function instead
                fn = self._fn
            positionals = parsed_dict["positionals"] if parsed.positionals != "" else []
            # We only allow positionals for arguments that have positional=True
            # ِ We filter out the OrderedDict this way to ensure we don't lose the
            # order of the arguments. We also filter out arguments that have
            # been passed by name already. The order of the positional arguments
            # follows the order of the function definition.
            can_be_positional = self._positional_arguments(
                args_metadata, args_dict.keys()
            )

            if len(positionals) > len(can_be_positional):
                if len(can_be_positional) == 0:
                    err = "This command does not support positional arguments"
                else:
                    # We have more positionals than we should
                    err = (
                        "This command only supports ({}) positional arguments, "
                        "namely arguments ({}). You have passed {} arguments ({})"
                        " instead!"
                    ).format(
                        len(can_be_positional),
                        ", ".join(can_be_positional.keys()),
                        len(positionals),
                        ", ".join(str(x) for x in positionals),
                    )
                cprint(err, "red")
                return 2
            # constuct key_value dict from positional arguments.
            args_from_positionals = {
                key: value for value, key in zip(positionals, can_be_positional)
            }
            # update the total arguments dict with the positionals
            args_dict.update(args_from_positionals)

            # Run some validations on number of arguments provided

            # do we have keys that are supplied in both positionals and
            # key_value style?
            duplicate_keys = set(args_from_positionals.keys()).intersection(
                set(key_values.keys())
            )
            if duplicate_keys:
                cprint(
                    "Arguments '{}' have been passed already, cannot have"
                    " duplicate keys".format(list(duplicate_keys)),
                    "red",
                )
                return 2

            # check for verbosity override in kwargs
            ctx = context.get_context()
            old_verbose = ctx.args.verbose
            if "verbose" in args_dict:
                ctx.set_verbose(args_dict["verbose"])
                del args_dict["verbose"]
                del key_values["verbose"]

            # do we have keys that we know nothing about?
            extra_keys = set(args_dict.keys()) - set(args_metadata)
            if extra_keys:
                cprint(
                    "Unknown argument(s) {} were" " passed".format(list(extra_keys)),
                    "magenta",
                )
                return 2

            # is there any required keys that were not resolved from positionals
            # nor key_values?
            missing_keys = set(args_metadata) - set(args_dict.keys())
            if missing_keys:
                required_missing = []
                for key in missing_keys:
                    if not args_metadata[key].default_value_set:
                        required_missing.append(key)
                if required_missing:
                    cprint(
                        "Missing required argument(s) {} for command"
                        " {}".format(required_missing, command_name),
                        "yellow",
                    )
                    return 3

            # convert expected types for arguments
            for key, value in args_dict.items():
                target_type = args_metadata[key].type
                if target_type is None:
                    target_type = str
                try:
                    new_value = apply_typing(value, target_type)
                except ValueError as e:
                    fn_name = function_to_str(target_type, False, False)
                    cprint(
                        'Cannot convert value "{}" to {} on argument {}'.format(
                            value, fn_name, key
                        ),
                        "yellow",
                    )
                    return 4
                else:
                    args_dict[key] = new_value

            # Validate that arguments with `choices` are supplied with the
            # acceptable values.
            for arg, value in args_dict.items():
                choices = args_metadata[arg].choices
                if choices:
                    # Validate the choices in the case of values and list of
                    # values.
                    if issubclass_(args_metadata[arg].type, typing.List):
                        bad_inputs = [v for v in value if v not in choices]
                        if bad_inputs:
                            cprint(
                                f"Argument '{arg}' got an unexpected "
                                f"value(s) '{bad_inputs}'. Expected one "
                                f"or more of {choices}.",
                                "red",
                            )
                            return 4
                    elif value not in choices:
                        cprint(
                            f"Argument '{arg}' got an unexpected value "
                            f"'{value}'. Expected one of "
                            f"{choices}.",
                            "red",
                        )
                        return 4

            # arguments appear to be fine, time to run the function
            try:
                # convert argument names back to match the function signature
                args_dict = {args_metadata[k].arg: v for k, v in args_dict.items()}
                if inspect.iscoroutinefunction(fn):
                    loop = asyncio.get_event_loop()
                    ret = loop.run_until_complete(fn(**args_dict))
                else:
                    ret = fn(**args_dict)
                ctx.set_verbose(old_verbose)
            except Exception as e:
                cprint("Error running command: {}".format(str(e)), "red")
                cprint("-" * 60, "yellow")
                traceback.print_exc(file=sys.stderr)
                cprint("-" * 60, "yellow")
                return 1

            return ret

        except CommandParseError as e:
            cprint("Error parsing command", "red")
            cprint(cmd + " " + args, "white", attrs=["bold"])
            cprint((" " * (e.col + len(cmd))) + "^", "white", attrs=["bold"])
            cprint(str(e), "yellow")
            return 1

    def _positional_arguments(self, args_metadata, filter_out):
        positionals = OrderedDict()
        for k, v in args_metadata.items():
            if v.positional and k not in filter_out:
                positionals[k] = v
        return positionals

    def subcommand_metadata(self, name: str) -> FunctionInspection:
        assert self.super_command
        subcommands = self.metadata.subcommands
        for _, inspection in subcommands:
            if inspection.command.name == name:
                return inspection

    def _find_subcommand_attr(self, name):
        assert self.super_command
        subcommands = self.metadata.subcommands
        for attr, inspection in subcommands:
            if inspection.command.name == name:
                return attr
        # be explicit about returning None for readability
        return None

    def _get_subcommands(self) -> Iterable[str]:
        assert self.super_command
        return [inspection.command.name for _, inspection in self.metadata.subcommands]

    def _kwargs_for_fn(self, fn, args):
        return {
            k: v
            for k, v in get_arguments_for_command(fn, args).items()
            if v is not None
        }

    def run_cli(self, args):
        # if this is a super-command, we need to dispatch the call to the
        # correct function
        kwargs = self._kwargs_for_fn(self._fn, args)
        try:
            if self._is_super_command:
                # let's instantiate an instance of the klass
                instance = self._fn(**kwargs)
                # we need to find the actual method we want to call, in addition to
                # this we need to extract the correct kwargs for this method
                # find which function it is in the sub commands
                attrname = self._find_subcommand_attr(args._subcmd)
                assert attrname is not None
                fn = getattr(instance, attrname)
                kwargs = self._kwargs_for_fn(fn, args)
            else:
                fn = self._fn
            if inspect.iscoroutinefunction(fn):
                # execute in an event loop
                loop = asyncio.get_event_loop()
                return loop.run_until_complete(fn(**kwargs))
            else:
                return fn(**kwargs)
        except Exception as e:
            cprint("Error running command: {}".format(str(e)), "red")
            cprint("-" * 60, "yellow")
            traceback.print_exc(file=sys.stderr)
            cprint("-" * 60, "yellow")
            return 1

    @property
    def super_command(self):
        return self._is_super_command

    def has_subcommand(self, subcommand):
        assert self.super_command
        return subcommand.lower() in self._subcommand_names

    def add_arguments(self, parser):
        register_command(parser, self.metadata)

    def get_command_names(self):
        command = self.metadata.command
        return [command.name] + command.aliases

    def get_completions(
        self, _: str, document: Document, complete_event: CompleteEvent
    ) -> Iterable[Completion]:
        if self._is_super_command:
            exploded = document.text.lstrip().split(" ", 1)
            # Are we at the first word? we expect a sub-command here
            if len(exploded) <= 1:
                return self._commands_completer.get_completions(
                    document, complete_event
                )

        state_machine = AutoCommandCompletion(self, document, complete_event)
        return state_machine.get_completions()

    def get_help(self, cmd, *args):
        help = self.metadata.command.help
        return dedent(help).strip() if help else None
def test_word_completer_match_middle():
    completer = WordCompleter(['abc', 'def', 'abca'], match_middle=True)
    completions = completer.get_completions(Document('bc'), CompleteEvent())
    assert [c.text for c in completions] == ['abc', 'abca']