]>
Commit | Line | Data |
---|---|---|
95e4a97a PA |
1 | SAVE_IFS=$IFS |
2 | IFS=" | |
3 | " | |
4 | _OVSDB_SERVER_LOCATION="" | |
5 | ||
6 | # Run ovs-vsctl and make sure that ovs-vsctl is always called with | |
7 | # the correct --db argument. | |
8 | _ovs_vsctl () { | |
9 | local _db | |
10 | ||
11 | if [ -n "$_OVSDB_SERVER_LOCATION" ]; then | |
12 | _db="--db=$_OVSDB_SERVER_LOCATION" | |
13 | fi | |
14 | ovs-vsctl ${_db} "$@" | |
15 | } | |
16 | ||
17 | # ovs-vsctl --commands outputs in this format: | |
18 | # | |
19 | # main = <localopts>,<name>,<options> | |
20 | # localopts = ([<localopt>] )* | |
21 | # localopt = --[^]]* | |
22 | # name = [^,]* | |
23 | # arguments = ((!argument|?argument|*argument|+argument) )* | |
24 | # argument = ([^ ]*|argument\|argument) | |
25 | # | |
26 | # The [] characters in local options are just delimiters. The | |
27 | # argument prefixes mean: | |
28 | # !argument :: The argument is required | |
29 | # ?argument :: The argument is optional | |
30 | # *argument :: The argument may appear any number (0 or more) times | |
31 | # +argument :: The argument may appear one or more times | |
32 | # A bar (|) character in an argument means thing before bar OR thing | |
33 | # after bar; for example, del-port can take a port or an interface. | |
34 | ||
35 | _OVS_VSCTL_COMMANDS="$(_ovs_vsctl --commands)" | |
36 | ||
37 | # This doesn't complete on short arguments, so it filters them out. | |
38 | _OVS_VSCTL_OPTIONS="$(_ovs_vsctl --options | awk '/^--/ { print $0 }' \ | |
39 | | sed -e 's/\(.*\)=ARG/\1=/')" | |
40 | IFS=$SAVE_IFS | |
41 | ||
42 | declare -A _OVS_VSCTL_PARSED_ARGS | |
43 | declare -A _OVS_VSCTL_NEW_RECORDS | |
44 | ||
45 | # This is a convenience function to make sure that user input is | |
46 | # looked at as a fixed string when being compared to something. $1 is | |
47 | # the input; this behaves like 'grep "^$1"' but deals with regex | |
48 | # metacharacters in $1. | |
49 | _ovs_vsctl_check_startswith_string () { | |
50 | awk 'index($0, thearg)==1' thearg="$1" | |
51 | } | |
52 | ||
53 | # $1 = word to complete on. | |
54 | # Complete on global options. | |
55 | _ovs_vsctl_bashcomp_globalopt () { | |
56 | local options result | |
57 | ||
58 | options="" | |
59 | result=$(printf "%s\n" "${_OVS_VSCTL_OPTIONS}" \ | |
60 | | _ovs_vsctl_check_startswith_string "${1%=*}") | |
61 | if [[ $result =~ "=" ]]; then | |
62 | options="NOSPACE" | |
63 | fi | |
64 | printf -- "${options}\nEO\n${result}" | |
65 | } | |
66 | ||
67 | # $1 = word to complete on. | |
68 | # Complete on local options. | |
69 | _ovs_vsctl_bashcomp_localopt () { | |
70 | local options result possible_opts | |
71 | ||
72 | possible_opts=$(printf "%s\n" "${_OVS_VSCTL_COMMANDS}" | cut -f1 -d',') | |
73 | # This finds all options that could go together with the | |
74 | # already-seen ones | |
75 | for prefix_arg in $1; do | |
76 | possible_opts=$(printf "%s\n" "$possible_opts" \ | |
77 | | grep -- "\[${prefix_arg%%=*}=\?\]") | |
78 | done | |
79 | result=$(printf "%s\n" "${possible_opts}" \ | |
80 | | tr ' ' '\n' | tr -s '\n' | sort | uniq) | |
81 | # This removes the already-seen options from the list so that | |
82 | # users aren't completed for the same option twice. | |
83 | for prefix_arg in $1; do | |
84 | result=$(printf "%s\n" "${result}" \ | |
85 | | grep -v -- "\[${prefix_arg%%=*}=\?\]") | |
86 | done | |
87 | result=$(printf "%s\n" "${result}" | sed -ne 's/\[\(.*\)\]/\1/p' \ | |
88 | | _ovs_vsctl_check_startswith_string "$2") | |
89 | if [[ $result =~ "=" ]]; then | |
90 | options="NOSPACE" | |
91 | fi | |
92 | printf -- "${options}\nEO\n${result}" | |
93 | } | |
94 | ||
95 | # $1 = given local options. | |
96 | # $2 = word to complete on. | |
97 | # Complete on command that could contain the given local options. | |
98 | _ovs_vsctl_bashcomp_command () { | |
99 | local result possible_cmds | |
100 | ||
101 | possible_cmds=$(printf "%s\n" "${_OVS_VSCTL_COMMANDS}") | |
102 | for prefix_arg in $1; do | |
103 | possible_cmds=$(printf "%s\n" "$possible_cmds" \ | |
104 | | grep -- "\[$prefix_arg=\?\]") | |
105 | done | |
106 | result=$(printf "%s\n" "${possible_cmds}" \ | |
107 | | cut -f2 -d',' \ | |
108 | | _ovs_vsctl_check_startswith_string "$2") | |
109 | printf -- "${result}" | |
110 | } | |
111 | ||
112 | # $1 = completion result to check. | |
113 | # Return 0 if the completion result is non-empty, otherwise return 1. | |
114 | _ovs_vsctl_detect_nonzero_completions () { | |
115 | local tmp newarg | |
116 | ||
117 | newarg=${1#*EO} | |
118 | readarray tmp <<< "$newarg" | |
119 | if [ "${#tmp[@]}" -eq 1 ] && [ "${#newarg}" -eq 0 ]; then | |
120 | return 1 | |
121 | fi | |
122 | return 0 | |
123 | } | |
124 | ||
125 | # $1 = argument format to expand. | |
126 | # Expand '+ARGUMENT' in argument format to '!ARGUMENT *ARGUMENT'. | |
127 | _ovs_vsctl_expand_command () { | |
128 | result=$(printf "%s\n" "${_OVS_VSCTL_COMMANDS}" \ | |
129 | | grep -- ",$1," | cut -f3 -d',' | tr ' ' '\n' \ | |
130 | | awk '/\+.*/ { name=substr($0,2); | |
131 | print "!"name; print "*"name; next; } | |
132 | 1') | |
133 | printf -- "${result}\n!--" | |
134 | } | |
135 | ||
136 | # $1 = word to complete on. | |
137 | # Complete on table. | |
138 | _ovs_vsctl_complete_table () { | |
139 | local result | |
140 | ||
141 | result=$(ovsdb-client --no-heading list-tables $_OVSDB_SERVER_LOCATION Open_vSwitch \ | |
142 | | _ovs_vsctl_check_startswith_string "$1") | |
143 | printf -- "EO\n%s\n" "${result}" | |
144 | } | |
145 | ||
146 | # $1 = word to complete on. | |
147 | # Complete on record. Provide both the name and uuid. | |
148 | _ovs_vsctl_complete_record () { | |
149 | local table uuids names new_record | |
150 | ||
151 | table="${_OVS_VSCTL_PARSED_ARGS[TABLE]}" | |
152 | new_record="${_OVS_VSCTL_NEW_RECORDS[${table^^}]}" | |
153 | # Tables should always have an _uuid column | |
154 | uuids=$(_ovs_vsctl --no-heading -f table -d bare --columns=_uuid \ | |
155 | list $table | _ovs_vsctl_check_startswith_string "$1") | |
156 | # Names don't always exist, silently ignore if the name column is | |
157 | # unavailable. | |
158 | names=$(_ovs_vsctl --no-heading -f table -d bare \ | |
159 | --columns=name list $table \ | |
160 | 2>/dev/null \ | |
161 | | _ovs_vsctl_check_startswith_string "$1") | |
162 | printf -- "EO\n%s\n%s\n%s\n" "${uuids}" "${names}" "${new_record}" | |
163 | } | |
164 | ||
165 | # $1 = word to complete on. | |
166 | # Complete on bridge. | |
167 | _ovs_vsctl_complete_bridge () { | |
168 | local result | |
169 | ||
170 | result=$(_ovs_vsctl list-br | _ovs_vsctl_check_startswith_string "$1") | |
171 | printf -- "EO\n%s\n" "${result}" | |
172 | } | |
173 | ||
174 | # $1 = word to complete on. | |
175 | # Complete on port. If a bridge has already been specified, | |
176 | # just complete for that bridge. | |
177 | _ovs_vsctl_complete_port () { | |
178 | local ports result | |
179 | ||
180 | if [ -n "${_OVS_VSCTL_PARSED_ARGS[BRIDGE]}" ]; then | |
181 | ports=$(_ovs_vsctl list-ports "${_OVS_VSCTL_PARSED_ARGS[BRIDGE]}") | |
182 | else | |
183 | local all_ports | |
184 | all_ports=$(_ovs_vsctl --format=table \ | |
185 | --no-headings \ | |
186 | --columns=name \ | |
187 | list Port) | |
188 | ports=$(printf "$all_ports" | tr -d '" ' | sort -u) | |
189 | fi | |
190 | result=$(_ovs_vsctl_check_startswith_string "$1" <<< "$ports") | |
191 | printf -- "EO\n%s\n" "${result}" | |
192 | } | |
193 | ||
194 | # $1: Atom to complete (as usual) | |
195 | # $2: Table to complete the key in | |
196 | # $3: Column to find keys in | |
197 | # $4: Prefix for each completion | |
198 | # Complete on key based on given table and column info. | |
199 | _ovs_vsctl_complete_key_given_table_column () { | |
200 | local keys | |
201 | ||
202 | keys=$(_ovs_vsctl --no-heading --columns="$3" list \ | |
203 | "$2" \ | |
204 | | tr -d '{\"}' | tr -s ', ' '\n' | cut -d'=' -f1 \ | |
bb5dbe78 | 205 | | xargs printf "$4%s\n" | _ovs_vsctl_check_startswith_string "$4$1") |
95e4a97a PA |
206 | result="${keys}" |
207 | printf -- "%s\n" "${result}" | |
208 | } | |
209 | ||
210 | # $1 = word to complete on. | |
211 | # Complete on key. | |
212 | __complete_key () { | |
213 | # KEY is used in both br-set-external-id/br-get-external id (in | |
214 | # which case it is implicitly a key in the external-id column) and | |
215 | # in remove, where it is a table key. This checks to see if table | |
216 | # is set (the remove scenario), and then decides what to do. | |
217 | local result | |
218 | ||
219 | if [ -n "${_OVS_VSCTL_PARSED_ARGS[TABLE]}" ]; then | |
220 | local column=$(tr -d '\n' <<< ${_OVS_VSCTL_PARSED_ARGS["COLUMN"]}) | |
221 | result=$(_ovs_vsctl_complete_key_given_table_column \ | |
222 | "$1" \ | |
223 | ${_OVS_VSCTL_PARSED_ARGS["TABLE"]} \ | |
224 | $column \ | |
225 | "") | |
226 | else | |
227 | result=$(_ovs_vsctl br-get-external-id \ | |
228 | ${_OVS_VSCTL_PARSED_ARGS["BRIDGE"]} \ | |
229 | | cut -d'=' -f1 | _ovs_vsctl_check_startswith_string "$1") | |
230 | fi | |
231 | printf -- "%s" "${result}" | |
232 | } | |
233 | ||
234 | # $1 = word to complete on. | |
235 | # Complete on key. | |
236 | _ovs_vsctl_complete_key () { | |
237 | # KEY is used in both br-set-external-id/br-get-external id (in | |
238 | # which case it is implicitly a key in the external-id column) and | |
239 | # in remove, where it is a table key. This checks to see if table | |
240 | # is set (the remove scenario), and then decides what to do. | |
241 | local result | |
242 | ||
243 | result="$(__complete_key $1)" | |
244 | # If result is empty, just use user input as result. | |
245 | if [ -z "$result" ]; then | |
246 | result=$1 | |
247 | fi | |
248 | printf -- "EO\n%s\n" "${result}" | |
249 | } | |
250 | ||
251 | # $1 = word to complete on. | |
252 | # Complete on value. | |
253 | _ovs_vsctl_complete_value () { | |
254 | local result | |
255 | ||
256 | # Just use user input as result. | |
257 | result=$1 | |
258 | ||
259 | printf -- "EO\n%s\n" "${result}" | |
260 | } | |
261 | ||
262 | # $1 = word to complete on. | |
263 | # Complete on key=value. | |
264 | _ovs_vsctl_complete_key_value () { | |
265 | local orig_completions new_completions | |
266 | ||
267 | orig_completions=$(__complete_key "$1") | |
268 | for completion in ${orig_completions#*EO}; do | |
269 | new_completions="${new_completions} ${completion}=" | |
270 | done | |
271 | # If 'new_completions' is empty, just use user input as result. | |
272 | if [ -z "$new_completions" ]; then | |
273 | new_completions=$1 | |
274 | fi | |
275 | printf -- "NOSPACE\nEO\n%s" "${new_completions}" | |
276 | } | |
277 | ||
278 | # $1 = word to complete on. | |
279 | # Complete on column. | |
280 | _ovs_vsctl_complete_column () { | |
281 | local columns result | |
282 | ||
283 | columns=$(ovsdb-client --no-headings list-columns $_OVSDB_SERVER_LOCATION \ | |
284 | Open_vSwitch ${_OVS_VSCTL_PARSED_ARGS["TABLE"]}) | |
285 | result=$(printf "%s\n" "${columns}" \ | |
286 | | tr -d ':' | cut -d' ' -f1 \ | |
287 | | _ovs_vsctl_check_startswith_string "$1" | sort | uniq) | |
288 | printf -- "EO\n%s\n" "${result}" | |
289 | } | |
290 | ||
291 | # Extract all system interfaces. | |
292 | _ovs_vsctl_get_sys_intf () { | |
293 | local result | |
294 | ||
295 | case "$(uname -o)" in | |
296 | *Linux*) | |
297 | result=$(ip -o link 2>/dev/null | cut -d':' -f2 \ | |
298 | | sed -e 's/^ \(.*\)/\1/') | |
299 | ;; | |
300 | *) | |
301 | result=$(ifconfig -a -s 2>/dev/null | cut -f1 -d' ' | tail -n +2) | |
302 | ;; | |
303 | esac | |
304 | printf "%s\n" "${result}" | |
305 | } | |
306 | ||
307 | # $1 = word to complete on. | |
308 | # Complete on system interface. | |
309 | _ovs_vsctl_complete_sysiface () { | |
310 | local result | |
311 | ||
312 | result=$(_ovs_vsctl_get_sys_intf | _ovs_vsctl_check_startswith_string "$1") | |
313 | printf -- "EO\n%s\n" "${result}" | |
314 | } | |
315 | ||
316 | # $1 = word to complete on. | |
317 | # Complete on interface. If a bridge has already been specified, | |
318 | # just complete for that bridge. | |
319 | _ovs_vsctl_complete_iface () { | |
320 | local result | |
321 | ||
322 | if [ -n "${_OVS_VSCTL_PARSED_ARGS[BRIDGE]}" ]; then | |
323 | result=$(_ovs_vsctl list-ifaces "${_OVS_VSCTL_PARSED_ARGS[BRIDGE]}") | |
324 | else | |
325 | for bridge in $(_ovs_vsctl list-br); do | |
326 | local ifaces | |
327 | ||
328 | ifaces=$(_ovs_vsctl list-ifaces "${bridge}") | |
329 | result="${result} ${ifaces}" | |
330 | done | |
331 | fi | |
332 | printf "EO\n%s\n" "${result}" | |
333 | } | |
334 | ||
335 | # $1 = word to complete on. | |
336 | # Complete on COLUMN?:KEY=VALUE. | |
337 | _ovs_vsctl_complete_column_optkey_value () { | |
338 | local result column key value completion | |
339 | ||
340 | column=$(printf "%s\n" "$1" | cut -d '=' -f1 | cut -d':' -f1) | |
341 | key=$(printf "%s\n" "$1" | cut -d '=' -f1 | cut -s -d':' -f2) | |
342 | # The tr -d '\n' <<< makes sure that there are no leading or | |
343 | # trailing accidental newlines. | |
344 | table=$(tr -d '\n' <<< ${_OVS_VSCTL_PARSED_ARGS["TABLE"]}) | |
345 | # This might also be called after add-port or add-bond; in those | |
346 | # cases, the table should implicitly be assumed to be "Port". | |
347 | # This is done by checking if a NEW- parameter has been | |
348 | # encountered and, if it has, using that type without the NEW- as | |
349 | # the table. | |
350 | if [ -z "$table" ]; then | |
351 | if [ -n ${_OVS_VSCTL_PARSED_ARGS["NEW-PORT"]} ] \ | |
352 | || [ -n ${_OVS_VSCTL_PARSED_ARGS["NEW-BOND-PORT"]} ]; then | |
353 | table="Port" | |
354 | fi | |
355 | fi | |
356 | if [ -z "$key" ]; then | |
357 | local columns=$(ovsdb-client --no-headings list-columns \ | |
358 | $_OVSDB_SERVER_LOCATION Open_vSwitch $table) | |
359 | ||
360 | result=$(printf "%s\n" "${columns}" \ | |
361 | | awk '/key.*value/ { print $1":"; next } | |
362 | { print $1; next }' \ | |
363 | | _ovs_vsctl_check_startswith_string "$1" | sort | uniq) | |
364 | fi | |
365 | if [[ $1 =~ ":" ]]; then | |
366 | result=$(_ovs_vsctl_complete_key_given_table_column \ | |
367 | "$key" "$table" "$column" "$column:") | |
368 | fi | |
369 | # If result is empty, just use user input as result. | |
370 | if [ -z "$result" ]; then | |
371 | result=$1 | |
372 | fi | |
373 | printf -- "NOSPACE\nEO\n%s\n" "${result}" | |
374 | } | |
375 | ||
376 | # $1 = word to complete on. | |
377 | # Complete on filename. | |
378 | _ovs_vsctl_complete_filename () { | |
379 | local result | |
380 | ||
381 | result=$(compgen -o filenames -A file "$1") | |
382 | printf -- "EO\n%s\n" "${result}" | |
383 | } | |
384 | ||
385 | _ovs_vsctl_complete_bridge_fail_mode () { | |
386 | printf -- "EO\nstandalone\nsecure" | |
387 | } | |
388 | ||
389 | # $1 = word to complete on. | |
390 | # Complete on target. | |
391 | _ovs_vsctl_complete_target () { | |
392 | local result | |
393 | ||
394 | if [[ "$1" =~ ^p?u ]]; then | |
395 | local protocol pathname expansion_base result | |
396 | ||
397 | protocol=$(cut -d':' -f1 <<< "$1") | |
398 | pathname=$(cut -s -d':' -f2 <<< "$1") | |
399 | expansion_base=$(compgen -W "unix punix" "$protocol") | |
400 | expansion_base="$expansion_base:" | |
401 | result=$(compgen -o filenames -A file \ | |
402 | -P $expansion_base "${pathname}") | |
403 | printf -- "NOSPACE\nEO\n%s\n" "${result}" | |
404 | else | |
405 | printf -- "NOSPACE\nEO\nssl:\ntcp:\nunix:\npssl:\nptcp:\npunix:" | |
406 | fi | |
407 | } | |
408 | ||
409 | # Extract PS1 prompt. | |
410 | _ovs_vsctl_get_PS1 () { | |
411 | if [ "$test" = "true" ]; then | |
412 | printf -- "> " | |
413 | return; | |
414 | fi | |
415 | ||
416 | # Original inspiration from | |
417 | # http://stackoverflow.com/questions/10060500/bash-how-to-evaluate-ps1-ps2, | |
418 | # but changed quite a lot to make it more robust. | |
419 | ||
420 | # Make sure the PS1 used doesn't include any of the special | |
421 | # strings used to identify the prompt | |
422 | myPS1="$(sed 's/Begin prompt/\\Begin prompt/; s/End prompt/\\End prompt/' <<< "$PS1")" | |
423 | # Export the current environment in case the prompt uses any | |
424 | vars="$(env | cut -d'=' -f1)" | |
425 | for var in $vars; do export $var; done | |
426 | funcs="$(declare -F | cut -d' ' -f3)" | |
427 | for func in $funcs; do export -f $func; done | |
428 | # Get the prompt | |
429 | v="$(bash --norc --noprofile -i 2>&1 <<< $'PS1=\"'"$myPS1"$'\" \n# Begin prompt\n# End prompt')" | |
430 | v="${v##*# Begin prompt}" | |
431 | printf -- "$(tail -n +2 <<< "${v%# End prompt*}" | sed 's/\\Begin prompt/Begin prompt/; s/\\End prompt/End prompt/')" | |
432 | ||
433 | } | |
434 | ||
435 | # Request a new value from user. Nothing to complete on. | |
436 | _ovs_vsctl_complete_new () { | |
437 | local two_word_type message result | |
438 | ||
439 | if [ ! "$1" = "--" ]; then | |
440 | two_word_type="${2/-/ }" | |
441 | message="\nEnter a ${two_word_type,,}:\n$(_ovs_vsctl_get_PS1)$COMP_LINE" | |
442 | if [ -n "$1" ]; then | |
443 | result="$1" | |
444 | fi | |
445 | printf -- "NOCOMP\nBM%sEM\nEO\n%s\n" "${message}" "${result}" | |
446 | fi | |
447 | } | |
448 | ||
449 | _ovs_vsctl_complete_dashdash () { | |
450 | printf -- "EO\n%s\n" "--" | |
451 | } | |
452 | ||
453 | ||
454 | # These functions are given two arguments: | |
455 | # | |
456 | # $1 is the word being completed | |
457 | # | |
458 | # $2 is the type of completion --- only currently useful for the | |
459 | # NEW-* functions. | |
460 | # | |
461 | # Note that the NEW-* functions actually are ``completed''; currently | |
462 | # the completions are just used to save the fact that they have | |
463 | # appeared for later use (i.e. implicit table calculation). | |
464 | # | |
465 | # The output is of the form <options>EO<completions>, where EO stands | |
466 | # for end options. Currently available options are: | |
467 | # - NOSPACE: Do not add a space at the end of each completion | |
468 | # - NOCOMP: Do not complete, but store the output of the completion | |
469 | # func in _OVS_VSCTL_PARSED_ARGS for later usage. | |
470 | # - BM<message>EM: Print the <message> | |
471 | declare -A _OVS_VSCTL_ARG_COMPLETION_FUNCS=( | |
472 | ["TABLE"]=_ovs_vsctl_complete_table | |
473 | ["RECORD"]=_ovs_vsctl_complete_record | |
474 | ["BRIDGE"]=_ovs_vsctl_complete_bridge | |
475 | ["PARENT"]=_ovs_vsctl_complete_bridge | |
476 | ["PORT"]=_ovs_vsctl_complete_port | |
477 | ["KEY"]=_ovs_vsctl_complete_key | |
478 | ["VALUE"]=_ovs_vsctl_complete_value | |
479 | ["ARG"]=_ovs_vsctl_complete_value | |
480 | ["IFACE"]=_ovs_vsctl_complete_iface | |
481 | ["SYSIFACE"]=_ovs_vsctl_complete_sysiface | |
482 | ["COLUMN"]=_ovs_vsctl_complete_column | |
483 | ["COLUMN?:KEY"]=_ovs_vsctl_complete_column_optkey_value | |
484 | ["COLUMN?:KEY=VALUE"]=_ovs_vsctl_complete_column_optkey_value | |
485 | ["KEY=VALUE"]=_ovs_vsctl_complete_key_value | |
486 | ["?KEY=VALUE"]=_ovs_vsctl_complete_key_value | |
487 | ["PRIVATE-KEY"]=_ovs_vsctl_complete_filename | |
488 | ["CERTIFICATE"]=_ovs_vsctl_complete_filename | |
489 | ["CA-CERT"]=_ovs_vsctl_complete_filename | |
490 | ["MODE"]=_ovs_vsctl_complete_bridge_fail_mode | |
491 | ["TARGET"]=_ovs_vsctl_complete_target | |
492 | ["NEW-BRIDGE"]=_ovs_vsctl_complete_new | |
493 | ["NEW-PORT"]=_ovs_vsctl_complete_new | |
494 | ["NEW-BOND-PORT"]=_ovs_vsctl_complete_new | |
495 | ["NEW-VLAN"]=_ovs_vsctl_complete_new | |
496 | ["--"]=_ovs_vsctl_complete_dashdash | |
497 | ) | |
498 | ||
499 | # $1: Argument type, may include vertical bars to mean OR | |
500 | # $2: Beginning of completion | |
501 | # | |
502 | # Note that this checks for existance in | |
503 | # _OVS_VSCTL_ARG_COMPLETION_FUNCS; if the argument type ($1) is not | |
504 | # there it will fail gracefully. | |
505 | _ovs_vsctl_possible_completions_of_argument () { | |
506 | local possible_types completions tmp | |
507 | ||
508 | completions="EO" | |
509 | ||
510 | possible_types=$(printf "%s\n" "$1" | tr '|' '\n') | |
511 | for type in $possible_types; do | |
512 | if [ ${_OVS_VSCTL_ARG_COMPLETION_FUNCS["${type^^}"]} ]; then | |
513 | tmp=$(${_OVS_VSCTL_ARG_COMPLETION_FUNCS["${type^^}"]} \ | |
514 | "$2" "${type^^}") | |
515 | tmp_noEO="${tmp#*EO}" | |
516 | tmp_EO="${tmp%%EO*}" | |
517 | completions=$(printf "%s%s\n%s" "${tmp_EO}" \ | |
518 | "${completions}" "${tmp_noEO}") | |
519 | fi | |
520 | done | |
521 | printf "%s\n" "${completions}" | |
522 | } | |
523 | ||
524 | # $1 = List of argument types | |
525 | # $2 = current pointer into said list | |
526 | # $3 = word to complete on | |
527 | # Outputs list of possible completions | |
528 | # The return value is the index in the cmd_args($1) list that should | |
529 | # next be matched, if only one of them did, or 254 if there are no | |
530 | # matches, so it doesn't know what comes next. | |
531 | _ovs_vsctl_complete_argument() { | |
532 | local cmd_args arg expansion index | |
533 | ||
534 | new=$(printf "%s\n" "$1" | grep -- '.\+') | |
535 | readarray -t cmd_args <<< "$new"; | |
536 | arg=${cmd_args[$2]} | |
537 | case ${arg:0:1} in | |
538 | !) | |
539 | expansion=$(_ovs_vsctl_possible_completions_of_argument \ | |
540 | "${arg:1}" $3) | |
541 | index=$(($2+1)) | |
542 | ;; | |
543 | \?|\*) | |
544 | local tmp1 tmp2 arg2_index tmp2_noEO tmp2_EO | |
545 | tmp1=$(_ovs_vsctl_possible_completions_of_argument "${arg:1}" $3) | |
546 | tmp2=$(_ovs_vsctl_complete_argument "$1" "$(($2+1))" "$3") | |
547 | arg2_index=$? | |
548 | if _ovs_vsctl_detect_nonzero_completions "$tmp1" \ | |
549 | && _ovs_vsctl_detect_nonzero_completions "$tmp2"; then | |
550 | if [ "${arg:0:1}" = "*" ]; then | |
551 | index=$2; | |
552 | else | |
553 | index=$(($2+1)); | |
554 | fi | |
555 | fi | |
556 | if _ovs_vsctl_detect_nonzero_completions "$tmp1" \ | |
557 | && (! _ovs_vsctl_detect_nonzero_completions "$tmp2"); then | |
558 | if [ "${arg:0:1}" = "*" ]; then | |
559 | index=$2; | |
560 | else | |
561 | index=$(($2+1)); | |
562 | fi | |
563 | fi | |
564 | if (! _ovs_vsctl_detect_nonzero_completions "$tmp1") \ | |
565 | && _ovs_vsctl_detect_nonzero_completions "$tmp2"; then | |
566 | index=$arg2_index | |
567 | fi | |
568 | if (! _ovs_vsctl_detect_nonzero_completions "$tmp1") \ | |
569 | && (! _ovs_vsctl_detect_nonzero_completions "$tmp2"); then | |
570 | index=254 | |
571 | fi | |
572 | # Don't allow secondary completions to inhibit primary | |
573 | # completions: | |
574 | if [[ $tmp2 =~ ^([^E]|E[^O])*NOCOMP ]]; then | |
575 | tmp2="" | |
576 | fi | |
577 | tmp2_noEO="${tmp2#*EO}" | |
578 | tmp2_EO="${tmp2%%EO*}" | |
579 | expansion=$(printf "%s%s\n%s" "${tmp2_EO}" \ | |
580 | "${tmp1}" "${tmp2_noEO}") | |
581 | ;; | |
582 | esac | |
583 | printf "%s\n" "$expansion" | |
584 | return $index | |
585 | } | |
586 | ||
587 | _ovs_vsctl_detect_nospace () { | |
588 | if [[ $1 =~ ^([^E]|E[^O])*NOSPACE ]]; then | |
589 | _OVS_VSCTL_COMP_NOSPACE=true | |
590 | fi | |
591 | } | |
592 | ||
593 | _ovs_vsctl_process_messages () { | |
594 | local message | |
595 | ||
596 | message="${1#*BM}" | |
597 | message="${message%%EM*}" | |
598 | if [ "$test" = "true" ]; then | |
599 | printf -- "--- BEGIN MESSAGE" | |
600 | fi | |
601 | printf "${message}" | |
602 | if [ "$test" = "true" ]; then | |
603 | printf -- "--- END MESSAGE" | |
604 | fi | |
605 | } | |
606 | ||
bb5dbe78 AW |
607 | # colon, equal sign will mess up the completion output, just |
608 | # removes the colon-word and equal-word prefix from COMPREPLY items. | |
609 | # | |
610 | # Implementation of this function refers to the __ltrim_colon_completions | |
611 | # function defined in bash_completion module. | |
612 | # | |
613 | # $1: Current argument | |
614 | # $2: $COMP_WORDBREAKS | |
615 | # $3: ${COMPREPLY[@]} | |
616 | _ovs_vsctl_trim_compreply() { | |
617 | local cur comp_wordbreaks | |
618 | local compreply | |
619 | ||
620 | cur=$1 && shift | |
621 | comp_wordbreaks=$1 && shift | |
622 | compreply=( $@ ) | |
623 | ||
624 | if [[ "$cur" == *:* && "$comp_wordbreaks" == *:* ]]; then | |
625 | local colon_word=${cur%${cur##*:}} | |
626 | local i=${#compreply[*]} | |
627 | cur=${cur##*:} | |
628 | while [ $((--i)) -ge 0 ]; do | |
629 | compreply[$i]=${compreply[$i]#"$colon_word"} | |
630 | done | |
631 | fi | |
632 | ||
633 | if [[ "$cur" == *=* && "$comp_wordbreaks" == *=* ]]; then | |
634 | local equal_word=${cur%${cur##*=}} | |
635 | local i=${#compreply[*]} | |
636 | while [ $((--i)) -ge 0 ]; do | |
637 | compreply[$i]=${compreply[$i]#"$equal_word"} | |
638 | done | |
639 | fi | |
640 | ||
641 | printf "%s " "${compreply[@]}" | |
642 | } | |
643 | ||
95e4a97a PA |
644 | # The general strategy here is that the same functions that decide |
645 | # completions can also capture the necessary context for later | |
646 | # completions. This means that there is no distinction between the | |
647 | # processing for words that are not the current word and words that | |
648 | # are the current word. | |
649 | # | |
650 | # Parsing up until the command word happens starts with everything | |
651 | # valid; as the syntax order of ovs-vsctl is fairly strict, when types | |
652 | # of words that preclude other words from happending can turn them | |
653 | # off; this is controlled by valid_globals, valid_opts, and | |
654 | # valid_commands. given_opts is used to narrow down which commands | |
655 | # are valid based on the previously given options. | |
656 | # | |
657 | # After the command has been detected, the parsing becomes more | |
658 | # complicated. The cmd_pos variable is set to 0 when the command is | |
659 | # detected; it is used as a pointer into an array of the argument | |
660 | # types for that given command. The argument types are stored in both | |
661 | # cmd_args and raw_cmd as the main loop uses properties of arrays to | |
662 | # detect certain conditions, but arrays cannot be passed to functions. | |
663 | # To be able to deal with optional or repeatable arguments, the exit | |
664 | # status of the function _ovs_vsctl_complete_argument represents where | |
665 | # it has determined that the next argument will be. | |
666 | _ovs_vsctl_bashcomp () { | |
bb5dbe78 | 667 | local words cword valid_globals cmd_args raw_cmd cmd_pos valid_globals valid_opts |
95e4a97a PA |
668 | local test="false" |
669 | ||
ce34e37f AW |
670 | # Does not support BASH_VERSION < 4.0 |
671 | if [ ${BASH_VERSINFO[0]} -lt 4 ]; then | |
672 | return 0 | |
673 | fi | |
674 | ||
95e4a97a PA |
675 | # Prepare the COMP_* variables based on input. |
676 | if [ "$1" = "test" ]; then | |
677 | test="true" | |
678 | export COMP_LINE="ovs-vsctl $2" | |
679 | tmp="ovs-vsctl"$'\n'"$(tr ' ' '\n' <<< "${COMP_LINE}x")" | |
680 | tmp="${tmp%x}" | |
681 | readarray -t COMP_WORDS \ | |
682 | <<< "$tmp" | |
683 | export COMP_WORDS | |
684 | export COMP_CWORD="$((${#COMP_WORDS[@]}-1))" | |
bb5dbe78 AW |
685 | else |
686 | # If not in test mode, reassembles the COMP_WORDS and COMP_CWORD | |
687 | # using just space as word break. | |
688 | _get_comp_words_by_ref -n "\"'><=;|&(:" -w words -i cword | |
689 | COMP_WORDS=( "${words[@]}" ) | |
690 | COMP_CWORD=${cword} | |
95e4a97a PA |
691 | fi |
692 | ||
693 | # Extract the conf.db path. | |
694 | db=$(sed -n 's/.*--db=\([^ ]*\).*/\1/p' <<< "$COMP_LINE") | |
695 | if [ -n "$db" ]; then | |
696 | _OVSDB_SERVER_LOCATION="$db" | |
697 | fi | |
698 | ||
699 | # If having trouble accessing the database, return. | |
bb5dbe78 | 700 | if ! _ovs_vsctl get-manager 1>/dev/null 2>/dev/null; then |
95e4a97a PA |
701 | return 1; |
702 | fi | |
703 | ||
704 | _OVS_VSCTL_PARSED_ARGS=() | |
705 | _OVS_VSCTL_NEW_RECORDS=() | |
706 | cmd_pos=-1 | |
95e4a97a PA |
707 | valid_globals=true |
708 | valid_opts=true | |
709 | valid_commands=true | |
710 | given_opts="" | |
711 | index=1 | |
95e4a97a PA |
712 | for word in "${COMP_WORDS[@]:1:${COMP_CWORD}} "; do |
713 | _OVS_VSCTL_COMP_NOSPACE=false | |
714 | local completion | |
715 | completion="" | |
716 | if [ $cmd_pos -gt -1 ]; then | |
717 | local tmp tmp_noop arg possible_newindex | |
718 | tmp=$(_ovs_vsctl_complete_argument "$raw_cmd" "$cmd_pos" "$word") | |
719 | possible_newindex=$? | |
720 | # Check for nospace. | |
721 | _ovs_vsctl_detect_nospace $tmp | |
722 | # Remove all options. | |
723 | tmp_noop="${tmp#*EO}" | |
724 | ||
725 | # Allow commands to specify that they should not be | |
726 | # completed | |
727 | if ! [[ $tmp =~ ^([^E]|E[^O])*NOCOMP ]]; then | |
728 | # Directly assignment, since 'completion' is guaranteed to | |
729 | # to be empty. | |
730 | completion="$tmp_noop" | |
731 | # If intermediate completion is empty, it means that the current | |
732 | # argument is invalid. And we should not continue. | |
733 | if [ $index -lt $COMP_CWORD ] \ | |
734 | && (! _ovs_vsctl_detect_nonzero_completions "$completion"); then | |
735 | _ovs_vsctl_process_messages "BM\nCannot complete \'${COMP_WORDS[$index]}\' at index ${index}:\n$(_ovs_vsctl_get_PS1)${COMP_LINE}EM\nEO\n" | |
736 | return 1 | |
737 | fi | |
738 | else | |
739 | # Only allow messages when there is no completion | |
740 | # printout and when on the current word. | |
741 | if [ $index -eq $COMP_CWORD ]; then | |
742 | _ovs_vsctl_process_messages "${tmp}" | |
743 | fi | |
744 | # Append the new record to _OVS_VSCTL_NEW_RECORDS. | |
745 | _OVS_VSCTL_NEW_RECORDS["${cmd_args[$cmd_pos]##*-}"]="${_OVS_VSCTL_NEW_RECORDS["${cmd_args[$cmd_pos]##*-}"]} $tmp_noop" | |
746 | fi | |
747 | if [[ $cmd_pos -lt ${#cmd_args} ]]; then | |
748 | _OVS_VSCTL_PARSED_ARGS["${cmd_args[$cmd_pos]:1}"]=$word | |
749 | fi | |
750 | if [ $possible_newindex -lt 254 ]; then | |
751 | cmd_pos=$possible_newindex | |
752 | fi | |
753 | fi | |
754 | ||
755 | if [ $valid_globals == true ]; then | |
756 | tmp=$(_ovs_vsctl_bashcomp_globalopt $word) | |
757 | _ovs_vsctl_detect_nospace $tmp | |
758 | completion="${completion} ${tmp#*EO}" | |
759 | fi | |
760 | if [ $valid_opts == true ]; then | |
761 | tmp=$(_ovs_vsctl_bashcomp_localopt "$given_opts" $word) | |
762 | _ovs_vsctl_detect_nospace $tmp | |
763 | completion="${completion} ${tmp#*EO}" | |
764 | if [ $index -lt $COMP_CWORD ] \ | |
765 | && _ovs_vsctl_detect_nonzero_completions "$tmp"; then | |
766 | valid_globals=false | |
767 | given_opts="${given_opts} ${word}" | |
768 | fi | |
769 | fi | |
770 | if [ $valid_commands = true ]; then | |
771 | tmp=$(_ovs_vsctl_bashcomp_command "$given_opts" $word) | |
772 | _ovs_vsctl_detect_nospace $tmp | |
773 | completion="${completion} ${tmp#*EO}" | |
774 | if [ $index -lt $COMP_CWORD ] \ | |
775 | && _ovs_vsctl_detect_nonzero_completions "$tmp"; then | |
776 | valid_globals=false | |
777 | valid_opts=false | |
778 | valid_commands=false | |
779 | cmd_pos=0 | |
780 | raw_cmd=$(_ovs_vsctl_expand_command "$word") | |
781 | readarray -t cmd_args <<< "$raw_cmd" | |
782 | fi | |
783 | fi | |
784 | if [ "$word" = "--" ] && [ $index -lt $COMP_CWORD ]; then | |
785 | # Empty the parsed args array. | |
786 | _OVS_VSCTL_PARSED_AGS=() | |
787 | cmd_pos=-1 | |
788 | # No longer allow global options after '--'. | |
789 | valid_globals=false | |
790 | valid_opts=true | |
791 | valid_commands=true | |
792 | given_opts="" | |
793 | fi | |
794 | completion="$(sort -u <<< "$(tr ' ' '\n' <<< ${completion})")" | |
795 | if [ $index -eq $COMP_CWORD ]; then | |
796 | if [ "$test" = "true" ]; then | |
bb5dbe78 AW |
797 | completion="$(_ovs_vsctl_trim_compreply "$word" ":=" ${completion} | \ |
798 | tr ' ' '\n')" | |
95e4a97a PA |
799 | if [ "${_OVS_VSCTL_COMP_NOSPACE}" = "true" ]; then |
800 | printf "%s" "$completion" | sed -e '/^$/d' | |
801 | else | |
802 | printf "%s" "$completion" | sed -e '/^$/d; s/$/ /g' | |
803 | fi | |
804 | printf "\n" | |
805 | else | |
806 | if [ "${_OVS_VSCTL_COMP_NOSPACE}" = "true" ]; then | |
807 | compopt -o nospace | |
808 | COMPREPLY=( $(compgen -W "${completion}" -- $word) ) | |
809 | else | |
810 | compopt +o nospace | |
811 | COMPREPLY=( $(compgen -W "${completion}" -- $word) ) | |
812 | fi | |
bb5dbe78 AW |
813 | COMPREPLY=( $(_ovs_vsctl_trim_compreply "$word" \ |
814 | "${COMP_WORDBREAKS}" ${COMPREPLY[@]}) ) | |
95e4a97a PA |
815 | fi |
816 | fi | |
817 | index=$(($index+1)) | |
818 | done | |
819 | } | |
820 | ||
821 | if [ "$1" = "test" ]; then | |
822 | _ovs_vsctl_bashcomp "$@" | |
823 | else | |
824 | complete -F _ovs_vsctl_bashcomp ovs-vsctl | |
825 | fi |