vendor/github.com/spf13/cobra/bash_completionsV2.go
changeset 256 6d9efbef00a9
child 260 445e01aede7e
equal deleted inserted replaced
255:4f153a23adab 256:6d9efbef00a9
       
     1 package cobra
       
     2 
       
     3 import (
       
     4 	"bytes"
       
     5 	"fmt"
       
     6 	"io"
       
     7 	"os"
       
     8 )
       
     9 
       
    10 func (c *Command) genBashCompletion(w io.Writer, includeDesc bool) error {
       
    11 	buf := new(bytes.Buffer)
       
    12 	genBashComp(buf, c.Name(), includeDesc)
       
    13 	_, err := buf.WriteTo(w)
       
    14 	return err
       
    15 }
       
    16 
       
    17 func genBashComp(buf io.StringWriter, name string, includeDesc bool) {
       
    18 	compCmd := ShellCompRequestCmd
       
    19 	if !includeDesc {
       
    20 		compCmd = ShellCompNoDescRequestCmd
       
    21 	}
       
    22 
       
    23 	WriteStringAndCheck(buf, fmt.Sprintf(`# bash completion V2 for %-36[1]s -*- shell-script -*-
       
    24 
       
    25 __%[1]s_debug()
       
    26 {
       
    27     if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then
       
    28         echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
       
    29     fi
       
    30 }
       
    31 
       
    32 # Macs have bash3 for which the bash-completion package doesn't include
       
    33 # _init_completion. This is a minimal version of that function.
       
    34 __%[1]s_init_completion()
       
    35 {
       
    36     COMPREPLY=()
       
    37     _get_comp_words_by_ref "$@" cur prev words cword
       
    38 }
       
    39 
       
    40 # This function calls the %[1]s program to obtain the completion
       
    41 # results and the directive.  It fills the 'out' and 'directive' vars.
       
    42 __%[1]s_get_completion_results() {
       
    43     local requestComp lastParam lastChar args
       
    44 
       
    45     # Prepare the command to request completions for the program.
       
    46     # Calling ${words[0]} instead of directly %[1]s allows to handle aliases
       
    47     args=("${words[@]:1}")
       
    48     requestComp="${words[0]} %[2]s ${args[*]}"
       
    49 
       
    50     lastParam=${words[$((${#words[@]}-1))]}
       
    51     lastChar=${lastParam:$((${#lastParam}-1)):1}
       
    52     __%[1]s_debug "lastParam ${lastParam}, lastChar ${lastChar}"
       
    53 
       
    54     if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then
       
    55         # If the last parameter is complete (there is a space following it)
       
    56         # We add an extra empty parameter so we can indicate this to the go method.
       
    57         __%[1]s_debug "Adding extra empty parameter"
       
    58         requestComp="${requestComp} ''"
       
    59     fi
       
    60 
       
    61     # When completing a flag with an = (e.g., %[1]s -n=<TAB>)
       
    62     # bash focuses on the part after the =, so we need to remove
       
    63     # the flag part from $cur
       
    64     if [[ "${cur}" == -*=* ]]; then
       
    65         cur="${cur#*=}"
       
    66     fi
       
    67 
       
    68     __%[1]s_debug "Calling ${requestComp}"
       
    69     # Use eval to handle any environment variables and such
       
    70     out=$(eval "${requestComp}" 2>/dev/null)
       
    71 
       
    72     # Extract the directive integer at the very end of the output following a colon (:)
       
    73     directive=${out##*:}
       
    74     # Remove the directive
       
    75     out=${out%%:*}
       
    76     if [ "${directive}" = "${out}" ]; then
       
    77         # There is not directive specified
       
    78         directive=0
       
    79     fi
       
    80     __%[1]s_debug "The completion directive is: ${directive}"
       
    81     __%[1]s_debug "The completions are: ${out[*]}"
       
    82 }
       
    83 
       
    84 __%[1]s_process_completion_results() {
       
    85     local shellCompDirectiveError=%[3]d
       
    86     local shellCompDirectiveNoSpace=%[4]d
       
    87     local shellCompDirectiveNoFileComp=%[5]d
       
    88     local shellCompDirectiveFilterFileExt=%[6]d
       
    89     local shellCompDirectiveFilterDirs=%[7]d
       
    90 
       
    91     if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
       
    92         # Error code.  No completion.
       
    93         __%[1]s_debug "Received error from custom completion go code"
       
    94         return
       
    95     else
       
    96         if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
       
    97             if [[ $(type -t compopt) = "builtin" ]]; then
       
    98                 __%[1]s_debug "Activating no space"
       
    99                 compopt -o nospace
       
   100             else
       
   101                 __%[1]s_debug "No space directive not supported in this version of bash"
       
   102             fi
       
   103         fi
       
   104         if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
       
   105             if [[ $(type -t compopt) = "builtin" ]]; then
       
   106                 __%[1]s_debug "Activating no file completion"
       
   107                 compopt +o default
       
   108             else
       
   109                 __%[1]s_debug "No file completion directive not supported in this version of bash"
       
   110             fi
       
   111         fi
       
   112     fi
       
   113 
       
   114     if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
       
   115         # File extension filtering
       
   116         local fullFilter filter filteringCmd
       
   117 
       
   118         # Do not use quotes around the $out variable or else newline
       
   119         # characters will be kept.
       
   120         for filter in ${out[*]}; do
       
   121             fullFilter+="$filter|"
       
   122         done
       
   123 
       
   124         filteringCmd="_filedir $fullFilter"
       
   125         __%[1]s_debug "File filtering command: $filteringCmd"
       
   126         $filteringCmd
       
   127     elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
       
   128         # File completion for directories only
       
   129 
       
   130         # Use printf to strip any trailing newline
       
   131         local subdir
       
   132         subdir=$(printf "%%s" "${out[0]}")
       
   133         if [ -n "$subdir" ]; then
       
   134             __%[1]s_debug "Listing directories in $subdir"
       
   135             pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
       
   136         else
       
   137             __%[1]s_debug "Listing directories in ."
       
   138             _filedir -d
       
   139         fi
       
   140     else
       
   141         __%[1]s_handle_standard_completion_case
       
   142     fi
       
   143 
       
   144     __%[1]s_handle_special_char "$cur" :
       
   145     __%[1]s_handle_special_char "$cur" =
       
   146 }
       
   147 
       
   148 __%[1]s_handle_standard_completion_case() {
       
   149     local tab comp
       
   150     tab=$(printf '\t')
       
   151 
       
   152     local longest=0
       
   153     # Look for the longest completion so that we can format things nicely
       
   154     while IFS='' read -r comp; do
       
   155         # Strip any description before checking the length
       
   156         comp=${comp%%%%$tab*}
       
   157         # Only consider the completions that match
       
   158         comp=$(compgen -W "$comp" -- "$cur")
       
   159         if ((${#comp}>longest)); then
       
   160             longest=${#comp}
       
   161         fi
       
   162     done < <(printf "%%s\n" "${out[@]}")
       
   163 
       
   164     local completions=()
       
   165     while IFS='' read -r comp; do
       
   166         if [ -z "$comp" ]; then
       
   167             continue
       
   168         fi
       
   169 
       
   170         __%[1]s_debug "Original comp: $comp"
       
   171         comp="$(__%[1]s_format_comp_descriptions "$comp" "$longest")"
       
   172         __%[1]s_debug "Final comp: $comp"
       
   173         completions+=("$comp")
       
   174     done < <(printf "%%s\n" "${out[@]}")
       
   175 
       
   176     while IFS='' read -r comp; do
       
   177         COMPREPLY+=("$comp")
       
   178     done < <(compgen -W "${completions[*]}" -- "$cur")
       
   179 
       
   180     # If there is a single completion left, remove the description text
       
   181     if [ ${#COMPREPLY[*]} -eq 1 ]; then
       
   182         __%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
       
   183         comp="${COMPREPLY[0]%%%% *}"
       
   184         __%[1]s_debug "Removed description from single completion, which is now: ${comp}"
       
   185         COMPREPLY=()
       
   186         COMPREPLY+=("$comp")
       
   187     fi
       
   188 }
       
   189 
       
   190 __%[1]s_handle_special_char()
       
   191 {
       
   192     local comp="$1"
       
   193     local char=$2
       
   194     if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then
       
   195         local word=${comp%%"${comp##*${char}}"}
       
   196         local idx=${#COMPREPLY[*]}
       
   197         while [[ $((--idx)) -ge 0 ]]; do
       
   198             COMPREPLY[$idx]=${COMPREPLY[$idx]#"$word"}
       
   199         done
       
   200     fi
       
   201 }
       
   202 
       
   203 __%[1]s_format_comp_descriptions()
       
   204 {
       
   205     local tab
       
   206     tab=$(printf '\t')
       
   207     local comp="$1"
       
   208     local longest=$2
       
   209 
       
   210     # Properly format the description string which follows a tab character if there is one
       
   211     if [[ "$comp" == *$tab* ]]; then
       
   212         desc=${comp#*$tab}
       
   213         comp=${comp%%%%$tab*}
       
   214 
       
   215         # $COLUMNS stores the current shell width.
       
   216         # Remove an extra 4 because we add 2 spaces and 2 parentheses.
       
   217         maxdesclength=$(( COLUMNS - longest - 4 ))
       
   218 
       
   219         # Make sure we can fit a description of at least 8 characters
       
   220         # if we are to align the descriptions.
       
   221         if [[ $maxdesclength -gt 8 ]]; then
       
   222             # Add the proper number of spaces to align the descriptions
       
   223             for ((i = ${#comp} ; i < longest ; i++)); do
       
   224                 comp+=" "
       
   225             done
       
   226         else
       
   227             # Don't pad the descriptions so we can fit more text after the completion
       
   228             maxdesclength=$(( COLUMNS - ${#comp} - 4 ))
       
   229         fi
       
   230 
       
   231         # If there is enough space for any description text,
       
   232         # truncate the descriptions that are too long for the shell width
       
   233         if [ $maxdesclength -gt 0 ]; then
       
   234             if [ ${#desc} -gt $maxdesclength ]; then
       
   235                 desc=${desc:0:$(( maxdesclength - 1 ))}
       
   236                 desc+="…"
       
   237             fi
       
   238             comp+="  ($desc)"
       
   239         fi
       
   240     fi
       
   241 
       
   242     # Must use printf to escape all special characters
       
   243     printf "%%q" "${comp}"
       
   244 }
       
   245 
       
   246 __start_%[1]s()
       
   247 {
       
   248     local cur prev words cword split
       
   249 
       
   250     COMPREPLY=()
       
   251 
       
   252     # Call _init_completion from the bash-completion package
       
   253     # to prepare the arguments properly
       
   254     if declare -F _init_completion >/dev/null 2>&1; then
       
   255         _init_completion -n "=:" || return
       
   256     else
       
   257         __%[1]s_init_completion -n "=:" || return
       
   258     fi
       
   259 
       
   260     __%[1]s_debug
       
   261     __%[1]s_debug "========= starting completion logic =========="
       
   262     __%[1]s_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword"
       
   263 
       
   264     # The user could have moved the cursor backwards on the command-line.
       
   265     # We need to trigger completion from the $cword location, so we need
       
   266     # to truncate the command-line ($words) up to the $cword location.
       
   267     words=("${words[@]:0:$cword+1}")
       
   268     __%[1]s_debug "Truncated words[*]: ${words[*]},"
       
   269 
       
   270     local out directive
       
   271     __%[1]s_get_completion_results
       
   272     __%[1]s_process_completion_results
       
   273 }
       
   274 
       
   275 if [[ $(type -t compopt) = "builtin" ]]; then
       
   276     complete -o default -F __start_%[1]s %[1]s
       
   277 else
       
   278     complete -o default -o nospace -F __start_%[1]s %[1]s
       
   279 fi
       
   280 
       
   281 # ex: ts=4 sw=4 et filetype=sh
       
   282 `, name, compCmd,
       
   283 		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
       
   284 		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
       
   285 }
       
   286 
       
   287 // GenBashCompletionFileV2 generates Bash completion version 2.
       
   288 func (c *Command) GenBashCompletionFileV2(filename string, includeDesc bool) error {
       
   289 	outFile, err := os.Create(filename)
       
   290 	if err != nil {
       
   291 		return err
       
   292 	}
       
   293 	defer outFile.Close()
       
   294 
       
   295 	return c.GenBashCompletionV2(outFile, includeDesc)
       
   296 }
       
   297 
       
   298 // GenBashCompletionV2 generates Bash completion file version 2
       
   299 // and writes it to the passed writer.
       
   300 func (c *Command) GenBashCompletionV2(w io.Writer, includeDesc bool) error {
       
   301 	return c.genBashCompletion(w, includeDesc)
       
   302 }