Initial commit (Clean history)

This commit is contained in:
anhduy-tech
2025-12-30 11:27:14 +07:00
commit ef48c93de0
19255 changed files with 3248867 additions and 0 deletions

View File

@@ -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 :

View File

@@ -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))
)

View File

@@ -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})'

View File

@@ -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 :