2023-03-06 02:28:31 +00:00
// Copyright 2013-2023 The Cobra Authors
2022-09-16 13:55:56 +02:00
//
// Licensed under the Apache License, Version 2.0 ( the "License" ) ;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2015-03-16 15:31:03 -04:00
package cobra
import (
2017-05-14 13:20:20 +02:00
"bytes"
2015-03-16 15:31:03 -04:00
"fmt"
2016-01-03 13:01:52 +01:00
"io"
2015-03-16 15:31:03 -04:00
"os"
2015-04-13 18:44:05 -04:00
"sort"
2015-03-16 15:31:03 -04:00
"strings"
"github.com/spf13/pflag"
)
2017-01-24 11:30:45 -05:00
// Annotations for Bash completion.
2015-03-16 15:31:03 -04:00
const (
2016-08-20 12:04:53 +05:00
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions"
2016-03-20 21:05:18 +01:00
BashCompCustom = "cobra_annotation_bash_completion_custom"
2015-03-16 15:31:03 -04:00
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
2015-08-09 13:30:58 -06:00
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
2015-03-16 15:31:03 -04:00
)
2021-02-08 00:08:50 +00:00
func writePreamble( buf io.StringWriter, name string) {
WriteStringAndCheck( buf, fmt.Sprintf( "# bash completion for %-36s -*- shell-script -*-\n" , name) )
WriteStringAndCheck( buf, fmt.Sprintf( `
2018-02-08 21:34:46 +00:00
__%[ 1] s_debug( )
2015-03-16 15:31:03 -04:00
{
2021-12-08 06:44:39 +08:00
if [ [ -n ${ BASH_COMP_DEBUG_FILE :- } ] ] ; then
2015-05-04 14:40:27 -04:00
echo " $* " >> " ${ BASH_COMP_DEBUG_FILE } "
2015-03-16 15:31:03 -04:00
fi
}
2015-09-03 16:59:10 -04:00
# Homebrew on Macs have version 1.3 of bash-completion which doesn't include
# _init_completion. This is a very minimal version of that function.
2018-02-08 21:34:46 +00:00
__%[ 1] s_init_completion( )
2015-09-03 16:59:10 -04:00
{
COMPREPLY = ( )
2016-03-20 20:30:21 +01:00
_get_comp_words_by_ref " $@ " cur prev words cword
2015-09-03 16:59:10 -04:00
}
2018-02-08 21:34:46 +00:00
__%[ 1] s_index_of_word( )
2015-03-16 15:31:03 -04:00
{
local w word = $1
shift
index = 0
for w in " $@ " ; do
[ [ $w = " $word " ] ] && return
index = $(( index+1))
done
index = -1
}
2018-02-08 21:34:46 +00:00
__%[ 1] s_contains_word( )
2015-03-16 15:31:03 -04:00
{
local w word = $1 ; shift
for w in " $@ " ; do
[ [ $w = " $word " ] ] && return
done
return 1
}
2020-04-03 15:43:43 -04:00
__%[ 1] s_handle_go_custom_completion( )
{
__%[ 1] s_debug " ${ FUNCNAME [0] } : cur is ${ cur } , words[*] is ${ words [*] } , #words[@] is ${# words [@] } "
2020-06-29 15:52:14 -04:00
local shellCompDirectiveError = %[ 3] d
local shellCompDirectiveNoSpace = %[ 4] d
local shellCompDirectiveNoFileComp = %[ 5] d
local shellCompDirectiveFilterFileExt = %[ 6] d
local shellCompDirectiveFilterDirs = %[ 7] d
2020-04-03 15:43:43 -04:00
local out requestComp lastParam lastChar comp directive args
# Prepare the command to request completions for the program.
2023-06-13 18:12:49 +03:00
# Calling ${words[0]} instead of directly %[1]s allows handling aliases
2020-04-03 15:43:43 -04:00
args = ( " ${ words [@] : 1 } " )
2022-06-15 20:08:16 -04:00
# Disable ActiveHelp which is not supported for bash completion v1
requestComp = " %[8]s=0 ${ words [0] } %[2]s ${ args [*] } "
2020-04-03 15:43:43 -04:00
lastParam = ${ words [ $(( ${# words [@] } - 1 )) ] }
lastChar = ${ lastParam : $(( ${# lastParam } - 1 )) : 1 }
__%[ 1] s_debug " ${ FUNCNAME [0] } : lastParam ${ lastParam } , lastChar ${ lastChar } "
if [ -z " ${ cur } " ] && [ " ${ lastChar } " != "=" ] ; then
# If the last parameter is complete (there is a space following it)
# We add an extra empty parameter so we can indicate this to the go method.
__%[ 1] s_debug " ${ FUNCNAME [0] } : Adding extra empty parameter "
requestComp = " ${ requestComp } \"\" "
fi
__%[ 1] s_debug " ${ FUNCNAME [0] } : calling ${ requestComp } "
# Use eval to handle any environment variables and such
out = $( eval " ${ requestComp } " 2>/dev/null)
# Extract the directive integer at the very end of the output following a colon (:)
directive = ${ out ##* : }
# Remove the directive
out = ${ out %% : * }
if [ " ${ directive } " = " ${ out } " ] ; then
# There is not directive specified
directive = 0
fi
__%[ 1] s_debug " ${ FUNCNAME [0] } : the completion directive is: ${ directive } "
2022-05-03 04:41:07 +03:00
__%[ 1] s_debug " ${ FUNCNAME [0] } : the completions are: ${ out } "
2020-04-03 15:43:43 -04:00
2020-06-29 15:52:14 -04:00
if [ $(( directive & shellCompDirectiveError)) -ne 0 ] ; then
2020-04-03 15:43:43 -04:00
# Error code. No completion.
__%[ 1] s_debug " ${ FUNCNAME [0] } : received error from custom completion go code "
return
else
2020-06-29 15:52:14 -04:00
if [ $(( directive & shellCompDirectiveNoSpace)) -ne 0 ] ; then
2020-04-03 15:43:43 -04:00
if [ [ $( type -t compopt) = "builtin" ] ] ; then
__%[ 1] s_debug " ${ FUNCNAME [0] } : activating no space "
compopt -o nospace
fi
fi
2020-06-29 15:52:14 -04:00
if [ $(( directive & shellCompDirectiveNoFileComp)) -ne 0 ] ; then
2020-04-03 15:43:43 -04:00
if [ [ $( type -t compopt) = "builtin" ] ] ; then
__%[ 1] s_debug " ${ FUNCNAME [0] } : activating no file completion "
compopt +o default
fi
fi
2020-06-29 15:52:14 -04:00
fi
2020-04-03 15:43:43 -04:00
2020-06-29 15:52:14 -04:00
if [ $(( directive & shellCompDirectiveFilterFileExt)) -ne 0 ] ; then
# File extension filtering
local fullFilter filter filteringCmd
# Do not use quotes around the $out variable or else newline
# characters will be kept.
2022-05-03 04:41:07 +03:00
for filter in ${ out } ; do
2020-06-29 15:52:14 -04:00
fullFilter += " $filter | "
done
filteringCmd = " _filedir $fullFilter "
__%[ 1] s_debug " File filtering command: $filteringCmd "
$filteringCmd
elif [ $(( directive & shellCompDirectiveFilterDirs)) -ne 0 ] ; then
# File completion for directories only
2021-12-07 16:53:38 -06:00
local subdir
2020-06-29 15:52:14 -04:00
# Use printf to strip any trailing newline
2022-05-03 04:41:07 +03:00
subdir = $( printf "%%s" " ${ out } " )
2020-06-29 15:52:14 -04:00
if [ -n " $subdir " ] ; then
__%[ 1] s_debug " Listing directories in $subdir "
__%[ 1] s_handle_subdirs_in_dir_flag " $subdir "
else
__%[ 1] s_debug "Listing directories in ."
_filedir -d
fi
else
2020-04-03 15:43:43 -04:00
while IFS = '' read -r comp; do
COMPREPLY += ( " $comp " )
2022-05-03 04:41:07 +03:00
done < <( compgen -W " ${ out } " -- " $cur " )
2020-04-03 15:43:43 -04:00
fi
}
2018-02-08 21:34:46 +00:00
__%[ 1] s_handle_reply( )
2015-03-16 15:31:03 -04:00
{
2018-02-08 21:34:46 +00:00
__%[ 1] s_debug " ${ FUNCNAME [0] } "
2019-12-26 12:55:42 -05:00
local comp
2015-03-16 15:31:03 -04:00
case $cur in
-*)
2015-11-07 04:33:18 -07:00
if [ [ $( type -t compopt) = "builtin" ] ] ; then
compopt -o nospace
2015-11-03 13:31:28 -05:00
fi
2015-03-16 15:31:03 -04:00
local allflags
if [ ${# must_have_one_flag [@] } -ne 0 ] ; then
allflags = ( " ${ must_have_one_flag [@] } " )
else
allflags = ( " ${ flags [*] } ${ two_word_flags [*] } " )
fi
2019-12-26 12:55:42 -05:00
while IFS = '' read -r comp; do
COMPREPLY += ( " $comp " )
2019-07-11 20:18:47 +02:00
done < <( compgen -W " ${ allflags [*] } " -- " $cur " )
2015-11-07 04:33:18 -07:00
if [ [ $( type -t compopt) = "builtin" ] ] ; then
2016-04-02 22:45:01 +02:00
[ [ " ${ COMPREPLY [0] } " = = *= ] ] || compopt +o nospace
2015-11-03 13:31:28 -05:00
fi
2016-03-20 20:30:21 +01:00
# complete after --flag=abc
if [ [ $cur = = *= * ] ] ; then
if [ [ $( type -t compopt) = "builtin" ] ] ; then
compopt +o nospace
fi
local index flag
flag = " ${ cur %%=* } "
2018-02-08 21:34:46 +00:00
__%[ 1] s_index_of_word " ${ flag } " " ${ flags_with_completion [@] } "
2017-04-08 17:45:37 +03:00
COMPREPLY = ( )
2016-03-20 20:30:21 +01:00
if [ [ ${ index } -ge 0 ] ] ; then
2016-04-02 22:45:01 +02:00
PREFIX = ""
cur = " ${ cur #*= } "
2016-03-20 20:30:21 +01:00
${ flags_completion [ ${ index } ] }
2021-12-08 06:44:39 +08:00
if [ -n " ${ ZSH_VERSION :- } " ] ; then
2017-10-11 12:02:03 +09:00
# zsh completion needs --flag= prefix
2016-04-02 22:45:01 +02:00
eval " COMPREPLY=( \"\${COMPREPLY[@]/#/ ${ flag } =}\" ) "
2016-03-20 20:30:21 +01:00
fi
fi
fi
2021-11-01 15:01:33 -04:00
if [ [ -z " ${ flag_parsing_disabled } " ] ] ; then
# If flag parsing is enabled, we have completed the flags and can return.
# If flag parsing is disabled, we may not know all (or any) of the flags, so we fallthrough
# to possibly call handle_go_custom_completion.
return 0;
fi
2015-03-16 15:31:03 -04:00
; ;
esac
# check if we are handling a flag with special work handling
local index
2018-02-08 21:34:46 +00:00
__%[ 1] s_index_of_word " ${ prev } " " ${ flags_with_completion [@] } "
2015-03-16 15:31:03 -04:00
if [ [ ${ index } -ge 0 ] ] ; then
${ flags_completion [ ${ index } ] }
return
fi
# we are parsing a flag and don't have a special handler, no completion
if [ [ ${ cur } != " ${ words [cword] } " ] ] ; then
return
fi
local completions
2016-06-03 12:43:20 -04:00
completions = ( " ${ commands [@] } " )
if [ [ ${# must_have_one_noun [@] } -ne 0 ] ] ; then
2020-05-07 17:04:14 -04:00
completions += ( " ${ must_have_one_noun [@] } " )
2020-04-03 15:43:43 -04:00
elif [ [ -n " ${ has_completion_function } " ] ] ; then
# if a go completion function is provided, defer to that function
__%[ 1] s_handle_go_custom_completion
2016-06-03 12:43:20 -04:00
fi
if [ [ ${# must_have_one_flag [@] } -ne 0 ] ] ; then
completions += ( " ${ must_have_one_flag [@] } " )
2015-03-16 15:31:03 -04:00
fi
2019-12-26 12:55:42 -05:00
while IFS = '' read -r comp; do
COMPREPLY += ( " $comp " )
2019-07-11 20:18:47 +02:00
done < <( compgen -W " ${ completions [*] } " -- " $cur " )
2015-03-16 15:31:03 -04:00
2016-03-25 16:05:56 +01:00
if [ [ ${# COMPREPLY [@] } -eq 0 && ${# noun_aliases [@] } -gt 0 && ${# must_have_one_noun [@] } -ne 0 ] ] ; then
2019-12-26 12:55:42 -05:00
while IFS = '' read -r comp; do
COMPREPLY += ( " $comp " )
2019-07-11 20:18:47 +02:00
done < <( compgen -W " ${ noun_aliases [*] } " -- " $cur " )
2016-03-25 16:05:56 +01:00
fi
2015-03-16 15:31:03 -04:00
if [ [ ${# COMPREPLY [@] } -eq 0 ] ] ; then
2021-12-07 23:57:57 +01:00
if declare -F __%[ 1] s_custom_func >/dev/null; then
# try command name qualified custom func
__%[ 1] s_custom_func
else
# otherwise fall back to unqualified for compatibility
declare -F __custom_func >/dev/null && __custom_func
fi
2015-03-16 15:31:03 -04:00
fi
2015-12-09 20:57:45 -08:00
2017-06-05 16:18:07 +01:00
# available in bash-completion >= 2, not always present on macOS
if declare -F __ltrim_colon_completions >/dev/null; then
__ltrim_colon_completions " $cur "
fi
2018-02-21 07:50:56 -08:00
# If there is only 1 completion and it is a flag with an = it will be completed
# but we don't want a space after the =
if [ [ " ${# COMPREPLY [@] } " -eq "1" ] ] && [ [ $( type -t compopt) = "builtin" ] ] && [ [ " ${ COMPREPLY [0] } " = = --*= ] ] ; then
compopt -o nospace
fi
2015-03-16 15:31:03 -04:00
}
2015-05-04 15:44:07 -04:00
# The arguments should be in the form "ext1|ext2|extn"
2018-02-08 21:34:46 +00:00
__%[ 1] s_handle_filename_extension_flag( )
2015-05-04 15:44:07 -04:00
{
local ext = " $1 "
_filedir " @( ${ ext } ) "
}
2018-02-08 21:34:46 +00:00
__%[ 1] s_handle_subdirs_in_dir_flag( )
2015-08-09 13:30:58 -06:00
{
local dir = " $1 "
2019-07-11 20:18:47 +02:00
pushd " ${ dir } " >/dev/null 2>& 1 && _filedir -d && popd >/dev/null 2>& 1 || return
2015-08-09 13:30:58 -06:00
}
2018-02-08 21:34:46 +00:00
__%[ 1] s_handle_flag( )
2015-03-16 15:31:03 -04:00
{
2018-02-08 21:34:46 +00:00
__%[ 1] s_debug " ${ FUNCNAME [0] } : c is $c words[c] is ${ words [c] } "
2015-03-16 15:31:03 -04:00
# if a command required a flag, and we found it, unset must_have_one_flag()
local flagname = ${ words [c] }
2021-12-08 06:44:39 +08:00
local flagvalue = ""
2015-03-16 15:31:03 -04:00
# if the word contained an =
if [ [ ${ words [c] } = = *"=" * ] ] ; then
2015-12-06 02:57:45 +09:00
flagvalue = ${ flagname #*= } # take in as flagvalue after the =
2015-03-16 15:31:03 -04:00
flagname = ${ flagname %%=* } # strip everything after the =
flagname = " ${ flagname } = " # but put the = back
fi
2018-02-08 21:34:46 +00:00
__%[ 1] s_debug " ${ FUNCNAME [0] } : looking for ${ flagname } "
if __%[ 1] s_contains_word " ${ flagname } " " ${ must_have_one_flag [@] } " ; then
2015-03-16 15:31:03 -04:00
must_have_one_flag = ( )
fi
2016-06-03 12:37:40 -04:00
# if you set a flag which only applies to this command, don't show subcommands
2018-02-08 21:34:46 +00:00
if __%[ 1] s_contains_word " ${ flagname } " " ${ local_nonpersistent_flags [@] } " ; then
2016-06-03 12:37:40 -04:00
commands = ( )
fi
2015-12-06 02:57:45 +09:00
# keep flag value with flagname as flaghash
2018-02-05 01:42:17 +09:00
# flaghash variable is an associative array which is only supported in bash > 3.
2021-12-08 06:44:39 +08:00
if [ [ -z " ${ BASH_VERSION :- } " || " ${ BASH_VERSINFO [0] :- } " -gt 3 ] ] ; then
2018-02-05 01:42:17 +09:00
if [ -n " ${ flagvalue } " ] ; then
flaghash[ ${ flagname } ] = ${ flagvalue }
elif [ -n " ${ words [ $(( c+1)) ] } " ] ; then
flaghash[ ${ flagname } ] = ${ words [ $(( c+1)) ] }
else
flaghash[ ${ flagname } ] = "true" # pad "true" for bool flag
fi
2015-12-06 02:57:45 +09:00
fi
2015-03-16 15:31:03 -04:00
# skip the argument to a two word flag
2019-03-11 21:55:09 +09:00
if [ [ ${ words [c] } != *"=" * ] ] && __%[ 1] s_contains_word " ${ words [c] } " " ${ two_word_flags [@] } " ; then
2021-12-07 23:57:57 +01:00
__%[ 1] s_debug " ${ FUNCNAME [0] } : found a flag ${ words [c] } , skip the next argument "
2015-03-16 15:31:03 -04:00
c = $(( c+1))
# if we are looking for a flags value, don't show commands
if [ [ $c -eq $cword ] ] ; then
commands = ( )
fi
fi
c = $(( c+1))
}
2018-02-08 21:34:46 +00:00
__%[ 1] s_handle_noun( )
2015-03-16 15:31:03 -04:00
{
2018-02-08 21:34:46 +00:00
__%[ 1] s_debug " ${ FUNCNAME [0] } : c is $c words[c] is ${ words [c] } "
2015-03-16 15:31:03 -04:00
2018-02-08 21:34:46 +00:00
if __%[ 1] s_contains_word " ${ words [c] } " " ${ must_have_one_noun [@] } " ; then
2015-03-16 15:31:03 -04:00
must_have_one_noun = ( )
2018-02-08 21:34:46 +00:00
elif __%[ 1] s_contains_word " ${ words [c] } " " ${ noun_aliases [@] } " ; then
2016-03-20 23:21:49 +01:00
must_have_one_noun = ( )
2015-03-16 15:31:03 -04:00
fi
nouns += ( " ${ words [c] } " )
c = $(( c+1))
}
2018-02-08 21:34:46 +00:00
__%[ 1] s_handle_command( )
2015-03-16 15:31:03 -04:00
{
2018-02-08 21:34:46 +00:00
__%[ 1] s_debug " ${ FUNCNAME [0] } : c is $c words[c] is ${ words [c] } "
2015-03-16 15:31:03 -04:00
local next_command
if [ [ -n ${ last_command } ] ] ; then
2015-12-09 20:57:45 -08:00
next_command = " _ ${ last_command } _ ${ words [c]// : /__ } "
2015-03-16 15:31:03 -04:00
else
2016-02-08 02:29:17 +08:00
if [ [ $c -eq 0 ] ] ; then
2018-02-21 09:51:53 -08:00
next_command = "_%[1]s_root_command"
2016-02-08 02:29:17 +08:00
else
next_command = " _ ${ words [c]// : /__ } "
fi
2015-03-16 15:31:03 -04:00
fi
c = $(( c+1))
2018-02-08 21:34:46 +00:00
__%[ 1] s_debug " ${ FUNCNAME [0] } : looking for ${ next_command } "
2017-04-02 10:14:34 -04:00
declare -F " $next_command " >/dev/null && $next_command
2015-03-16 15:31:03 -04:00
}
2018-02-08 21:34:46 +00:00
__%[ 1] s_handle_word( )
2015-03-16 15:31:03 -04:00
{
if [ [ $c -ge $cword ] ] ; then
2018-02-08 21:34:46 +00:00
__%[ 1] s_handle_reply
2015-11-07 04:33:18 -07:00
return
2015-03-16 15:31:03 -04:00
fi
2018-02-08 21:34:46 +00:00
__%[ 1] s_debug " ${ FUNCNAME [0] } : c is $c words[c] is ${ words [c] } "
2015-03-16 15:31:03 -04:00
if [ [ " ${ words [c] } " = = -* ] ] ; then
2018-02-08 21:34:46 +00:00
__%[ 1] s_handle_flag
elif __%[ 1] s_contains_word " ${ words [c] } " " ${ commands [@] } " ; then
__%[ 1] s_handle_command
2018-02-21 09:51:53 -08:00
elif [ [ $c -eq 0 ] ] ; then
2018-02-08 21:34:46 +00:00
__%[ 1] s_handle_command
2018-04-23 05:47:20 -07:00
elif __%[ 1] s_contains_word " ${ words [c] } " " ${ command_aliases [@] } " ; then
# aliashash variable is an associative array which is only supported in bash > 3.
2021-12-08 06:44:39 +08:00
if [ [ -z " ${ BASH_VERSION :- } " || " ${ BASH_VERSINFO [0] :- } " -gt 3 ] ] ; then
2018-04-23 05:47:20 -07:00
words[ c] = ${ aliashash [ ${ words [c] } ] }
__%[ 1] s_handle_command
else
__%[ 1] s_handle_noun
fi
2015-03-16 15:31:03 -04:00
else
2018-02-08 21:34:46 +00:00
__%[ 1] s_handle_noun
2015-03-16 15:31:03 -04:00
fi
2018-02-08 21:34:46 +00:00
__%[ 1] s_handle_word
2015-03-16 15:31:03 -04:00
}
2020-06-29 15:52:14 -04:00
` , name, ShellCompNoDescRequestCmd,
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
2022-06-15 20:08:16 -04:00
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar( name) ) )
2015-03-16 15:31:03 -04:00
}
2025-04-26 20:13:18 +00:00
// writePostscript writes postscript code to the provided buffer for a given command name.
//
// Parameters:
// - buf: io.StringWriter where the postscript code will be written.
// - name: The name of the command for which the postscript code is being generated.
//
// This function handles the generation of completion script postscript code for a Bash or similar shell. It replaces colons in the command name with double underscores, initializes completion variables and functions, and sets up the completion behavior using the 'complete' command.
2021-02-08 00:08:50 +00:00
func writePostscript( buf io.StringWriter, name string) {
2022-05-14 22:10:36 +02:00
name = strings.ReplaceAll( name, ":" , "__" )
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( "__start_%s()\n" , name) )
WriteStringAndCheck( buf, fmt.Sprintf( ` {
2021-02-18 17:26:03 +02:00
local cur prev words cword split
2016-02-05 13:04:36 +08:00
declare -A flaghash 2>/dev/null || :
2018-04-23 05:47:20 -07:00
declare -A aliashash 2>/dev/null || :
2015-11-03 15:09:37 -05:00
if declare -F _init_completion >/dev/null 2>& 1; then
2015-09-03 16:59:10 -04:00
_init_completion -s || return
else
2018-02-08 21:34:46 +00:00
__%[ 1] s_init_completion -n "=" || return
2015-09-03 16:59:10 -04:00
fi
2015-03-16 15:31:03 -04:00
local c = 0
2021-11-01 15:01:33 -04:00
local flag_parsing_disabled =
2015-03-16 15:31:03 -04:00
local flags = ( )
local two_word_flags = ( )
2016-06-03 12:37:40 -04:00
local local_nonpersistent_flags = ( )
2015-03-16 15:31:03 -04:00
local flags_with_completion = ( )
local flags_completion = ( )
2018-02-08 21:34:46 +00:00
local commands = ( "%[1]s" )
2021-02-18 17:26:03 +02:00
local command_aliases = ( )
2015-03-16 15:31:03 -04:00
local must_have_one_flag = ( )
local must_have_one_noun = ( )
2021-12-08 06:44:39 +08:00
local has_completion_function = ""
local last_command = ""
2015-03-16 15:31:03 -04:00
local nouns = ( )
2021-02-18 17:26:03 +02:00
local noun_aliases = ( )
2015-03-16 15:31:03 -04:00
2018-02-08 21:34:46 +00:00
__%[ 1] s_handle_word
2015-03-16 15:31:03 -04:00
}
2017-05-14 13:20:20 +02:00
` , name) )
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( ` if [ [ $( type -t compopt) = "builtin" ] ] ; then
2016-01-02 08:11:35 +02:00
complete -o default -F __start_%s %s
2015-11-07 04:33:18 -07:00
else
2016-01-02 08:11:35 +02:00
complete -o default -o nospace -F __start_%s %s
2015-11-07 04:33:18 -07:00
fi
2017-05-14 13:20:20 +02:00
` , name, name, name, name) )
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, "# ex: ts=4 sw=4 et filetype=sh\n" )
2015-03-16 15:31:03 -04:00
}
2025-04-26 20:13:18 +00:00
// writeCommands writes the command definitions to a buffer.
//
// It takes an io.StringWriter and a *Command as parameters. The function iterates through
// the commands associated with the given Command instance. If a command is not available and
// is not the help command, it skips that command. For each valid command, it writes its name to the buffer
// in the format 'commands+=(<command_name>)' and calls writeCmdAliases to write any aliases for the command.
//
// The function includes a newline character at the end of the output.
//
// It returns nothing but may log errors through WriteStringAndCheck.
2021-02-08 00:08:50 +00:00
func writeCommands( buf io.StringWriter, cmd *Command) {
WriteStringAndCheck( buf, " commands=()\n" )
2015-03-16 15:31:03 -04:00
for _, c := range cmd.Commands( ) {
2020-06-16 16:49:26 -04:00
if !c.IsAvailableCommand( ) && c != cmd.helpCommand {
2015-04-07 17:38:22 -04:00
continue
}
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( " commands+=(%q)\n" , c.Name( ) ) )
2018-04-23 05:47:20 -07:00
writeCmdAliases( buf, c)
2015-03-16 15:31:03 -04:00
}
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, "\n" )
2015-03-16 15:31:03 -04:00
}
2025-04-26 20:13:18 +00:00
// writeFlagHandler writes the flag completion handler for a given command and its annotations.
//
// Parameters:
// buf - an io.StringWriter where the completion handler will be written
// name - the name of the flag
// annotations - a map containing annotations for the flag, including types like BashCompFilenameExt, BashCompCustom, and BashCompSubdirsInDir
// cmd - the command associated with the flag
//
// Returns:
// This function does not return any value. It writes to the buffer provided.
2021-02-08 00:08:50 +00:00
func writeFlagHandler( buf io.StringWriter, name string, annotations map[ string] [ ] string, cmd *Command) {
2015-03-16 15:31:03 -04:00
for key, value := range annotations {
switch key {
case BashCompFilenameExt:
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( " flags_with_completion+=(%q)\n" , name) )
2015-03-16 15:31:03 -04:00
2017-05-14 13:20:20 +02:00
var ext string
2015-06-22 14:28:16 -04:00
if len( value) > 0 {
2018-02-28 05:38:38 +00:00
ext = fmt.Sprintf( "__%s_handle_filename_extension_flag " , cmd.Root( ) .Name( ) ) + strings.Join( value, "|" )
2015-06-22 14:28:16 -04:00
} else {
2017-05-14 13:20:20 +02:00
ext = "_filedir"
2015-06-22 14:28:16 -04:00
}
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( " flags_completion+=(%q)\n" , ext) )
2016-03-20 21:05:18 +01:00
case BashCompCustom:
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( " flags_with_completion+=(%q)\n" , name) )
2016-03-20 21:05:18 +01:00
if len( value) > 0 {
handlers := strings.Join( value, "; " )
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( " flags_completion+=(%q)\n" , handlers) )
2016-03-20 21:05:18 +01:00
} else {
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, " flags_completion+=(:)\n" )
2016-03-20 21:05:18 +01:00
}
2015-08-09 13:30:58 -06:00
case BashCompSubdirsInDir:
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( " flags_with_completion+=(%q)\n" , name) )
2015-08-09 13:30:58 -06:00
2017-05-14 13:20:20 +02:00
var ext string
2015-08-09 13:30:58 -06:00
if len( value) = = 1 {
2018-02-28 05:38:38 +00:00
ext = fmt.Sprintf( "__%s_handle_subdirs_in_dir_flag " , cmd.Root( ) .Name( ) ) + value[ 0]
2015-08-09 13:30:58 -06:00
} else {
2017-05-14 13:20:20 +02:00
ext = "_filedir -d"
2015-08-09 13:30:58 -06:00
}
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( " flags_completion+=(%q)\n" , ext) )
2015-03-16 15:31:03 -04:00
}
}
}
2021-02-08 00:08:50 +00:00
const cbn = "\")\n"
2025-04-26 20:13:18 +00:00
// writeShortFlag writes a short flag to the provided buffer.
//
// Parameters:
// - buf: The io.StringWriter where the flag will be written.
// - flag: A pointer to the pflag.Flag representing the flag to write.
// - cmd: A pointer to the Command associated with the flag.
//
// It constructs a formatted string based on the flag' s shorthand and annotations, then writes it to the buffer using WriteStringAndCheck.
// Additionally, it calls writeFlagHandler to handle any specific logic for the flag.
2021-02-08 00:08:50 +00:00
func writeShortFlag( buf io.StringWriter, flag *pflag.Flag, cmd *Command) {
2015-03-16 15:31:03 -04:00
name := flag.Shorthand
format := " "
2017-05-14 13:20:20 +02:00
if len( flag.NoOptDefVal) = = 0 {
2015-03-16 15:31:03 -04:00
format += "two_word_"
}
2021-02-08 00:08:50 +00:00
format += "flags+=(\"-%s" + cbn
WriteStringAndCheck( buf, fmt.Sprintf( format, name) )
2018-02-08 21:34:46 +00:00
writeFlagHandler( buf, "-" +name, flag.Annotations, cmd)
2015-03-16 15:31:03 -04:00
}
2025-04-26 20:13:18 +00:00
// writeFlag writes a flag definition to the buffer in a specific format.
//
// Parameters:
// - buf: The io.StringWriter where the flag definition will be written.
// - flag: The pflag.Flag object representing the flag being written.
// - cmd: The Command object associated with the flag.
//
// Returns:
// None
//
// This function handles the formatting and writing of a single flag, including handling cases for flags without default values and two-word flags. It also calls writeFlagHandler to process any additional annotations or handlers related to the flag.
2021-02-08 00:08:50 +00:00
func writeFlag( buf io.StringWriter, flag *pflag.Flag, cmd *Command) {
2015-03-16 15:31:03 -04:00
name := flag.Name
format := " flags+=(\"--%s"
2017-05-14 13:20:20 +02:00
if len( flag.NoOptDefVal) = = 0 {
2015-03-16 15:31:03 -04:00
format += "="
}
2021-02-08 00:08:50 +00:00
format += cbn
WriteStringAndCheck( buf, fmt.Sprintf( format, name) )
2019-03-11 21:55:09 +09:00
if len( flag.NoOptDefVal) = = 0 {
2021-02-08 00:08:50 +00:00
format = " two_word_flags+=(\"--%s" + cbn
WriteStringAndCheck( buf, fmt.Sprintf( format, name) )
2019-03-11 21:55:09 +09:00
}
2018-02-08 21:34:46 +00:00
writeFlagHandler( buf, "--" +name, flag.Annotations, cmd)
2015-03-16 15:31:03 -04:00
}
2025-04-26 20:13:18 +00:00
// writeLocalNonPersistentFlag writes the local non-persistent flags to the provided buffer.
//
// Parameters:
// - buf: The io.StringWriter where the flags will be written.
// - flag: A pointer to the pflag.Flag that contains the details of the flag.
//
// This function constructs a string representation of the flag and appends it to the buffer. If the flag has a shorthand,
// it also writes the shorthand version of the flag to the buffer.
2021-02-08 00:08:50 +00:00
func writeLocalNonPersistentFlag( buf io.StringWriter, flag *pflag.Flag) {
2016-06-03 12:37:40 -04:00
name := flag.Name
2021-02-08 00:08:50 +00:00
format := " local_nonpersistent_flags+=(\"--%[1]s" + cbn
2017-05-14 13:20:20 +02:00
if len( flag.NoOptDefVal) = = 0 {
2021-02-08 00:08:50 +00:00
format += " local_nonpersistent_flags+=(\"--%[1]s=" + cbn
2016-06-03 12:37:40 -04:00
}
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( format, name) )
2020-09-09 17:34:51 +02:00
if len( flag.Shorthand) > 0 {
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( " local_nonpersistent_flags+=(\"-%s\")\n" , flag.Shorthand) )
2020-09-09 17:34:51 +02:00
}
2016-06-03 12:37:40 -04:00
}
2025-04-26 20:13:18 +00:00
// prepareCustomAnnotationsForFlags sets up custom annotations for go completions for registered flags.
//
// It takes a pointer to a Command and iterates over the flagCompletionFunctions map, adding custom annotations to each flag' s Annotations field. The custom annotation is a Bash completion command that calls the __*_go_custom_completion function for the specified flag, ensuring that the root command name is correctly set for the prefix of the completion script.
2020-04-03 15:43:43 -04:00
func prepareCustomAnnotationsForFlags( cmd *Command) {
2021-07-02 17:25:47 +02:00
flagCompletionMutex.RLock( )
defer flagCompletionMutex.RUnlock( )
for flag := range flagCompletionFunctions {
2020-04-03 15:43:43 -04:00
// Make sure the completion script calls the __*_go_custom_completion function for
// every registered flag. We need to do this here ( and not when the flag was registered
// for completion) so that we can know the root command name for the prefix
// of __<prefix>_go_custom_completion
if flag.Annotations = = nil {
flag.Annotations = map[ string] [ ] string{ }
}
flag.Annotations[ BashCompCustom] = [ ] string{ fmt.Sprintf( "__%[1]s_handle_go_custom_completion" , cmd.Root( ) .Name( ) ) }
}
}
2025-04-26 20:13:18 +00:00
// writeFlags writes the flags for a command to the provided buffer.
// It handles both local and inherited flags, excluding non-completable ones.
// If flag parsing is disabled, it sets the corresponding variable in the buffer.
2021-02-08 00:08:50 +00:00
func writeFlags( buf io.StringWriter, cmd *Command) {
2020-04-03 15:43:43 -04:00
prepareCustomAnnotationsForFlags( cmd)
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, ` flags = ( )
2015-03-16 15:31:03 -04:00
two_word_flags = ( )
2016-06-03 12:37:40 -04:00
local_nonpersistent_flags = ( )
2015-03-16 15:31:03 -04:00
flags_with_completion = ( )
flags_completion = ( )
` )
2021-11-01 15:01:33 -04:00
if cmd.DisableFlagParsing {
WriteStringAndCheck( buf, " flag_parsing_disabled=1\n" )
}
2016-06-03 12:37:40 -04:00
localNonPersistentFlags := cmd.LocalNonPersistentFlags( )
2015-03-16 15:31:03 -04:00
cmd.NonInheritedFlags( ) .VisitAll( func( flag *pflag.Flag) {
2016-08-02 15:01:33 -07:00
if nonCompletableFlag( flag) {
2016-08-02 14:49:33 -07:00
return
}
2018-02-08 21:34:46 +00:00
writeFlag( buf, flag, cmd)
2015-03-16 15:31:03 -04:00
if len( flag.Shorthand) > 0 {
2018-02-08 21:34:46 +00:00
writeShortFlag( buf, flag, cmd)
2015-03-16 15:31:03 -04:00
}
2020-09-09 17:34:51 +02:00
// localNonPersistentFlags are used to stop the completion of subcommands when one is set
// if TraverseChildren is true we should allow to complete subcommands
if localNonPersistentFlags.Lookup( flag.Name) != nil && !cmd.Root( ) .TraverseChildren {
2017-05-14 13:20:20 +02:00
writeLocalNonPersistentFlag( buf, flag)
2016-06-03 12:37:40 -04:00
}
2015-03-16 15:31:03 -04:00
} )
2015-11-04 14:40:11 -05:00
cmd.InheritedFlags( ) .VisitAll( func( flag *pflag.Flag) {
2016-08-02 15:01:33 -07:00
if nonCompletableFlag( flag) {
2016-08-02 14:49:33 -07:00
return
}
2018-02-08 21:34:46 +00:00
writeFlag( buf, flag, cmd)
2015-11-04 14:40:11 -05:00
if len( flag.Shorthand) > 0 {
2018-02-08 21:34:46 +00:00
writeShortFlag( buf, flag, cmd)
2015-11-04 14:40:11 -05:00
}
} )
2015-03-16 15:31:03 -04:00
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, "\n" )
2015-03-16 15:31:03 -04:00
}
2025-04-26 20:13:18 +00:00
// writeRequiredFlag appends to the buffer a line that specifies which flags are required for the command.
// It takes an io.StringWriter and a Command as parameters. The function checks each non-inherited flag of the command
// and adds it to the must_have_one_flag array if it is annotated with BashCompOneRequiredFlag. If the flag is not of type bool,
// it appends "=" after the flag name in the array.
// This function does not return any values, but it may write strings to the buffer and handle errors through WriteStringAndCheck.
// It also uses nonCompletableFlag to determine if a flag should be skipped.
2021-02-08 00:08:50 +00:00
func writeRequiredFlag( buf io.StringWriter, cmd *Command) {
WriteStringAndCheck( buf, " must_have_one_flag=()\n" )
2015-03-16 15:31:03 -04:00
flags := cmd.NonInheritedFlags( )
flags.VisitAll( func( flag *pflag.Flag) {
2016-08-02 15:01:33 -07:00
if nonCompletableFlag( flag) {
2016-08-02 14:49:33 -07:00
return
}
2024-04-01 12:42:08 +00:00
if _, ok := flag.Annotations[ BashCompOneRequiredFlag] ; ok {
format := " must_have_one_flag+=(\"--%s"
if flag.Value.Type( ) != "bool" {
format += "="
}
format += cbn
WriteStringAndCheck( buf, fmt.Sprintf( format, flag.Name) )
if len( flag.Shorthand) > 0 {
WriteStringAndCheck( buf, fmt.Sprintf( " must_have_one_flag+=(\"-%s" +cbn, flag.Shorthand) )
2015-03-16 15:31:03 -04:00
}
}
} )
}
2025-04-26 20:13:18 +00:00
// writeRequiredNouns writes the required nouns for a command to a buffer.
//
// It takes an io.StringWriter and a pointer to a Command as parameters. The Command should have valid arguments defined in ValidArgs or a function to provide them through ValidArgsFunction.
//
// This function sorts the valid arguments, removes any descriptions following a tab character ( not supported by bash completion) , and appends each noun to the buffer in the format required for command-line interface validation.
//
// If a ValidArgsFunction is provided, it also sets a flag indicating that a completion function is available.
2021-02-08 00:08:50 +00:00
func writeRequiredNouns( buf io.StringWriter, cmd *Command) {
WriteStringAndCheck( buf, " must_have_one_noun=()\n" )
sort.Strings( cmd.ValidArgs)
2015-03-16 15:31:03 -04:00
for _, value := range cmd.ValidArgs {
2020-04-10 15:56:28 -04:00
// Remove any description that may be included following a tab character.
// Descriptions are not supported by bash completion.
2023-11-23 19:24:33 +02:00
value = strings.SplitN( value, "\t" , 2) [ 0]
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( " must_have_one_noun+=(%q)\n" , value) )
2015-03-16 15:31:03 -04:00
}
2020-04-03 15:43:43 -04:00
if cmd.ValidArgsFunction != nil {
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, " has_completion_function=1\n" )
2020-04-03 15:43:43 -04:00
}
2015-03-16 15:31:03 -04:00
}
2025-04-26 20:13:18 +00:00
// writeCmdAliases writes the command aliases to the provided buffer.
//
// Parameters:
// - buf: An io.StringWriter where the aliases will be written.
// - cmd: A pointer to a Command struct containing the aliases to be written.
//
// Returns:
// - None
2021-02-08 00:08:50 +00:00
func writeCmdAliases( buf io.StringWriter, cmd *Command) {
2018-04-23 05:47:20 -07:00
if len( cmd.Aliases) = = 0 {
return
}
2021-02-08 00:08:50 +00:00
sort.Strings( cmd.Aliases)
2018-04-23 05:47:20 -07:00
2021-12-08 06:44:39 +08:00
WriteStringAndCheck( buf, fmt.Sprint( ` if [ [ -z " ${ BASH_VERSION :- } " || " ${ BASH_VERSINFO [0] :- } " -gt 3 ] ] ; then ` , "\n" ) )
2018-04-23 05:47:20 -07:00
for _, value := range cmd.Aliases {
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( " command_aliases+=(%q)\n" , value) )
WriteStringAndCheck( buf, fmt.Sprintf( " aliashash[%q]=%q\n" , value, cmd.Name( ) ) )
2018-04-23 05:47:20 -07:00
}
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, ` fi ` )
WriteStringAndCheck( buf, "\n" )
2018-04-23 05:47:20 -07:00
}
2025-04-26 20:13:18 +00:00
// writeArgAliases writes argument aliases for a command to the buffer and checks for errors.
//
// Parameters:
// - buf: The string writer where the alias information will be written.
// - cmd: The command whose aliases are being written.
//
// This function formats and appends argument aliases of the given command to the buffer,
// ensuring they are sorted alphabetically. It also handles any errors that occur during
// the writing process using WriteStringAndCheck.
2021-02-08 00:08:50 +00:00
func writeArgAliases( buf io.StringWriter, cmd *Command) {
WriteStringAndCheck( buf, " noun_aliases=()\n" )
sort.Strings( cmd.ArgAliases)
2016-03-25 16:05:56 +01:00
for _, value := range cmd.ArgAliases {
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( " noun_aliases+=(%q)\n" , value) )
2016-03-25 16:05:56 +01:00
}
}
2025-04-26 20:13:18 +00:00
// gen recursively generates a set of functions to call the provided command and its subcommands.
//
// Parameters:
// - buf: an io.StringWriter to write the generated functions to.
// - cmd: the current command to generate functions for .
2021-02-08 00:08:50 +00:00
func gen( buf io.StringWriter, cmd *Command) {
2015-03-16 15:31:03 -04:00
for _, c := range cmd.Commands( ) {
2020-06-16 16:49:26 -04:00
if !c.IsAvailableCommand( ) && c != cmd.helpCommand {
2015-04-07 17:38:22 -04:00
continue
}
2017-05-14 13:20:20 +02:00
gen( buf, c)
2015-03-16 15:31:03 -04:00
}
commandName := cmd.CommandPath( )
2022-05-14 22:10:36 +02:00
commandName = strings.ReplaceAll( commandName, " " , "_" )
commandName = strings.ReplaceAll( commandName, ":" , "__" )
2018-02-21 09:51:53 -08:00
if cmd.Root( ) = = cmd {
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( "_%s_root_command()\n{\n" , commandName) )
2018-02-21 09:51:53 -08:00
} else {
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( "_%s()\n{\n" , commandName) )
2018-02-21 09:51:53 -08:00
}
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, fmt.Sprintf( " last_command=%q\n" , commandName) )
WriteStringAndCheck( buf, "\n" )
WriteStringAndCheck( buf, " command_aliases=()\n" )
WriteStringAndCheck( buf, "\n" )
2018-04-23 05:47:20 -07:00
2017-05-14 13:20:20 +02:00
writeCommands( buf, cmd)
writeFlags( buf, cmd)
writeRequiredFlag( buf, cmd)
writeRequiredNouns( buf, cmd)
writeArgAliases( buf, cmd)
2021-02-08 00:08:50 +00:00
WriteStringAndCheck( buf, "}\n\n" )
2015-03-16 15:31:03 -04:00
}
2025-04-26 20:13:18 +00:00
// GenBashCompletion generates a bash completion file and writes it to the provided writer.
//
// Parameters:
// - w: io.Writer - The writer to which the bash completion file will be written.
//
// Returns:
// - error: If an error occurs during the generation or writing process, it is returned.
2017-09-05 13:20:51 -04:00
func ( c *Command) GenBashCompletion( w io.Writer) error {
2017-05-14 13:20:20 +02:00
buf := new( bytes.Buffer)
2017-09-05 13:20:51 -04:00
writePreamble( buf, c.Name( ) )
if len( c.BashCompletionFunction) > 0 {
buf.WriteString( c.BashCompletionFunction + "\n" )
2015-03-16 15:31:03 -04:00
}
2017-09-05 13:20:51 -04:00
gen( buf, c)
writePostscript( buf, c.Name( ) )
2017-05-14 13:20:20 +02:00
_, err := buf.WriteTo( w)
return err
2015-03-16 15:31:03 -04:00
}
2025-04-26 20:13:18 +00:00
// nonCompletableFlag checks if a flag is hidden or deprecated and returns true if it should not be completed.
2016-08-02 15:01:33 -07:00
func nonCompletableFlag( flag *pflag.Flag) bool {
return flag.Hidden || len( flag.Deprecated) > 0
}
2025-04-26 20:13:18 +00:00
// GenBashCompletionFile generates a bash completion file for the command.
//
// Parameters:
// filename - The path to the output bash completion file.
//
// Returns:
// error - An error if the file could not be created or written, nil otherwise.
2017-09-05 13:20:51 -04:00
func ( c *Command) GenBashCompletionFile( filename string) error {
2015-03-16 15:31:03 -04:00
outFile, err := os.Create( filename)
if err != nil {
return err
}
defer outFile.Close( )
2017-09-05 13:20:51 -04:00
return c.GenBashCompletion( outFile)
2015-03-16 15:31:03 -04:00
}