Source code for xonsh.completers.argparser

import argparse as ap
import typing as tp

from xonsh.built_ins import XSH
from import RichCompletion
from xonsh.parsers.completion_context import CommandContext

[docs]class ArgparseCompleter: """A completer function for ArgParserAlias commands""" def __init__(self, parser: ap.ArgumentParser, command: CommandContext, **kwargs): args = tuple(c.value for c in command.args[: command.arg_index]) self.parser, self.remaining_args = self.get_parser(parser, args[1:]) self.command = command kwargs["command"] = command self.kwargs = kwargs """will be sent to completer function"""
[docs] @staticmethod def get_parser(parser, args) -> tp.Tuple[ap.ArgumentParser, tp.Tuple[str, ...]]: """Check for sub-parsers""" sub_parsers = {} for act in parser._get_positional_actions(): if act.nargs == ap.PARSER: sub_parsers = act.choices # there should be only one subparser if sub_parsers: for idx, pos in enumerate(args): if pos in sub_parsers: # get the correct parser return ArgparseCompleter.get_parser( sub_parsers[pos], args[idx + 1 :] ) # base scenario return parser, args
[docs] def filled(self, act: ap.Action) -> int: """Consume remaining_args for the given action""" args_len = 0 for arg in self.remaining_args: if arg and arg[0] in self.parser.prefix_chars: # stop when other --option explicitly given break args_len += 1 nargs = ( act.nargs if isinstance(act.nargs, int) else args_len + 1 if act.nargs in {ap.ONE_OR_MORE, ap.ZERO_OR_MORE} else 1 ) if len(self.remaining_args) >= nargs: # consume n-number of args self.remaining_args = self.remaining_args[nargs:] # complete for next action return True return False
def _complete(self, act: ap.Action, **kwargs): if act.choices: yield from act.choices elif hasattr(act, "completer") and callable(act.completer): # type: ignore # call the completer function from xonsh.built_ins import XSH kwargs.update(self.kwargs) yield from act.completer(xsh=XSH, action=act, completer=self, **kwargs) # type: ignore def _complete_pos(self, act): if isinstance(act.choices, dict): # sub-parsers for choice, sub_parser in act.choices.items(): yield RichCompletion( choice, description=sub_parser.description or "", append_space=True, ) else: yield from self._complete(act)
[docs] def complete(self): # options will come before/after positionals options = {act: None for act in self.parser._get_optional_actions()} # remove options that are already filled opt_completions = self._complete_options(options) if opt_completions: yield from opt_completions return for act in self.parser._get_positional_actions(): # number of arguments it consumes if self.filled(act): continue yield from self._complete_pos(act) # close after a valid positional arg completion break opt_completions = self._complete_options(options) if opt_completions: yield from opt_completions return # complete remaining options only if requested or enabled show_opts = XSH.env.get("ALIAS_COMPLETIONS_OPTIONS_BY_DEFAULT", False) if not show_opts: if not ( self.command.prefix and self.command.prefix[0] in self.parser.prefix_chars ): return # in the end after positionals show remaining unfilled options for act in options: for flag in act.option_strings: desc = "" if formatter = self.parser._get_formatter() try: desc = formatter._expand_help(act) except KeyError: desc = yield RichCompletion(flag, description=desc)
def _complete_options(self, options): while self.remaining_args: arg = self.remaining_args[0] act_res = self.parser._parse_optional(arg) if not act_res: # it is not a option string: pass break # it is a valid option and advance self.remaining_args = self.remaining_args[1:] act, _, value = act_res # remove the found option # todo: not remove if append/extend options.pop(act, None) if self.filled(act): continue # stop suggestion until current option is complete return self._complete(act)
[docs]def complete_argparser(parser, command: CommandContext, **kwargs): completer = ArgparseCompleter(parser, command=command, **kwargs) yield from completer.complete()