github.com/codefresh-io/kcfi@v0.0.0-20230301195427-c1578715cc46/cmd/kcfi/completion.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package main
    17  
    18  import (
    19  	"fmt"
    20  	"io"
    21  	"os"
    22  	"path/filepath"
    23  
    24  	"github.com/pkg/errors"
    25  	"github.com/spf13/cobra"
    26  )
    27  
    28  const completionDesc = `
    29  Generate autocompletions script for Helm for the specified shell (bash or zsh).
    30  
    31  This command can generate shell autocompletions. e.g.
    32  
    33      $ helm completion bash
    34  
    35  Can be sourced as such
    36  
    37      $ source <(helm completion bash)
    38  `
    39  
    40  var (
    41  	completionShells = map[string]func(out io.Writer, cmd *cobra.Command) error{
    42  		"bash": runCompletionBash,
    43  		"zsh":  runCompletionZsh,
    44  	}
    45  )
    46  
    47  func newCompletionCmd(out io.Writer) *cobra.Command {
    48  	shells := []string{}
    49  	for s := range completionShells {
    50  		shells = append(shells, s)
    51  	}
    52  
    53  	cmd := &cobra.Command{
    54  		Use:   "completion SHELL",
    55  		Short: "generate autocompletions script for the specified shell (bash or zsh)",
    56  		Long:  completionDesc,
    57  		RunE: func(cmd *cobra.Command, args []string) error {
    58  			return runCompletion(out, cmd, args)
    59  		},
    60  		ValidArgs: shells,
    61  	}
    62  
    63  	return cmd
    64  }
    65  
    66  func runCompletion(out io.Writer, cmd *cobra.Command, args []string) error {
    67  	if len(args) == 0 {
    68  		return errors.New("shell not specified")
    69  	}
    70  	if len(args) > 1 {
    71  		return errors.New("too many arguments, expected only the shell type")
    72  	}
    73  	run, found := completionShells[args[0]]
    74  	if !found {
    75  		return errors.Errorf("unsupported shell type %q", args[0])
    76  	}
    77  
    78  	return run(out, cmd)
    79  }
    80  
    81  func runCompletionBash(out io.Writer, cmd *cobra.Command) error {
    82  	err := cmd.Root().GenBashCompletion(out)
    83  
    84  	// In case the user renamed the helm binary (e.g., to be able to run
    85  	// both helm2 and helm3), we hook the new binary name to the completion function
    86  	if binary := filepath.Base(os.Args[0]); binary != "helm" {
    87  		renamedBinaryHook := `
    88  # Hook the command used to generate the completion script
    89  # to the helm completion function to handle the case where
    90  # the user renamed the helm binary
    91  if [[ $(type -t compopt) = "builtin" ]]; then
    92      complete -o default -F __start_helm %[1]s
    93  else
    94      complete -o default -o nospace -F __start_helm %[1]s
    95  fi
    96  `
    97  		fmt.Fprintf(out, renamedBinaryHook, binary)
    98  	}
    99  
   100  	return err
   101  }
   102  
   103  func runCompletionZsh(out io.Writer, cmd *cobra.Command) error {
   104  	zshInitialization := `#compdef helm
   105  
   106  __helm_bash_source() {
   107  	alias shopt=':'
   108  	alias _expand=_bash_expand
   109  	alias _complete=_bash_comp
   110  	emulate -L sh
   111  	setopt kshglob noshglob braceexpand
   112  	source "$@"
   113  }
   114  __helm_type() {
   115  	# -t is not supported by zsh
   116  	if [ "$1" == "-t" ]; then
   117  		shift
   118  		# fake Bash 4 to disable "complete -o nospace". Instead
   119  		# "compopt +-o nospace" is used in the code to toggle trailing
   120  		# spaces. We don't support that, but leave trailing spaces on
   121  		# all the time
   122  		if [ "$1" = "__helm_compopt" ]; then
   123  			echo builtin
   124  			return 0
   125  		fi
   126  	fi
   127  	type "$@"
   128  }
   129  __helm_compgen() {
   130  	local completions w
   131  	completions=( $(compgen "$@") ) || return $?
   132  	# filter by given word as prefix
   133  	while [[ "$1" = -* && "$1" != -- ]]; do
   134  		shift
   135  		shift
   136  	done
   137  	if [[ "$1" == -- ]]; then
   138  		shift
   139  	fi
   140  	for w in "${completions[@]}"; do
   141  		if [[ "${w}" = "$1"* ]]; then
   142  			# Use printf instead of echo beause it is possible that
   143  			# the value to print is -n, which would be interpreted
   144  			# as a flag to echo
   145  			printf "%s\n" "${w}"
   146  		fi
   147  	done
   148  }
   149  __helm_compopt() {
   150  	true # don't do anything. Not supported by bashcompinit in zsh
   151  }
   152  __helm_ltrim_colon_completions()
   153  {
   154  	if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
   155  		# Remove colon-word prefix from COMPREPLY items
   156  		local colon_word=${1%${1##*:}}
   157  		local i=${#COMPREPLY[*]}
   158  		while [[ $((--i)) -ge 0 ]]; do
   159  			COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
   160  		done
   161  	fi
   162  }
   163  __helm_get_comp_words_by_ref() {
   164  	cur="${COMP_WORDS[COMP_CWORD]}"
   165  	prev="${COMP_WORDS[${COMP_CWORD}-1]}"
   166  	words=("${COMP_WORDS[@]}")
   167  	cword=("${COMP_CWORD[@]}")
   168  }
   169  __helm_filedir() {
   170  	local RET OLD_IFS w qw
   171  	__debug "_filedir $@ cur=$cur"
   172  	if [[ "$1" = \~* ]]; then
   173  		# somehow does not work. Maybe, zsh does not call this at all
   174  		eval echo "$1"
   175  		return 0
   176  	fi
   177  	OLD_IFS="$IFS"
   178  	IFS=$'\n'
   179  	if [ "$1" = "-d" ]; then
   180  		shift
   181  		RET=( $(compgen -d) )
   182  	else
   183  		RET=( $(compgen -f) )
   184  	fi
   185  	IFS="$OLD_IFS"
   186  	IFS="," __debug "RET=${RET[@]} len=${#RET[@]}"
   187  	for w in ${RET[@]}; do
   188  		if [[ ! "${w}" = "${cur}"* ]]; then
   189  			continue
   190  		fi
   191  		if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then
   192  			qw="$(__helm_quote "${w}")"
   193  			if [ -d "${w}" ]; then
   194  				COMPREPLY+=("${qw}/")
   195  			else
   196  				COMPREPLY+=("${qw}")
   197  			fi
   198  		fi
   199  	done
   200  }
   201  __helm_quote() {
   202  	if [[ $1 == \'* || $1 == \"* ]]; then
   203  		# Leave out first character
   204  		printf %q "${1:1}"
   205  	else
   206  		printf %q "$1"
   207  	fi
   208  }
   209  autoload -U +X bashcompinit && bashcompinit
   210  # use word boundary patterns for BSD or GNU sed
   211  LWORD='[[:<:]]'
   212  RWORD='[[:>:]]'
   213  if sed --help 2>&1 | grep -q 'GNU\|BusyBox'; then
   214  	LWORD='\<'
   215  	RWORD='\>'
   216  fi
   217  __helm_convert_bash_to_zsh() {
   218  	sed \
   219  	-e 's/declare -F/whence -w/' \
   220  	-e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \
   221  	-e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \
   222  	-e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \
   223  	-e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \
   224  	-e "s/${LWORD}_filedir${RWORD}/__helm_filedir/g" \
   225  	-e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__helm_get_comp_words_by_ref/g" \
   226  	-e "s/${LWORD}__ltrim_colon_completions${RWORD}/__helm_ltrim_colon_completions/g" \
   227  	-e "s/${LWORD}compgen${RWORD}/__helm_compgen/g" \
   228  	-e "s/${LWORD}compopt${RWORD}/__helm_compopt/g" \
   229  	-e "s/${LWORD}declare${RWORD}/builtin declare/g" \
   230  	-e "s/\\\$(type${RWORD}/\$(__helm_type/g" \
   231  	-e 's/aliashash\["\(.\{1,\}\)"\]/aliashash[\1]/g' \
   232  	-e 's/FUNCNAME/funcstack/g' \
   233  	<<'BASH_COMPLETION_EOF'
   234  `
   235  	out.Write([]byte(zshInitialization))
   236  
   237  	runCompletionBash(out, cmd)
   238  
   239  	zshTail := `
   240  BASH_COMPLETION_EOF
   241  }
   242  __helm_bash_source <(__helm_convert_bash_to_zsh)
   243  `
   244  	out.Write([]byte(zshTail))
   245  	return nil
   246  }