about summary refs log tree commit diff
path: root/Completion/Base/_long_options
blob: a50edee1cdf017cc5e1731bf88111437625cfc51 (plain) (blame)
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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
#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, so you
# should be careful to make sure that this function is not called for
# a command that does not support this option.
#
# For options that get an argument after a `=', the function also tries
# to automatically find out what should be completed 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 arguments 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 that 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 accepts the following options:
#
# -t   do completion only on words starting with two hyphens
#
# -i   list of patterns. Options, matching these patterns, are ignored.
#      The list may be given as a array name or as a literal list in braces.
#      E.g. _long_options -i '(--(enable|disable)-FEATURE*)' will ignore
#      --enable-FEATURE, that is listed in configure help output
#
# -s   list of pattern/replacement pairs. The argument is the same as above.
#      E.g. configure often lists only --enable but accepts both
#      --enable and --disable options.
#      _long_options -s '(#--enable- --disable)' will accept both forms.
#
# This function also accepts the `-X', `-J', and `-V' options which
# are given to `compadd'. 

local opt expl group test i name action ret=1 tmp suffix iopts sopts

setopt extendedglob

# Get the options.

group=()
expl=()
if [[ $1 = -*~--* ]]; then
  while getopts "J:V:X:ti:s:" opt; do
    case "$opt" in
      [JV]) group=("-$opt" "$OPTARG");;
      X)    expl=(-X "$OPTARG");;
      t)    test=yes;;
      i)    if [[ "$OPTARG[1]" = '(' ]]; then
              iopts=( ${=OPTARG[2,-2]} )
	    else
              iopts=( ${(P)${OPTARG}} )
	    fi
      ;;
      s)    if [[ "$OPTARG[1]" = '(' ]]; then
              sopts=( ${=OPTARG[2,-2]} )
	    else
              sopts=( ${(P)${OPTARG}} )
	    fi
      ;;
    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
  typeset -U opts

  # 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. 

  opts=("--${(@)^${(@)${(@M)${(@ps:\n:j:\n:)${(@)${(@M)${(@f)$("$words[1]" --help)}:#[ 	]#-*}//,/
}}:#[ 	]#--*}#*--}%%[, ]*}")

  # Now remove all ignored options ...

  while (($#iopts)) ; do
    opts=( ${opts:#$~iopts[1]} )
    shift iopts
  done

  # ... and add "same" options

  while (($#sopts)) ; do
    opts=( $opts ${opts/$sopts[1]/$sopts[2]} )
    shift 2 sopts
  done

  # 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-zA-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%%\=*}"

  # 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
      IPREFIX="${oipre}${pre}="
      PREFIX="${str#*\=}"
      SUFFIX=""
      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 `-'.

    PREFIX="${str%%\=*}"
    SUFFIX=""
    compadd -O tmp -M 'r:|-=* r:|=*' - "${(@P)name}"

    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="${oipre}${parto}="
    PREFIX="${str#*\=}"
    SUFFIX=""
    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 add the option strings 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