diff --git a/CHANGELOG.md b/CHANGELOG.md index 30184e10..7d5120d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ `env:`, and `requires:` sections. Dynamic variables show their shell command (e.g., `sh: echo "hello"`) instead of the evaluated value (#2486 ,#2524 by @vmaerten). +- Improved shell completion scripts (Zsh, Fish, PowerShell) by adding missing + flags and dynamic experimental feature detection (#2532 by @vmaerten). ## v3.45.5 - 2025-11-11 diff --git a/completion/fish/task.fish b/completion/fish/task.fish index e8640fe4..3b7683fe 100644 --- a/completion/fish/task.fish +++ b/completion/fish/task.fish @@ -1,5 +1,32 @@ set -l GO_TASK_PROGNAME task +# 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 + 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 (task --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 @@ -36,19 +63,49 @@ 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)" -complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)' -complete -c $GO_TASK_PROGNAME -s d -l dir -d 'sets directory of execution' -complete -c $GO_TASK_PROGNAME -l dry -d 'compiles and prints tasks in the order that they would be run, without executing them' -complete -c $GO_TASK_PROGNAME -s f -l force -d 'forces execution even when the task is up-to-date' -complete -c $GO_TASK_PROGNAME -s h -l help -d 'shows Task usage' -complete -c $GO_TASK_PROGNAME -s i -l init -d 'creates a new Taskfile.yml in the current folder' -complete -c $GO_TASK_PROGNAME -s l -l list -d 'lists tasks with description of current Taskfile' -complete -c $GO_TASK_PROGNAME -s o -l output -d 'sets output style: [interleaved|group|prefixed]' -xa "interleaved group prefixed" -complete -c $GO_TASK_PROGNAME -s p -l parallel -d 'executes tasks provided on command line in parallel' -complete -c $GO_TASK_PROGNAME -s s -l silent -d 'disables echoing' -complete -c $GO_TASK_PROGNAME -l status -d 'exits with non-zero exit code if any of the given tasks is not up-to-date' -complete -c $GO_TASK_PROGNAME -l summary -d 'show summary about a task' -complete -c $GO_TASK_PROGNAME -s t -l taskfile -d 'choose which Taskfile to run. Defaults to "Taskfile.yml"' -complete -c $GO_TASK_PROGNAME -s v -l verbose -d 'enables verbose mode' -complete -c $GO_TASK_PROGNAME -l version -d 'show Task version' -complete -c $GO_TASK_PROGNAME -s w -l watch -d 'enables watch of the given task' +# 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" +complete -c $GO_TASK_PROGNAME -s d -l dir -d 'set directory of execution' +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 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 -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' + +# 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' + +# RemoteTaskfiles experiment - Options +complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l offline -d 'use only local or cached Taskfiles' +complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l timeout -d 'timeout for remote Taskfile downloads' +complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l expiry -d 'cache expiry duration' + +# RemoteTaskfiles experiment - Operations +complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l download -d 'download remote Taskfile' +complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l clear-cache -d 'clear remote Taskfile cache' diff --git a/completion/ps/task.ps1 b/completion/ps/task.ps1 index 2a389e8a..156faa44 100644 --- a/completion/ps/task.ps1 +++ b/completion/ps/task.ps1 @@ -5,22 +5,78 @@ Register-ArgumentCompleter -CommandName task -ScriptBlock { if ($commandName.StartsWith('-')) { $completions = @( - [CompletionResult]::new('--list-all ', '--list-all ', [CompletionResultType]::ParameterName, 'list all tasks'), - [CompletionResult]::new('--color ', '--color', [CompletionResultType]::ParameterName, '--color'), - [CompletionResult]::new('--concurrency=', '--concurrency=', [CompletionResultType]::ParameterName, 'concurrency'), - [CompletionResult]::new('--interval=', '--interval=', [CompletionResultType]::ParameterName, 'interval'), - [CompletionResult]::new('--output=interleaved ', '--output=interleaved', [CompletionResultType]::ParameterName, '--output='), - [CompletionResult]::new('--output=group ', '--output=group', [CompletionResultType]::ParameterName, '--output='), - [CompletionResult]::new('--output=prefixed ', '--output=prefixed', [CompletionResultType]::ParameterName, '--output='), - [CompletionResult]::new('--dry ', '--dry', [CompletionResultType]::ParameterName, '--dry'), - [CompletionResult]::new('--force ', '--force', [CompletionResultType]::ParameterName, '--force'), - [CompletionResult]::new('--parallel ', '--parallel', [CompletionResultType]::ParameterName, '--parallel'), - [CompletionResult]::new('--silent ', '--silent', [CompletionResultType]::ParameterName, '--silent'), - [CompletionResult]::new('--status ', '--status', [CompletionResultType]::ParameterName, '--status'), - [CompletionResult]::new('--verbose ', '--verbose', [CompletionResultType]::ParameterName, '--verbose'), - [CompletionResult]::new('--watch ', '--watch', [CompletionResultType]::ParameterName, '--watch') + # 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('-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, '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('-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') ) + # 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') + } + + if ($experiments -match '\* REMOTE_TASKFILES:.*on') { + # Options + $completions += [CompletionResult]::new('--offline', '--offline', [CompletionResultType]::ParameterName, 'use cached Taskfiles') + $completions += [CompletionResult]::new('--timeout', '--timeout', [CompletionResultType]::ParameterName, 'download timeout') + $completions += [CompletionResult]::new('--expiry', '--expiry', [CompletionResultType]::ParameterName, 'cache expiry') + # Operations + $completions += [CompletionResult]::new('--download', '--download', [CompletionResultType]::ParameterName, 'download remote Taskfile') + $completions += [CompletionResult]::new('--clear-cache', '--clear-cache', [CompletionResultType]::ParameterName, 'clear cache') + } + return $completions.Where{ $_.CompletionText.StartsWith($commandName) } } diff --git a/completion/zsh/_task b/completion/zsh/_task index 7fe50aed..08670531 100755 --- a/completion/zsh/_task +++ b/completion/zsh/_task @@ -4,6 +4,12 @@ typeset -A opt_args _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 @@ -36,29 +42,74 @@ function __task_list() { } _task() { - _arguments \ - '(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: ' \ - '(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]' \ - '(-f --force)'{-f,--force}'[run even if task is up-to-date]' \ - '(-c --color)'{-c,--color}'[colored output]' \ - '(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' \ - '(--dry)--dry[dry-run mode, compile and print tasks only]' \ - '(-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: ' \ - '(-s --silent)'{-s,--silent}'[disable echoing]' \ - '(--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]' \ - + '(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]' \ - '*: :__task_list' + 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 --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)' + '(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' + '(-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]' + '(-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]' + ) + + # 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 + + if __task_is_experiment_enabled "REMOTE_TASKFILES"; then + standard_args+=( + '(--offline)--offline[use only local or cached Taskfiles]' + '(--timeout)--timeout[timeout for remote Taskfile downloads]:duration: ' + '(--expiry)--expiry[cache expiry duration]:duration: ' + ) + fi + + operation_args=( + + '(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]' + ) + + # Experimental operations (dynamically added based on enabled experiments) + if __task_is_experiment_enabled "REMOTE_TASKFILES"; then + operation_args+=( + '--download[download remote Taskfile]' + '--clear-cache[clear remote Taskfile cache]' + ) + fi + + # Task names completion (must be last) + operation_args+=('*: :__task_list') + + _arguments $standard_args $operation_args } # don't run the completion function when being source-ed or eval-ed