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 }