github.com/jenkins-x/jx/v2@v2.1.155/pkg/cmd/completion.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "io" 6 "strings" 7 8 v1 "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io/v1" 9 "github.com/jenkins-x/jx/v2/pkg/cmd/helper" 10 11 "github.com/jenkins-x/jx/v2/pkg/cmd/opts" 12 "github.com/jenkins-x/jx/v2/pkg/cmd/templates" 13 "github.com/spf13/cobra" 14 "gopkg.in/AlecAivazis/survey.v1" 15 ) 16 17 const boilerPlate = "" 18 19 var ( 20 completion_long = templates.LongDesc(` 21 Output shell completion code for the given shell (bash or zsh). 22 23 This command prints shell code which must be evaluation to provide interactive 24 completion of jx commands. 25 26 $ source <(jx completion bash) 27 28 will load the jx completion code for bash. Note that this depends on the 29 bash-completion framework. It must be sourced before sourcing the jx 30 completion, e.g. on the Mac: 31 32 $ brew install bash-completion 33 $ source $(brew --prefix)/etc/bash_completion 34 $ source <(jx completion bash) 35 36 On a Mac it often works better to generate a file with the completion and source that: 37 38 $ jx completion bash > ~/.jx/bash 39 $ source ~/.jx/bash 40 41 If you use zsh[1], the following will load jx zsh completion: 42 43 $ source <(jx completion zsh) 44 45 [1] zsh completions are only supported in versions of zsh >= 5.2`) 46 ) 47 48 var ( 49 completion_shells = map[string]func(out io.Writer, cmd *cobra.Command) error{ 50 "bash": runCompletionBash, 51 "zsh": runCompletionZsh, 52 } 53 // It is likely that the user has the completions for kubectl loaded, so reusing function from there if they exist 54 bashCompletionFunctions = ` 55 __jx_get_env() { 56 local jx_out 57 if jx_out=$(jx get env | tail -n +2 | cut -d' ' -f1 2>/dev/null); then 58 COMPREPLY=( $( compgen -W "${jx_out[*]}" -- "$cur" ) ) 59 fi 60 } 61 62 __jx_get_promotionstrategies() { 63 COMPREPLY=( $(compgen -W "` + strings.Join(v1.PromotionStrategyTypeValues, " ") + `" -- ${cur}) ) 64 } 65 66 __jx_custom_func() { 67 case ${last_command} in 68 jx_environment ) 69 __jx_get_env 70 return 71 ;; 72 jx_namespace ) 73 declare -f __kubectl_get_resource_namespace > /dev/null && __kubectl_get_resource_namespace 74 return 75 ;; 76 *) 77 ;; 78 esac 79 } 80 ` 81 ) 82 83 // CompletionOptions options for completion command 84 type CompletionOptions struct { 85 *opts.CommonOptions 86 } 87 88 func NewCmdCompletion(commonOpts *opts.CommonOptions) *cobra.Command { 89 options := &CompletionOptions{ 90 CommonOptions: commonOpts, 91 } 92 93 shells := []string{} 94 for s := range completion_shells { 95 shells = append(shells, s) 96 } 97 98 cmd := &cobra.Command{ 99 Use: "completion SHELL", 100 Short: "Output shell completion code for the given shell (bash or zsh)", 101 Long: completion_long, 102 Run: func(cmd *cobra.Command, args []string) { 103 options.Cmd = cmd 104 options.Args = args 105 err := options.Run() 106 helper.CheckErr(err) 107 }, 108 ValidArgs: shells, 109 } 110 111 return cmd 112 } 113 114 // Run executes the completion command 115 func (o *CompletionOptions) Run() error { 116 surveyOpts := survey.WithStdio(o.In, o.Out, o.Err) 117 shells := []string{} 118 for s := range completion_shells { 119 shells = append(shells, s) 120 } 121 var ShellName string 122 cmd := o.Cmd 123 args := o.Args 124 if len(args) == 0 { 125 prompts := &survey.Select{ 126 Message: "Shell", 127 Options: shells, 128 PageSize: len(shells), 129 Help: "The name of the shell", 130 } 131 err := survey.AskOne(prompts, &ShellName, nil, surveyOpts) 132 if err != nil { 133 return err 134 } 135 136 } 137 if len(args) > 1 { 138 return helper.UsageError(cmd, "Too many arguments. Expected only the shell type.") 139 } 140 if ShellName == "" { 141 ShellName = args[0] 142 } 143 144 run, found := completion_shells[ShellName] 145 146 if !found { 147 return helper.UsageError(cmd, "Unsupported shell type %q.", args[0]) 148 } 149 150 cmd.Parent().BashCompletionFunction = bashCompletionFunctions 151 152 return run(o.Out, cmd.Parent()) 153 } 154 155 func runCompletionBash(out io.Writer, cmd *cobra.Command) error { 156 if boilerPlate != "" { 157 _, err := out.Write([]byte(boilerPlate)) 158 if err != nil { 159 return err 160 } 161 } 162 return cmd.GenBashCompletion(out) 163 } 164 165 func runCompletionZsh(out io.Writer, cmd *cobra.Command) error { 166 zsh_head := "#compdef jx\n" 167 168 _, err := out.Write([]byte(zsh_head)) 169 if err != nil { 170 return err 171 } 172 173 if boilerPlate != "" { 174 _, err := out.Write([]byte(boilerPlate)) 175 if err != nil { 176 return err 177 } 178 } 179 zsh_initialization := ` 180 __jx_bash_source() { 181 alias shopt=':' 182 alias _expand=_bash_expand 183 alias _complete=_bash_comp 184 emulate -L sh 185 setopt kshglob noshglob braceexpand 186 source "$@" 187 } 188 __jx_type() { 189 # -t is not supported by zsh 190 if [ "$1" == "-t" ]; then 191 shift 192 # fake Bash 4 to disable "complete -o nospace". Instead 193 # "compopt +-o nospace" is used in the code to toggle trailing 194 # spaces. We don't support that, but leave trailing spaces on 195 # all the time 196 if [ "$1" = "__jx_compopt" ]; then 197 echo builtin 198 return 0 199 fi 200 fi 201 type "$@" 202 } 203 __jx_compgen() { 204 local completions w 205 completions=( $(compgen "$@") ) || return $? 206 # filter by given word as prefix 207 while [[ "$1" = -* && "$1" != -- ]]; do 208 shift 209 shift 210 done 211 if [[ "$1" == -- ]]; then 212 shift 213 fi 214 for w in "${completions[@]}"; do 215 if [[ "${w}" = "$1"* ]]; then 216 echo "${w}" 217 fi 218 done 219 } 220 __jx_compopt() { 221 true # don't do anything. Not supported by bashcompinit in zsh 222 } 223 __jx_ltrim_colon_completions() 224 { 225 if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then 226 # Remove colon-word prefix from COMPREPLY items 227 local colon_word=${1%${1##*:}} 228 local i=${#COMPREPLY[*]} 229 while [[ $((--i)) -ge 0 ]]; do 230 COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"} 231 done 232 fi 233 } 234 __jx_get_comp_words_by_ref() { 235 cur="${COMP_WORDS[COMP_CWORD]}" 236 prev="${COMP_WORDS[${COMP_CWORD}-1]}" 237 words=("${COMP_WORDS[@]}") 238 cword=("${COMP_CWORD[@]}") 239 } 240 __jx_filedir() { 241 local RET OLD_IFS w qw 242 __jx_debug "_filedir $@ cur=$cur" 243 if [[ "$1" = \~* ]]; then 244 # somehow does not work. Maybe, zsh does not call this at all 245 eval echo "$1" 246 return 0 247 fi 248 OLD_IFS="$IFS" 249 IFS=$'\n' 250 if [ "$1" = "-d" ]; then 251 shift 252 RET=( $(compgen -d) ) 253 else 254 RET=( $(compgen -f) ) 255 fi 256 IFS="$OLD_IFS" 257 IFS="," __jx_debug "RET=${RET[@]} len=${#RET[@]}" 258 for w in ${RET[@]}; do 259 if [[ ! "${w}" = "${cur}"* ]]; then 260 continue 261 fi 262 if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then 263 qw="$(__jx_quote "${w}")" 264 if [ -d "${w}" ]; then 265 COMPREPLY+=("${qw}/") 266 else 267 COMPREPLY+=("${qw}") 268 fi 269 fi 270 done 271 } 272 __jx_quote() { 273 if [[ $1 == \'* || $1 == \"* ]]; then 274 # Leave out first character 275 printf %q "${1:1}" 276 else 277 printf %q "$1" 278 fi 279 } 280 autoload -U +X bashcompinit && bashcompinit 281 # use word boundary patterns for BSD or GNU sed 282 LWORD='[[:<:]]' 283 RWORD='[[:>:]]' 284 if sed --help 2>&1 | grep -q GNU; then 285 LWORD='\<' 286 RWORD='\>' 287 fi 288 __jx_convert_bash_to_zsh() { 289 sed \ 290 -e 's/declare -F/whence -w/' \ 291 -e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \ 292 -e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \ 293 -e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \ 294 -e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \ 295 -e "s/${LWORD}_filedir${RWORD}/__jx_filedir/g" \ 296 -e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__jx_get_comp_words_by_ref/g" \ 297 -e "s/${LWORD}__ltrim_colon_completions${RWORD}/__jx_ltrim_colon_completions/g" \ 298 -e "s/${LWORD}compgen${RWORD}/__jx_compgen/g" \ 299 -e "s/${LWORD}compopt${RWORD}/__jx_compopt/g" \ 300 -e "s/${LWORD}declare${RWORD}/builtin declare/g" \ 301 -e "s/\\\$(type${RWORD}/\$(__jx_type/g" \ 302 <<'BASH_COMPLETION_EOF' 303 ` 304 _, err = out.Write([]byte(zsh_initialization)) 305 if err != nil { 306 return err 307 } 308 309 buf := new(bytes.Buffer) 310 err = cmd.GenBashCompletion(buf) 311 if err != nil { 312 return err 313 } 314 _, err = out.Write(buf.Bytes()) 315 if err != nil { 316 return err 317 } 318 319 zsh_tail := ` 320 BASH_COMPLETION_EOF 321 } 322 __jx_bash_source <(__jx_convert_bash_to_zsh) 323 _complete jx 2>/dev/null 324 ` 325 _, err = out.Write([]byte(zsh_tail)) 326 if err != nil { 327 return err 328 } 329 return nil 330 }