|
1 // Copyright © 2017 Mikael Berthe <mikael@lilotux.net> |
|
2 // |
|
3 // Licensed under the MIT license. |
|
4 // Please see the LICENSE file is this directory. |
|
5 |
|
6 package cmd |
|
7 |
|
8 import ( |
|
9 "bytes" |
|
10 "io" |
|
11 "os" |
|
12 "strings" |
|
13 |
|
14 "github.com/spf13/cobra" |
|
15 ) |
|
16 |
|
17 var completionCmd = &cobra.Command{ |
|
18 Use: "completion bash|zsh", |
|
19 Short: "Generate shell completion", |
|
20 ValidArgs: []string{"bash", "zsh"}, |
|
21 Run: func(cmd *cobra.Command, args []string) { |
|
22 if len(args) < 1 { |
|
23 errPrint("Please specify your shell") |
|
24 os.Exit(1) |
|
25 } |
|
26 |
|
27 switch args[0] { |
|
28 case "bash": |
|
29 if err := runCompletionBash(os.Stdout, RootCmd); err != nil { |
|
30 errPrint("Error: %s", err.Error()) |
|
31 os.Exit(1) |
|
32 } |
|
33 case "zsh": |
|
34 if err := runCompletionZsh(os.Stdout, RootCmd); err != nil { |
|
35 errPrint("Error: %s", err.Error()) |
|
36 os.Exit(1) |
|
37 } |
|
38 default: |
|
39 errPrint("Only bash is supported at the moment") |
|
40 os.Exit(1) |
|
41 } |
|
42 }, |
|
43 } |
|
44 |
|
45 func init() { |
|
46 RootCmd.AddCommand(completionCmd) |
|
47 } |
|
48 |
|
49 func runCompletionBash(out io.Writer, c *cobra.Command) error { |
|
50 return c.GenBashCompletion(out) |
|
51 } |
|
52 |
|
53 // Many thanks to the Kubernetes project for this one! |
|
54 func runCompletionZsh(out io.Writer, c *cobra.Command) error { |
|
55 const zshInitialization = `# Copyright 2016 The Kubernetes Authors. |
|
56 # |
|
57 # Licensed under the Apache License, Version 2.0 (the "License"); |
|
58 # you may not use this file except in compliance with the License. |
|
59 # You may obtain a copy of the License at |
|
60 # |
|
61 # http://www.apache.org/licenses/LICENSE-2.0 |
|
62 # |
|
63 # Unless required by applicable law or agreed to in writing, software |
|
64 # distributed under the License is distributed on an "AS IS" BASIS, |
|
65 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
66 # See the License for the specific language governing permissions and |
|
67 # limitations under the License. |
|
68 |
|
69 __madonctl_bash_source() { |
|
70 alias shopt=':' |
|
71 alias _expand=_bash_expand |
|
72 alias _complete=_bash_comp |
|
73 emulate -L sh |
|
74 setopt kshglob noshglob braceexpand |
|
75 |
|
76 source "$@" |
|
77 } |
|
78 |
|
79 __madonctl_type() { |
|
80 # -t is not supported by zsh |
|
81 if [ "$1" == "-t" ]; then |
|
82 shift |
|
83 |
|
84 # fake Bash 4 to disable "complete -o nospace". Instead |
|
85 # "compopt +-o nospace" is used in the code to toggle trailing |
|
86 # spaces. We don't support that, but leave trailing spaces on |
|
87 # all the time |
|
88 if [ "$1" = "__madonctl_compopt" ]; then |
|
89 echo builtin |
|
90 return 0 |
|
91 fi |
|
92 fi |
|
93 type "$@" |
|
94 } |
|
95 |
|
96 __madonctl_compgen() { |
|
97 local completions w |
|
98 completions=( $(compgen "$@") ) || return $? |
|
99 |
|
100 # filter by given word as prefix |
|
101 while [[ "$1" = -* && "$1" != -- ]]; do |
|
102 shift |
|
103 shift |
|
104 done |
|
105 if [[ "$1" == -- ]]; then |
|
106 shift |
|
107 fi |
|
108 for w in "${completions[@]}"; do |
|
109 if [[ "${w}" = "$1"* ]]; then |
|
110 echo "${w}" |
|
111 fi |
|
112 done |
|
113 } |
|
114 |
|
115 __madonctl_compopt() { |
|
116 true # don't do anything. Not supported by bashcompinit in zsh |
|
117 } |
|
118 |
|
119 __madonctl_declare() { |
|
120 if [ "$1" == "-F" ]; then |
|
121 whence -w "$@" |
|
122 else |
|
123 builtin declare "$@" |
|
124 fi |
|
125 } |
|
126 |
|
127 __madonctl_ltrim_colon_completions() |
|
128 { |
|
129 if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then |
|
130 # Remove colon-word prefix from COMPREPLY items |
|
131 local colon_word=${1%${1##*:}} |
|
132 local i=${#COMPREPLY[*]} |
|
133 while [[ $((--i)) -ge 0 ]]; do |
|
134 COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"} |
|
135 done |
|
136 fi |
|
137 } |
|
138 |
|
139 __madonctl_get_comp_words_by_ref() { |
|
140 cur="${COMP_WORDS[COMP_CWORD]}" |
|
141 prev="${COMP_WORDS[${COMP_CWORD}-1]}" |
|
142 words=("${COMP_WORDS[@]}") |
|
143 cword=("${COMP_CWORD[@]}") |
|
144 } |
|
145 |
|
146 __madonctl_filedir() { |
|
147 local RET OLD_IFS w qw |
|
148 |
|
149 __debug "_filedir $@ cur=$cur" |
|
150 if [[ "$1" = \~* ]]; then |
|
151 # somehow does not work. Maybe, zsh does not call this at all |
|
152 eval echo "$1" |
|
153 return 0 |
|
154 fi |
|
155 |
|
156 OLD_IFS="$IFS" |
|
157 IFS=$'\n' |
|
158 if [ "$1" = "-d" ]; then |
|
159 shift |
|
160 RET=( $(compgen -d) ) |
|
161 else |
|
162 RET=( $(compgen -f) ) |
|
163 fi |
|
164 IFS="$OLD_IFS" |
|
165 |
|
166 IFS="," __debug "RET=${RET[@]} len=${#RET[@]}" |
|
167 |
|
168 for w in ${RET[@]}; do |
|
169 if [[ ! "${w}" = "${cur}"* ]]; then |
|
170 continue |
|
171 fi |
|
172 if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then |
|
173 qw="$(__madonctl_quote "${w}")" |
|
174 if [ -d "${w}" ]; then |
|
175 COMPREPLY+=("${qw}/") |
|
176 else |
|
177 COMPREPLY+=("${qw}") |
|
178 fi |
|
179 fi |
|
180 done |
|
181 } |
|
182 |
|
183 __madonctl_quote() { |
|
184 if [[ $1 == \'* || $1 == \"* ]]; then |
|
185 # Leave out first character |
|
186 printf %q "${1:1}" |
|
187 else |
|
188 printf %q "$1" |
|
189 fi |
|
190 } |
|
191 |
|
192 autoload -U +X bashcompinit && bashcompinit |
|
193 |
|
194 # use word boundary patterns for BSD or GNU sed |
|
195 LWORD='[[:<:]]' |
|
196 RWORD='[[:>:]]' |
|
197 if sed --help 2>&1 | grep -q GNU; then |
|
198 LWORD='\<' |
|
199 RWORD='\>' |
|
200 fi |
|
201 |
|
202 __madonctl_convert_bash_to_zsh() { |
|
203 sed \ |
|
204 -e 's/declare -F/whence -w/' \ |
|
205 -e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \ |
|
206 -e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \ |
|
207 -e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \ |
|
208 -e "s/${LWORD}_filedir${RWORD}/__madonctl_filedir/g" \ |
|
209 -e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__madonctl_get_comp_words_by_ref/g" \ |
|
210 -e "s/${LWORD}__ltrim_colon_completions${RWORD}/__madonctl_ltrim_colon_completions/g" \ |
|
211 -e "s/${LWORD}compgen${RWORD}/__madonctl_compgen/g" \ |
|
212 -e "s/${LWORD}compopt${RWORD}/__madonctl_compopt/g" \ |
|
213 -e "s/${LWORD}declare${RWORD}/__madonctl_declare/g" \ |
|
214 -e "s/\\\$(type${RWORD}/\$(__madonctl_type/g" \ |
|
215 <<'BASH_COMPLETION_EOF' |
|
216 ` |
|
217 |
|
218 const zshTail = ` |
|
219 BASH_COMPLETION_EOF |
|
220 } |
|
221 |
|
222 __madonctl_bash_source <(__madonctl_convert_bash_to_zsh) |
|
223 ` |
|
224 |
|
225 buf := new(bytes.Buffer) |
|
226 out.Write([]byte(zshInitialization)) |
|
227 err := c.GenBashCompletion(buf) |
|
228 script := strings.Replace(buf.String(), "flaghash[${flagname}]", `flaghash[workaround]`, -1) |
|
229 out.Write([]byte(script)) |
|
230 out.Write([]byte(zshTail)) |
|
231 return err |
|
232 } |