mirror of
https://github.com/go-task/task.git
synced 2026-09-01 19:50:16 +02:00
feat(completion): make the engine the default behind --completion
The engine shipped opt-in behind --new-completion, with the old scripts still on --completion. That split was never released, so flip it now rather than carry a third flag through a deprecation later. --completion serves the engine wrappers; the hand-written scripts move to completion/legacy/ and stay reachable via --legacy-completion for a release or two. .goreleaser.yml needed to follow: it packages the static files from completion/ into the deb/rpm/apk contents and the Homebrew cask, so leaving it untouched would have shipped the engine to anyone running `eval "$(task --completion zsh)"` and the old scripts to everyone installing from a package. The paths it references are unchanged and now resolve to the wrappers. Its archive glob is narrowed at the same time, so the test harness under completion/tests/ stops being shipped in the release archives.
This commit is contained in:
@@ -43,7 +43,12 @@ archives:
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- completion/**/*
|
||||
- completion/bash/*
|
||||
- completion/fish/*
|
||||
- completion/nu/*
|
||||
- completion/ps/*
|
||||
- completion/zsh/*
|
||||
- completion/legacy/**/*
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
formats: [zip]
|
||||
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -4,14 +4,14 @@
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Added a new completion engine that unifies Bash, Fish, Zsh, Nushell and
|
||||
PowerShell behind a single `task __complete` command, so every shell offers
|
||||
the same suggestions: task names, aliases, flags, flag values and per-task CLI
|
||||
variables. The Zsh `show-aliases` and `verbose` zstyles keep working, now
|
||||
backed by the `--no-aliases` and `--no-descriptions` completion flags. It is
|
||||
opt-in for now via `task --new-completion <shell>`, leaving `--completion`
|
||||
unchanged, and will become the default in a future release (#2897 by
|
||||
@vmaerten).
|
||||
- `task --completion <shell>` now serves a new completion engine that unifies
|
||||
Bash, Fish, Zsh, Nushell and PowerShell behind a single `task __complete`
|
||||
command, so every shell offers the same suggestions: task names, aliases,
|
||||
flags, flag values and per-task CLI variables. The Zsh `show-aliases` and
|
||||
`verbose` zstyles keep working, now backed by the `--no-aliases` and
|
||||
`--no-descriptions` completion flags. The previous hand-written scripts remain
|
||||
available as `task --legacy-completion <shell>`; they are deprecated and will
|
||||
be removed in a future release (#2897 by @vmaerten).
|
||||
|
||||
### 📦 Package API
|
||||
|
||||
|
||||
@@ -133,8 +133,8 @@ func run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if flags.NewCompletion != "" {
|
||||
script, err := task.CompletionNext(flags.NewCompletion)
|
||||
if flags.LegacyCompletion != "" {
|
||||
script, err := task.LegacyCompletion(flags.LegacyCompletion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Thin wrappers around the `task __complete` engine, served by `--completion`.
|
||||
|
||||
//go:embed completion/bash/task.bash
|
||||
var completionBash string
|
||||
|
||||
@@ -20,23 +22,23 @@ var completionPowershell string
|
||||
//go:embed completion/zsh/_task
|
||||
var completionZsh string
|
||||
|
||||
// Thin wrappers around the `task __complete` engine, served via
|
||||
// `--new-completion` until the engine becomes the default.
|
||||
// The self-contained scripts that predate the engine, kept behind
|
||||
// `--legacy-completion` as an escape hatch for a couple of releases.
|
||||
|
||||
//go:embed completion/next/bash/task.bash
|
||||
var completionBashNext string
|
||||
//go:embed completion/legacy/bash/task.bash
|
||||
var completionBashLegacy string
|
||||
|
||||
//go:embed completion/next/fish/task.fish
|
||||
var completionFishNext string
|
||||
//go:embed completion/legacy/fish/task.fish
|
||||
var completionFishLegacy string
|
||||
|
||||
//go:embed completion/next/nu/task-completions.nu
|
||||
var completionNuNext string
|
||||
//go:embed completion/legacy/nu/task-completions.nu
|
||||
var completionNuLegacy string
|
||||
|
||||
//go:embed completion/next/ps/task.ps1
|
||||
var completionPowershellNext string
|
||||
//go:embed completion/legacy/ps/task.ps1
|
||||
var completionPowershellLegacy string
|
||||
|
||||
//go:embed completion/next/zsh/_task
|
||||
var completionZshNext string
|
||||
//go:embed completion/legacy/zsh/_task
|
||||
var completionZshLegacy string
|
||||
|
||||
// The maps accept `nushell` as an alias of `nu`.
|
||||
var completionScripts = map[string]string{
|
||||
@@ -48,21 +50,21 @@ var completionScripts = map[string]string{
|
||||
"zsh": completionZsh,
|
||||
}
|
||||
|
||||
var completionScriptsNext = map[string]string{
|
||||
"bash": completionBashNext,
|
||||
"fish": completionFishNext,
|
||||
"nu": completionNuNext,
|
||||
"nushell": completionNuNext,
|
||||
"powershell": completionPowershellNext,
|
||||
"zsh": completionZshNext,
|
||||
var completionScriptsLegacy = map[string]string{
|
||||
"bash": completionBashLegacy,
|
||||
"fish": completionFishLegacy,
|
||||
"nu": completionNuLegacy,
|
||||
"nushell": completionNuLegacy,
|
||||
"powershell": completionPowershellLegacy,
|
||||
"zsh": completionZshLegacy,
|
||||
}
|
||||
|
||||
func Completion(shell string) (string, error) {
|
||||
return completionScript(completionScripts, shell)
|
||||
}
|
||||
|
||||
func CompletionNext(shell string) (string, error) {
|
||||
return completionScript(completionScriptsNext, shell)
|
||||
func LegacyCompletion(shell string) (string, error) {
|
||||
return completionScript(completionScriptsLegacy, shell)
|
||||
}
|
||||
|
||||
func completionScript(scripts map[string]string, shell string) (string, error) {
|
||||
|
||||
@@ -1,60 +1,94 @@
|
||||
# vim: set tabstop=2 shiftwidth=2 expandtab:
|
||||
#
|
||||
# Thin wrapper around `task __complete`: all suggestion logic lives in the Go engine.
|
||||
|
||||
_GO_TASK_COMPLETION_LIST_OPTION='--list-all'
|
||||
TASK_CMD="${TASK_EXE:-task}"
|
||||
|
||||
function _task()
|
||||
{
|
||||
local cur prev words cword
|
||||
_init_completion -n : || return
|
||||
# `=` stays inside the current word (see `_init_completion -n =:`), so an inline
|
||||
# `--flag=` prefix must be stripped before _filedir and re-applied after.
|
||||
_task_filedir() {
|
||||
local fpfx="" savecur="$cur"
|
||||
if [[ "$cur" == -*=* ]]; then
|
||||
fpfx="${cur%%=*}="
|
||||
cur="${cur#*=}"
|
||||
fi
|
||||
_filedir ${1:+"$1"}
|
||||
cur="$savecur"
|
||||
if [[ -n "$fpfx" ]]; then
|
||||
COMPREPLY=( ${COMPREPLY[@]+"${COMPREPLY[@]/#/$fpfx}"} )
|
||||
fi
|
||||
}
|
||||
|
||||
# Check for `--` within command-line and quit or strip suffix.
|
||||
local i
|
||||
for i in "${!words[@]}"; do
|
||||
if [ "${words[$i]}" == "--" ]; then
|
||||
# Do not complete words following `--` passed to CLI_ARGS.
|
||||
[ $cword -gt $i ] && return
|
||||
# Remove the words following `--` to not put --list in CLI_ARGS.
|
||||
words=( "${words[@]:0:$i}" )
|
||||
break
|
||||
_task() {
|
||||
local cur prev words cword
|
||||
|
||||
# Completion directives, mirroring internal/complete/complete.go.
|
||||
local -ri NO_SPACE=2 NO_FILE_COMP=4 FILTER_FILE_EXT=8 FILTER_DIRS=16 KEEP_ORDER=32
|
||||
|
||||
# `=` and `:` out of the word breaks: `--output=`, `docs:serve` stay one token.
|
||||
_init_completion -n =: || return
|
||||
|
||||
local -a args=( "${words[@]:1:cword}" )
|
||||
if (( ${#args[@]} == 0 )); then
|
||||
args=( "" )
|
||||
fi
|
||||
|
||||
local output
|
||||
output=$("$TASK_CMD" __complete "${args[@]}" 2>/dev/null)
|
||||
if [[ -z "$output" ]]; then
|
||||
_task_filedir
|
||||
return
|
||||
fi
|
||||
|
||||
local -a lines=()
|
||||
local line
|
||||
while IFS= read -r line; do
|
||||
lines+=( "$line" )
|
||||
done <<< "$output"
|
||||
|
||||
local last_idx=$(( ${#lines[@]} - 1 ))
|
||||
local directive="${lines[$last_idx]#:}"
|
||||
unset 'lines[$last_idx]'
|
||||
|
||||
if (( directive & FILTER_FILE_EXT )); then
|
||||
local exts=""
|
||||
# ${arr[@]+…} guards an empty array under `set -u` in bash 3.2 (macOS).
|
||||
for line in ${lines[@]+"${lines[@]}"}; do
|
||||
exts+="${exts:+|}$line"
|
||||
done
|
||||
_task_filedir "@($exts)"
|
||||
return
|
||||
fi
|
||||
|
||||
if (( directive & FILTER_DIRS )); then
|
||||
_task_filedir -d
|
||||
return
|
||||
fi
|
||||
|
||||
# Not `compgen -W`: it splits the word list on IFS, mangling values with spaces.
|
||||
local value
|
||||
COMPREPLY=()
|
||||
for line in ${lines[@]+"${lines[@]}"}; do
|
||||
value="${line%%$'\t'*}"
|
||||
if [[ -z "$cur" || "$value" == "$cur"* ]]; then
|
||||
COMPREPLY+=( "$value" )
|
||||
fi
|
||||
done
|
||||
|
||||
# Handle special arguments of options.
|
||||
case "$prev" in
|
||||
-d|--dir|--remote-cache-dir)
|
||||
_filedir -d
|
||||
return $?
|
||||
;;
|
||||
--cacert|--cert|--cert-key)
|
||||
_filedir
|
||||
return $?
|
||||
;;
|
||||
-t|--taskfile)
|
||||
_filedir yaml || return $?
|
||||
_filedir yml
|
||||
return $?
|
||||
;;
|
||||
-o|--output)
|
||||
COMPREPLY=( $( compgen -W "interleaved group prefixed" -- $cur ) )
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
if (( directive & NO_SPACE )); then
|
||||
compopt -o nospace 2>/dev/null
|
||||
fi
|
||||
|
||||
# Handle normal options.
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "$(_parse_help $1)" -- $cur ) )
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
# nosort needs bash 4.4; the 3.2 shipped by macOS ignores it and stays sorted.
|
||||
if (( directive & KEEP_ORDER )); then
|
||||
compopt -o nosort 2>/dev/null
|
||||
fi
|
||||
|
||||
# Prepare task name completions.
|
||||
local tasks=( $( "${words[@]}" --silent $_GO_TASK_COMPLETION_LIST_OPTION 2> /dev/null ) )
|
||||
COMPREPLY=( $( compgen -W "${tasks[*]}" -- "$cur" ) )
|
||||
|
||||
# Post-process because task names might contain colons.
|
||||
__ltrim_colon_completions "$cur"
|
||||
|
||||
if (( ${#COMPREPLY[@]} == 0 )) && ! (( directive & NO_FILE_COMP )); then
|
||||
_task_filedir
|
||||
fi
|
||||
}
|
||||
|
||||
complete -F _task "$TASK_CMD"
|
||||
|
||||
@@ -1,116 +1,98 @@
|
||||
# Thin wrapper around `task __complete`: all suggestion logic lives in the Go engine.
|
||||
|
||||
set -l GO_TASK_PROGNAME (if set -q GO_TASK_PROGNAME; echo $GO_TASK_PROGNAME; else if set -q TASK_EXE; echo $TASK_EXE; else; echo task; end)
|
||||
|
||||
# Cache variables for experiments (global)
|
||||
set -g __task_experiments_cache ""
|
||||
set -g __task_experiments_cache_time 0
|
||||
# Completion directives, mirroring internal/complete/complete.go. `math` has no
|
||||
# bitwise operators, hence __task_test_bit. NoSpace (2) and KeepOrder (32) need
|
||||
# none: fish appends no space and keeps the order.
|
||||
set -g __task_directive_no_file_comp 4
|
||||
set -g __task_directive_filter_file_ext 8
|
||||
set -g __task_directive_filter_dirs 16
|
||||
|
||||
# Helper function to get experiments with 1-second cache
|
||||
function __task_get_experiments --inherit-variable GO_TASK_PROGNAME
|
||||
set -l now (date +%s)
|
||||
set -l ttl 1 # Cache for 1 second only
|
||||
|
||||
# Return cached value if still valid
|
||||
if test (math "$now - $__task_experiments_cache_time") -lt $ttl
|
||||
printf '%s\n' $__task_experiments_cache
|
||||
return
|
||||
end
|
||||
|
||||
# Refresh cache
|
||||
set -g __task_experiments_cache ($GO_TASK_PROGNAME --experiments 2>/dev/null)
|
||||
set -g __task_experiments_cache_time $now
|
||||
printf '%s\n' $__task_experiments_cache
|
||||
function __task_test_bit --argument-names value bit
|
||||
test (math "floor($value / $bit) % 2") -eq 1
|
||||
end
|
||||
|
||||
# Helper function to check if an experiment is enabled
|
||||
function __task_is_experiment_enabled
|
||||
set -l experiment $argv[1]
|
||||
__task_get_experiments | string match -qr "^\* $experiment:.*on"
|
||||
end
|
||||
|
||||
function __task_get_tasks --description "Prints all available tasks with their description" --inherit-variable GO_TASK_PROGNAME
|
||||
# Check if the global task is requested
|
||||
set -l global_task false
|
||||
commandline --current-process | read --tokenize --list --local cmd_args
|
||||
for arg in $cmd_args
|
||||
if test "_$arg" = "_--"
|
||||
break # ignore arguments to be passed to the task
|
||||
end
|
||||
if test "_$arg" = "_--global" -o "_$arg" = "_-g"
|
||||
set global_task true
|
||||
break
|
||||
end
|
||||
function __task_complete --inherit-variable GO_TASK_PROGNAME
|
||||
set -l tokens (commandline -opc)
|
||||
set -l current (commandline -ct)
|
||||
set -l args
|
||||
if test (count $tokens) -gt 1
|
||||
set args $tokens[2..-1]
|
||||
end
|
||||
set args $args $current
|
||||
|
||||
# Read the list of tasks (and potential errors)
|
||||
if $global_task
|
||||
$GO_TASK_PROGNAME --global --list-all
|
||||
else
|
||||
$GO_TASK_PROGNAME --list-all
|
||||
end 2>&1 | read -lz rawOutput
|
||||
|
||||
# Return on non-zero exit code (for cases when there is no Taskfile found or etc.)
|
||||
if test $status -ne 0
|
||||
set -l output ($GO_TASK_PROGNAME __complete $args 2>/dev/null)
|
||||
set -l count (count $output)
|
||||
if test $count -eq 0
|
||||
return
|
||||
end
|
||||
|
||||
# Grab names and descriptions (if any) of the tasks
|
||||
set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):[[:space:]]\{2,\}\(.*\)[[:space:]]\{2,\}(\(aliases.*\))/\1\t\2\t\3/' -e 's/\* \(.*\):[[:space:]]\{2,\}\(.*\)/\1\t\2/'| string split0)
|
||||
if test $output
|
||||
echo $output
|
||||
set -l last $output[$count]
|
||||
if not string match -q ':*' -- $last
|
||||
# Protocol violation: emit raw lines as a fallback.
|
||||
printf '%s\n' $output
|
||||
return
|
||||
end
|
||||
|
||||
set -l directive (string replace -r '^:' '' -- $last)
|
||||
set -l data
|
||||
if test $count -gt 1
|
||||
set data $output[1..(math $count - 1)]
|
||||
end
|
||||
|
||||
# The registration below passes `--no-files`, so every file-completion
|
||||
# directive must be served here or nothing is offered at all.
|
||||
|
||||
# fish replaces the whole token, so an inline `--flag=` must be kept on every
|
||||
# candidate.
|
||||
set -l flagpfx ""
|
||||
set -l pathcur $current
|
||||
if string match -qr '^--?[^=]+=' -- $current
|
||||
set flagpfx (string replace -r '=.*$' '=' -- $current)
|
||||
set pathcur (string replace -r '^--?[^=]+=' '' -- $current)
|
||||
end
|
||||
|
||||
# __fish_complete_suffix prioritizes the extension instead of filtering.
|
||||
if __task_test_bit $directive $__task_directive_filter_file_ext
|
||||
for entry in (__fish_complete_path $pathcur)
|
||||
set -l name (string split -f1 \t -- $entry)
|
||||
if string match -qr '/$' -- $name
|
||||
printf '%s%s\n' $flagpfx $entry
|
||||
continue
|
||||
end
|
||||
for ext in $data
|
||||
if string match -qr "\.$ext\$" -- $name
|
||||
printf '%s%s\n' $flagpfx $entry
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if __task_test_bit $directive $__task_directive_filter_dirs
|
||||
for entry in (__fish_complete_directories $pathcur)
|
||||
printf '%s%s\n' $flagpfx $entry
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
for line in $data
|
||||
printf '%s\n' $line
|
||||
end
|
||||
|
||||
# NoFileComp unset → offer files too (DirectiveDefault).
|
||||
if not __task_test_bit $directive $__task_directive_no_file_comp
|
||||
for entry in (__fish_complete_path $pathcur)
|
||||
printf '%s%s\n' $flagpfx $entry
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
complete -c $GO_TASK_PROGNAME \
|
||||
-d 'Runs the specified task(s). Falls back to the "default" task if no task name was specified, or lists all tasks if an unknown task name was specified.' \
|
||||
-xa "(__task_get_tasks)" \
|
||||
-n "not __fish_seen_subcommand_from --"
|
||||
# fish accumulates `complete` entries instead of replacing them, so an older
|
||||
# completion would keep contributing alongside the engine.
|
||||
complete -c $GO_TASK_PROGNAME -e
|
||||
|
||||
# Standard flags
|
||||
complete -c $GO_TASK_PROGNAME -s a -l list-all -d 'list all tasks'
|
||||
complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)'
|
||||
complete -c $GO_TASK_PROGNAME -s C -l concurrency -d 'limit number of concurrent tasks'
|
||||
complete -c $GO_TASK_PROGNAME -l completion -d 'generate shell completion script' -xa "bash zsh fish powershell nu"
|
||||
complete -c $GO_TASK_PROGNAME -s d -l dir -d 'set directory of execution'
|
||||
complete -c $GO_TASK_PROGNAME -l disable-fuzzy -d 'disable fuzzy matching for task names'
|
||||
complete -c $GO_TASK_PROGNAME -s n -l dry -d 'compile and print tasks without executing'
|
||||
complete -c $GO_TASK_PROGNAME -s x -l exit-code -d 'pass-through exit code of task command'
|
||||
complete -c $GO_TASK_PROGNAME -l experiments -d 'list available experiments'
|
||||
complete -c $GO_TASK_PROGNAME -s F -l failfast -d 'when running tasks in parallel, stop all tasks if one fails'
|
||||
complete -c $GO_TASK_PROGNAME -s f -l force -d 'force execution even when up-to-date'
|
||||
complete -c $GO_TASK_PROGNAME -s g -l global -d 'run global Taskfile from home directory'
|
||||
complete -c $GO_TASK_PROGNAME -s h -l help -d 'show help'
|
||||
complete -c $GO_TASK_PROGNAME -s i -l init -d 'create new Taskfile'
|
||||
complete -c $GO_TASK_PROGNAME -l insecure -d 'allow insecure Taskfile downloads'
|
||||
complete -c $GO_TASK_PROGNAME -s I -l interval -d 'interval to watch for changes'
|
||||
complete -c $GO_TASK_PROGNAME -s j -l json -d 'format task list as JSON'
|
||||
complete -c $GO_TASK_PROGNAME -s l -l list -d 'list tasks with descriptions'
|
||||
complete -c $GO_TASK_PROGNAME -l nested -d 'nest namespaces when listing as JSON'
|
||||
complete -c $GO_TASK_PROGNAME -l no-status -d 'ignore status when listing as JSON'
|
||||
complete -c $GO_TASK_PROGNAME -l interactive -d 'prompt for missing required variables'
|
||||
complete -c $GO_TASK_PROGNAME -s o -l output -d 'set output style' -xa "interleaved group prefixed"
|
||||
complete -c $GO_TASK_PROGNAME -l output-group-begin -d 'message template before grouped output'
|
||||
complete -c $GO_TASK_PROGNAME -l output-group-end -d 'message template after grouped output'
|
||||
complete -c $GO_TASK_PROGNAME -l output-group-error-only -d 'hide output from successful tasks'
|
||||
complete -c $GO_TASK_PROGNAME -s p -l parallel -d 'execute tasks in parallel'
|
||||
complete -c $GO_TASK_PROGNAME -s s -l silent -d 'disable echoing'
|
||||
complete -c $GO_TASK_PROGNAME -l sort -d 'set task sorting order' -xa "default alphanumeric none"
|
||||
complete -c $GO_TASK_PROGNAME -l status -d 'exit non-zero if tasks not up-to-date'
|
||||
complete -c $GO_TASK_PROGNAME -l summary -d 'show task summary'
|
||||
complete -c $GO_TASK_PROGNAME -s t -l taskfile -d 'choose Taskfile to run'
|
||||
complete -c $GO_TASK_PROGNAME -s v -l verbose -d 'verbose output'
|
||||
complete -c $GO_TASK_PROGNAME -l version -d 'show version'
|
||||
complete -c $GO_TASK_PROGNAME -s w -l watch -d 'watch mode, re-run on changes'
|
||||
complete -c $GO_TASK_PROGNAME -s y -l yes -d 'assume yes to all prompts'
|
||||
complete -c $GO_TASK_PROGNAME -l offline -d 'use only local or cached Taskfiles'
|
||||
complete -c $GO_TASK_PROGNAME -l timeout -d 'timeout for remote Taskfile downloads'
|
||||
complete -c $GO_TASK_PROGNAME -l expiry -d 'cache expiry duration'
|
||||
complete -c $GO_TASK_PROGNAME -l remote-cache-dir -d 'directory to cache remote Taskfiles' -xa "(__fish_complete_directories)"
|
||||
complete -c $GO_TASK_PROGNAME -l cacert -d 'custom CA certificate for TLS' -r
|
||||
complete -c $GO_TASK_PROGNAME -l cert -d 'client certificate for mTLS' -r
|
||||
complete -c $GO_TASK_PROGNAME -l cert-key -d 'client certificate private key' -r
|
||||
complete -c $GO_TASK_PROGNAME -l download -d 'download remote Taskfile'
|
||||
complete -c $GO_TASK_PROGNAME -l clear-cache -d 'clear remote Taskfile cache'
|
||||
|
||||
# Experimental flags (dynamically checked at completion time via -n condition)
|
||||
# GentleForce experiment
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled GENTLE_FORCE" -l force-all -d 'force execution of task and all dependencies'
|
||||
# `--no-files` keeps fish from mixing in files against the engine's directive.
|
||||
complete -c $GO_TASK_PROGNAME --no-files -a "(__task_complete)"
|
||||
|
||||
60
completion/legacy/bash/task.bash
Normal file
60
completion/legacy/bash/task.bash
Normal file
@@ -0,0 +1,60 @@
|
||||
# vim: set tabstop=2 shiftwidth=2 expandtab:
|
||||
|
||||
_GO_TASK_COMPLETION_LIST_OPTION='--list-all'
|
||||
TASK_CMD="${TASK_EXE:-task}"
|
||||
|
||||
function _task()
|
||||
{
|
||||
local cur prev words cword
|
||||
_init_completion -n : || return
|
||||
|
||||
# Check for `--` within command-line and quit or strip suffix.
|
||||
local i
|
||||
for i in "${!words[@]}"; do
|
||||
if [ "${words[$i]}" == "--" ]; then
|
||||
# Do not complete words following `--` passed to CLI_ARGS.
|
||||
[ $cword -gt $i ] && return
|
||||
# Remove the words following `--` to not put --list in CLI_ARGS.
|
||||
words=( "${words[@]:0:$i}" )
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Handle special arguments of options.
|
||||
case "$prev" in
|
||||
-d|--dir|--remote-cache-dir)
|
||||
_filedir -d
|
||||
return $?
|
||||
;;
|
||||
--cacert|--cert|--cert-key)
|
||||
_filedir
|
||||
return $?
|
||||
;;
|
||||
-t|--taskfile)
|
||||
_filedir yaml || return $?
|
||||
_filedir yml
|
||||
return $?
|
||||
;;
|
||||
-o|--output)
|
||||
COMPREPLY=( $( compgen -W "interleaved group prefixed" -- $cur ) )
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Handle normal options.
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "$(_parse_help $1)" -- $cur ) )
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Prepare task name completions.
|
||||
local tasks=( $( "${words[@]}" --silent $_GO_TASK_COMPLETION_LIST_OPTION 2> /dev/null ) )
|
||||
COMPREPLY=( $( compgen -W "${tasks[*]}" -- "$cur" ) )
|
||||
|
||||
# Post-process because task names might contain colons.
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
complete -F _task "$TASK_CMD"
|
||||
116
completion/legacy/fish/task.fish
Normal file
116
completion/legacy/fish/task.fish
Normal file
@@ -0,0 +1,116 @@
|
||||
set -l GO_TASK_PROGNAME (if set -q GO_TASK_PROGNAME; echo $GO_TASK_PROGNAME; else if set -q TASK_EXE; echo $TASK_EXE; else; echo task; end)
|
||||
|
||||
# Cache variables for experiments (global)
|
||||
set -g __task_experiments_cache ""
|
||||
set -g __task_experiments_cache_time 0
|
||||
|
||||
# Helper function to get experiments with 1-second cache
|
||||
function __task_get_experiments --inherit-variable GO_TASK_PROGNAME
|
||||
set -l now (date +%s)
|
||||
set -l ttl 1 # Cache for 1 second only
|
||||
|
||||
# Return cached value if still valid
|
||||
if test (math "$now - $__task_experiments_cache_time") -lt $ttl
|
||||
printf '%s\n' $__task_experiments_cache
|
||||
return
|
||||
end
|
||||
|
||||
# Refresh cache
|
||||
set -g __task_experiments_cache ($GO_TASK_PROGNAME --experiments 2>/dev/null)
|
||||
set -g __task_experiments_cache_time $now
|
||||
printf '%s\n' $__task_experiments_cache
|
||||
end
|
||||
|
||||
# Helper function to check if an experiment is enabled
|
||||
function __task_is_experiment_enabled
|
||||
set -l experiment $argv[1]
|
||||
__task_get_experiments | string match -qr "^\* $experiment:.*on"
|
||||
end
|
||||
|
||||
function __task_get_tasks --description "Prints all available tasks with their description" --inherit-variable GO_TASK_PROGNAME
|
||||
# Check if the global task is requested
|
||||
set -l global_task false
|
||||
commandline --current-process | read --tokenize --list --local cmd_args
|
||||
for arg in $cmd_args
|
||||
if test "_$arg" = "_--"
|
||||
break # ignore arguments to be passed to the task
|
||||
end
|
||||
if test "_$arg" = "_--global" -o "_$arg" = "_-g"
|
||||
set global_task true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Read the list of tasks (and potential errors)
|
||||
if $global_task
|
||||
$GO_TASK_PROGNAME --global --list-all
|
||||
else
|
||||
$GO_TASK_PROGNAME --list-all
|
||||
end 2>&1 | read -lz rawOutput
|
||||
|
||||
# Return on non-zero exit code (for cases when there is no Taskfile found or etc.)
|
||||
if test $status -ne 0
|
||||
return
|
||||
end
|
||||
|
||||
# Grab names and descriptions (if any) of the tasks
|
||||
set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):[[:space:]]\{2,\}\(.*\)[[:space:]]\{2,\}(\(aliases.*\))/\1\t\2\t\3/' -e 's/\* \(.*\):[[:space:]]\{2,\}\(.*\)/\1\t\2/'| string split0)
|
||||
if test $output
|
||||
echo $output
|
||||
end
|
||||
end
|
||||
|
||||
complete -c $GO_TASK_PROGNAME \
|
||||
-d 'Runs the specified task(s). Falls back to the "default" task if no task name was specified, or lists all tasks if an unknown task name was specified.' \
|
||||
-xa "(__task_get_tasks)" \
|
||||
-n "not __fish_seen_subcommand_from --"
|
||||
|
||||
# Standard flags
|
||||
complete -c $GO_TASK_PROGNAME -s a -l list-all -d 'list all tasks'
|
||||
complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)'
|
||||
complete -c $GO_TASK_PROGNAME -s C -l concurrency -d 'limit number of concurrent tasks'
|
||||
complete -c $GO_TASK_PROGNAME -l completion -d 'generate shell completion script' -xa "bash zsh fish powershell nu"
|
||||
complete -c $GO_TASK_PROGNAME -s d -l dir -d 'set directory of execution'
|
||||
complete -c $GO_TASK_PROGNAME -l disable-fuzzy -d 'disable fuzzy matching for task names'
|
||||
complete -c $GO_TASK_PROGNAME -s n -l dry -d 'compile and print tasks without executing'
|
||||
complete -c $GO_TASK_PROGNAME -s x -l exit-code -d 'pass-through exit code of task command'
|
||||
complete -c $GO_TASK_PROGNAME -l experiments -d 'list available experiments'
|
||||
complete -c $GO_TASK_PROGNAME -s F -l failfast -d 'when running tasks in parallel, stop all tasks if one fails'
|
||||
complete -c $GO_TASK_PROGNAME -s f -l force -d 'force execution even when up-to-date'
|
||||
complete -c $GO_TASK_PROGNAME -s g -l global -d 'run global Taskfile from home directory'
|
||||
complete -c $GO_TASK_PROGNAME -s h -l help -d 'show help'
|
||||
complete -c $GO_TASK_PROGNAME -s i -l init -d 'create new Taskfile'
|
||||
complete -c $GO_TASK_PROGNAME -l insecure -d 'allow insecure Taskfile downloads'
|
||||
complete -c $GO_TASK_PROGNAME -s I -l interval -d 'interval to watch for changes'
|
||||
complete -c $GO_TASK_PROGNAME -s j -l json -d 'format task list as JSON'
|
||||
complete -c $GO_TASK_PROGNAME -s l -l list -d 'list tasks with descriptions'
|
||||
complete -c $GO_TASK_PROGNAME -l nested -d 'nest namespaces when listing as JSON'
|
||||
complete -c $GO_TASK_PROGNAME -l no-status -d 'ignore status when listing as JSON'
|
||||
complete -c $GO_TASK_PROGNAME -l interactive -d 'prompt for missing required variables'
|
||||
complete -c $GO_TASK_PROGNAME -s o -l output -d 'set output style' -xa "interleaved group prefixed"
|
||||
complete -c $GO_TASK_PROGNAME -l output-group-begin -d 'message template before grouped output'
|
||||
complete -c $GO_TASK_PROGNAME -l output-group-end -d 'message template after grouped output'
|
||||
complete -c $GO_TASK_PROGNAME -l output-group-error-only -d 'hide output from successful tasks'
|
||||
complete -c $GO_TASK_PROGNAME -s p -l parallel -d 'execute tasks in parallel'
|
||||
complete -c $GO_TASK_PROGNAME -s s -l silent -d 'disable echoing'
|
||||
complete -c $GO_TASK_PROGNAME -l sort -d 'set task sorting order' -xa "default alphanumeric none"
|
||||
complete -c $GO_TASK_PROGNAME -l status -d 'exit non-zero if tasks not up-to-date'
|
||||
complete -c $GO_TASK_PROGNAME -l summary -d 'show task summary'
|
||||
complete -c $GO_TASK_PROGNAME -s t -l taskfile -d 'choose Taskfile to run'
|
||||
complete -c $GO_TASK_PROGNAME -s v -l verbose -d 'verbose output'
|
||||
complete -c $GO_TASK_PROGNAME -l version -d 'show version'
|
||||
complete -c $GO_TASK_PROGNAME -s w -l watch -d 'watch mode, re-run on changes'
|
||||
complete -c $GO_TASK_PROGNAME -s y -l yes -d 'assume yes to all prompts'
|
||||
complete -c $GO_TASK_PROGNAME -l offline -d 'use only local or cached Taskfiles'
|
||||
complete -c $GO_TASK_PROGNAME -l timeout -d 'timeout for remote Taskfile downloads'
|
||||
complete -c $GO_TASK_PROGNAME -l expiry -d 'cache expiry duration'
|
||||
complete -c $GO_TASK_PROGNAME -l remote-cache-dir -d 'directory to cache remote Taskfiles' -xa "(__fish_complete_directories)"
|
||||
complete -c $GO_TASK_PROGNAME -l cacert -d 'custom CA certificate for TLS' -r
|
||||
complete -c $GO_TASK_PROGNAME -l cert -d 'client certificate for mTLS' -r
|
||||
complete -c $GO_TASK_PROGNAME -l cert-key -d 'client certificate private key' -r
|
||||
complete -c $GO_TASK_PROGNAME -l download -d 'download remote Taskfile'
|
||||
complete -c $GO_TASK_PROGNAME -l clear-cache -d 'clear remote Taskfile cache'
|
||||
|
||||
# Experimental flags (dynamically checked at completion time via -n condition)
|
||||
# GentleForce experiment
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled GENTLE_FORCE" -l force-all -d 'force execution of task and all dependencies'
|
||||
180
completion/legacy/nu/task-completions.nu
Normal file
180
completion/legacy/nu/task-completions.nu
Normal file
@@ -0,0 +1,180 @@
|
||||
# Nushell completions for Task (https://taskfile.dev).
|
||||
#
|
||||
# Nushell cannot source a script from stdin, so save this file where Nushell
|
||||
# picks it up automatically:
|
||||
# mkdir ($nu.data-dir | path join "vendor/autoload")
|
||||
# task --completion nu | save --force ($nu.data-dir | path join "vendor/autoload/task-completions.nu")
|
||||
#
|
||||
# The file must not be named task.nu: Nushell refuses to export a known external
|
||||
# named like its module, which would break `use task-completions.nu *`.
|
||||
|
||||
# Name or path of the Task executable, like the other completion scripts.
|
||||
# The *completed* command is always `task`: an `extern` declaration requires a
|
||||
# literal name. For a renamed executable, alias it instead: `alias go-task = task`.
|
||||
def "nu-complete task-exe" [] {
|
||||
$env.TASK_EXE? | default "task"
|
||||
}
|
||||
|
||||
def "nu-complete task-words" [context: string] {
|
||||
$context | split row --regex '\s+' | where {|word| $word != "" }
|
||||
}
|
||||
|
||||
# Strips the quotes the user may have typed around a value and expands `~`,
|
||||
# which Nushell does not do for a value coming from a variable.
|
||||
def "nu-complete task-value" [value: string] {
|
||||
let unquoted = ($value | str trim --char '"' | str trim --char "'")
|
||||
if ($unquoted | str starts-with "~") {
|
||||
$unquoted | path expand --no-symlink
|
||||
} else {
|
||||
$unquoted
|
||||
}
|
||||
}
|
||||
|
||||
# Rebuilds the flags deciding *which* Taskfile is read, so the task list follows
|
||||
# the `-t/--taskfile`, `-d/--dir` and `-g/--global` already on the command line.
|
||||
def "nu-complete task-scope" [words: list<string>] {
|
||||
mut scope: list<string> = []
|
||||
mut pending = ""
|
||||
|
||||
for word in ($words | skip 1) {
|
||||
if $pending != "" {
|
||||
$scope = ($scope | append [$pending, (nu-complete task-value $word)])
|
||||
$pending = ""
|
||||
continue
|
||||
}
|
||||
|
||||
let parts = ($word | split row "=")
|
||||
let name = ($parts | first)
|
||||
let inline = if ($parts | length) > 1 { $parts | skip 1 | str join "=" } else { null }
|
||||
|
||||
if $name in ["-g", "--global"] {
|
||||
$scope = ($scope | append "--global")
|
||||
} else if $name in ["-t", "--taskfile", "-d", "--dir"] {
|
||||
let long = if $name in ["-t", "--taskfile"] { "--taskfile" } else { "--dir" }
|
||||
if $inline != null {
|
||||
$scope = ($scope | append [$long, (nu-complete task-value $inline)])
|
||||
} else {
|
||||
$pending = $long
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope
|
||||
}
|
||||
|
||||
# Lists the tasks of the targeted Taskfile. `--no-status` keeps completion fast:
|
||||
# without it Task fingerprints every task's sources on each keystroke. Returns an
|
||||
# empty list when Task exits non-zero (no Taskfile, invalid Taskfile).
|
||||
def "nu-complete task-list" [words: list<string>] {
|
||||
let exe = (nu-complete task-exe)
|
||||
let args = [...(nu-complete task-scope $words) "--list-all" "--json" "--no-status"]
|
||||
let result = (try { do { ^$exe ...$args } | complete } catch { null })
|
||||
|
||||
if ($result | is-empty) or $result.exit_code != 0 {
|
||||
return []
|
||||
}
|
||||
|
||||
try { $result.stdout | from json | get tasks } catch { [] }
|
||||
}
|
||||
|
||||
def "nu-complete task" [context: string] {
|
||||
let words = (nu-complete task-words $context)
|
||||
|
||||
# Words after `--` are forwarded to the task as CLI_ARGS: stop offering task
|
||||
# names and let Nushell fall back to its own file completion.
|
||||
if "--" in $words {
|
||||
return null
|
||||
}
|
||||
|
||||
let completions = (
|
||||
nu-complete task-list $words
|
||||
| each {|item|
|
||||
# `task` is the invocable name; `name` may be a display-only label.
|
||||
let name = ($item.task | str trim --right --char ':')
|
||||
let desc = ($item.desc? | default "")
|
||||
let aliases = (
|
||||
$item.aliases?
|
||||
| default []
|
||||
| each {|alias| {
|
||||
value: ($alias | str trim --right --char ':')
|
||||
description: (if ($desc | is-empty) { $"alias of ($name)" } else { $"($desc) \(alias of ($name)\)" })
|
||||
} }
|
||||
)
|
||||
[{ value: $name, description: $desc }] | append $aliases
|
||||
}
|
||||
| flatten
|
||||
)
|
||||
|
||||
# `sort: false` keeps the order Task chose, which honours --sort and .taskrc.
|
||||
{ options: { sort: false }, completions: $completions }
|
||||
}
|
||||
|
||||
def "nu-complete task-shells" [] {
|
||||
["bash", "zsh", "fish", "powershell", "nu"]
|
||||
}
|
||||
|
||||
def "nu-complete task-output" [] {
|
||||
["interleaved", "group", "prefixed"]
|
||||
}
|
||||
|
||||
def "nu-complete task-sort" [] {
|
||||
["default", "alphanumeric", "none"]
|
||||
}
|
||||
|
||||
# Runs the specified task(s). Falls back to the "default" task if no task name
|
||||
# was specified, or lists all tasks if an unknown task name was specified.
|
||||
#
|
||||
# An `extern` signature is static, so the experimental flag at the bottom is
|
||||
# always offered; Task rejects it when the experiment is off. Run
|
||||
# `task --experiments` to see which experiments are enabled.
|
||||
export extern "task" [
|
||||
...tasks: string@"nu-complete task" # task(s) to run
|
||||
--list(-l) # list tasks with a description
|
||||
--list-all(-a) # list all tasks, with or without a description
|
||||
--json(-j) # format the task list as JSON
|
||||
--no-status # ignore status when listing tasks as JSON
|
||||
--nested # nest namespaces when listing tasks as JSON
|
||||
--sort: string@"nu-complete task-sort" # change the order of the tasks when listed
|
||||
--init(-i) # create a new Taskfile.yml in the current folder
|
||||
--completion: string@"nu-complete task-shells" # generate a shell completion script
|
||||
--taskfile(-t): glob # choose which Taskfile to run
|
||||
--dir(-d): directory # set the directory in which Task will execute
|
||||
--global(-g) # run the global Taskfile from $HOME
|
||||
--temp-dir: directory # directory used to store Task temporary files
|
||||
--force(-f) # force execution even when the task is up-to-date
|
||||
--status # exit with a non-zero code if tasks are not up-to-date
|
||||
--dry(-n) # compile and print the tasks without executing them
|
||||
--summary # show the summary of a task instead of running it
|
||||
--watch(-w) # watch the given tasks and re-run them on changes
|
||||
--interval(-I): string # interval to watch for changes, e.g. 500ms
|
||||
--parallel(-p) # run the tasks given on the command line in parallel
|
||||
--concurrency(-C): int # limit the number of tasks run concurrently
|
||||
--failfast(-F) # when running in parallel, stop everything if one task fails
|
||||
--exit-code(-x) # pass through the exit code of the task command
|
||||
--interactive # prompt for missing required variables
|
||||
--yes(-y) # assume "yes" as the answer to all prompts
|
||||
--output(-o): string@"nu-complete task-output" # set the output style
|
||||
--output-group-begin: string # message template printed before a task's grouped output
|
||||
--output-group-end: string # message template printed after a task's grouped output
|
||||
--output-group-error-only # swallow the output of successful tasks
|
||||
--color(-c) # colored output, enabled by default
|
||||
--silent(-s) # disable echoing
|
||||
--verbose(-v) # enable verbose mode
|
||||
--disable-fuzzy # disable fuzzy matching for task names
|
||||
--download # download a cached version of a remote Taskfile
|
||||
--offline # only use local or cached Taskfiles
|
||||
--clear-cache # clear the remote Taskfile cache
|
||||
--trusted-hosts: string # trusted hosts for remote Taskfiles (comma-separated)
|
||||
--timeout: string # timeout for downloading remote Taskfiles
|
||||
--expiry: string # expiry duration for cached remote Taskfiles
|
||||
--remote-cache-dir: directory # directory used to cache remote Taskfiles
|
||||
--cacert: path # custom CA certificate for HTTPS connections
|
||||
--cert: path # client certificate for HTTPS connections
|
||||
--cert-key: path # client certificate key for HTTPS connections
|
||||
--insecure # allow Taskfiles to be downloaded over insecure connections
|
||||
--experiments # list the available experiments and whether they are enabled
|
||||
--version # show the Task version
|
||||
--help(-h) # show Task usage
|
||||
|
||||
--force-all # [GENTLE_FORCE] force the called task and all its dependencies
|
||||
]
|
||||
89
completion/legacy/ps/task.ps1
Normal file
89
completion/legacy/ps/task.ps1
Normal file
@@ -0,0 +1,89 @@
|
||||
using namespace System.Management.Automation
|
||||
|
||||
$cmdNames = @('task') + (Get-Alias -Definition task,task.exe,*\task,*\task.exe -ErrorAction SilentlyContinue).Name | Select-Object -Unique
|
||||
|
||||
Register-ArgumentCompleter -CommandName $cmdNames -ScriptBlock {
|
||||
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
|
||||
|
||||
if ($commandName.StartsWith('-')) {
|
||||
$completions = @(
|
||||
# Standard flags (alphabetical order)
|
||||
[CompletionResult]::new('-a', '-a', [CompletionResultType]::ParameterName, 'list all tasks'),
|
||||
[CompletionResult]::new('--list-all', '--list-all', [CompletionResultType]::ParameterName, 'list all tasks'),
|
||||
[CompletionResult]::new('-c', '-c', [CompletionResultType]::ParameterName, 'colored output'),
|
||||
[CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'colored output'),
|
||||
[CompletionResult]::new('-C', '-C', [CompletionResultType]::ParameterName, 'limit concurrent tasks'),
|
||||
[CompletionResult]::new('--concurrency', '--concurrency', [CompletionResultType]::ParameterName, 'limit concurrent tasks'),
|
||||
[CompletionResult]::new('--completion', '--completion', [CompletionResultType]::ParameterName, 'generate shell completion'),
|
||||
[CompletionResult]::new('-d', '-d', [CompletionResultType]::ParameterName, 'set directory'),
|
||||
[CompletionResult]::new('--dir', '--dir', [CompletionResultType]::ParameterName, 'set directory'),
|
||||
[CompletionResult]::new('--disable-fuzzy', '--disable-fuzzy', [CompletionResultType]::ParameterName, 'disable fuzzy matching'),
|
||||
[CompletionResult]::new('-n', '-n', [CompletionResultType]::ParameterName, 'dry run'),
|
||||
[CompletionResult]::new('--dry', '--dry', [CompletionResultType]::ParameterName, 'dry run'),
|
||||
[CompletionResult]::new('-x', '-x', [CompletionResultType]::ParameterName, 'pass-through exit code'),
|
||||
[CompletionResult]::new('--exit-code', '--exit-code', [CompletionResultType]::ParameterName, 'pass-through exit code'),
|
||||
[CompletionResult]::new('--experiments', '--experiments', [CompletionResultType]::ParameterName, 'list experiments'),
|
||||
[CompletionResult]::new('-F', '-F', [CompletionResultType]::ParameterName, 'fail fast on pallalel tasks'),
|
||||
[CompletionResult]::new('--failfast', '--failfast', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('--force', '--force', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('-g', '-g', [CompletionResultType]::ParameterName, 'run global Taskfile'),
|
||||
[CompletionResult]::new('--global', '--global', [CompletionResultType]::ParameterName, 'run global Taskfile'),
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'show help'),
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'show help'),
|
||||
[CompletionResult]::new('-i', '-i', [CompletionResultType]::ParameterName, 'create new Taskfile'),
|
||||
[CompletionResult]::new('--init', '--init', [CompletionResultType]::ParameterName, 'create new Taskfile'),
|
||||
[CompletionResult]::new('--insecure', '--insecure', [CompletionResultType]::ParameterName, 'allow insecure downloads'),
|
||||
[CompletionResult]::new('-I', '-I', [CompletionResultType]::ParameterName, 'watch interval'),
|
||||
[CompletionResult]::new('--interval', '--interval', [CompletionResultType]::ParameterName, 'watch interval'),
|
||||
[CompletionResult]::new('-j', '-j', [CompletionResultType]::ParameterName, 'format as JSON'),
|
||||
[CompletionResult]::new('--json', '--json', [CompletionResultType]::ParameterName, 'format as JSON'),
|
||||
[CompletionResult]::new('-l', '-l', [CompletionResultType]::ParameterName, 'list tasks'),
|
||||
[CompletionResult]::new('--list', '--list', [CompletionResultType]::ParameterName, 'list tasks'),
|
||||
[CompletionResult]::new('--nested', '--nested', [CompletionResultType]::ParameterName, 'nest namespaces in JSON'),
|
||||
[CompletionResult]::new('--no-status', '--no-status', [CompletionResultType]::ParameterName, 'ignore status in JSON'),
|
||||
[CompletionResult]::new('--interactive', '--interactive', [CompletionResultType]::ParameterName, 'prompt for missing required variables'),
|
||||
[CompletionResult]::new('-o', '-o', [CompletionResultType]::ParameterName, 'set output style'),
|
||||
[CompletionResult]::new('--output', '--output', [CompletionResultType]::ParameterName, 'set output style'),
|
||||
[CompletionResult]::new('--output-group-begin', '--output-group-begin', [CompletionResultType]::ParameterName, 'template before group'),
|
||||
[CompletionResult]::new('--output-group-end', '--output-group-end', [CompletionResultType]::ParameterName, 'template after group'),
|
||||
[CompletionResult]::new('--output-group-error-only', '--output-group-error-only', [CompletionResultType]::ParameterName, 'hide successful output'),
|
||||
[CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'execute in parallel'),
|
||||
[CompletionResult]::new('--parallel', '--parallel', [CompletionResultType]::ParameterName, 'execute in parallel'),
|
||||
[CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'silent mode'),
|
||||
[CompletionResult]::new('--silent', '--silent', [CompletionResultType]::ParameterName, 'silent mode'),
|
||||
[CompletionResult]::new('--sort', '--sort', [CompletionResultType]::ParameterName, 'task sorting order'),
|
||||
[CompletionResult]::new('--status', '--status', [CompletionResultType]::ParameterName, 'check task status'),
|
||||
[CompletionResult]::new('--summary', '--summary', [CompletionResultType]::ParameterName, 'show task summary'),
|
||||
[CompletionResult]::new('-t', '-t', [CompletionResultType]::ParameterName, 'choose Taskfile'),
|
||||
[CompletionResult]::new('--taskfile', '--taskfile', [CompletionResultType]::ParameterName, 'choose Taskfile'),
|
||||
[CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'verbose output'),
|
||||
[CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'verbose output'),
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'show version'),
|
||||
[CompletionResult]::new('-w', '-w', [CompletionResultType]::ParameterName, 'watch mode'),
|
||||
[CompletionResult]::new('--watch', '--watch', [CompletionResultType]::ParameterName, 'watch mode'),
|
||||
[CompletionResult]::new('-y', '-y', [CompletionResultType]::ParameterName, 'assume yes'),
|
||||
[CompletionResult]::new('--yes', '--yes', [CompletionResultType]::ParameterName, 'assume yes'),
|
||||
[CompletionResult]::new('--offline', '--offline', [CompletionResultType]::ParameterName, 'use cached Taskfiles'),
|
||||
[CompletionResult]::new('--timeout', '--timeout', [CompletionResultType]::ParameterName, 'download timeout'),
|
||||
[CompletionResult]::new('--expiry', '--expiry', [CompletionResultType]::ParameterName, 'cache expiry'),
|
||||
[CompletionResult]::new('--remote-cache-dir', '--remote-cache-dir', [CompletionResultType]::ParameterName, 'cache directory'),
|
||||
[CompletionResult]::new('--cacert', '--cacert', [CompletionResultType]::ParameterName, 'custom CA certificate'),
|
||||
[CompletionResult]::new('--cert', '--cert', [CompletionResultType]::ParameterName, 'client certificate'),
|
||||
[CompletionResult]::new('--cert-key', '--cert-key', [CompletionResultType]::ParameterName, 'client private key'),
|
||||
[CompletionResult]::new('--download', '--download', [CompletionResultType]::ParameterName, 'download remote Taskfile'),
|
||||
[CompletionResult]::new('--clear-cache', '--clear-cache', [CompletionResultType]::ParameterName, 'clear cache')
|
||||
)
|
||||
|
||||
# Experimental flags (dynamically added based on enabled experiments)
|
||||
$experiments = & task --experiments 2>$null | Out-String
|
||||
|
||||
if ($experiments -match '\* GENTLE_FORCE:.*on') {
|
||||
$completions += [CompletionResult]::new('--force-all', '--force-all', [CompletionResultType]::ParameterName, 'force all dependencies')
|
||||
}
|
||||
|
||||
return $completions.Where{ $_.CompletionText.StartsWith($commandName) }
|
||||
}
|
||||
|
||||
return $(task --list-all --silent) | Where-Object { $_.StartsWith($commandName) } | ForEach-Object { return $_ + " " }
|
||||
}
|
||||
158
completion/legacy/zsh/_task
Executable file
158
completion/legacy/zsh/_task
Executable file
@@ -0,0 +1,158 @@
|
||||
#compdef task
|
||||
typeset -A opt_args
|
||||
TASK_CMD="${TASK_EXE:-task}"
|
||||
compdef _task "$TASK_CMD"
|
||||
|
||||
_GO_TASK_COMPLETION_LIST_OPTION="${GO_TASK_COMPLETION_LIST_OPTION:---list-all}"
|
||||
|
||||
# Check if an experiment is enabled
|
||||
function __task_is_experiment_enabled() {
|
||||
local experiment=$1
|
||||
task --experiments 2>/dev/null | grep -q "^\* ${experiment}:.*on"
|
||||
}
|
||||
|
||||
# Listing commands from Taskfile.yml
|
||||
function __task_list() {
|
||||
local -a scripts cmd task_aliases match mbegin mend
|
||||
local -i enabled=0
|
||||
local taskfile item task desc task_alias
|
||||
|
||||
cmd=($TASK_CMD)
|
||||
taskfile=${(Qv)opt_args[(i)-t|--taskfile]}
|
||||
taskfile=${taskfile//\~/$HOME}
|
||||
|
||||
for arg in "${words[@]:0:$CURRENT}"; do
|
||||
if [[ "$arg" = "--" ]]; then
|
||||
# Use default completion for words after `--` as they are CLI_ARGS.
|
||||
_default
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$taskfile" && -f "$taskfile" ]]; then
|
||||
cmd+=(--taskfile "$taskfile")
|
||||
fi
|
||||
|
||||
# Check if global flag is set
|
||||
if (( ${+opt_args[-g]} || ${+opt_args[--global]} )); then
|
||||
cmd+=(--global)
|
||||
fi
|
||||
|
||||
if output=$("${cmd[@]}" $_GO_TASK_COMPLETION_LIST_OPTION 2>/dev/null); then
|
||||
enabled=1
|
||||
fi
|
||||
|
||||
(( enabled )) || return 0
|
||||
|
||||
scripts=()
|
||||
|
||||
# Read zstyle verbose option (default = true via -T)
|
||||
local show_desc
|
||||
zstyle -T ":completion:${curcontext}:" verbose && show_desc=true || show_desc=false
|
||||
|
||||
# Read zstyle show-aliases option (default = true via -T)
|
||||
local show_aliases
|
||||
zstyle -T ":completion:${curcontext}:" show-aliases && show_aliases=true || show_aliases=false
|
||||
|
||||
for item in "${(@)${(f)output}[2,-1]#\* }"; do
|
||||
task="${item%%:[[:space:]]*}"
|
||||
|
||||
# Extract the aliases listed in the trailing "(aliases: a, b)" column.
|
||||
# NB: `aliases` is a reserved zsh parameter, so use a different name.
|
||||
task_aliases=()
|
||||
if [[ "$show_aliases" == "true" && "$item" == (#b)*'(aliases: '(*)')' ]]; then
|
||||
task_aliases=( "${(@s:, :)match[1]}" )
|
||||
fi
|
||||
|
||||
if [[ "$show_desc" == "true" ]]; then
|
||||
local desc="${item##[^[:space:]]##[[:space:]]##}"
|
||||
scripts+=( "${task//:/\\:}:$desc" )
|
||||
for task_alias in $task_aliases; do
|
||||
scripts+=( "${task_alias//:/\\:}:$desc (alias of $task)" )
|
||||
done
|
||||
else
|
||||
scripts+=( "$task" )
|
||||
for task_alias in $task_aliases; do
|
||||
scripts+=( "$task_alias" )
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$show_desc" == "true" ]]; then
|
||||
_describe 'Task to run' scripts
|
||||
else
|
||||
compadd -Q -a scripts
|
||||
fi
|
||||
}
|
||||
|
||||
_task() {
|
||||
local -a standard_args operation_args
|
||||
|
||||
standard_args=(
|
||||
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: '
|
||||
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]'
|
||||
'(-F --failfast)'{-F,--failfast}'[when running tasks in parallel, stop all tasks if one fails]'
|
||||
'(-f --force)'{-f,--force}'[run even if task is up-to-date]'
|
||||
'(-c --color)'{-c,--color}'[colored output]'
|
||||
'(--completion)--completion[generate shell completion script]:shell:(bash zsh fish powershell nu)'
|
||||
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs'
|
||||
'(--disable-fuzzy)--disable-fuzzy[disable fuzzy matching for task names]'
|
||||
'(-n --dry)'{-n,--dry}'[compiles and prints tasks without executing]'
|
||||
'(--dry)--dry[dry-run mode, compile and print tasks only]'
|
||||
'(-x --exit-code)'{-x,--exit-code}'[pass-through exit code of task command]'
|
||||
'(--experiments)--experiments[list available experiments]'
|
||||
'(-g --global)'{-g,--global}'[run global Taskfile from home directory]'
|
||||
'(--insecure)--insecure[allow insecure Taskfile downloads]'
|
||||
'(-I --interval)'{-I,--interval}'[interval to watch for changes]:duration: '
|
||||
'(-j --json)'{-j,--json}'[format task list as JSON]'
|
||||
'(--nested)--nested[nest namespaces when listing as JSON]'
|
||||
'(--no-status)--no-status[ignore status when listing as JSON]'
|
||||
'(--interactive)--interactive[prompt for missing required variables]'
|
||||
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)'
|
||||
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: '
|
||||
'(--output-group-end)--output-group-end[message template after grouped output]:template text: '
|
||||
'(--output-group-error-only)--output-group-error-only[hide output from successful tasks]'
|
||||
'(-s --silent)'{-s,--silent}'[disable echoing]'
|
||||
'(--sort)--sort[set task sorting order]:order:(default alphanumeric none)'
|
||||
'(--status)--status[exit non-zero if supplied tasks not up-to-date]'
|
||||
'(--summary)--summary[show summary\: field from tasks instead of running them]'
|
||||
'(-t --taskfile)'{-t,--taskfile}'[specify a different taskfile]:taskfile:_files'
|
||||
'(-v --verbose)'{-v,--verbose}'[verbose mode]'
|
||||
'(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]'
|
||||
'(-y --yes)'{-y,--yes}'[assume yes to all prompts]'
|
||||
'(--offline --clear-cache)--download[download remote Taskfile]'
|
||||
'(--offline --download)--offline[use only local or cached Taskfiles]'
|
||||
'(--timeout)--timeout[timeout for remote Taskfile downloads]:duration: '
|
||||
'(--expiry)--expiry[cache expiry duration]:duration: '
|
||||
'(--remote-cache-dir)--remote-cache-dir[directory to cache remote Taskfiles]:cache dir:_dirs'
|
||||
'(--cacert)--cacert[custom CA certificate for TLS]:file:_files'
|
||||
'(--cert)--cert[client certificate for mTLS]:file:_files'
|
||||
'(--cert-key)--cert-key[client certificate private key]:file:_files'
|
||||
)
|
||||
|
||||
# Experimental flags (dynamically added based on enabled experiments)
|
||||
# Options (modify behavior)
|
||||
if __task_is_experiment_enabled "GENTLE_FORCE"; then
|
||||
standard_args+=('(--force-all)--force-all[force execution of task and all dependencies]')
|
||||
fi
|
||||
|
||||
operation_args=(
|
||||
# Task names completion (can be specified multiple times)
|
||||
'(operation)*: :__task_list'
|
||||
# Operational args completion (mutually exclusive)
|
||||
+ '(operation)'
|
||||
'(*)'{-l,--list}'[list describable tasks]'
|
||||
'(*)'{-a,--list-all}'[list all tasks]'
|
||||
'(*)'{-i,--init}'[create new Taskfile.yml]'
|
||||
'(- *)'{-h,--help}'[show help]'
|
||||
'(- *)--version[show version and exit]'
|
||||
'(* --download)--clear-cache[clear remote Taskfile cache]'
|
||||
)
|
||||
|
||||
_arguments -S $standard_args $operation_args
|
||||
}
|
||||
|
||||
# don't run the completion function when being source-ed or eval-ed
|
||||
if [ "$funcstack[1]" = "_task" ]; then
|
||||
_task "$@"
|
||||
fi
|
||||
@@ -1,94 +0,0 @@
|
||||
# vim: set tabstop=2 shiftwidth=2 expandtab:
|
||||
#
|
||||
# Thin wrapper around `task __complete`: all suggestion logic lives in the Go engine.
|
||||
|
||||
TASK_CMD="${TASK_EXE:-task}"
|
||||
|
||||
# `=` stays inside the current word (see `_init_completion -n =:`), so an inline
|
||||
# `--flag=` prefix must be stripped before _filedir and re-applied after.
|
||||
_task_filedir() {
|
||||
local fpfx="" savecur="$cur"
|
||||
if [[ "$cur" == -*=* ]]; then
|
||||
fpfx="${cur%%=*}="
|
||||
cur="${cur#*=}"
|
||||
fi
|
||||
_filedir ${1:+"$1"}
|
||||
cur="$savecur"
|
||||
if [[ -n "$fpfx" ]]; then
|
||||
COMPREPLY=( ${COMPREPLY[@]+"${COMPREPLY[@]/#/$fpfx}"} )
|
||||
fi
|
||||
}
|
||||
|
||||
_task() {
|
||||
local cur prev words cword
|
||||
|
||||
# Completion directives, mirroring internal/complete/complete.go.
|
||||
local -ri NO_SPACE=2 NO_FILE_COMP=4 FILTER_FILE_EXT=8 FILTER_DIRS=16 KEEP_ORDER=32
|
||||
|
||||
# `=` and `:` out of the word breaks: `--output=`, `docs:serve` stay one token.
|
||||
_init_completion -n =: || return
|
||||
|
||||
local -a args=( "${words[@]:1:cword}" )
|
||||
if (( ${#args[@]} == 0 )); then
|
||||
args=( "" )
|
||||
fi
|
||||
|
||||
local output
|
||||
output=$("$TASK_CMD" __complete "${args[@]}" 2>/dev/null)
|
||||
if [[ -z "$output" ]]; then
|
||||
_task_filedir
|
||||
return
|
||||
fi
|
||||
|
||||
local -a lines=()
|
||||
local line
|
||||
while IFS= read -r line; do
|
||||
lines+=( "$line" )
|
||||
done <<< "$output"
|
||||
|
||||
local last_idx=$(( ${#lines[@]} - 1 ))
|
||||
local directive="${lines[$last_idx]#:}"
|
||||
unset 'lines[$last_idx]'
|
||||
|
||||
if (( directive & FILTER_FILE_EXT )); then
|
||||
local exts=""
|
||||
# ${arr[@]+…} guards an empty array under `set -u` in bash 3.2 (macOS).
|
||||
for line in ${lines[@]+"${lines[@]}"}; do
|
||||
exts+="${exts:+|}$line"
|
||||
done
|
||||
_task_filedir "@($exts)"
|
||||
return
|
||||
fi
|
||||
|
||||
if (( directive & FILTER_DIRS )); then
|
||||
_task_filedir -d
|
||||
return
|
||||
fi
|
||||
|
||||
# Not `compgen -W`: it splits the word list on IFS, mangling values with spaces.
|
||||
local value
|
||||
COMPREPLY=()
|
||||
for line in ${lines[@]+"${lines[@]}"}; do
|
||||
value="${line%%$'\t'*}"
|
||||
if [[ -z "$cur" || "$value" == "$cur"* ]]; then
|
||||
COMPREPLY+=( "$value" )
|
||||
fi
|
||||
done
|
||||
|
||||
if (( directive & NO_SPACE )); then
|
||||
compopt -o nospace 2>/dev/null
|
||||
fi
|
||||
|
||||
# nosort needs bash 4.4; the 3.2 shipped by macOS ignores it and stays sorted.
|
||||
if (( directive & KEEP_ORDER )); then
|
||||
compopt -o nosort 2>/dev/null
|
||||
fi
|
||||
|
||||
__ltrim_colon_completions "$cur"
|
||||
|
||||
if (( ${#COMPREPLY[@]} == 0 )) && ! (( directive & NO_FILE_COMP )); then
|
||||
_task_filedir
|
||||
fi
|
||||
}
|
||||
|
||||
complete -F _task "$TASK_CMD"
|
||||
@@ -1,98 +0,0 @@
|
||||
# Thin wrapper around `task __complete`: all suggestion logic lives in the Go engine.
|
||||
|
||||
set -l GO_TASK_PROGNAME (if set -q GO_TASK_PROGNAME; echo $GO_TASK_PROGNAME; else if set -q TASK_EXE; echo $TASK_EXE; else; echo task; end)
|
||||
|
||||
# Completion directives, mirroring internal/complete/complete.go. `math` has no
|
||||
# bitwise operators, hence __task_test_bit. NoSpace (2) and KeepOrder (32) need
|
||||
# none: fish appends no space and keeps the order.
|
||||
set -g __task_directive_no_file_comp 4
|
||||
set -g __task_directive_filter_file_ext 8
|
||||
set -g __task_directive_filter_dirs 16
|
||||
|
||||
function __task_test_bit --argument-names value bit
|
||||
test (math "floor($value / $bit) % 2") -eq 1
|
||||
end
|
||||
|
||||
function __task_complete --inherit-variable GO_TASK_PROGNAME
|
||||
set -l tokens (commandline -opc)
|
||||
set -l current (commandline -ct)
|
||||
set -l args
|
||||
if test (count $tokens) -gt 1
|
||||
set args $tokens[2..-1]
|
||||
end
|
||||
set args $args $current
|
||||
|
||||
set -l output ($GO_TASK_PROGNAME __complete $args 2>/dev/null)
|
||||
set -l count (count $output)
|
||||
if test $count -eq 0
|
||||
return
|
||||
end
|
||||
|
||||
set -l last $output[$count]
|
||||
if not string match -q ':*' -- $last
|
||||
# Protocol violation: emit raw lines as a fallback.
|
||||
printf '%s\n' $output
|
||||
return
|
||||
end
|
||||
|
||||
set -l directive (string replace -r '^:' '' -- $last)
|
||||
set -l data
|
||||
if test $count -gt 1
|
||||
set data $output[1..(math $count - 1)]
|
||||
end
|
||||
|
||||
# The registration below passes `--no-files`, so every file-completion
|
||||
# directive must be served here or nothing is offered at all.
|
||||
|
||||
# fish replaces the whole token, so an inline `--flag=` must be kept on every
|
||||
# candidate.
|
||||
set -l flagpfx ""
|
||||
set -l pathcur $current
|
||||
if string match -qr '^--?[^=]+=' -- $current
|
||||
set flagpfx (string replace -r '=.*$' '=' -- $current)
|
||||
set pathcur (string replace -r '^--?[^=]+=' '' -- $current)
|
||||
end
|
||||
|
||||
# __fish_complete_suffix prioritizes the extension instead of filtering.
|
||||
if __task_test_bit $directive $__task_directive_filter_file_ext
|
||||
for entry in (__fish_complete_path $pathcur)
|
||||
set -l name (string split -f1 \t -- $entry)
|
||||
if string match -qr '/$' -- $name
|
||||
printf '%s%s\n' $flagpfx $entry
|
||||
continue
|
||||
end
|
||||
for ext in $data
|
||||
if string match -qr "\.$ext\$" -- $name
|
||||
printf '%s%s\n' $flagpfx $entry
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if __task_test_bit $directive $__task_directive_filter_dirs
|
||||
for entry in (__fish_complete_directories $pathcur)
|
||||
printf '%s%s\n' $flagpfx $entry
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
for line in $data
|
||||
printf '%s\n' $line
|
||||
end
|
||||
|
||||
# NoFileComp unset → offer files too (DirectiveDefault).
|
||||
if not __task_test_bit $directive $__task_directive_no_file_comp
|
||||
for entry in (__fish_complete_path $pathcur)
|
||||
printf '%s%s\n' $flagpfx $entry
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# fish accumulates `complete` entries instead of replacing them, so an older
|
||||
# completion would keep contributing alongside the engine.
|
||||
complete -c $GO_TASK_PROGNAME -e
|
||||
|
||||
# `--no-files` keeps fish from mixing in files against the engine's directive.
|
||||
complete -c $GO_TASK_PROGNAME --no-files -a "(__task_complete)"
|
||||
@@ -1,86 +0,0 @@
|
||||
# Thin wrapper around `task __complete`: all suggestion logic lives in the Go engine.
|
||||
|
||||
# The `{completions, options}` record documented for `def` completers is
|
||||
# rejected for an external one: return records or null, nothing else.
|
||||
def task-external-completer [spans: list<string>] {
|
||||
let exe = ($env.TASK_EXE? | default "task")
|
||||
|
||||
# The trailing empty word tells the engine the cursor is on a fresh word.
|
||||
let words = ($spans | skip 1)
|
||||
let args = (if ($words | is-empty) { [""] } else { $words })
|
||||
let current = ($args | last)
|
||||
|
||||
# `complete` keeps stderr off the prompt; a missing binary raises, hence `try`.
|
||||
let result = (try { do { ^$exe "__complete" ...$args } | complete } catch { null })
|
||||
if ($result | is-empty) or $result.exit_code != 0 {
|
||||
return null
|
||||
}
|
||||
|
||||
let lines = ($result.stdout | lines)
|
||||
let last = ($lines | last)
|
||||
# Protocol violation: offer nothing rather than garbage.
|
||||
if ($last | is-empty) or (not ($last | str starts-with ":")) {
|
||||
return null
|
||||
}
|
||||
let directive = (try { $last | str substring 1.. | into int } catch { 0 })
|
||||
let data = ($lines | drop 1)
|
||||
|
||||
# Completion directives, mirroring internal/complete/complete.go. NoSpace (2)
|
||||
# and KeepOrder (32) need none: no space is appended, order is kept.
|
||||
let no_file_comp = (($directive | bits and 4) != 0)
|
||||
let filter_file_ext = (($directive | bits and 8) != 0)
|
||||
let filter_dirs = (($directive | bits and 16) != 0)
|
||||
|
||||
# Nushell replaces the whole token, so an inline `--flag=` must be re-applied.
|
||||
let inline = ($current | parse --regex '^(?<flag>--?[^=]+=)(?<path>.*)$')
|
||||
let flag_prefix = (if ($inline | is-empty) { "" } else { $inline.0.flag })
|
||||
let path_arg = (if ($inline | is-empty) { $current } else { $inline.0.path })
|
||||
|
||||
if $filter_file_ext or $filter_dirs {
|
||||
# `into glob` turns the literal path into a pattern; matching nothing raises.
|
||||
let entries = (try { ls ($"($path_arg)*" | into glob) } catch { [] })
|
||||
let matched = (if $filter_file_ext {
|
||||
$entries | where {|entry| $entry.type == "dir" or ($entry.name | path parse | get extension) in $data }
|
||||
} else {
|
||||
$entries | where type == "dir"
|
||||
})
|
||||
return ($matched | each {|entry|
|
||||
# Without a trailing separator a second <tab> matches the dir again.
|
||||
let name = (if $entry.type == "dir" { $"($entry.name)(char path_sep)" } else { $entry.name })
|
||||
{ value: $"($flag_prefix)($name)" }
|
||||
})
|
||||
}
|
||||
|
||||
# Nushell does not filter an external completer's results.
|
||||
let candidates = ($data
|
||||
| each {|line|
|
||||
let parts = ($line | split row --number 2 "\t")
|
||||
let value = ($parts | first)
|
||||
if ($parts | length) > 1 { { value: $value, description: ($parts | last) } } else { { value: $value } }
|
||||
}
|
||||
| where {|candidate| $candidate.value | str starts-with --ignore-case $current })
|
||||
|
||||
if ($candidates | is-empty) and (not $no_file_comp) {
|
||||
return null
|
||||
}
|
||||
|
||||
$candidates
|
||||
}
|
||||
|
||||
# Nushell shares one external completer between every command, so chain to the
|
||||
# installed one instead of breaking every other tool.
|
||||
let task_previous_completer = ($env.config.completions.external.completer? | default null)
|
||||
|
||||
$env.config.completions.external.completer = {|spans|
|
||||
let exe = ($env.TASK_EXE? | default "task")
|
||||
# Compare basenames so `./task`, `/usr/local/bin/task` and `task.exe` match.
|
||||
let head = ($spans | first | path basename | str replace --regex '(?i)\.exe$' '')
|
||||
let name = ($exe | path basename | str replace --regex '(?i)\.exe$' '')
|
||||
if $head == $name {
|
||||
task-external-completer $spans
|
||||
} else if $task_previous_completer != null {
|
||||
do $task_previous_completer $spans
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
using namespace System.Management.Automation
|
||||
using namespace System.Management.Automation.Language
|
||||
|
||||
# Thin wrapper around `task __complete`: all suggestion logic lives in the Go engine.
|
||||
|
||||
$cmdNames = @('task') + (Get-Alias -Definition task,task.exe,*\task,*\task.exe -ErrorAction SilentlyContinue).Name | Select-Object -Unique
|
||||
|
||||
Register-ArgumentCompleter -Native -CommandName $cmdNames -ScriptBlock {
|
||||
param($wordToComplete, $commandAst, $cursorPosition)
|
||||
|
||||
$TaskExe = if ($env:TASK_EXE) { $env:TASK_EXE } else { 'task' }
|
||||
|
||||
# The current word arrives with the quote the user opened.
|
||||
$current = $wordToComplete
|
||||
if ($current.Length -ge 1 -and ($current[0] -eq '"' -or $current[0] -eq "'")) {
|
||||
$quoteChar = $current[0]
|
||||
$current = $current.Substring(1)
|
||||
if ($current.EndsWith($quoteChar)) {
|
||||
$current = $current.Substring(0, $current.Length - 1)
|
||||
}
|
||||
}
|
||||
|
||||
# A string element yields its Value, so `--dir "a b"` arrives unquoted.
|
||||
$argsToPass = @()
|
||||
$elements = $commandAst.CommandElements
|
||||
for ($i = 1; $i -lt $elements.Count; $i++) {
|
||||
$el = $elements[$i]
|
||||
if ($el.Extent.StartOffset -ge $cursorPosition) { break }
|
||||
$argsToPass += if ($el -is [StringConstantExpressionAst] -or $el -is [ExpandableStringExpressionAst]) {
|
||||
$el.Value
|
||||
} else {
|
||||
$el.ToString()
|
||||
}
|
||||
}
|
||||
# The trailing word tells the engine the cursor is on a fresh word.
|
||||
if ($argsToPass.Count -eq 0 -or $argsToPass[-1] -ne $current) {
|
||||
$argsToPass += $current
|
||||
}
|
||||
|
||||
$output = & $TaskExe __complete @argsToPass 2>$null
|
||||
if (-not $output) { return }
|
||||
|
||||
$lines = @($output)
|
||||
$last = $lines[-1]
|
||||
if (-not $last.StartsWith(':')) { return }
|
||||
|
||||
$directive = [int]($last.Substring(1))
|
||||
$data = if ($lines.Count -gt 1) { $lines[0..($lines.Count - 2)] } else { @() }
|
||||
|
||||
# Completion directives, mirroring internal/complete/complete.go.
|
||||
$NoFileComp = 4
|
||||
$FilterFileExt = 8
|
||||
$FilterDirs = 16
|
||||
|
||||
# PowerShell replaces the whole token, so the flag and directory prefix must
|
||||
# be prepended back to every candidate.
|
||||
$flagPrefix = ''
|
||||
$pathArg = $current
|
||||
if ($current -match '^(--?[^=]+=)(.*)$') {
|
||||
$flagPrefix = $Matches[1]
|
||||
$pathArg = $Matches[2]
|
||||
}
|
||||
$pathPrefix = $flagPrefix + ($pathArg -replace '[^\\/]*$', '')
|
||||
|
||||
# DirectiveNoSpace cannot be honored: CompletionResult has no per-item "no
|
||||
# trailing space" option, so `VAR=` gets one anyway.
|
||||
|
||||
# The text replaces the token as-is, so a value holding a space must be quoted.
|
||||
$asCompletionText = {
|
||||
param($text)
|
||||
if ($text -match '[\s'']') { "'" + $text.Replace("'", "''") + "'" } else { $text }
|
||||
}
|
||||
|
||||
$asPathResult = {
|
||||
param($item)
|
||||
$type = if ($item.PSIsContainer) { [CompletionResultType]::ProviderContainer } else { [CompletionResultType]::ProviderItem }
|
||||
[CompletionResult]::new((& $asCompletionText "$pathPrefix$($item.Name)"), $item.Name, $type, $item.Name)
|
||||
}
|
||||
|
||||
# Directories are kept so the user can descend. `-Include` needs `-Recurse`.
|
||||
if ($directive -band $FilterFileExt) {
|
||||
$exts = $data | ForEach-Object { ".$_" }
|
||||
return Get-ChildItem -Path "$pathArg*" -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.PSIsContainer -or $exts -contains $_.Extension } |
|
||||
ForEach-Object { & $asPathResult $_ }
|
||||
}
|
||||
|
||||
if ($directive -band $FilterDirs) {
|
||||
return Get-ChildItem -Path "$pathArg*" -Directory -ErrorAction SilentlyContinue |
|
||||
ForEach-Object { & $asPathResult $_ }
|
||||
}
|
||||
|
||||
# PowerShell does not filter native argument-completer results itself.
|
||||
$results = @($data | ForEach-Object {
|
||||
$parts = $_ -split "`t", 2
|
||||
$value = $parts[0]
|
||||
if ($current -and -not $value.StartsWith($current, [System.StringComparison]::OrdinalIgnoreCase)) { return }
|
||||
$desc = if ($parts.Count -gt 1 -and $parts[1]) { $parts[1] } else { $value }
|
||||
[CompletionResult]::new((& $asCompletionText $value), $value, [CompletionResultType]::ParameterValue, $desc)
|
||||
})
|
||||
|
||||
# NoFileComp unset and nothing matched → DirectiveDefault, so offer files.
|
||||
if ($results.Count -eq 0 -and -not ($directive -band $NoFileComp)) {
|
||||
return Get-ChildItem -Path "$pathArg*" -ErrorAction SilentlyContinue |
|
||||
ForEach-Object { & $asPathResult $_ }
|
||||
}
|
||||
|
||||
return $results
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
#compdef task
|
||||
#
|
||||
# Thin wrapper around `task __complete`: all suggestion logic lives in the Go engine.
|
||||
|
||||
TASK_CMD="${TASK_EXE:-task}"
|
||||
|
||||
_task() {
|
||||
local -a args lines completions describe_opts compadd_opts ctl
|
||||
local output directive line
|
||||
|
||||
# Completion directives, mirroring internal/complete/complete.go.
|
||||
local -ri NO_SPACE=2 NO_FILE_COMP=4 FILTER_FILE_EXT=8 FILTER_DIRS=16 KEEP_ORDER=32
|
||||
|
||||
# `-T` is true when the style is unset, so a flag goes out only when it is off.
|
||||
zstyle -T ":completion:${curcontext}:" show-aliases || ctl+=(--no-aliases)
|
||||
zstyle -T ":completion:${curcontext}:" verbose || ctl+=(--no-descriptions)
|
||||
|
||||
# (@) preserves the trailing empty word the engine reads as a fresh cursor.
|
||||
args=("${(@)words[2,CURRENT]}")
|
||||
(( ${#args} == 0 )) && args=("")
|
||||
|
||||
output=$("$TASK_CMD" __complete "${ctl[@]}" "${args[@]}" 2>/dev/null)
|
||||
if [[ -z "$output" ]]; then
|
||||
_files
|
||||
return
|
||||
fi
|
||||
|
||||
lines=("${(f)output}")
|
||||
directive="${lines[-1]#:}"
|
||||
lines=("${(@)lines[1,-2]}")
|
||||
|
||||
if (( directive & FILTER_FILE_EXT )); then
|
||||
local -a globs
|
||||
for line in "${lines[@]}"; do
|
||||
globs+=("*.${line}")
|
||||
done
|
||||
# Inline `--flag=` into IPREFIX so file completion runs on the value. Only
|
||||
# here: globally it would break `_describe` on inline enums.
|
||||
compset -P '*='
|
||||
_files -g "(${(j:|:)globs})"
|
||||
return
|
||||
fi
|
||||
|
||||
if (( directive & FILTER_DIRS )); then
|
||||
compset -P '*='
|
||||
_path_files -/
|
||||
return
|
||||
fi
|
||||
|
||||
# _describe splits on the first unescaped colon: "docs:serve" → "docs".
|
||||
local value desc
|
||||
for line in "${lines[@]}"; do
|
||||
if [[ "$line" == *$'\t'* ]]; then
|
||||
value="${line%%$'\t'*}"
|
||||
desc="${line#*$'\t'}"
|
||||
completions+=("${value//:/\\:}:$desc")
|
||||
else
|
||||
completions+=("${line//:/\\:}")
|
||||
fi
|
||||
done
|
||||
|
||||
# -S is a compadd option, passed after the array; -V belongs to _describe.
|
||||
# In the compadd zone it would take the next argument as a group name.
|
||||
(( directive & NO_SPACE )) && compadd_opts+=(-S '')
|
||||
(( directive & KEEP_ORDER )) && describe_opts+=(-V)
|
||||
|
||||
if (( ${#completions} > 0 )); then
|
||||
_describe "${describe_opts[@]}" -t tasks 'task' completions "${compadd_opts[@]}"
|
||||
fi
|
||||
|
||||
(( directive & NO_FILE_COMP )) && return
|
||||
compset -P '*='
|
||||
_files
|
||||
}
|
||||
|
||||
compdef _task "$TASK_CMD"
|
||||
@@ -1,180 +1,86 @@
|
||||
# Nushell completions for Task (https://taskfile.dev).
|
||||
#
|
||||
# Nushell cannot source a script from stdin, so save this file where Nushell
|
||||
# picks it up automatically:
|
||||
# mkdir ($nu.data-dir | path join "vendor/autoload")
|
||||
# task --completion nu | save --force ($nu.data-dir | path join "vendor/autoload/task-completions.nu")
|
||||
#
|
||||
# The file must not be named task.nu: Nushell refuses to export a known external
|
||||
# named like its module, which would break `use task-completions.nu *`.
|
||||
# Thin wrapper around `task __complete`: all suggestion logic lives in the Go engine.
|
||||
|
||||
# Name or path of the Task executable, like the other completion scripts.
|
||||
# The *completed* command is always `task`: an `extern` declaration requires a
|
||||
# literal name. For a renamed executable, alias it instead: `alias go-task = task`.
|
||||
def "nu-complete task-exe" [] {
|
||||
$env.TASK_EXE? | default "task"
|
||||
}
|
||||
# The `{completions, options}` record documented for `def` completers is
|
||||
# rejected for an external one: return records or null, nothing else.
|
||||
def task-external-completer [spans: list<string>] {
|
||||
let exe = ($env.TASK_EXE? | default "task")
|
||||
|
||||
def "nu-complete task-words" [context: string] {
|
||||
$context | split row --regex '\s+' | where {|word| $word != "" }
|
||||
}
|
||||
|
||||
# Strips the quotes the user may have typed around a value and expands `~`,
|
||||
# which Nushell does not do for a value coming from a variable.
|
||||
def "nu-complete task-value" [value: string] {
|
||||
let unquoted = ($value | str trim --char '"' | str trim --char "'")
|
||||
if ($unquoted | str starts-with "~") {
|
||||
$unquoted | path expand --no-symlink
|
||||
} else {
|
||||
$unquoted
|
||||
}
|
||||
}
|
||||
|
||||
# Rebuilds the flags deciding *which* Taskfile is read, so the task list follows
|
||||
# the `-t/--taskfile`, `-d/--dir` and `-g/--global` already on the command line.
|
||||
def "nu-complete task-scope" [words: list<string>] {
|
||||
mut scope: list<string> = []
|
||||
mut pending = ""
|
||||
|
||||
for word in ($words | skip 1) {
|
||||
if $pending != "" {
|
||||
$scope = ($scope | append [$pending, (nu-complete task-value $word)])
|
||||
$pending = ""
|
||||
continue
|
||||
}
|
||||
|
||||
let parts = ($word | split row "=")
|
||||
let name = ($parts | first)
|
||||
let inline = if ($parts | length) > 1 { $parts | skip 1 | str join "=" } else { null }
|
||||
|
||||
if $name in ["-g", "--global"] {
|
||||
$scope = ($scope | append "--global")
|
||||
} else if $name in ["-t", "--taskfile", "-d", "--dir"] {
|
||||
let long = if $name in ["-t", "--taskfile"] { "--taskfile" } else { "--dir" }
|
||||
if $inline != null {
|
||||
$scope = ($scope | append [$long, (nu-complete task-value $inline)])
|
||||
} else {
|
||||
$pending = $long
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope
|
||||
}
|
||||
|
||||
# Lists the tasks of the targeted Taskfile. `--no-status` keeps completion fast:
|
||||
# without it Task fingerprints every task's sources on each keystroke. Returns an
|
||||
# empty list when Task exits non-zero (no Taskfile, invalid Taskfile).
|
||||
def "nu-complete task-list" [words: list<string>] {
|
||||
let exe = (nu-complete task-exe)
|
||||
let args = [...(nu-complete task-scope $words) "--list-all" "--json" "--no-status"]
|
||||
let result = (try { do { ^$exe ...$args } | complete } catch { null })
|
||||
# The trailing empty word tells the engine the cursor is on a fresh word.
|
||||
let words = ($spans | skip 1)
|
||||
let args = (if ($words | is-empty) { [""] } else { $words })
|
||||
let current = ($args | last)
|
||||
|
||||
# `complete` keeps stderr off the prompt; a missing binary raises, hence `try`.
|
||||
let result = (try { do { ^$exe "__complete" ...$args } | complete } catch { null })
|
||||
if ($result | is-empty) or $result.exit_code != 0 {
|
||||
return []
|
||||
}
|
||||
|
||||
try { $result.stdout | from json | get tasks } catch { [] }
|
||||
}
|
||||
|
||||
def "nu-complete task" [context: string] {
|
||||
let words = (nu-complete task-words $context)
|
||||
|
||||
# Words after `--` are forwarded to the task as CLI_ARGS: stop offering task
|
||||
# names and let Nushell fall back to its own file completion.
|
||||
if "--" in $words {
|
||||
return null
|
||||
}
|
||||
|
||||
let completions = (
|
||||
nu-complete task-list $words
|
||||
| each {|item|
|
||||
# `task` is the invocable name; `name` may be a display-only label.
|
||||
let name = ($item.task | str trim --right --char ':')
|
||||
let desc = ($item.desc? | default "")
|
||||
let aliases = (
|
||||
$item.aliases?
|
||||
| default []
|
||||
| each {|alias| {
|
||||
value: ($alias | str trim --right --char ':')
|
||||
description: (if ($desc | is-empty) { $"alias of ($name)" } else { $"($desc) \(alias of ($name)\)" })
|
||||
} }
|
||||
)
|
||||
[{ value: $name, description: $desc }] | append $aliases
|
||||
let lines = ($result.stdout | lines)
|
||||
let last = ($lines | last)
|
||||
# Protocol violation: offer nothing rather than garbage.
|
||||
if ($last | is-empty) or (not ($last | str starts-with ":")) {
|
||||
return null
|
||||
}
|
||||
let directive = (try { $last | str substring 1.. | into int } catch { 0 })
|
||||
let data = ($lines | drop 1)
|
||||
|
||||
# Completion directives, mirroring internal/complete/complete.go. NoSpace (2)
|
||||
# and KeepOrder (32) need none: no space is appended, order is kept.
|
||||
let no_file_comp = (($directive | bits and 4) != 0)
|
||||
let filter_file_ext = (($directive | bits and 8) != 0)
|
||||
let filter_dirs = (($directive | bits and 16) != 0)
|
||||
|
||||
# Nushell replaces the whole token, so an inline `--flag=` must be re-applied.
|
||||
let inline = ($current | parse --regex '^(?<flag>--?[^=]+=)(?<path>.*)$')
|
||||
let flag_prefix = (if ($inline | is-empty) { "" } else { $inline.0.flag })
|
||||
let path_arg = (if ($inline | is-empty) { $current } else { $inline.0.path })
|
||||
|
||||
if $filter_file_ext or $filter_dirs {
|
||||
# `into glob` turns the literal path into a pattern; matching nothing raises.
|
||||
let entries = (try { ls ($"($path_arg)*" | into glob) } catch { [] })
|
||||
let matched = (if $filter_file_ext {
|
||||
$entries | where {|entry| $entry.type == "dir" or ($entry.name | path parse | get extension) in $data }
|
||||
} else {
|
||||
$entries | where type == "dir"
|
||||
})
|
||||
return ($matched | each {|entry|
|
||||
# Without a trailing separator a second <tab> matches the dir again.
|
||||
let name = (if $entry.type == "dir" { $"($entry.name)(char path_sep)" } else { $entry.name })
|
||||
{ value: $"($flag_prefix)($name)" }
|
||||
})
|
||||
}
|
||||
|
||||
# Nushell does not filter an external completer's results.
|
||||
let candidates = ($data
|
||||
| each {|line|
|
||||
let parts = ($line | split row --number 2 "\t")
|
||||
let value = ($parts | first)
|
||||
if ($parts | length) > 1 { { value: $value, description: ($parts | last) } } else { { value: $value } }
|
||||
}
|
||||
| flatten
|
||||
)
|
||||
| where {|candidate| $candidate.value | str starts-with --ignore-case $current })
|
||||
|
||||
# `sort: false` keeps the order Task chose, which honours --sort and .taskrc.
|
||||
{ options: { sort: false }, completions: $completions }
|
||||
if ($candidates | is-empty) and (not $no_file_comp) {
|
||||
return null
|
||||
}
|
||||
|
||||
$candidates
|
||||
}
|
||||
|
||||
def "nu-complete task-shells" [] {
|
||||
["bash", "zsh", "fish", "powershell", "nu"]
|
||||
# Nushell shares one external completer between every command, so chain to the
|
||||
# installed one instead of breaking every other tool.
|
||||
let task_previous_completer = ($env.config.completions.external.completer? | default null)
|
||||
|
||||
$env.config.completions.external.completer = {|spans|
|
||||
let exe = ($env.TASK_EXE? | default "task")
|
||||
# Compare basenames so `./task`, `/usr/local/bin/task` and `task.exe` match.
|
||||
let head = ($spans | first | path basename | str replace --regex '(?i)\.exe$' '')
|
||||
let name = ($exe | path basename | str replace --regex '(?i)\.exe$' '')
|
||||
if $head == $name {
|
||||
task-external-completer $spans
|
||||
} else if $task_previous_completer != null {
|
||||
do $task_previous_completer $spans
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
def "nu-complete task-output" [] {
|
||||
["interleaved", "group", "prefixed"]
|
||||
}
|
||||
|
||||
def "nu-complete task-sort" [] {
|
||||
["default", "alphanumeric", "none"]
|
||||
}
|
||||
|
||||
# Runs the specified task(s). Falls back to the "default" task if no task name
|
||||
# was specified, or lists all tasks if an unknown task name was specified.
|
||||
#
|
||||
# An `extern` signature is static, so the experimental flag at the bottom is
|
||||
# always offered; Task rejects it when the experiment is off. Run
|
||||
# `task --experiments` to see which experiments are enabled.
|
||||
export extern "task" [
|
||||
...tasks: string@"nu-complete task" # task(s) to run
|
||||
--list(-l) # list tasks with a description
|
||||
--list-all(-a) # list all tasks, with or without a description
|
||||
--json(-j) # format the task list as JSON
|
||||
--no-status # ignore status when listing tasks as JSON
|
||||
--nested # nest namespaces when listing tasks as JSON
|
||||
--sort: string@"nu-complete task-sort" # change the order of the tasks when listed
|
||||
--init(-i) # create a new Taskfile.yml in the current folder
|
||||
--completion: string@"nu-complete task-shells" # generate a shell completion script
|
||||
--taskfile(-t): glob # choose which Taskfile to run
|
||||
--dir(-d): directory # set the directory in which Task will execute
|
||||
--global(-g) # run the global Taskfile from $HOME
|
||||
--temp-dir: directory # directory used to store Task temporary files
|
||||
--force(-f) # force execution even when the task is up-to-date
|
||||
--status # exit with a non-zero code if tasks are not up-to-date
|
||||
--dry(-n) # compile and print the tasks without executing them
|
||||
--summary # show the summary of a task instead of running it
|
||||
--watch(-w) # watch the given tasks and re-run them on changes
|
||||
--interval(-I): string # interval to watch for changes, e.g. 500ms
|
||||
--parallel(-p) # run the tasks given on the command line in parallel
|
||||
--concurrency(-C): int # limit the number of tasks run concurrently
|
||||
--failfast(-F) # when running in parallel, stop everything if one task fails
|
||||
--exit-code(-x) # pass through the exit code of the task command
|
||||
--interactive # prompt for missing required variables
|
||||
--yes(-y) # assume "yes" as the answer to all prompts
|
||||
--output(-o): string@"nu-complete task-output" # set the output style
|
||||
--output-group-begin: string # message template printed before a task's grouped output
|
||||
--output-group-end: string # message template printed after a task's grouped output
|
||||
--output-group-error-only # swallow the output of successful tasks
|
||||
--color(-c) # colored output, enabled by default
|
||||
--silent(-s) # disable echoing
|
||||
--verbose(-v) # enable verbose mode
|
||||
--disable-fuzzy # disable fuzzy matching for task names
|
||||
--download # download a cached version of a remote Taskfile
|
||||
--offline # only use local or cached Taskfiles
|
||||
--clear-cache # clear the remote Taskfile cache
|
||||
--trusted-hosts: string # trusted hosts for remote Taskfiles (comma-separated)
|
||||
--timeout: string # timeout for downloading remote Taskfiles
|
||||
--expiry: string # expiry duration for cached remote Taskfiles
|
||||
--remote-cache-dir: directory # directory used to cache remote Taskfiles
|
||||
--cacert: path # custom CA certificate for HTTPS connections
|
||||
--cert: path # client certificate for HTTPS connections
|
||||
--cert-key: path # client certificate key for HTTPS connections
|
||||
--insecure # allow Taskfiles to be downloaded over insecure connections
|
||||
--experiments # list the available experiments and whether they are enabled
|
||||
--version # show the Task version
|
||||
--help(-h) # show Task usage
|
||||
|
||||
--force-all # [GENTLE_FORCE] force the called task and all its dependencies
|
||||
]
|
||||
|
||||
@@ -1,89 +1,109 @@
|
||||
using namespace System.Management.Automation
|
||||
using namespace System.Management.Automation.Language
|
||||
|
||||
# Thin wrapper around `task __complete`: all suggestion logic lives in the Go engine.
|
||||
|
||||
$cmdNames = @('task') + (Get-Alias -Definition task,task.exe,*\task,*\task.exe -ErrorAction SilentlyContinue).Name | Select-Object -Unique
|
||||
|
||||
Register-ArgumentCompleter -CommandName $cmdNames -ScriptBlock {
|
||||
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
|
||||
Register-ArgumentCompleter -Native -CommandName $cmdNames -ScriptBlock {
|
||||
param($wordToComplete, $commandAst, $cursorPosition)
|
||||
|
||||
if ($commandName.StartsWith('-')) {
|
||||
$completions = @(
|
||||
# Standard flags (alphabetical order)
|
||||
[CompletionResult]::new('-a', '-a', [CompletionResultType]::ParameterName, 'list all tasks'),
|
||||
[CompletionResult]::new('--list-all', '--list-all', [CompletionResultType]::ParameterName, 'list all tasks'),
|
||||
[CompletionResult]::new('-c', '-c', [CompletionResultType]::ParameterName, 'colored output'),
|
||||
[CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'colored output'),
|
||||
[CompletionResult]::new('-C', '-C', [CompletionResultType]::ParameterName, 'limit concurrent tasks'),
|
||||
[CompletionResult]::new('--concurrency', '--concurrency', [CompletionResultType]::ParameterName, 'limit concurrent tasks'),
|
||||
[CompletionResult]::new('--completion', '--completion', [CompletionResultType]::ParameterName, 'generate shell completion'),
|
||||
[CompletionResult]::new('-d', '-d', [CompletionResultType]::ParameterName, 'set directory'),
|
||||
[CompletionResult]::new('--dir', '--dir', [CompletionResultType]::ParameterName, 'set directory'),
|
||||
[CompletionResult]::new('--disable-fuzzy', '--disable-fuzzy', [CompletionResultType]::ParameterName, 'disable fuzzy matching'),
|
||||
[CompletionResult]::new('-n', '-n', [CompletionResultType]::ParameterName, 'dry run'),
|
||||
[CompletionResult]::new('--dry', '--dry', [CompletionResultType]::ParameterName, 'dry run'),
|
||||
[CompletionResult]::new('-x', '-x', [CompletionResultType]::ParameterName, 'pass-through exit code'),
|
||||
[CompletionResult]::new('--exit-code', '--exit-code', [CompletionResultType]::ParameterName, 'pass-through exit code'),
|
||||
[CompletionResult]::new('--experiments', '--experiments', [CompletionResultType]::ParameterName, 'list experiments'),
|
||||
[CompletionResult]::new('-F', '-F', [CompletionResultType]::ParameterName, 'fail fast on pallalel tasks'),
|
||||
[CompletionResult]::new('--failfast', '--failfast', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('--force', '--force', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('-g', '-g', [CompletionResultType]::ParameterName, 'run global Taskfile'),
|
||||
[CompletionResult]::new('--global', '--global', [CompletionResultType]::ParameterName, 'run global Taskfile'),
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'show help'),
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'show help'),
|
||||
[CompletionResult]::new('-i', '-i', [CompletionResultType]::ParameterName, 'create new Taskfile'),
|
||||
[CompletionResult]::new('--init', '--init', [CompletionResultType]::ParameterName, 'create new Taskfile'),
|
||||
[CompletionResult]::new('--insecure', '--insecure', [CompletionResultType]::ParameterName, 'allow insecure downloads'),
|
||||
[CompletionResult]::new('-I', '-I', [CompletionResultType]::ParameterName, 'watch interval'),
|
||||
[CompletionResult]::new('--interval', '--interval', [CompletionResultType]::ParameterName, 'watch interval'),
|
||||
[CompletionResult]::new('-j', '-j', [CompletionResultType]::ParameterName, 'format as JSON'),
|
||||
[CompletionResult]::new('--json', '--json', [CompletionResultType]::ParameterName, 'format as JSON'),
|
||||
[CompletionResult]::new('-l', '-l', [CompletionResultType]::ParameterName, 'list tasks'),
|
||||
[CompletionResult]::new('--list', '--list', [CompletionResultType]::ParameterName, 'list tasks'),
|
||||
[CompletionResult]::new('--nested', '--nested', [CompletionResultType]::ParameterName, 'nest namespaces in JSON'),
|
||||
[CompletionResult]::new('--no-status', '--no-status', [CompletionResultType]::ParameterName, 'ignore status in JSON'),
|
||||
[CompletionResult]::new('--interactive', '--interactive', [CompletionResultType]::ParameterName, 'prompt for missing required variables'),
|
||||
[CompletionResult]::new('-o', '-o', [CompletionResultType]::ParameterName, 'set output style'),
|
||||
[CompletionResult]::new('--output', '--output', [CompletionResultType]::ParameterName, 'set output style'),
|
||||
[CompletionResult]::new('--output-group-begin', '--output-group-begin', [CompletionResultType]::ParameterName, 'template before group'),
|
||||
[CompletionResult]::new('--output-group-end', '--output-group-end', [CompletionResultType]::ParameterName, 'template after group'),
|
||||
[CompletionResult]::new('--output-group-error-only', '--output-group-error-only', [CompletionResultType]::ParameterName, 'hide successful output'),
|
||||
[CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'execute in parallel'),
|
||||
[CompletionResult]::new('--parallel', '--parallel', [CompletionResultType]::ParameterName, 'execute in parallel'),
|
||||
[CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'silent mode'),
|
||||
[CompletionResult]::new('--silent', '--silent', [CompletionResultType]::ParameterName, 'silent mode'),
|
||||
[CompletionResult]::new('--sort', '--sort', [CompletionResultType]::ParameterName, 'task sorting order'),
|
||||
[CompletionResult]::new('--status', '--status', [CompletionResultType]::ParameterName, 'check task status'),
|
||||
[CompletionResult]::new('--summary', '--summary', [CompletionResultType]::ParameterName, 'show task summary'),
|
||||
[CompletionResult]::new('-t', '-t', [CompletionResultType]::ParameterName, 'choose Taskfile'),
|
||||
[CompletionResult]::new('--taskfile', '--taskfile', [CompletionResultType]::ParameterName, 'choose Taskfile'),
|
||||
[CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'verbose output'),
|
||||
[CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'verbose output'),
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'show version'),
|
||||
[CompletionResult]::new('-w', '-w', [CompletionResultType]::ParameterName, 'watch mode'),
|
||||
[CompletionResult]::new('--watch', '--watch', [CompletionResultType]::ParameterName, 'watch mode'),
|
||||
[CompletionResult]::new('-y', '-y', [CompletionResultType]::ParameterName, 'assume yes'),
|
||||
[CompletionResult]::new('--yes', '--yes', [CompletionResultType]::ParameterName, 'assume yes'),
|
||||
[CompletionResult]::new('--offline', '--offline', [CompletionResultType]::ParameterName, 'use cached Taskfiles'),
|
||||
[CompletionResult]::new('--timeout', '--timeout', [CompletionResultType]::ParameterName, 'download timeout'),
|
||||
[CompletionResult]::new('--expiry', '--expiry', [CompletionResultType]::ParameterName, 'cache expiry'),
|
||||
[CompletionResult]::new('--remote-cache-dir', '--remote-cache-dir', [CompletionResultType]::ParameterName, 'cache directory'),
|
||||
[CompletionResult]::new('--cacert', '--cacert', [CompletionResultType]::ParameterName, 'custom CA certificate'),
|
||||
[CompletionResult]::new('--cert', '--cert', [CompletionResultType]::ParameterName, 'client certificate'),
|
||||
[CompletionResult]::new('--cert-key', '--cert-key', [CompletionResultType]::ParameterName, 'client private key'),
|
||||
[CompletionResult]::new('--download', '--download', [CompletionResultType]::ParameterName, 'download remote Taskfile'),
|
||||
[CompletionResult]::new('--clear-cache', '--clear-cache', [CompletionResultType]::ParameterName, 'clear cache')
|
||||
)
|
||||
$TaskExe = if ($env:TASK_EXE) { $env:TASK_EXE } else { 'task' }
|
||||
|
||||
# Experimental flags (dynamically added based on enabled experiments)
|
||||
$experiments = & task --experiments 2>$null | Out-String
|
||||
|
||||
if ($experiments -match '\* GENTLE_FORCE:.*on') {
|
||||
$completions += [CompletionResult]::new('--force-all', '--force-all', [CompletionResultType]::ParameterName, 'force all dependencies')
|
||||
# The current word arrives with the quote the user opened.
|
||||
$current = $wordToComplete
|
||||
if ($current.Length -ge 1 -and ($current[0] -eq '"' -or $current[0] -eq "'")) {
|
||||
$quoteChar = $current[0]
|
||||
$current = $current.Substring(1)
|
||||
if ($current.EndsWith($quoteChar)) {
|
||||
$current = $current.Substring(0, $current.Length - 1)
|
||||
}
|
||||
|
||||
return $completions.Where{ $_.CompletionText.StartsWith($commandName) }
|
||||
}
|
||||
|
||||
return $(task --list-all --silent) | Where-Object { $_.StartsWith($commandName) } | ForEach-Object { return $_ + " " }
|
||||
# A string element yields its Value, so `--dir "a b"` arrives unquoted.
|
||||
$argsToPass = @()
|
||||
$elements = $commandAst.CommandElements
|
||||
for ($i = 1; $i -lt $elements.Count; $i++) {
|
||||
$el = $elements[$i]
|
||||
if ($el.Extent.StartOffset -ge $cursorPosition) { break }
|
||||
$argsToPass += if ($el -is [StringConstantExpressionAst] -or $el -is [ExpandableStringExpressionAst]) {
|
||||
$el.Value
|
||||
} else {
|
||||
$el.ToString()
|
||||
}
|
||||
}
|
||||
# The trailing word tells the engine the cursor is on a fresh word.
|
||||
if ($argsToPass.Count -eq 0 -or $argsToPass[-1] -ne $current) {
|
||||
$argsToPass += $current
|
||||
}
|
||||
|
||||
$output = & $TaskExe __complete @argsToPass 2>$null
|
||||
if (-not $output) { return }
|
||||
|
||||
$lines = @($output)
|
||||
$last = $lines[-1]
|
||||
if (-not $last.StartsWith(':')) { return }
|
||||
|
||||
$directive = [int]($last.Substring(1))
|
||||
$data = if ($lines.Count -gt 1) { $lines[0..($lines.Count - 2)] } else { @() }
|
||||
|
||||
# Completion directives, mirroring internal/complete/complete.go.
|
||||
$NoFileComp = 4
|
||||
$FilterFileExt = 8
|
||||
$FilterDirs = 16
|
||||
|
||||
# PowerShell replaces the whole token, so the flag and directory prefix must
|
||||
# be prepended back to every candidate.
|
||||
$flagPrefix = ''
|
||||
$pathArg = $current
|
||||
if ($current -match '^(--?[^=]+=)(.*)$') {
|
||||
$flagPrefix = $Matches[1]
|
||||
$pathArg = $Matches[2]
|
||||
}
|
||||
$pathPrefix = $flagPrefix + ($pathArg -replace '[^\\/]*$', '')
|
||||
|
||||
# DirectiveNoSpace cannot be honored: CompletionResult has no per-item "no
|
||||
# trailing space" option, so `VAR=` gets one anyway.
|
||||
|
||||
# The text replaces the token as-is, so a value holding a space must be quoted.
|
||||
$asCompletionText = {
|
||||
param($text)
|
||||
if ($text -match '[\s'']') { "'" + $text.Replace("'", "''") + "'" } else { $text }
|
||||
}
|
||||
|
||||
$asPathResult = {
|
||||
param($item)
|
||||
$type = if ($item.PSIsContainer) { [CompletionResultType]::ProviderContainer } else { [CompletionResultType]::ProviderItem }
|
||||
[CompletionResult]::new((& $asCompletionText "$pathPrefix$($item.Name)"), $item.Name, $type, $item.Name)
|
||||
}
|
||||
|
||||
# Directories are kept so the user can descend. `-Include` needs `-Recurse`.
|
||||
if ($directive -band $FilterFileExt) {
|
||||
$exts = $data | ForEach-Object { ".$_" }
|
||||
return Get-ChildItem -Path "$pathArg*" -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.PSIsContainer -or $exts -contains $_.Extension } |
|
||||
ForEach-Object { & $asPathResult $_ }
|
||||
}
|
||||
|
||||
if ($directive -band $FilterDirs) {
|
||||
return Get-ChildItem -Path "$pathArg*" -Directory -ErrorAction SilentlyContinue |
|
||||
ForEach-Object { & $asPathResult $_ }
|
||||
}
|
||||
|
||||
# PowerShell does not filter native argument-completer results itself.
|
||||
$results = @($data | ForEach-Object {
|
||||
$parts = $_ -split "`t", 2
|
||||
$value = $parts[0]
|
||||
if ($current -and -not $value.StartsWith($current, [System.StringComparison]::OrdinalIgnoreCase)) { return }
|
||||
$desc = if ($parts.Count -gt 1 -and $parts[1]) { $parts[1] } else { $value }
|
||||
[CompletionResult]::new((& $asCompletionText $value), $value, [CompletionResultType]::ParameterValue, $desc)
|
||||
})
|
||||
|
||||
# NoFileComp unset and nothing matched → DirectiveDefault, so offer files.
|
||||
if ($results.Count -eq 0 -and -not ($directive -band $NoFileComp)) {
|
||||
return Get-ChildItem -Path "$pathArg*" -ErrorAction SilentlyContinue |
|
||||
ForEach-Object { & $asPathResult $_ }
|
||||
}
|
||||
|
||||
return $results
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ _filedir() { CAP+="filedir:$* cur=$cur"$'\n'; }
|
||||
compopt() { CAP+="compopt:$*"$'\n'; }
|
||||
__ltrim_colon_completions() { :; }
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/../next/bash/task.bash"
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/../bash/task.bash"
|
||||
|
||||
run() {
|
||||
CAP=""
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Set up by run.sh: TASK_FIXTURE, and `task` on PATH = the binary under test.
|
||||
|
||||
cd $TASK_FIXTURE
|
||||
source (dirname (status -f))/../next/fish/task.fish
|
||||
source (dirname (status -f))/../fish/task.fish
|
||||
|
||||
set -g fails 0
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# Set up by run.sh: $env.TASK_FIXTURE, and `task` on PATH = the binary under test.
|
||||
|
||||
# `source` needs a parse-time constant path.
|
||||
const TASK_NU = (path self "../next/nu/task-completions.nu")
|
||||
const TASK_NU = (path self "../nu/task-completions.nu")
|
||||
|
||||
# Installed before the wrapper is sourced, to assert the delegation path.
|
||||
$env.config.completions.external.completer = {|spans| [{ value: $"prev:($spans | first)" }] }
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# the binary under test.
|
||||
|
||||
Set-Location $env:TASK_FIXTURE
|
||||
. "$PSScriptRoot/../next/ps/task.ps1"
|
||||
. "$PSScriptRoot/../ps/task.ps1"
|
||||
|
||||
$fails = 0
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ _files() { CAP+="files:$*"$'\n' }
|
||||
_path_files() { CAP+="path_files:$*"$'\n' }
|
||||
|
||||
# Sourcing avoids the autoload first-call quirk; `compdef` is stubbed above.
|
||||
source ${0:A:h}/../next/zsh/_task
|
||||
source ${0:A:h}/../zsh/_task
|
||||
|
||||
run() {
|
||||
CAP=""
|
||||
|
||||
@@ -1,158 +1,76 @@
|
||||
#compdef task
|
||||
typeset -A opt_args
|
||||
#
|
||||
# Thin wrapper around `task __complete`: all suggestion logic lives in the Go engine.
|
||||
|
||||
TASK_CMD="${TASK_EXE:-task}"
|
||||
compdef _task "$TASK_CMD"
|
||||
|
||||
_GO_TASK_COMPLETION_LIST_OPTION="${GO_TASK_COMPLETION_LIST_OPTION:---list-all}"
|
||||
|
||||
# Check if an experiment is enabled
|
||||
function __task_is_experiment_enabled() {
|
||||
local experiment=$1
|
||||
task --experiments 2>/dev/null | grep -q "^\* ${experiment}:.*on"
|
||||
}
|
||||
|
||||
# Listing commands from Taskfile.yml
|
||||
function __task_list() {
|
||||
local -a scripts cmd task_aliases match mbegin mend
|
||||
local -i enabled=0
|
||||
local taskfile item task desc task_alias
|
||||
|
||||
cmd=($TASK_CMD)
|
||||
taskfile=${(Qv)opt_args[(i)-t|--taskfile]}
|
||||
taskfile=${taskfile//\~/$HOME}
|
||||
|
||||
for arg in "${words[@]:0:$CURRENT}"; do
|
||||
if [[ "$arg" = "--" ]]; then
|
||||
# Use default completion for words after `--` as they are CLI_ARGS.
|
||||
_default
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$taskfile" && -f "$taskfile" ]]; then
|
||||
cmd+=(--taskfile "$taskfile")
|
||||
fi
|
||||
|
||||
# Check if global flag is set
|
||||
if (( ${+opt_args[-g]} || ${+opt_args[--global]} )); then
|
||||
cmd+=(--global)
|
||||
fi
|
||||
|
||||
if output=$("${cmd[@]}" $_GO_TASK_COMPLETION_LIST_OPTION 2>/dev/null); then
|
||||
enabled=1
|
||||
fi
|
||||
|
||||
(( enabled )) || return 0
|
||||
|
||||
scripts=()
|
||||
|
||||
# Read zstyle verbose option (default = true via -T)
|
||||
local show_desc
|
||||
zstyle -T ":completion:${curcontext}:" verbose && show_desc=true || show_desc=false
|
||||
|
||||
# Read zstyle show-aliases option (default = true via -T)
|
||||
local show_aliases
|
||||
zstyle -T ":completion:${curcontext}:" show-aliases && show_aliases=true || show_aliases=false
|
||||
|
||||
for item in "${(@)${(f)output}[2,-1]#\* }"; do
|
||||
task="${item%%:[[:space:]]*}"
|
||||
|
||||
# Extract the aliases listed in the trailing "(aliases: a, b)" column.
|
||||
# NB: `aliases` is a reserved zsh parameter, so use a different name.
|
||||
task_aliases=()
|
||||
if [[ "$show_aliases" == "true" && "$item" == (#b)*'(aliases: '(*)')' ]]; then
|
||||
task_aliases=( "${(@s:, :)match[1]}" )
|
||||
fi
|
||||
|
||||
if [[ "$show_desc" == "true" ]]; then
|
||||
local desc="${item##[^[:space:]]##[[:space:]]##}"
|
||||
scripts+=( "${task//:/\\:}:$desc" )
|
||||
for task_alias in $task_aliases; do
|
||||
scripts+=( "${task_alias//:/\\:}:$desc (alias of $task)" )
|
||||
done
|
||||
else
|
||||
scripts+=( "$task" )
|
||||
for task_alias in $task_aliases; do
|
||||
scripts+=( "$task_alias" )
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$show_desc" == "true" ]]; then
|
||||
_describe 'Task to run' scripts
|
||||
else
|
||||
compadd -Q -a scripts
|
||||
fi
|
||||
}
|
||||
|
||||
_task() {
|
||||
local -a standard_args operation_args
|
||||
local -a args lines completions describe_opts compadd_opts ctl
|
||||
local output directive line
|
||||
|
||||
standard_args=(
|
||||
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: '
|
||||
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]'
|
||||
'(-F --failfast)'{-F,--failfast}'[when running tasks in parallel, stop all tasks if one fails]'
|
||||
'(-f --force)'{-f,--force}'[run even if task is up-to-date]'
|
||||
'(-c --color)'{-c,--color}'[colored output]'
|
||||
'(--completion)--completion[generate shell completion script]:shell:(bash zsh fish powershell nu)'
|
||||
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs'
|
||||
'(--disable-fuzzy)--disable-fuzzy[disable fuzzy matching for task names]'
|
||||
'(-n --dry)'{-n,--dry}'[compiles and prints tasks without executing]'
|
||||
'(--dry)--dry[dry-run mode, compile and print tasks only]'
|
||||
'(-x --exit-code)'{-x,--exit-code}'[pass-through exit code of task command]'
|
||||
'(--experiments)--experiments[list available experiments]'
|
||||
'(-g --global)'{-g,--global}'[run global Taskfile from home directory]'
|
||||
'(--insecure)--insecure[allow insecure Taskfile downloads]'
|
||||
'(-I --interval)'{-I,--interval}'[interval to watch for changes]:duration: '
|
||||
'(-j --json)'{-j,--json}'[format task list as JSON]'
|
||||
'(--nested)--nested[nest namespaces when listing as JSON]'
|
||||
'(--no-status)--no-status[ignore status when listing as JSON]'
|
||||
'(--interactive)--interactive[prompt for missing required variables]'
|
||||
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)'
|
||||
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: '
|
||||
'(--output-group-end)--output-group-end[message template after grouped output]:template text: '
|
||||
'(--output-group-error-only)--output-group-error-only[hide output from successful tasks]'
|
||||
'(-s --silent)'{-s,--silent}'[disable echoing]'
|
||||
'(--sort)--sort[set task sorting order]:order:(default alphanumeric none)'
|
||||
'(--status)--status[exit non-zero if supplied tasks not up-to-date]'
|
||||
'(--summary)--summary[show summary\: field from tasks instead of running them]'
|
||||
'(-t --taskfile)'{-t,--taskfile}'[specify a different taskfile]:taskfile:_files'
|
||||
'(-v --verbose)'{-v,--verbose}'[verbose mode]'
|
||||
'(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]'
|
||||
'(-y --yes)'{-y,--yes}'[assume yes to all prompts]'
|
||||
'(--offline --clear-cache)--download[download remote Taskfile]'
|
||||
'(--offline --download)--offline[use only local or cached Taskfiles]'
|
||||
'(--timeout)--timeout[timeout for remote Taskfile downloads]:duration: '
|
||||
'(--expiry)--expiry[cache expiry duration]:duration: '
|
||||
'(--remote-cache-dir)--remote-cache-dir[directory to cache remote Taskfiles]:cache dir:_dirs'
|
||||
'(--cacert)--cacert[custom CA certificate for TLS]:file:_files'
|
||||
'(--cert)--cert[client certificate for mTLS]:file:_files'
|
||||
'(--cert-key)--cert-key[client certificate private key]:file:_files'
|
||||
)
|
||||
# Completion directives, mirroring internal/complete/complete.go.
|
||||
local -ri NO_SPACE=2 NO_FILE_COMP=4 FILTER_FILE_EXT=8 FILTER_DIRS=16 KEEP_ORDER=32
|
||||
|
||||
# Experimental flags (dynamically added based on enabled experiments)
|
||||
# Options (modify behavior)
|
||||
if __task_is_experiment_enabled "GENTLE_FORCE"; then
|
||||
standard_args+=('(--force-all)--force-all[force execution of task and all dependencies]')
|
||||
fi
|
||||
# `-T` is true when the style is unset, so a flag goes out only when it is off.
|
||||
zstyle -T ":completion:${curcontext}:" show-aliases || ctl+=(--no-aliases)
|
||||
zstyle -T ":completion:${curcontext}:" verbose || ctl+=(--no-descriptions)
|
||||
|
||||
operation_args=(
|
||||
# Task names completion (can be specified multiple times)
|
||||
'(operation)*: :__task_list'
|
||||
# Operational args completion (mutually exclusive)
|
||||
+ '(operation)'
|
||||
'(*)'{-l,--list}'[list describable tasks]'
|
||||
'(*)'{-a,--list-all}'[list all tasks]'
|
||||
'(*)'{-i,--init}'[create new Taskfile.yml]'
|
||||
'(- *)'{-h,--help}'[show help]'
|
||||
'(- *)--version[show version and exit]'
|
||||
'(* --download)--clear-cache[clear remote Taskfile cache]'
|
||||
)
|
||||
# (@) preserves the trailing empty word the engine reads as a fresh cursor.
|
||||
args=("${(@)words[2,CURRENT]}")
|
||||
(( ${#args} == 0 )) && args=("")
|
||||
|
||||
_arguments -S $standard_args $operation_args
|
||||
output=$("$TASK_CMD" __complete "${ctl[@]}" "${args[@]}" 2>/dev/null)
|
||||
if [[ -z "$output" ]]; then
|
||||
_files
|
||||
return
|
||||
fi
|
||||
|
||||
lines=("${(f)output}")
|
||||
directive="${lines[-1]#:}"
|
||||
lines=("${(@)lines[1,-2]}")
|
||||
|
||||
if (( directive & FILTER_FILE_EXT )); then
|
||||
local -a globs
|
||||
for line in "${lines[@]}"; do
|
||||
globs+=("*.${line}")
|
||||
done
|
||||
# Inline `--flag=` into IPREFIX so file completion runs on the value. Only
|
||||
# here: globally it would break `_describe` on inline enums.
|
||||
compset -P '*='
|
||||
_files -g "(${(j:|:)globs})"
|
||||
return
|
||||
fi
|
||||
|
||||
if (( directive & FILTER_DIRS )); then
|
||||
compset -P '*='
|
||||
_path_files -/
|
||||
return
|
||||
fi
|
||||
|
||||
# _describe splits on the first unescaped colon: "docs:serve" → "docs".
|
||||
local value desc
|
||||
for line in "${lines[@]}"; do
|
||||
if [[ "$line" == *$'\t'* ]]; then
|
||||
value="${line%%$'\t'*}"
|
||||
desc="${line#*$'\t'}"
|
||||
completions+=("${value//:/\\:}:$desc")
|
||||
else
|
||||
completions+=("${line//:/\\:}")
|
||||
fi
|
||||
done
|
||||
|
||||
# -S is a compadd option, passed after the array; -V belongs to _describe.
|
||||
# In the compadd zone it would take the next argument as a group name.
|
||||
(( directive & NO_SPACE )) && compadd_opts+=(-S '')
|
||||
(( directive & KEEP_ORDER )) && describe_opts+=(-V)
|
||||
|
||||
if (( ${#completions} > 0 )); then
|
||||
_describe "${describe_opts[@]}" -t tasks 'task' completions "${compadd_opts[@]}"
|
||||
fi
|
||||
|
||||
(( directive & NO_FILE_COMP )) && return
|
||||
compset -P '*='
|
||||
_files
|
||||
}
|
||||
|
||||
# don't run the completion function when being source-ed or eval-ed
|
||||
if [ "$funcstack[1]" = "_task" ]; then
|
||||
_task "$@"
|
||||
fi
|
||||
compdef _task "$TASK_CMD"
|
||||
|
||||
@@ -29,7 +29,7 @@ func newTestFlagSet() *pflag.FlagSet {
|
||||
fs.StringVar(&s, "sort", "", "Sort order")
|
||||
fs.StringVar(&s, "cacert", "", "CA cert path")
|
||||
fs.StringVar(&s, "completion", "", "Generate a completion script")
|
||||
fs.StringVar(&s, "new-completion", "", "Generate a completion script")
|
||||
fs.StringVar(&s, "legacy-completion", "", "Generate a completion script")
|
||||
return fs
|
||||
}
|
||||
|
||||
@@ -419,7 +419,7 @@ func TestNeedsTaskfile_StdinEntrypoint(t *testing.T) {
|
||||
func TestCompletionShells(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, flag := range []string{"--completion", "--new-completion"} {
|
||||
for _, flag := range []string{"--completion", "--legacy-completion"} {
|
||||
suggs, dir := complete.Complete(nil, newTestFlagSet(), []string{flag, ""}, complete.Options{})
|
||||
require.Equal(t, complete.DirectiveNoFileComp, dir)
|
||||
require.NotEmpty(t, suggs)
|
||||
@@ -427,7 +427,7 @@ func TestCompletionShells(t *testing.T) {
|
||||
for _, shell := range values(suggs) {
|
||||
_, err := task.Completion(shell)
|
||||
require.NoErrorf(t, err, "%s offers %q", flag, shell)
|
||||
_, err = task.CompletionNext(shell)
|
||||
_, err = task.LegacyCompletion(shell)
|
||||
require.NoErrorf(t, err, "%s offers %q", flag, shell)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ var completionShells = []string{"bash", "zsh", "fish", "powershell", "nu"}
|
||||
|
||||
// Keep in sync with the help strings in internal/flags/flags.go.
|
||||
var flagEnums = map[string][]string{
|
||||
"output": {"interleaved", "group", "prefixed"},
|
||||
"sort": {"default", "alphanumeric", "none"},
|
||||
"completion": completionShells,
|
||||
"new-completion": completionShells,
|
||||
"output": {"interleaved", "group", "prefixed"},
|
||||
"sort": {"default", "alphanumeric", "none"},
|
||||
"completion": completionShells,
|
||||
"legacy-completion": completionShells,
|
||||
}
|
||||
|
||||
// A flag absent here falls back to the shell's default file completion.
|
||||
|
||||
@@ -49,7 +49,7 @@ var (
|
||||
Help bool
|
||||
Init bool
|
||||
Completion string
|
||||
NewCompletion string
|
||||
LegacyCompletion string
|
||||
List bool
|
||||
ListAll bool
|
||||
ListJson bool
|
||||
@@ -126,7 +126,7 @@ func init() {
|
||||
pflag.BoolVarP(&Help, "help", "h", false, "Shows Task usage.")
|
||||
pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.")
|
||||
pflag.StringVar(&Completion, "completion", "", "Generates shell completion script.")
|
||||
pflag.StringVar(&NewCompletion, "new-completion", "", "Generates the new (experimental) shell completion script, powered by the `task __complete` engine.")
|
||||
pflag.StringVar(&LegacyCompletion, "legacy-completion", "", "Generates the pre-engine shell completion script. Deprecated: use --completion.")
|
||||
pflag.BoolVarP(&List, "list", "l", false, "Lists tasks with description of current Taskfile.")
|
||||
pflag.BoolVarP(&ListAll, "list-all", "a", false, "Lists tasks with or without a description.")
|
||||
pflag.BoolVarP(&ListJson, "json", "j", false, "Formats task list as JSON.")
|
||||
|
||||
@@ -369,8 +369,14 @@ go tool task {arguments...}
|
||||
Some installation methods will automatically install completions too, but if
|
||||
this isn't working for you or your chosen method doesn't include them, you can
|
||||
run `task --completion <shell>` to output a completion script for any supported
|
||||
shell. There are a couple of ways these completions can be added to your shell
|
||||
config:
|
||||
shell.
|
||||
|
||||
Every shell shares a single source of truth: the script is a thin wrapper that
|
||||
asks the `task` binary itself what to suggest, so Bash, Zsh, Fish, Nushell and
|
||||
PowerShell all offer the same task names, aliases, flags, flag values and
|
||||
`requires` vars.
|
||||
|
||||
There are a couple of ways these completions can be added to your shell config:
|
||||
|
||||
### Option 1. Load the completions in your shell's startup config (Recommended)
|
||||
|
||||
@@ -468,73 +474,10 @@ to an autoload directory. Option 1 rewrites it at every startup, which keeps it
|
||||
in sync with the installed version of Task — the refreshed completions are picked
|
||||
up by the next shell. With option 2, re-run the command after upgrading Task.
|
||||
|
||||
The completions are attached to an `extern "task"` declaration, which Nushell
|
||||
requires to be static. Three consequences are worth knowing:
|
||||
|
||||
- The experimental flags (`--force-all`, `--download`, `--offline`, …) are always
|
||||
offered, even when the corresponding experiment is disabled. Their description
|
||||
is prefixed with the experiment name, and `task --experiments` lists the ones
|
||||
that are enabled.
|
||||
- Passing a value to a boolean flag with `=` does not work: Nushell forwards
|
||||
`--color=false` as two arguments, so Task reads `false` as a task name. Use
|
||||
`NO_COLOR=1`, or bypass the declaration with `^task --color=false`.
|
||||
- `TASK_EXE` selects the executable that is run, but not the command name the
|
||||
completions are attached to, which is always `task`. For a renamed executable,
|
||||
alias it instead:
|
||||
|
||||
```nu
|
||||
use ($nu.data-dir | path join "vendor/autoload/task-completions.nu") *
|
||||
alias go-task = task
|
||||
```
|
||||
|
||||
### Trying the new completion engine (experimental)
|
||||
|
||||
Task is migrating to a new completion engine, where every shell shares a single
|
||||
source of truth: the `task __complete` command. This gives Bash, Zsh, Fish,
|
||||
Nushell and PowerShell the exact same suggestions (task names, aliases, flags,
|
||||
flag values and `requires` vars, including their enums). It is currently
|
||||
**opt-in** and will become the default of `--completion` in a future release.
|
||||
|
||||
To try it, swap `--completion` for `--new-completion` in any of the snippets
|
||||
above, for example:
|
||||
|
||||
::: code-group
|
||||
|
||||
```shell [bash]
|
||||
# ~/.bashrc
|
||||
eval "$(task --new-completion bash)"
|
||||
```
|
||||
|
||||
```shell [zsh]
|
||||
# ~/.zshrc
|
||||
eval "$(task --new-completion zsh)"
|
||||
```
|
||||
|
||||
```shell [fish]
|
||||
# ~/.config/fish/config.fish
|
||||
task --new-completion fish | source
|
||||
```
|
||||
|
||||
```powershell [powershell]
|
||||
# $PROFILE\Microsoft.PowerShell_profile.ps1
|
||||
Invoke-Expression (&task --new-completion powershell | Out-String)
|
||||
```
|
||||
|
||||
```nu [nushell]
|
||||
# ~/.config/nushell/config.nu
|
||||
mkdir ($nu.data-dir | path join "vendor/autoload")
|
||||
task --new-completion nu | save --force ($nu.data-dir | path join "vendor/autoload/task-completions.nu")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The `verbose` and `show-aliases` zstyles documented above work with the new Zsh
|
||||
completion too.
|
||||
|
||||
Nushell shares a single external completer between every command, so the script
|
||||
chains to the one already configured — carapace and friends keep working. Load
|
||||
it from an autoload directory as shown above rather than from `config.nu`, so
|
||||
that your own completer is the one being chained to. If you would rather wire it
|
||||
chains to the one already configured — carapace and friends keep working. Load it
|
||||
from an autoload directory as shown above rather than from `config.nu`, so that
|
||||
your own completer is the one being chained to. If you would rather wire it
|
||||
yourself, the script also exposes a `task-external-completer` command:
|
||||
|
||||
```nu
|
||||
@@ -549,3 +492,11 @@ $env.config.completions.external.completer = {|spans|
|
||||
Two engine directives behave differently under Nushell by design: it never
|
||||
appends a space after an external completion (so `NoSpace` is a no-op) and never
|
||||
re-sorts the results (so `KeepOrder` is always honoured).
|
||||
|
||||
### Legacy completion scripts
|
||||
|
||||
Before the engine, every shell carried its own hand-written completion script,
|
||||
each with its own idea of what to suggest. Those scripts are still shipped and
|
||||
available through `task --legacy-completion <shell>`, as an escape hatch should
|
||||
the engine misbehave in your setup. They are deprecated, will not receive further
|
||||
fixes, and will be removed in a future release.
|
||||
|
||||
Reference in New Issue
Block a user