0
|
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 |
} |