1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
|
#autoload
# This function tries to automatically complete long option names. For
# this it invokes the command from the line with the `--help' option
# and then parses the output to find possible option names. For
# options that get an argument after a `=', the function also tries to
# automatically find out what should be complete as the argument.
# The possible completions for option-arguments can be described with
# the arguments to this function. This is done by giving pairs of
# patterns and actions as consecutive arguments. The actions specify
# what should be done to complete arguemts of those options that match
# the pattern. The action may be a list of words in brackets or in
# parentheses, separated by spaces. A list in brackets denotes
# possible values for an optional argument, a list in parentheses
# gives words to complete for mandatory arguments. If the action does
# not start with a bracket or parentheses, it should be the name of a
# command (probably with arguments) that should be invoked to complete
# after the equal sign. E.g.:
#
# _long_options '*\*' '(yes no)' \
# '*=FILE*' '_files' \
# '*=DIR*' '_files -/'
#
# This makes `yes' and `no' be completed as the argument of options
# whose description ends in a star, file names for options that
# contain the substring `=FILE' in the description, and paths for
# options whose description contains `=DIR'. Note the last two
# patterns are not needed since this function always completes files
# for option descriptions containing `=FILE' and paths for option
# descriptions that contain `=DIR' or `=PATH'. These builtin patterns
# can be overridden by patterns given as arguments, though.
#
# This function also accepts the `-X', `-J', and `-V' options which
# are given to `compadd'. Finally, it accepts the option `-t'. If this
# is given, completion is only done on words starting with two hyphens.
local opt expl group test i name action ret=1 tmp suffix
setopt extendedglob
# Get the options.
group=()
expl=()
if [[ $1 = -*~--* ]]; then
while getopts "J:V:X:t" opt; do
case "$opt" in
[JV]) group=("-$opt" "$OPTARG");;
X) expl=(-X "$OPTARG");;
t) test=yes;;
esac
done
shift OPTIND-1
fi
# Test if we are completing after `--' if we were asked to do so.
[[ -n "$test" && "$PREFIX" != --* ]] && return 1
# We cache the information about options and the command name, see if
# we can use the cache.
if [[ "$words[1]" = (.|..)/* ]]; then
tmp="$PWD/$words[1]"
else
tmp="$words[1]"
fi
if [[ "$tmp" != $_lo_cache_cmd ]]; then
# No, store the new command name and clear the old parameters.
_lo_cache_cmd="$tmp"
(( $+_lo_cache_actions )) && unset "$_lo_cache_names[@]" _lo_cache_actions _lo_cache_names
local opts pattern anum=1 tmpo str
# Now get the long option names by calling the command with `--help'.
# The parameter expansion trickery first gets the lines as separate
# array elements. Then we select all lines whose first non-blank
# character is a hyphen. Since some commands document more than one
# option per line, separated by commas, we convert commas int
# newlines and then split the result again at newlines after joining
# the old array elements with newlines between them. Then we select
# those elements that start with two hyphens, remove anything up to
# those hyphens and anything from the space or comma after the
# option up to the end. Finally all elements with option strings
# that contain uppercase letters are removed.
opts=("--${(@)^${(@)${(@)${(@M)${(@ps:\n:j:\n:)${(@)${(@M)${(@f)$("$words[1]" --help)}:#[ ]#-*}//,/
}}:#[ ]#--*}#*--}%%[, ]*}:#(*-[A-Z]*|)}")
# The interpretation of the options is completely table driven. We
# use the positional parameters we were given and a few standard
# ones. Then we loop through this table.
set -- "$@" '*=FILE*' '_files' '*=(DIR|PATH)*' '_files -/' '*' ''
while [[ $# -gt 1 ]]; do
# First, we get the pattern and the action to use and take them
# from the positional parameters.
pattern="$1"
action="$2"
shift 2
# We get all options matching the pattern and take them from the
# list we have built. If no option matches the pattern, we
# continue with the next.
tmp=("${(@M)opts:##$~pattern}")
opts=("${(@)opts:##$~pattern}")
(( $#tmp )) || continue
# Now we collect the options for the pattern in an array. We also
# check if the options take an argument after a `=', and if this
# argument is optional. The name of the array built contains
# `_arg_' for mandatory arguments, `_optarg_' for optional
# arguments, and `_simple_' for options that don't get an
# argument. In `_lo_cache_names' we save the names of these
# arrays and in `_lo_cache_actions' the associated actions.
# If the action is a list of words in brackets, this denotes
# options that get an optional argument. If the action is a list
# of words in parentheses, the option has to get an argument.
# In both cases we just build the array name to use.
if [[ "$action[1]" = '[' ]]; then
name="_lo_cache_optarg_$anum"
elif [[ "$action[1]" = '(' ]]; then
name="_lo_cache_arg_$anum"
else
# If there are option strings with a `[=', we take make these
# get an optional argument...
tmpo=("${(@M)tmp:#*\[\=*}")
if (( $#tmpo )); then
# ...by removing them from the option list and storing them in
# an array.
tmp=("${(@)tmp:#*\[\=*}")
tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}")
_lo_cache_names[anum]="_lo_cache_optarg_$anum"
_lo_cache_actions[anum]="$action"
eval "_lo_cache_optarg_${anum}=(\"\$tmpo[@]\")"
(( anum++ ))
fi
# Now we do the same for option strings containing `=', these
# are options getting an argument.
tmpo=("${(@M)tmp:#*\=*}")
if (( $#tmpo )); then
tmp=("${(@)tmp:#*\=*}")
tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}")
_lo_cache_names[anum]="_lo_cache_arg_$anum"
_lo_cache_actions[anum]="$action"
eval "_lo_cache_arg_${anum}=(\"\$tmpo[@]\")"
(( anum++ ))
fi
# The name for the options without arguments, if any.
name="_lo_cache_simple_$anum"
fi
# Now filter out any option strings we don't like and stuff them
# in an array, if there are still some.
tmp=("${(@)${(@)tmp%%\=*}//[^a-z0-9-]}")
if (( $#tmp )); then
_lo_cache_names[anum]="$name"
_lo_cache_actions[anum]="$action"
eval "${name}=(\"\$tmp[@]\")"
(( anum++ ))
fi
done
fi
# We get the string from the line and and see if it already contains a
# equal sign.
str="$PREFIX$SUFFIX"
if [[ "$str" = *\=* ]]; then
# It contains a `=', now we ignore anything up to it, but first save
# the old contents of the special parameters we change.
local oipre opre osuf pre parto parta pat patflags anum=1
oipre="$IPREFIX"
opre="$PREFIX"
osuf="$SUFFIX"
pre="${str%%\=*}"
IPREFIX="${IPREFIX}${pre}="
PREFIX="${str#*\=}"
SUFFIX=""
# We will check if the arrays contain an option matching what's on
# the line. To do this good, we build a pattern.
[[ -n "$_comp_correct" && $#pre -le _comp_correct ]] && return 1
pat="${pre}*"
patflags=''
_match_pattern _long_options pat patflags
[[ -n "$_comp_correct" ]] && patflags="$patflags(#a$_comp_correct)"
# Then we walk through the array names. For each array we test if it
# contains the option string. If so, we `invoke' the action stored
# with the name. If the action is a list of words, we just add them,
# otherwise we invoke the command or function named.
for name in "$_lo_cache_names[@]"; do
action="$_lo_cache_actions[anum]"
if (( ${(@)${(@P)name}[(I)$pre]} )); then
if [[ "$action[1]" = (\[|\() ]]; then
compadd - ${=action[2,-2]}
elif (( $#action )); then
$=action
fi
# We found the option string, return.
return
fi
# The array did not contain the full option string, see if it
# contains a string matching the string from the line.
# If there is one, we store the option string in `parto' and the
# element from `_lo_actions' in `parta'. If we find more than one
# such option or if we already had one, we set `parto' to `-'.
tmp=("${(@M)${(@P)name}:#${~pat}}")
if [[ $#tmp -eq 1 ]]; then
if [[ -z "$parto" ]]; then
parto="$tmp[1]"
parta="$action"
else
parto=-
fi
elif (( $#tmp )); then
parto=-
fi
(( anum++ ))
done
# If we found only one matching option, we accept it and immediatly
# try to complete the string after the `='.
if [[ -n "$parto" && "$parto" != - ]]; then
IPREFIX="${parto}="
if (( $#parta )); then
if [[ "$parta[1]" = (\[|\() ]]; then
compadd - ${=parta[2,-2]}
else
$=parta
fi
else
compadd -S '' - "$PREFIX"
fi
return
fi
# The option string was not found, restore the special parameters.
IPREFIX="$oipre"
PREFIX="$opre"
SUFFIX="$osuf"
fi
# The string on the line did not contain a `=', or we couldn't
# complete the option string since there were more than one matching
# what's on the line. So we just ad the option string as possible
# matches, giving the string from the `=' on as a suffix.
if [[ "$str" = *\=* ]]; then
str="=${str#*\=}"
PREFIX="${PREFIX%%\=*}"
suffix=()
else
str=""
suffix=('-S=')
fi
anum=1
for name in "$_lo_cache_names[@]"; do
action="$_lo_cache_actions[anum]"
if [[ "$name" = *_optarg_* ]]; then
compadd -M 'r:|-=* r:|=*' -Qq "$suffix[@]" -s "$str" - \
"${(@P)name}" && ret=0
elif [[ "$name" = *_arg_* ]]; then
compadd -M 'r:|-=* r:|=*' -Q "$suffix[@]" -s "$str" - \
"${(@P)name}" && ret=0
elif [[ -z "$str" ]]; then
compadd -M 'r:|-=* r:|=*' -Q - \
"${(@P)name}" && ret=0
fi
(( anum++ ))
done
return ret
|