256
|
1 |
package cobra |
|
2 |
|
|
3 |
import ( |
|
4 |
"bytes" |
|
5 |
"fmt" |
|
6 |
"io" |
|
7 |
"os" |
|
8 |
) |
|
9 |
|
|
10 |
func (c *Command) genBashCompletion(w io.Writer, includeDesc bool) error { |
|
11 |
buf := new(bytes.Buffer) |
|
12 |
genBashComp(buf, c.Name(), includeDesc) |
|
13 |
_, err := buf.WriteTo(w) |
|
14 |
return err |
|
15 |
} |
|
16 |
|
|
17 |
func genBashComp(buf io.StringWriter, name string, includeDesc bool) { |
|
18 |
compCmd := ShellCompRequestCmd |
|
19 |
if !includeDesc { |
|
20 |
compCmd = ShellCompNoDescRequestCmd |
|
21 |
} |
|
22 |
|
|
23 |
WriteStringAndCheck(buf, fmt.Sprintf(`# bash completion V2 for %-36[1]s -*- shell-script -*- |
|
24 |
|
|
25 |
__%[1]s_debug() |
|
26 |
{ |
|
27 |
if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then |
|
28 |
echo "$*" >> "${BASH_COMP_DEBUG_FILE}" |
|
29 |
fi |
|
30 |
} |
|
31 |
|
|
32 |
# Macs have bash3 for which the bash-completion package doesn't include |
|
33 |
# _init_completion. This is a minimal version of that function. |
|
34 |
__%[1]s_init_completion() |
|
35 |
{ |
|
36 |
COMPREPLY=() |
|
37 |
_get_comp_words_by_ref "$@" cur prev words cword |
|
38 |
} |
|
39 |
|
|
40 |
# This function calls the %[1]s program to obtain the completion |
|
41 |
# results and the directive. It fills the 'out' and 'directive' vars. |
|
42 |
__%[1]s_get_completion_results() { |
|
43 |
local requestComp lastParam lastChar args |
|
44 |
|
|
45 |
# Prepare the command to request completions for the program. |
|
46 |
# Calling ${words[0]} instead of directly %[1]s allows to handle aliases |
|
47 |
args=("${words[@]:1}") |
|
48 |
requestComp="${words[0]} %[2]s ${args[*]}" |
|
49 |
|
|
50 |
lastParam=${words[$((${#words[@]}-1))]} |
|
51 |
lastChar=${lastParam:$((${#lastParam}-1)):1} |
|
52 |
__%[1]s_debug "lastParam ${lastParam}, lastChar ${lastChar}" |
|
53 |
|
|
54 |
if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then |
|
55 |
# If the last parameter is complete (there is a space following it) |
|
56 |
# We add an extra empty parameter so we can indicate this to the go method. |
|
57 |
__%[1]s_debug "Adding extra empty parameter" |
|
58 |
requestComp="${requestComp} ''" |
|
59 |
fi |
|
60 |
|
|
61 |
# When completing a flag with an = (e.g., %[1]s -n=<TAB>) |
|
62 |
# bash focuses on the part after the =, so we need to remove |
|
63 |
# the flag part from $cur |
|
64 |
if [[ "${cur}" == -*=* ]]; then |
|
65 |
cur="${cur#*=}" |
|
66 |
fi |
|
67 |
|
|
68 |
__%[1]s_debug "Calling ${requestComp}" |
|
69 |
# Use eval to handle any environment variables and such |
|
70 |
out=$(eval "${requestComp}" 2>/dev/null) |
|
71 |
|
|
72 |
# Extract the directive integer at the very end of the output following a colon (:) |
|
73 |
directive=${out##*:} |
|
74 |
# Remove the directive |
|
75 |
out=${out%%:*} |
|
76 |
if [ "${directive}" = "${out}" ]; then |
|
77 |
# There is not directive specified |
|
78 |
directive=0 |
|
79 |
fi |
|
80 |
__%[1]s_debug "The completion directive is: ${directive}" |
|
81 |
__%[1]s_debug "The completions are: ${out[*]}" |
|
82 |
} |
|
83 |
|
|
84 |
__%[1]s_process_completion_results() { |
|
85 |
local shellCompDirectiveError=%[3]d |
|
86 |
local shellCompDirectiveNoSpace=%[4]d |
|
87 |
local shellCompDirectiveNoFileComp=%[5]d |
|
88 |
local shellCompDirectiveFilterFileExt=%[6]d |
|
89 |
local shellCompDirectiveFilterDirs=%[7]d |
|
90 |
|
|
91 |
if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then |
|
92 |
# Error code. No completion. |
|
93 |
__%[1]s_debug "Received error from custom completion go code" |
|
94 |
return |
|
95 |
else |
|
96 |
if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then |
|
97 |
if [[ $(type -t compopt) = "builtin" ]]; then |
|
98 |
__%[1]s_debug "Activating no space" |
|
99 |
compopt -o nospace |
|
100 |
else |
|
101 |
__%[1]s_debug "No space directive not supported in this version of bash" |
|
102 |
fi |
|
103 |
fi |
|
104 |
if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then |
|
105 |
if [[ $(type -t compopt) = "builtin" ]]; then |
|
106 |
__%[1]s_debug "Activating no file completion" |
|
107 |
compopt +o default |
|
108 |
else |
|
109 |
__%[1]s_debug "No file completion directive not supported in this version of bash" |
|
110 |
fi |
|
111 |
fi |
|
112 |
fi |
|
113 |
|
|
114 |
if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then |
|
115 |
# File extension filtering |
|
116 |
local fullFilter filter filteringCmd |
|
117 |
|
|
118 |
# Do not use quotes around the $out variable or else newline |
|
119 |
# characters will be kept. |
|
120 |
for filter in ${out[*]}; do |
|
121 |
fullFilter+="$filter|" |
|
122 |
done |
|
123 |
|
|
124 |
filteringCmd="_filedir $fullFilter" |
|
125 |
__%[1]s_debug "File filtering command: $filteringCmd" |
|
126 |
$filteringCmd |
|
127 |
elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then |
|
128 |
# File completion for directories only |
|
129 |
|
|
130 |
# Use printf to strip any trailing newline |
|
131 |
local subdir |
|
132 |
subdir=$(printf "%%s" "${out[0]}") |
|
133 |
if [ -n "$subdir" ]; then |
|
134 |
__%[1]s_debug "Listing directories in $subdir" |
|
135 |
pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return |
|
136 |
else |
|
137 |
__%[1]s_debug "Listing directories in ." |
|
138 |
_filedir -d |
|
139 |
fi |
|
140 |
else |
|
141 |
__%[1]s_handle_standard_completion_case |
|
142 |
fi |
|
143 |
|
|
144 |
__%[1]s_handle_special_char "$cur" : |
|
145 |
__%[1]s_handle_special_char "$cur" = |
|
146 |
} |
|
147 |
|
|
148 |
__%[1]s_handle_standard_completion_case() { |
|
149 |
local tab comp |
|
150 |
tab=$(printf '\t') |
|
151 |
|
|
152 |
local longest=0 |
|
153 |
# Look for the longest completion so that we can format things nicely |
|
154 |
while IFS='' read -r comp; do |
|
155 |
# Strip any description before checking the length |
|
156 |
comp=${comp%%%%$tab*} |
|
157 |
# Only consider the completions that match |
|
158 |
comp=$(compgen -W "$comp" -- "$cur") |
|
159 |
if ((${#comp}>longest)); then |
|
160 |
longest=${#comp} |
|
161 |
fi |
|
162 |
done < <(printf "%%s\n" "${out[@]}") |
|
163 |
|
|
164 |
local completions=() |
|
165 |
while IFS='' read -r comp; do |
|
166 |
if [ -z "$comp" ]; then |
|
167 |
continue |
|
168 |
fi |
|
169 |
|
|
170 |
__%[1]s_debug "Original comp: $comp" |
|
171 |
comp="$(__%[1]s_format_comp_descriptions "$comp" "$longest")" |
|
172 |
__%[1]s_debug "Final comp: $comp" |
|
173 |
completions+=("$comp") |
|
174 |
done < <(printf "%%s\n" "${out[@]}") |
|
175 |
|
|
176 |
while IFS='' read -r comp; do |
|
177 |
COMPREPLY+=("$comp") |
|
178 |
done < <(compgen -W "${completions[*]}" -- "$cur") |
|
179 |
|
|
180 |
# If there is a single completion left, remove the description text |
|
181 |
if [ ${#COMPREPLY[*]} -eq 1 ]; then |
|
182 |
__%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}" |
|
183 |
comp="${COMPREPLY[0]%%%% *}" |
|
184 |
__%[1]s_debug "Removed description from single completion, which is now: ${comp}" |
|
185 |
COMPREPLY=() |
|
186 |
COMPREPLY+=("$comp") |
|
187 |
fi |
|
188 |
} |
|
189 |
|
|
190 |
__%[1]s_handle_special_char() |
|
191 |
{ |
|
192 |
local comp="$1" |
|
193 |
local char=$2 |
|
194 |
if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then |
|
195 |
local word=${comp%%"${comp##*${char}}"} |
|
196 |
local idx=${#COMPREPLY[*]} |
|
197 |
while [[ $((--idx)) -ge 0 ]]; do |
|
198 |
COMPREPLY[$idx]=${COMPREPLY[$idx]#"$word"} |
|
199 |
done |
|
200 |
fi |
|
201 |
} |
|
202 |
|
|
203 |
__%[1]s_format_comp_descriptions() |
|
204 |
{ |
|
205 |
local tab |
|
206 |
tab=$(printf '\t') |
|
207 |
local comp="$1" |
|
208 |
local longest=$2 |
|
209 |
|
|
210 |
# Properly format the description string which follows a tab character if there is one |
|
211 |
if [[ "$comp" == *$tab* ]]; then |
|
212 |
desc=${comp#*$tab} |
|
213 |
comp=${comp%%%%$tab*} |
|
214 |
|
|
215 |
# $COLUMNS stores the current shell width. |
|
216 |
# Remove an extra 4 because we add 2 spaces and 2 parentheses. |
|
217 |
maxdesclength=$(( COLUMNS - longest - 4 )) |
|
218 |
|
|
219 |
# Make sure we can fit a description of at least 8 characters |
|
220 |
# if we are to align the descriptions. |
|
221 |
if [[ $maxdesclength -gt 8 ]]; then |
|
222 |
# Add the proper number of spaces to align the descriptions |
|
223 |
for ((i = ${#comp} ; i < longest ; i++)); do |
|
224 |
comp+=" " |
|
225 |
done |
|
226 |
else |
|
227 |
# Don't pad the descriptions so we can fit more text after the completion |
|
228 |
maxdesclength=$(( COLUMNS - ${#comp} - 4 )) |
|
229 |
fi |
|
230 |
|
|
231 |
# If there is enough space for any description text, |
|
232 |
# truncate the descriptions that are too long for the shell width |
|
233 |
if [ $maxdesclength -gt 0 ]; then |
|
234 |
if [ ${#desc} -gt $maxdesclength ]; then |
|
235 |
desc=${desc:0:$(( maxdesclength - 1 ))} |
|
236 |
desc+="…" |
|
237 |
fi |
|
238 |
comp+=" ($desc)" |
|
239 |
fi |
|
240 |
fi |
|
241 |
|
|
242 |
# Must use printf to escape all special characters |
|
243 |
printf "%%q" "${comp}" |
|
244 |
} |
|
245 |
|
|
246 |
__start_%[1]s() |
|
247 |
{ |
|
248 |
local cur prev words cword split |
|
249 |
|
|
250 |
COMPREPLY=() |
|
251 |
|
|
252 |
# Call _init_completion from the bash-completion package |
|
253 |
# to prepare the arguments properly |
|
254 |
if declare -F _init_completion >/dev/null 2>&1; then |
|
255 |
_init_completion -n "=:" || return |
|
256 |
else |
|
257 |
__%[1]s_init_completion -n "=:" || return |
|
258 |
fi |
|
259 |
|
|
260 |
__%[1]s_debug |
|
261 |
__%[1]s_debug "========= starting completion logic ==========" |
|
262 |
__%[1]s_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword" |
|
263 |
|
|
264 |
# The user could have moved the cursor backwards on the command-line. |
|
265 |
# We need to trigger completion from the $cword location, so we need |
|
266 |
# to truncate the command-line ($words) up to the $cword location. |
|
267 |
words=("${words[@]:0:$cword+1}") |
|
268 |
__%[1]s_debug "Truncated words[*]: ${words[*]}," |
|
269 |
|
|
270 |
local out directive |
|
271 |
__%[1]s_get_completion_results |
|
272 |
__%[1]s_process_completion_results |
|
273 |
} |
|
274 |
|
|
275 |
if [[ $(type -t compopt) = "builtin" ]]; then |
|
276 |
complete -o default -F __start_%[1]s %[1]s |
|
277 |
else |
|
278 |
complete -o default -o nospace -F __start_%[1]s %[1]s |
|
279 |
fi |
|
280 |
|
|
281 |
# ex: ts=4 sw=4 et filetype=sh |
|
282 |
`, name, compCmd, |
|
283 |
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, |
|
284 |
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) |
|
285 |
} |
|
286 |
|
|
287 |
// GenBashCompletionFileV2 generates Bash completion version 2. |
|
288 |
func (c *Command) GenBashCompletionFileV2(filename string, includeDesc bool) error { |
|
289 |
outFile, err := os.Create(filename) |
|
290 |
if err != nil { |
|
291 |
return err |
|
292 |
} |
|
293 |
defer outFile.Close() |
|
294 |
|
|
295 |
return c.GenBashCompletionV2(outFile, includeDesc) |
|
296 |
} |
|
297 |
|
|
298 |
// GenBashCompletionV2 generates Bash completion file version 2 |
|
299 |
// and writes it to the passed writer. |
|
300 |
func (c *Command) GenBashCompletionV2(w io.Writer, includeDesc bool) error { |
|
301 |
return c.genBashCompletion(w, includeDesc) |
|
302 |
} |