github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/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/olli-ai/jx/v2/pkg/cmd/helper"
    10  
    11  	"github.com/olli-ai/jx/v2/pkg/cmd/opts"
    12  	"github.com/olli-ai/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  }