Initial commit (Clean history)
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,32 @@
|
||||
# Invoke tab-completion script to be sourced with Bash shell.
|
||||
# Known to work on Bash 3.x, untested on 4.x.
|
||||
|
||||
_complete_{binary}() {{
|
||||
local candidates
|
||||
|
||||
# COMP_WORDS contains the entire command string up til now (including
|
||||
# program name).
|
||||
# We hand it to Invoke so it can figure out the current context: spit back
|
||||
# core options, task names, the current task's options, or some combo.
|
||||
candidates=`{binary} --complete -- ${{COMP_WORDS[*]}}`
|
||||
|
||||
# `compgen -W` takes list of valid options & a partial word & spits back
|
||||
# possible matches. Necessary for any partial word completions (vs
|
||||
# completions performed when no partial words are present).
|
||||
#
|
||||
# $2 is the current word or token being tabbed on, either empty string or a
|
||||
# partial word, and thus wants to be compgen'd to arrive at some subset of
|
||||
# our candidate list which actually matches.
|
||||
#
|
||||
# COMPREPLY is the list of valid completions handed back to `complete`.
|
||||
COMPREPLY=( $(compgen -W "${{candidates}}" -- $2) )
|
||||
}}
|
||||
|
||||
|
||||
# Tell shell builtin to use the above for completing our invocations.
|
||||
# * -F: use given function name to generate completions.
|
||||
# * -o default: when function generates no results, use filenames.
|
||||
# * positional args: program names to complete for.
|
||||
complete -F _complete_{binary} -o default {spaced_names}
|
||||
|
||||
# vim: set ft=sh :
|
||||
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
Command-line completion mechanisms, executed by the core ``--complete`` flag.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..exceptions import Exit, ParseError
|
||||
from ..util import debug, task_name_sort_key
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..collection import Collection
|
||||
from ..parser import Parser, ParseResult, ParserContext
|
||||
|
||||
|
||||
def complete(
|
||||
names: List[str],
|
||||
core: "ParseResult",
|
||||
initial_context: "ParserContext",
|
||||
collection: "Collection",
|
||||
parser: "Parser",
|
||||
) -> Exit:
|
||||
# Strip out program name (scripts give us full command line)
|
||||
# TODO: this may not handle path/to/script though?
|
||||
invocation = re.sub(r"^({}) ".format("|".join(names)), "", core.remainder)
|
||||
debug("Completing for invocation: {!r}".format(invocation))
|
||||
# Tokenize (shlex will have to do)
|
||||
tokens = shlex.split(invocation)
|
||||
# Handle flags (partial or otherwise)
|
||||
if tokens and tokens[-1].startswith("-"):
|
||||
tail = tokens[-1]
|
||||
debug("Invocation's tail {!r} is flag-like".format(tail))
|
||||
# Gently parse invocation to obtain 'current' context.
|
||||
# Use last seen context in case of failure (required for
|
||||
# otherwise-invalid partial invocations being completed).
|
||||
|
||||
contexts: List[ParserContext]
|
||||
try:
|
||||
debug("Seeking context name in tokens: {!r}".format(tokens))
|
||||
contexts = parser.parse_argv(tokens)
|
||||
except ParseError as e:
|
||||
msg = "Got parser error ({!r}), grabbing its last-seen context {!r}" # noqa
|
||||
debug(msg.format(e, e.context))
|
||||
contexts = [e.context] if e.context is not None else []
|
||||
# Fall back to core context if no context seen.
|
||||
debug("Parsed invocation, contexts: {!r}".format(contexts))
|
||||
if not contexts or not contexts[-1]:
|
||||
context = initial_context
|
||||
else:
|
||||
context = contexts[-1]
|
||||
debug("Selected context: {!r}".format(context))
|
||||
# Unknown flags (could be e.g. only partially typed out; could be
|
||||
# wholly invalid; doesn't matter) complete with flags.
|
||||
debug("Looking for {!r} in {!r}".format(tail, context.flags))
|
||||
if tail not in context.flags:
|
||||
debug("Not found, completing with flag names")
|
||||
# Long flags - partial or just the dashes - complete w/ long flags
|
||||
if tail.startswith("--"):
|
||||
for name in filter(
|
||||
lambda x: x.startswith("--"), context.flag_names()
|
||||
):
|
||||
print(name)
|
||||
# Just a dash, completes with all flags
|
||||
elif tail == "-":
|
||||
for name in context.flag_names():
|
||||
print(name)
|
||||
# Otherwise, it's something entirely invalid (a shortflag not
|
||||
# recognized, or a java style flag like -foo) so return nothing
|
||||
# (the shell will still try completing with files, but that doesn't
|
||||
# hurt really.)
|
||||
else:
|
||||
pass
|
||||
# Known flags complete w/ nothing or tasks, depending
|
||||
else:
|
||||
# Flags expecting values: do nothing, to let default (usually
|
||||
# file) shell completion occur (which we actively want in this
|
||||
# case.)
|
||||
if context.flags[tail].takes_value:
|
||||
debug("Found, and it takes a value, so no completion")
|
||||
pass
|
||||
# Not taking values (eg bools): print task names
|
||||
else:
|
||||
debug("Found, takes no value, printing task names")
|
||||
print_task_names(collection)
|
||||
# If not a flag, is either task name or a flag value, so just complete
|
||||
# task names.
|
||||
else:
|
||||
debug("Last token isn't flag-like, just printing task names")
|
||||
print_task_names(collection)
|
||||
raise Exit
|
||||
|
||||
|
||||
def print_task_names(collection: "Collection") -> None:
|
||||
for name in sorted(collection.task_names, key=task_name_sort_key):
|
||||
print(name)
|
||||
# Just stick aliases after the thing they're aliased to. Sorting isn't
|
||||
# so important that it's worth bending over backwards here.
|
||||
for alias in collection.task_names[name]:
|
||||
print(alias)
|
||||
|
||||
|
||||
def print_completion_script(shell: str, names: List[str]) -> None:
|
||||
# Grab all .completion files in invoke/completion/. (These used to have no
|
||||
# suffix, but surprise, that's super fragile.
|
||||
completions = {
|
||||
os.path.splitext(os.path.basename(x))[0]: x
|
||||
for x in glob.glob(
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), "*.completion"
|
||||
)
|
||||
)
|
||||
}
|
||||
try:
|
||||
path = completions[shell]
|
||||
except KeyError:
|
||||
err = 'Completion for shell "{}" not supported (options are: {}).'
|
||||
raise ParseError(err.format(shell, ", ".join(sorted(completions))))
|
||||
debug("Printing completion script from {}".format(path))
|
||||
# Choose one arbitrary program name for script's own internal invocation
|
||||
# (also used to construct completion function names when necessary)
|
||||
binary = names[0]
|
||||
with open(path, "r") as script:
|
||||
print(
|
||||
script.read().format(binary=binary, spaced_names=" ".join(names))
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
# Invoke tab-completion script for the fish shell
|
||||
# Copy it to the ~/.config/fish/completions directory
|
||||
|
||||
function __complete_{binary}
|
||||
{binary} --complete -- (commandline --tokenize)
|
||||
end
|
||||
|
||||
# --no-files: Don't complete files unless invoke gives an empty result
|
||||
# TODO: find a way to honor all binary_names
|
||||
complete --command {binary} --no-files --arguments '(__complete_{binary})'
|
||||
@@ -0,0 +1,33 @@
|
||||
# Invoke tab-completion script to be sourced with the Z shell.
|
||||
# Known to work on zsh 5.0.x, probably works on later 4.x releases as well (as
|
||||
# it uses the older compctl completion system).
|
||||
|
||||
_complete_{binary}() {{
|
||||
# `words` contains the entire command string up til now (including
|
||||
# program name).
|
||||
#
|
||||
# We hand it to Invoke so it can figure out the current context: spit back
|
||||
# core options, task names, the current task's options, or some combo.
|
||||
#
|
||||
# Before doing so, we attempt to tease out any collection flag+arg so we
|
||||
# can ensure it is applied correctly.
|
||||
collection_arg=''
|
||||
if [[ "${{words}}" =~ "(-c|--collection) [^ ]+" ]]; then
|
||||
collection_arg=$MATCH
|
||||
fi
|
||||
# `reply` is the array of valid completions handed back to `compctl`.
|
||||
# Use ${{=...}} to force whitespace splitting in expansion of
|
||||
# $collection_arg
|
||||
reply=( $({binary} ${{=collection_arg}} --complete -- ${{words}}) )
|
||||
}}
|
||||
|
||||
|
||||
# Tell shell builtin to use the above for completing our given binary name(s).
|
||||
# * -K: use given function name to generate completions.
|
||||
# * +: specifies 'alternative' completion, where options after the '+' are only
|
||||
# used if the completion from the options before the '+' result in no matches.
|
||||
# * -f: when function generates no results, use filenames.
|
||||
# * positional args: program names to complete for.
|
||||
compctl -K _complete_{binary} + -f {spaced_names}
|
||||
|
||||
# vim: set ft=sh :
|
||||
Reference in New Issue
Block a user