Skip to content

Commit edc83ff

Browse files
Merge pull request openshift#455 from mjlshen/OSD-19092-complete
Use built-in functions to generate osdctl completion for bash, zsh, and fish
2 parents e7a1eb0 + 5f4bb83 commit edc83ff

File tree

2 files changed

+19
-182
lines changed

2 files changed

+19
-182
lines changed

cmd/cmd.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func NewCmdRoot(streams genericclioptions.IOStreams) *cobra.Command {
8686
rootCmd.AddCommand(account.NewCmdAccount(streams, kubeFlags, kubeClient, globalOpts))
8787
rootCmd.AddCommand(cluster.NewCmdCluster(streams, kubeFlags, kubeClient, globalOpts))
8888
rootCmd.AddCommand(clusterdeployment.NewCmdClusterDeployment(streams, kubeFlags, kubeClient))
89+
rootCmd.AddCommand(newCmdCompletion())
8990
rootCmd.AddCommand(env.NewCmdEnv(streams, kubeFlags))
9091
rootCmd.AddCommand(federatedrole.NewCmdFederatedRole(streams, kubeFlags, kubeClient))
9192
rootCmd.AddCommand(jumphost.NewCmdJumphost())
@@ -96,9 +97,6 @@ func NewCmdRoot(streams genericclioptions.IOStreams) *cobra.Command {
9697
rootCmd.AddCommand(promote.NewCmdPromote(kubeFlags, globalOpts))
9798
rootCmd.AddCommand(jira.Cmd)
9899

99-
// add completion command
100-
rootCmd.AddCommand(newCmdCompletion(streams))
101-
102100
// add options command to list global flags
103101
rootCmd.AddCommand(newCmdOptions(streams))
104102

cmd/completion.go

Lines changed: 18 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
package cmd
22

33
import (
4-
"bytes"
5-
"io"
6-
74
"github.com/spf13/cobra"
8-
9-
"k8s.io/cli-runtime/pkg/genericclioptions"
105
cmdutil "k8s.io/kubectl/pkg/cmd/util"
116
"k8s.io/kubectl/pkg/util/templates"
127
)
@@ -41,183 +36,27 @@ var (
4136
)
4237

4338
// newCmdCompletion creates the `completion` command
44-
func newCmdCompletion(streams genericclioptions.IOStreams) *cobra.Command {
45-
ops := newCompletionOptions(streams)
39+
func newCmdCompletion() *cobra.Command {
4640
cmd := &cobra.Command{
47-
Use: "completion SHELL",
48-
Short: "Output shell completion code for the specified shell (bash or zsh)",
49-
Example: completionExample,
50-
DisableAutoGenTag: true,
51-
DisableFlagsInUseLine: true,
52-
Run: func(cmd *cobra.Command, args []string) {
53-
cmdutil.CheckErr(ops.run(cmd, args))
41+
Use: "completion SHELL",
42+
Short: "Output shell completion code for the specified shell (bash or zsh)",
43+
Example: completionExample,
44+
DisableAutoGenTag: true,
45+
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
46+
ValidArgs: []string{"bash", "zsh", "fish"},
47+
RunE: func(cmd *cobra.Command, args []string) error {
48+
switch args[0] {
49+
case "bash":
50+
return cmd.Parent().GenBashCompletion(cmd.OutOrStdout())
51+
case "zsh":
52+
return cmd.Root().GenZshCompletion(cmd.OutOrStdout())
53+
case "fish":
54+
return cmd.Root().GenFishCompletion(cmd.OutOrStdout(), true)
55+
default:
56+
return cmdutil.UsageErrorf(cmd, "Unsupported shell type %q.", args[0])
57+
}
5458
},
5559
}
5660

5761
return cmd
5862
}
59-
60-
type completionOptions struct {
61-
supportedShells []string
62-
63-
genericclioptions.IOStreams
64-
}
65-
66-
func newCompletionOptions(streams genericclioptions.IOStreams) *completionOptions {
67-
return &completionOptions{
68-
IOStreams: streams,
69-
}
70-
}
71-
72-
func (o *completionOptions) run(cmd *cobra.Command, args []string) error {
73-
if len(args) == 0 {
74-
return cmdutil.UsageErrorf(cmd, "Shell not specified.")
75-
}
76-
if len(args) > 1 {
77-
return cmdutil.UsageErrorf(cmd, "Too many arguments. Expected only the shell type.")
78-
}
79-
switch args[0] {
80-
case "bash":
81-
return cmd.Parent().GenBashCompletion(o.Out)
82-
case "zsh":
83-
return genCompletionZsh(o.Out, cmd.Parent())
84-
default:
85-
return cmdutil.UsageErrorf(cmd, "Unsupported shell type %q.", args[0])
86-
}
87-
}
88-
89-
// Followed the trick https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/completion/completion.go#L145.
90-
func genCompletionZsh(out io.Writer, osdctl *cobra.Command) error {
91-
zshHead := "#compdef osdctl\n"
92-
_, err := out.Write([]byte(zshHead))
93-
if err != nil {
94-
return err
95-
}
96-
97-
zshInitialization := `
98-
__osdctl_bash_source() {
99-
alias shopt=':'
100-
emulate -L sh
101-
setopt kshglob noshglob braceexpand
102-
103-
source "$@"
104-
}
105-
106-
__osdctl_type() {
107-
# -t is not supported by zsh
108-
if [ "$1" == "-t" ]; then
109-
shift
110-
111-
# fake Bash 4 to disable "complete -o nospace". Instead
112-
# "compopt +-o nospace" is used in the code to toggle trailing
113-
# spaces. We don't support that, but leave trailing spaces on
114-
# all the time
115-
if [ "$1" = "__osdctl_compopt" ]; then
116-
echo builtin
117-
return 0
118-
fi
119-
fi
120-
type "$@"
121-
}
122-
123-
__osdctl_compgen() {
124-
local completions w
125-
completions=( $(compgen "$@") ) || return $?
126-
127-
# filter by given word as prefix
128-
while [[ "$1" = -* && "$1" != -- ]]; do
129-
shift
130-
shift
131-
done
132-
if [[ "$1" == -- ]]; then
133-
shift
134-
fi
135-
for w in "${completions[@]}"; do
136-
if [[ "${w}" = "$1"* ]]; then
137-
echo "${w}"
138-
fi
139-
done
140-
}
141-
142-
__osdctl_compopt() {
143-
true # don't do anything. Not supported by bashcompinit in zsh
144-
}
145-
146-
__osdctl_ltrim_colon_completions()
147-
{
148-
if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
149-
# Remove colon-word prefix from COMPREPLY items
150-
local colon_word=${1%${1##*:}}
151-
local i=${#COMPREPLY[*]}
152-
while [[ $((--i)) -ge 0 ]]; do
153-
COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
154-
done
155-
fi
156-
}
157-
158-
__osdctl_get_comp_words_by_ref() {
159-
cur="${COMP_WORDS[COMP_CWORD]}"
160-
prev="${COMP_WORDS[${COMP_CWORD}-1]}"
161-
words=("${COMP_WORDS[@]}")
162-
cword=("${COMP_CWORD[@]}")
163-
}
164-
165-
__osdctl_filedir() {
166-
# Don't need to do anything here.
167-
# Otherwise we will get trailing space without "compopt -o nospace"
168-
true
169-
}
170-
171-
autoload -U +X bashcompinit && bashcompinit
172-
173-
# use word boundary patterns for BSD or GNU sed
174-
LWORD='[[:<:]]'
175-
RWORD='[[:>:]]'
176-
if sed --help 2>&1 | grep -q 'GNU\|BusyBox'; then
177-
LWORD='\<'
178-
RWORD='\>'
179-
fi
180-
181-
__osdctl_convert_bash_to_zsh() {
182-
sed \
183-
-e 's/declare -F/whence -w/' \
184-
-e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \
185-
-e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \
186-
-e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \
187-
-e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \
188-
-e "s/${LWORD}_filedir${RWORD}/__osdctl_filedir/g" \
189-
-e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__osdctl_get_comp_words_by_ref/g" \
190-
-e "s/${LWORD}__ltrim_colon_completions${RWORD}/__osdctl_ltrim_colon_completions/g" \
191-
-e "s/${LWORD}compgen${RWORD}/__osdctl_compgen/g" \
192-
-e "s/${LWORD}compopt${RWORD}/__osdctl_compopt/g" \
193-
-e "s/${LWORD}declare${RWORD}/builtin declare/g" \
194-
-e "s/\\\$(type${RWORD}/\$(__osdctl_type/g" \
195-
<<'BASH_COMPLETION_EOF'
196-
`
197-
_, err = out.Write([]byte(zshInitialization))
198-
if err != nil {
199-
return err
200-
}
201-
202-
buf := new(bytes.Buffer)
203-
err = osdctl.GenBashCompletion(buf)
204-
if err != nil {
205-
return err
206-
}
207-
_, err = out.Write(buf.Bytes())
208-
if err != nil {
209-
return err
210-
}
211-
212-
zshTail := `
213-
BASH_COMPLETION_EOF
214-
}
215-
216-
__osdctl_bash_source <(__osdctl_convert_bash_to_zsh)
217-
`
218-
_, err = out.Write([]byte(zshTail))
219-
if err != nil {
220-
return err
221-
}
222-
return nil
223-
}

0 commit comments

Comments
 (0)