mirror of
https://github.com/YunoHost-Apps/zwave-js-ui_ynh.git
synced 2024-09-03 18:06:00 +02:00
722 lines
30 KiB
Python
722 lines
30 KiB
Python
# Copyright 2012-2021, Andrey Kislyuk and argcomplete contributors.
|
|
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
|
|
|
import argparse
|
|
import contextlib
|
|
import os
|
|
import sys
|
|
|
|
from . import completers
|
|
from . import my_shlex as shlex
|
|
from .completers import FilesCompleter, SuppressCompleter
|
|
from .my_argparse import IntrospectiveArgumentParser, action_is_greedy, action_is_open, action_is_satisfied
|
|
from .shell_integration import shellcode # noqa
|
|
|
|
_DEBUG = "_ARC_DEBUG" in os.environ
|
|
|
|
debug_stream = sys.stderr
|
|
|
|
|
|
def debug(*args):
|
|
if _DEBUG:
|
|
print(file=debug_stream, *args)
|
|
|
|
|
|
BASH_FILE_COMPLETION_FALLBACK = 79
|
|
BASH_DIR_COMPLETION_FALLBACK = 80
|
|
|
|
safe_actions = (
|
|
argparse._StoreAction,
|
|
argparse._StoreConstAction,
|
|
argparse._StoreTrueAction,
|
|
argparse._StoreFalseAction,
|
|
argparse._AppendAction,
|
|
argparse._AppendConstAction,
|
|
argparse._CountAction,
|
|
)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def mute_stdout():
|
|
stdout = sys.stdout
|
|
sys.stdout = open(os.devnull, "w")
|
|
try:
|
|
yield
|
|
finally:
|
|
sys.stdout = stdout
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def mute_stderr():
|
|
stderr = sys.stderr
|
|
sys.stderr = open(os.devnull, "w")
|
|
try:
|
|
yield
|
|
finally:
|
|
sys.stderr.close()
|
|
sys.stderr = stderr
|
|
|
|
|
|
class ArgcompleteException(Exception):
|
|
pass
|
|
|
|
|
|
def split_line(line, point=None):
|
|
if point is None:
|
|
point = len(line)
|
|
line = line[:point]
|
|
lexer = shlex.shlex(line, posix=True)
|
|
lexer.whitespace_split = True
|
|
lexer.wordbreaks = os.environ.get("_ARGCOMPLETE_COMP_WORDBREAKS", "")
|
|
words = []
|
|
|
|
def split_word(word):
|
|
# TODO: make this less ugly
|
|
point_in_word = len(word) + point - lexer.instream.tell()
|
|
if isinstance(lexer.state, (str, bytes)) and lexer.state in lexer.whitespace:
|
|
point_in_word += 1
|
|
if point_in_word > len(word):
|
|
debug("In trailing whitespace")
|
|
words.append(word)
|
|
word = ""
|
|
prefix, suffix = word[:point_in_word], word[point_in_word:]
|
|
prequote = ""
|
|
# posix
|
|
if lexer.state is not None and lexer.state in lexer.quotes:
|
|
prequote = lexer.state
|
|
# non-posix
|
|
# if len(prefix) > 0 and prefix[0] in lexer.quotes:
|
|
# prequote, prefix = prefix[0], prefix[1:]
|
|
|
|
return prequote, prefix, suffix, words, lexer.last_wordbreak_pos
|
|
|
|
while True:
|
|
try:
|
|
word = lexer.get_token()
|
|
if word == lexer.eof:
|
|
# TODO: check if this is ever unsafe
|
|
# raise ArgcompleteException("Unexpected end of input")
|
|
return "", "", "", words, None
|
|
if lexer.instream.tell() >= point:
|
|
debug("word", word, "split, lexer state: '{s}'".format(s=lexer.state))
|
|
return split_word(word)
|
|
words.append(word)
|
|
except ValueError:
|
|
debug("word", lexer.token, "split (lexer stopped, state: '{s}')".format(s=lexer.state))
|
|
if lexer.instream.tell() >= point:
|
|
return split_word(lexer.token)
|
|
else:
|
|
msg = (
|
|
"Unexpected internal state. "
|
|
"Please report this bug at https://github.com/kislyuk/argcomplete/issues."
|
|
)
|
|
raise ArgcompleteException(msg)
|
|
|
|
|
|
def default_validator(completion, prefix):
|
|
return completion.startswith(prefix)
|
|
|
|
|
|
class CompletionFinder(object):
|
|
"""
|
|
Inherit from this class if you wish to override any of the stages below. Otherwise, use
|
|
``argcomplete.autocomplete()`` directly (it's a convenience instance of this class). It has the same signature as
|
|
:meth:`CompletionFinder.__call__()`.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
argument_parser=None,
|
|
always_complete_options=True,
|
|
exclude=None,
|
|
validator=None,
|
|
print_suppressed=False,
|
|
default_completer=FilesCompleter(),
|
|
append_space=None,
|
|
):
|
|
self._parser = argument_parser
|
|
self.always_complete_options = always_complete_options
|
|
self.exclude = exclude
|
|
if validator is None:
|
|
validator = default_validator
|
|
self.validator = validator
|
|
self.print_suppressed = print_suppressed
|
|
self.completing = False
|
|
self._display_completions = {}
|
|
self.default_completer = default_completer
|
|
if append_space is None:
|
|
append_space = os.environ.get("_ARGCOMPLETE_SUPPRESS_SPACE") != "1"
|
|
self.append_space = append_space
|
|
|
|
def __call__(
|
|
self,
|
|
argument_parser,
|
|
always_complete_options=True,
|
|
exit_method=os._exit,
|
|
output_stream=None,
|
|
exclude=None,
|
|
validator=None,
|
|
print_suppressed=False,
|
|
append_space=None,
|
|
default_completer=FilesCompleter(),
|
|
):
|
|
"""
|
|
:param argument_parser: The argument parser to autocomplete on
|
|
:type argument_parser: :class:`argparse.ArgumentParser`
|
|
:param always_complete_options:
|
|
Controls the autocompletion of option strings if an option string opening character (normally ``-``) has not
|
|
been entered. If ``True`` (default), both short (``-x``) and long (``--x``) option strings will be
|
|
suggested. If ``False``, no option strings will be suggested. If ``long``, long options and short options
|
|
with no long variant will be suggested. If ``short``, short options and long options with no short variant
|
|
will be suggested.
|
|
:type always_complete_options: boolean or string
|
|
:param exit_method:
|
|
Method used to stop the program after printing completions. Defaults to :meth:`os._exit`. If you want to
|
|
perform a normal exit that calls exit handlers, use :meth:`sys.exit`.
|
|
:type exit_method: callable
|
|
:param exclude: List of strings representing options to be omitted from autocompletion
|
|
:type exclude: iterable
|
|
:param validator:
|
|
Function to filter all completions through before returning (called with two string arguments, completion
|
|
and prefix; return value is evaluated as a boolean)
|
|
:type validator: callable
|
|
:param print_suppressed:
|
|
Whether or not to autocomplete options that have the ``help=argparse.SUPPRESS`` keyword argument set.
|
|
:type print_suppressed: boolean
|
|
:param append_space:
|
|
Whether to append a space to unique matches. The default is ``True``.
|
|
:type append_space: boolean
|
|
|
|
.. note::
|
|
If you are not subclassing CompletionFinder to override its behaviors,
|
|
use ``argcomplete.autocomplete()`` directly. It has the same signature as this method.
|
|
|
|
Produces tab completions for ``argument_parser``. See module docs for more info.
|
|
|
|
Argcomplete only executes actions if their class is known not to have side effects. Custom action classes can be
|
|
added to argcomplete.safe_actions, if their values are wanted in the ``parsed_args`` completer argument, or
|
|
their execution is otherwise desirable.
|
|
"""
|
|
self.__init__(
|
|
argument_parser,
|
|
always_complete_options=always_complete_options,
|
|
exclude=exclude,
|
|
validator=validator,
|
|
print_suppressed=print_suppressed,
|
|
append_space=append_space,
|
|
default_completer=default_completer,
|
|
)
|
|
|
|
if "_ARGCOMPLETE" not in os.environ:
|
|
# not an argument completion invocation
|
|
return
|
|
|
|
global debug_stream
|
|
try:
|
|
debug_stream = os.fdopen(9, "w")
|
|
except Exception:
|
|
debug_stream = sys.stderr
|
|
debug()
|
|
|
|
if output_stream is None:
|
|
filename = os.environ.get("_ARGCOMPLETE_STDOUT_FILENAME")
|
|
if filename is not None:
|
|
debug("Using output file {}".format(filename))
|
|
output_stream = open(filename, "w")
|
|
|
|
if output_stream is None:
|
|
try:
|
|
output_stream = os.fdopen(8, "w")
|
|
except Exception:
|
|
debug("Unable to open fd 8 for writing, quitting")
|
|
exit_method(1)
|
|
|
|
# print("", stream=debug_stream)
|
|
# for v in "COMP_CWORD COMP_LINE COMP_POINT COMP_TYPE COMP_KEY _ARGCOMPLETE_COMP_WORDBREAKS COMP_WORDS".split():
|
|
# print(v, os.environ[v], stream=debug_stream)
|
|
|
|
ifs = os.environ.get("_ARGCOMPLETE_IFS", "\013")
|
|
if len(ifs) != 1:
|
|
debug("Invalid value for IFS, quitting [{v}]".format(v=ifs))
|
|
exit_method(1)
|
|
|
|
dfs = os.environ.get("_ARGCOMPLETE_DFS")
|
|
if dfs and len(dfs) != 1:
|
|
debug("Invalid value for DFS, quitting [{v}]".format(v=dfs))
|
|
exit_method(1)
|
|
|
|
comp_line = os.environ["COMP_LINE"]
|
|
comp_point = int(os.environ["COMP_POINT"])
|
|
|
|
cword_prequote, cword_prefix, cword_suffix, comp_words, last_wordbreak_pos = split_line(comp_line, comp_point)
|
|
|
|
# _ARGCOMPLETE is set by the shell script to tell us where comp_words
|
|
# should start, based on what we're completing.
|
|
# 1: <script> [args]
|
|
# 2: python <script> [args]
|
|
# 3: python -m <module> [args]
|
|
start = int(os.environ["_ARGCOMPLETE"]) - 1
|
|
comp_words = comp_words[start:]
|
|
|
|
if cword_prefix and cword_prefix[0] in self._parser.prefix_chars and "=" in cword_prefix:
|
|
# Special case for when the current word is "--optional=PARTIAL_VALUE". Give the optional to the parser.
|
|
comp_words.append(cword_prefix.split("=", 1)[0])
|
|
|
|
debug(
|
|
"\nLINE: {!r}".format(comp_line),
|
|
"\nPOINT: {!r}".format(comp_point),
|
|
"\nPREQUOTE: {!r}".format(cword_prequote),
|
|
"\nPREFIX: {!r}".format(cword_prefix),
|
|
"\nSUFFIX: {!r}".format(cword_suffix),
|
|
"\nWORDS:",
|
|
comp_words,
|
|
)
|
|
|
|
completions = self._get_completions(comp_words, cword_prefix, cword_prequote, last_wordbreak_pos)
|
|
|
|
if dfs:
|
|
display_completions = {
|
|
key_part: value.replace(ifs, " ") if value else ""
|
|
for key, value in self._display_completions.items()
|
|
for key_part in key
|
|
}
|
|
completions = [dfs.join((key, display_completions.get(key) or "")) for key in completions]
|
|
|
|
debug("\nReturning completions:", completions)
|
|
output_stream.write(ifs.join(completions))
|
|
output_stream.flush()
|
|
debug_stream.flush()
|
|
exit_method(0)
|
|
|
|
def _get_completions(self, comp_words, cword_prefix, cword_prequote, last_wordbreak_pos):
|
|
active_parsers = self._patch_argument_parser()
|
|
|
|
parsed_args = argparse.Namespace()
|
|
self.completing = True
|
|
|
|
try:
|
|
debug("invoking parser with", comp_words[1:])
|
|
with mute_stderr():
|
|
a = self._parser.parse_known_args(comp_words[1:], namespace=parsed_args)
|
|
debug("parsed args:", a)
|
|
except BaseException as e:
|
|
debug("\nexception", type(e), str(e), "while parsing args")
|
|
|
|
self.completing = False
|
|
|
|
if "--" in comp_words:
|
|
self.always_complete_options = False
|
|
|
|
completions = self.collect_completions(active_parsers, parsed_args, cword_prefix, debug)
|
|
completions = self.filter_completions(completions)
|
|
completions = self.quote_completions(completions, cword_prequote, last_wordbreak_pos)
|
|
return completions
|
|
|
|
def _patch_argument_parser(self):
|
|
"""
|
|
Since argparse doesn't support much introspection, we monkey-patch it to replace the parse_known_args method and
|
|
all actions with hooks that tell us which action was last taken or about to be taken, and let us have the parser
|
|
figure out which subparsers need to be activated (then recursively monkey-patch those).
|
|
We save all active ArgumentParsers to extract all their possible option names later.
|
|
"""
|
|
self.active_parsers = []
|
|
self.visited_positionals = []
|
|
|
|
completer = self
|
|
|
|
def patch(parser):
|
|
completer.visited_positionals.append(parser)
|
|
completer.active_parsers.append(parser)
|
|
|
|
if isinstance(parser, IntrospectiveArgumentParser):
|
|
return
|
|
|
|
classname = "MonkeyPatchedIntrospectiveArgumentParser"
|
|
|
|
parser.__class__ = type(classname, (IntrospectiveArgumentParser, parser.__class__), {})
|
|
|
|
for action in parser._actions:
|
|
if hasattr(action, "_orig_class"):
|
|
continue
|
|
|
|
# TODO: accomplish this with super
|
|
class IntrospectAction(action.__class__):
|
|
def __call__(self, parser, namespace, values, option_string=None):
|
|
debug("Action stub called on", self)
|
|
debug("\targs:", parser, namespace, values, option_string)
|
|
debug("\torig class:", self._orig_class)
|
|
debug("\torig callable:", self._orig_callable)
|
|
|
|
if not completer.completing:
|
|
self._orig_callable(parser, namespace, values, option_string=option_string)
|
|
elif issubclass(self._orig_class, argparse._SubParsersAction):
|
|
debug("orig class is a subparsers action: patching and running it")
|
|
patch(self._name_parser_map[values[0]])
|
|
self._orig_callable(parser, namespace, values, option_string=option_string)
|
|
elif self._orig_class in safe_actions:
|
|
if not self.option_strings:
|
|
completer.visited_positionals.append(self)
|
|
|
|
self._orig_callable(parser, namespace, values, option_string=option_string)
|
|
|
|
action._orig_class = action.__class__
|
|
action._orig_callable = action.__call__
|
|
action.__class__ = IntrospectAction
|
|
|
|
patch(self._parser)
|
|
|
|
debug("Active parsers:", self.active_parsers)
|
|
debug("Visited positionals:", self.visited_positionals)
|
|
|
|
return self.active_parsers
|
|
|
|
def _get_subparser_completions(self, parser, cword_prefix):
|
|
def filter_aliases(aliases, prefix):
|
|
return tuple(x for x in aliases if x.startswith(prefix))
|
|
|
|
aliases_by_parser = {}
|
|
for key in parser.choices.keys():
|
|
p = parser.choices[key]
|
|
aliases_by_parser.setdefault(p, []).append(key)
|
|
|
|
for action in parser._get_subactions():
|
|
subcmd_with_aliases = filter_aliases(aliases_by_parser[parser.choices[action.dest]], cword_prefix)
|
|
if subcmd_with_aliases:
|
|
self._display_completions[subcmd_with_aliases] = action.help
|
|
|
|
completions = [subcmd for subcmd in parser.choices.keys() if subcmd.startswith(cword_prefix)]
|
|
return completions
|
|
|
|
def _include_options(self, action, cword_prefix):
|
|
if len(cword_prefix) > 0 or self.always_complete_options is True:
|
|
return [opt for opt in action.option_strings if opt.startswith(cword_prefix)]
|
|
long_opts = [opt for opt in action.option_strings if len(opt) > 2]
|
|
short_opts = [opt for opt in action.option_strings if len(opt) <= 2]
|
|
if self.always_complete_options == "long":
|
|
return long_opts if long_opts else short_opts
|
|
elif self.always_complete_options == "short":
|
|
return short_opts if short_opts else long_opts
|
|
return []
|
|
|
|
def _get_option_completions(self, parser, cword_prefix):
|
|
self._display_completions.update(
|
|
[
|
|
[tuple(x for x in action.option_strings if x.startswith(cword_prefix)), action.help]
|
|
for action in parser._actions
|
|
if action.option_strings
|
|
]
|
|
)
|
|
|
|
option_completions = []
|
|
for action in parser._actions:
|
|
if not self.print_suppressed:
|
|
completer = getattr(action, "completer", None)
|
|
if isinstance(completer, SuppressCompleter) and completer.suppress():
|
|
continue
|
|
if action.help == argparse.SUPPRESS:
|
|
continue
|
|
if not self._action_allowed(action, parser):
|
|
continue
|
|
if not isinstance(action, argparse._SubParsersAction):
|
|
option_completions += self._include_options(action, cword_prefix)
|
|
return option_completions
|
|
|
|
@staticmethod
|
|
def _action_allowed(action, parser):
|
|
# Logic adapted from take_action in ArgumentParser._parse_known_args
|
|
# (members are saved by my_argparse.IntrospectiveArgumentParser)
|
|
for conflict_action in parser._action_conflicts.get(action, []):
|
|
if conflict_action in parser._seen_non_default_actions:
|
|
return False
|
|
return True
|
|
|
|
def _complete_active_option(self, parser, next_positional, cword_prefix, parsed_args, completions):
|
|
debug("Active actions (L={l}): {a}".format(l=len(parser.active_actions), a=parser.active_actions))
|
|
|
|
isoptional = cword_prefix and cword_prefix[0] in parser.prefix_chars
|
|
optional_prefix = ""
|
|
greedy_actions = [x for x in parser.active_actions if action_is_greedy(x, isoptional)]
|
|
if greedy_actions:
|
|
assert len(greedy_actions) == 1, "expect at most 1 greedy action"
|
|
# This means the action will fail to parse if the word under the cursor is not given
|
|
# to it, so give it exclusive control over completions (flush previous completions)
|
|
debug("Resetting completions because", greedy_actions[0], "must consume the next argument")
|
|
self._display_completions = {}
|
|
completions = []
|
|
elif isoptional:
|
|
if "=" in cword_prefix:
|
|
# Special case for when the current word is "--optional=PARTIAL_VALUE".
|
|
# The completer runs on PARTIAL_VALUE. The prefix is added back to the completions
|
|
# (and chopped back off later in quote_completions() by the COMP_WORDBREAKS logic).
|
|
optional_prefix, _, cword_prefix = cword_prefix.partition("=")
|
|
else:
|
|
# Only run completers if current word does not start with - (is not an optional)
|
|
return completions
|
|
|
|
complete_remaining_positionals = False
|
|
# Use the single greedy action (if there is one) or all active actions.
|
|
for active_action in greedy_actions or parser.active_actions:
|
|
if not active_action.option_strings: # action is a positional
|
|
if action_is_open(active_action):
|
|
# Any positional arguments after this may slide down into this action
|
|
# if more arguments are added (since the user may not be done yet),
|
|
# so it is extremely difficult to tell which completers to run.
|
|
# Running all remaining completers will probably show more than the user wants
|
|
# but it also guarantees we won't miss anything.
|
|
complete_remaining_positionals = True
|
|
if not complete_remaining_positionals:
|
|
if action_is_satisfied(active_action) and not action_is_open(active_action):
|
|
debug("Skipping", active_action)
|
|
continue
|
|
|
|
debug("Activating completion for", active_action, active_action._orig_class)
|
|
# completer = getattr(active_action, "completer", DefaultCompleter())
|
|
completer = getattr(active_action, "completer", None)
|
|
|
|
if completer is None:
|
|
if active_action.choices is not None and not isinstance(active_action, argparse._SubParsersAction):
|
|
completer = completers.ChoicesCompleter(active_action.choices)
|
|
elif not isinstance(active_action, argparse._SubParsersAction):
|
|
completer = self.default_completer
|
|
|
|
if completer:
|
|
if isinstance(completer, SuppressCompleter) and completer.suppress():
|
|
continue
|
|
|
|
if callable(completer):
|
|
completions_from_callable = [
|
|
c
|
|
for c in completer(
|
|
prefix=cword_prefix, action=active_action, parser=parser, parsed_args=parsed_args
|
|
)
|
|
if self.validator(c, cword_prefix)
|
|
]
|
|
|
|
if completions_from_callable:
|
|
completions += completions_from_callable
|
|
if isinstance(completer, completers.ChoicesCompleter):
|
|
self._display_completions.update(
|
|
[[(x,), active_action.help] for x in completions_from_callable]
|
|
)
|
|
else:
|
|
self._display_completions.update([[(x,), ""] for x in completions_from_callable])
|
|
else:
|
|
debug("Completer is not callable, trying the readline completer protocol instead")
|
|
for i in range(9999):
|
|
next_completion = completer.complete(cword_prefix, i)
|
|
if next_completion is None:
|
|
break
|
|
if self.validator(next_completion, cword_prefix):
|
|
self._display_completions.update({(next_completion,): ""})
|
|
completions.append(next_completion)
|
|
if optional_prefix:
|
|
completions = [optional_prefix + "=" + completion for completion in completions]
|
|
debug("Completions:", completions)
|
|
return completions
|
|
|
|
def collect_completions(self, active_parsers, parsed_args, cword_prefix, debug):
|
|
"""
|
|
Visits the active parsers and their actions, executes their completers or introspects them to collect their
|
|
option strings. Returns the resulting completions as a list of strings.
|
|
|
|
This method is exposed for overriding in subclasses; there is no need to use it directly.
|
|
"""
|
|
completions = []
|
|
|
|
debug("all active parsers:", active_parsers)
|
|
active_parser = active_parsers[-1]
|
|
debug("active_parser:", active_parser)
|
|
if self.always_complete_options or (len(cword_prefix) > 0 and cword_prefix[0] in active_parser.prefix_chars):
|
|
completions += self._get_option_completions(active_parser, cword_prefix)
|
|
debug("optional options:", completions)
|
|
|
|
next_positional = self._get_next_positional()
|
|
debug("next_positional:", next_positional)
|
|
|
|
if isinstance(next_positional, argparse._SubParsersAction):
|
|
completions += self._get_subparser_completions(next_positional, cword_prefix)
|
|
|
|
completions = self._complete_active_option(
|
|
active_parser, next_positional, cword_prefix, parsed_args, completions
|
|
)
|
|
debug("active options:", completions)
|
|
debug("display completions:", self._display_completions)
|
|
|
|
return completions
|
|
|
|
def _get_next_positional(self):
|
|
"""
|
|
Get the next positional action if it exists.
|
|
"""
|
|
active_parser = self.active_parsers[-1]
|
|
last_positional = self.visited_positionals[-1]
|
|
|
|
all_positionals = active_parser._get_positional_actions()
|
|
if not all_positionals:
|
|
return None
|
|
|
|
if active_parser == last_positional:
|
|
return all_positionals[0]
|
|
|
|
i = 0
|
|
for i in range(len(all_positionals)):
|
|
if all_positionals[i] == last_positional:
|
|
break
|
|
|
|
if i + 1 < len(all_positionals):
|
|
return all_positionals[i + 1]
|
|
|
|
return None
|
|
|
|
def filter_completions(self, completions):
|
|
"""
|
|
Ensures collected completions are Unicode text, de-duplicates them, and excludes those specified by ``exclude``.
|
|
Returns the filtered completions as an iterable.
|
|
|
|
This method is exposed for overriding in subclasses; there is no need to use it directly.
|
|
"""
|
|
# De-duplicate completions and remove excluded ones
|
|
if self.exclude is None:
|
|
self.exclude = set()
|
|
seen = set(self.exclude)
|
|
return [c for c in completions if c not in seen and not seen.add(c)]
|
|
|
|
def quote_completions(self, completions, cword_prequote, last_wordbreak_pos):
|
|
"""
|
|
If the word under the cursor started with a quote (as indicated by a nonempty ``cword_prequote``), escapes
|
|
occurrences of that quote character in the completions, and adds the quote to the beginning of each completion.
|
|
Otherwise, escapes all characters that bash splits words on (``COMP_WORDBREAKS``), and removes portions of
|
|
completions before the first colon if (``COMP_WORDBREAKS``) contains a colon.
|
|
|
|
If there is only one completion, and it doesn't end with a **continuation character** (``/``, ``:``, or ``=``),
|
|
adds a space after the completion.
|
|
|
|
This method is exposed for overriding in subclasses; there is no need to use it directly.
|
|
"""
|
|
special_chars = "\\"
|
|
# If the word under the cursor was quoted, escape the quote char.
|
|
# Otherwise, escape all special characters and specially handle all COMP_WORDBREAKS chars.
|
|
if cword_prequote == "":
|
|
# Bash mangles completions which contain characters in COMP_WORDBREAKS.
|
|
# This workaround has the same effect as __ltrim_colon_completions in bash_completion
|
|
# (extended to characters other than the colon).
|
|
if last_wordbreak_pos:
|
|
completions = [c[last_wordbreak_pos + 1 :] for c in completions]
|
|
special_chars += "();<>|&!`$* \t\n\"'"
|
|
elif cword_prequote == '"':
|
|
special_chars += '"`$!'
|
|
|
|
if os.environ.get("_ARGCOMPLETE_SHELL") in ("tcsh", "fish"):
|
|
# tcsh and fish escapes special characters itself.
|
|
special_chars = ""
|
|
elif cword_prequote == "'":
|
|
# Nothing can be escaped in single quotes, so we need to close
|
|
# the string, escape the single quote, then open a new string.
|
|
special_chars = ""
|
|
completions = [c.replace("'", r"'\''") for c in completions]
|
|
|
|
for char in special_chars:
|
|
completions = [c.replace(char, "\\" + char) for c in completions]
|
|
|
|
if self.append_space:
|
|
# Similar functionality in bash was previously turned off by supplying the "-o nospace" option to complete.
|
|
# Now it is conditionally disabled using "compopt -o nospace" if the match ends in a continuation character.
|
|
# This code is retained for environments where this isn't done natively.
|
|
continuation_chars = "=/:"
|
|
if len(completions) == 1 and completions[0][-1] not in continuation_chars:
|
|
if cword_prequote == "":
|
|
completions[0] += " "
|
|
|
|
return completions
|
|
|
|
def rl_complete(self, text, state):
|
|
"""
|
|
Alternate entry point for using the argcomplete completer in a readline-based REPL. See also
|
|
`rlcompleter <https://docs.python.org/3/library/rlcompleter.html#completer-objects>`_.
|
|
Usage:
|
|
|
|
.. code-block:: python
|
|
|
|
import argcomplete, argparse, readline
|
|
parser = argparse.ArgumentParser()
|
|
...
|
|
completer = argcomplete.CompletionFinder(parser)
|
|
readline.set_completer_delims("")
|
|
readline.set_completer(completer.rl_complete)
|
|
readline.parse_and_bind("tab: complete")
|
|
result = input("prompt> ")
|
|
"""
|
|
if state == 0:
|
|
cword_prequote, cword_prefix, cword_suffix, comp_words, first_colon_pos = split_line(text)
|
|
comp_words.insert(0, sys.argv[0])
|
|
matches = self._get_completions(comp_words, cword_prefix, cword_prequote, first_colon_pos)
|
|
self._rl_matches = [text + match[len(cword_prefix) :] for match in matches]
|
|
|
|
if state < len(self._rl_matches):
|
|
return self._rl_matches[state]
|
|
else:
|
|
return None
|
|
|
|
def get_display_completions(self):
|
|
"""
|
|
This function returns a mapping of option names to their help strings for displaying to the user
|
|
|
|
Usage:
|
|
|
|
.. code-block:: python
|
|
|
|
def display_completions(substitution, matches, longest_match_length):
|
|
_display_completions = argcomplete.autocomplete.get_display_completions()
|
|
print("")
|
|
if _display_completions:
|
|
help_len = [len(x) for x in _display_completions.values() if x]
|
|
|
|
if help_len:
|
|
maxlen = max([len(x) for x in _display_completions])
|
|
print("\\n".join("{0:{2}} -- {1}".format(k, v, maxlen)
|
|
for k, v in sorted(_display_completions.items())))
|
|
else:
|
|
print(" ".join(k for k in sorted(_display_completions)))
|
|
else:
|
|
print(" ".join(x for x in sorted(matches)))
|
|
|
|
import readline
|
|
print("cli /> {0}".format(readline.get_line_buffer()), end="")
|
|
readline.redisplay()
|
|
|
|
...
|
|
readline.set_completion_display_matches_hook(display_completions)
|
|
|
|
"""
|
|
return {" ".join(k): v for k, v in self._display_completions.items()}
|
|
|
|
|
|
class ExclusiveCompletionFinder(CompletionFinder):
|
|
@staticmethod
|
|
def _action_allowed(action, parser):
|
|
if not CompletionFinder._action_allowed(action, parser):
|
|
return False
|
|
|
|
append_classes = (argparse._AppendAction, argparse._AppendConstAction)
|
|
if action._orig_class in append_classes:
|
|
return True
|
|
|
|
if action not in parser._seen_non_default_actions:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
autocomplete = CompletionFinder()
|
|
autocomplete.__doc__ = """ Use this to access argcomplete. See :meth:`argcomplete.CompletionFinder.__call__()`. """
|
|
|
|
|
|
def warn(*args):
|
|
"""
|
|
Prints **args** to standard error when running completions. This will interrupt the user's command line interaction;
|
|
use it to indicate an error condition that is preventing your completer from working.
|
|
"""
|
|
# Don't be tempted to use `print("\n",..., *args)`,
|
|
# as that will indent **args** by one space character
|
|
print(file=debug_stream)
|
|
print(file=debug_stream, *args)
|