github.com/oam-dev/kubevela@v1.9.11/references/cli/completion.go (about)

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