]> git.proxmox.com Git - mirror_ovs.git/blob - utilities/ovs-appctl-bashcomp.bash
rhel: Add option to enable AF_XDP on rpm package.
[mirror_ovs.git] / utilities / ovs-appctl-bashcomp.bash
1 #
2 # A bash command completion script for ovs-appctl.
3 #
4 #
5 # Right now, the script can do the following:
6 #
7 # - display available completion or complete on unfinished user input
8 # (long option, subcommand, and argument).
9 #
10 # - once the subcommand (e.g. ofproto/trace) has been given, the
11 # script will print the subcommand format.
12 #
13 # - the script can convert between keywords like 'bridge/port/interface/dp'
14 # and the available record in ovsdb.
15 #
16 # The limitation are:
17 #
18 # - only support small set of important keywords
19 # (dp, datapath, bridge, switch, port, interface, iface).
20 #
21 # - does not support parsing of nested option
22 # (e.g. ovsdb-tool create [db [schema]]).
23 #
24 # - does not support expansion on repeatitive argument
25 # (e.g. ovs-dpctl show [dp...]).
26 #
27 # - only support matching on long options, and only in the format
28 # (--option [arg], i.e. should not use --option=[arg]).
29 #
30 #
31 #
32 # Keywords
33 # ========
34 #
35 #
36 #
37 # Expandable keywords.
38 _KWORDS=(bridge switch port interface iface dp_name dp)
39 # Command name.
40 _COMMAND=
41 # Printf enabler.
42 _PRINTF_ENABLE=
43 # Bash prompt.
44 _BASH_PROMPT=
45 # Output to the compgen.
46 _COMP_WORDLIST=
47
48 #
49 # For ovs-appctl command only.
50 #
51 # Target in the current completion, default ovs-vswitchd.
52 _APPCTL_TARGET=
53 # Possible targets.
54 _POSSIBLE_TARGETS="ovs-vswitchd ovsdb-server ovs-ofctl"
55
56 # Command Extraction
57 # ==================
58 #
59 #
60 #
61 # Extracts all subcommands of 'command'.
62 # If fails, returns nothing.
63 extract_subcmds() {
64 local command=$_COMMAND
65 local target=
66 local subcmds error
67
68 if [ -n "$_APPCTL_TARGET" ]; then
69 target="--target $_APPCTL_TARGET"
70 fi
71
72 subcmds="$($command $target list-commands 2>/dev/null | tail -n +2 | cut -c3- \
73 | cut -d ' ' -f1)" || error="TRUE"
74
75 if [ -z "$error" ]; then
76 echo "$subcmds"
77 fi
78 }
79
80 # Extracts all long options of ovs-appctl.
81 # If fails, returns nothing.
82 extract_options() {
83 local command=$_COMMAND
84 local options error
85
86 options="$($command --option 2>/dev/null | sort | sed -n '/^--.*/p' | cut -d '=' -f1)" \
87 || error="TRUE"
88
89 if [ -z "$error" ]; then
90 echo "$options"
91 fi
92 }
93
94 # Returns the option format, if the option asks for an argument.
95 # If fails, returns nothing.
96 option_require_arg() {
97 local command=$_COMMAND
98 local option=$1
99 local require_arg error
100
101 require_arg="$($command --option | sort | sed -n '/^--.*/p' | grep -- "$option" | grep -- "=")" \
102 || error="TRUE"
103
104 if [ -z "$error" ]; then
105 echo "$require_arg"
106 fi
107 }
108
109 # Combination Discovery
110 # =====================
111 #
112 #
113 #
114 # Given the subcommand formats, finds all possible completions
115 # at current completion level.
116 find_possible_comps() {
117 local combs="$@"
118 local comps=
119 local line
120
121 while read line; do
122 local arg=
123
124 for arg in $line; do
125 # If it is an optional argument, gets all completions,
126 # and continues.
127 if [ -n "$(sed -n '/^\[.*\]$/p' <<< "$arg")" ]; then
128 local opt_arg="$(sed -e 's/^\[\(.*\)\]$/\1/' <<< "$arg")"
129 local opt_args=()
130
131 IFS='|' read -a opt_args <<< "$opt_arg"
132 comps="${opt_args[@]} $comps"
133 # If it is in format "\[*", it is a start of nested
134 # option, do not parse.
135 elif [ -n "$(sed -n "/^\[.*$/p" <<< "$arg")" ]; then
136 break;
137 # If it is a compulsory argument, adds it to the comps
138 # and break, since all following args are for next stage.
139 else
140 local args=()
141
142 IFS='|' read -a args <<< "$arg"
143 comps="${args[@]} $comps"
144 break;
145 fi
146 done
147 done <<< "$combs"
148
149 echo "$comps"
150 }
151
152 # Given the subcommand format, and the current command line input,
153 # finds keywords of all possible completions.
154 subcmd_find_keyword_based_on_input() {
155 local format="$1"
156 local cmd_line=($2)
157 local mult=
158 local combs=
159 local comps=
160 local arg line
161
162 # finds all combinations by searching for '{}'.
163 # there should only be one '{}', otherwise, the
164 # command format should be changed to multiple commands.
165 mult="$(sed -n 's/^.*{\(.*\)}.*$/ \1/p' <<< "$format" | tr '|' '\n' | cut -c1-)"
166 if [ -n "$mult" ]; then
167 while read line; do
168 local tmp=
169
170 tmp="$(sed -e "s@{\(.*\)}@$line@" <<< "$format")"
171 combs="$combs@$tmp"
172 done <<< "$mult"
173 combs="$(tr '@' '\n' <<< "$combs")"
174 else
175 combs="$format"
176 fi
177
178 # Now, starts from the first argument, narrows down the
179 # subcommand format combinations.
180 for arg in "${subcmd_line[@]}"; do
181 local kword possible_comps
182
183 # Finds next level possible comps.
184 possible_comps=$(find_possible_comps "$combs")
185 # Finds the kword.
186 kword="$(arg_to_kwords "$arg" "$possible_comps")"
187 # Returns if could not find 'kword'
188 if [ -z "$kword" ]; then
189 return
190 fi
191 # Trims the 'combs', keeps context only after 'kword'.
192 if [ -n "$combs" ]; then
193 combs="$(sed -n "s@^.*\[\{0,1\}$kword|\{0,1\}[a-z_]*\]\{0,1\} @@p" <<< "$combs")"
194 fi
195 done
196 comps="$(find_possible_comps "$combs")"
197
198 echo "$comps"
199 }
200
201
202
203 # Helper
204 # ======
205 #
206 #
207 #
208 # Prints the input to stderr. $_PRINTF_ENABLE must be filled.
209 printf_stderr() {
210 local stderr_out="$@"
211
212 if [ -n "$_PRINTF_ENABLE" ]; then
213 printf "\n$stderr_out" 1>&2
214 fi
215 }
216
217 # Extracts the bash prompt PS1, outputs it with the input argument
218 # via 'printf_stderr'.
219 #
220 # Original idea inspired by:
221 # http://stackoverflow.com/questions/10060500/bash-how-to-evaluate-ps1-ps2
222 #
223 # The code below is taken from Peter Amidon. His change makes it more
224 # robust.
225 extract_bash_prompt() {
226 local myPS1 v
227
228 myPS1="$(sed 's/Begin prompt/\\Begin prompt/; s/End prompt/\\End prompt/' <<< "$PS1")"
229 v="$(bash --norc --noprofile -i 2>&1 <<< $'PS1=\"'"$myPS1"$'\" \n# Begin prompt\n# End prompt')"
230 v="${v##*# Begin prompt}"
231 _BASH_PROMPT="$(tail -n +2 <<< "${v%# End prompt*}" | sed 's/\\Begin prompt/Begin prompt/; s/\\End prompt/End prompt/')"
232 }
233
234
235
236 # Keyword Conversion
237 # ==================
238 #
239 #
240 #
241 # All completion functions.
242 complete_bridge () {
243 local result error
244
245 result=$(ovs-vsctl list-br 2>/dev/null | grep -- "^$1") || error="TRUE"
246
247 if [ -z "$error" ]; then
248 echo "${result}"
249 fi
250 }
251
252 complete_port () {
253 local ports result error
254 local all_ports
255
256 all_ports=$(ovs-vsctl --format=table \
257 --no-headings \
258 --columns=name \
259 list Port 2>/dev/null) || error="TRUE"
260 ports=$(printf "$all_ports" | sort | tr -d '"' | uniq -u)
261 result=$(grep -- "^$1" <<< "$ports")
262
263 if [ -z "$error" ]; then
264 echo "${result}"
265 fi
266 }
267
268 complete_iface () {
269 local bridge bridges result error
270
271 bridges=$(ovs-vsctl list-br 2>/dev/null) || error="TRUE"
272 for bridge in $bridges; do
273 local ifaces
274
275 ifaces=$(ovs-vsctl list-ifaces "${bridge}" 2>/dev/null) || error="TRUE"
276 result="${result} ${ifaces}"
277 done
278
279 if [ -z "$error" ]; then
280 echo "${result}"
281 fi
282 }
283
284 complete_dp () {
285 local dps result error
286
287 dps=$(ovs-appctl dpctl/dump-dps 2>/dev/null | cut -d '@' -f2) || error="TRUE"
288 result=$(grep -- "^$1" <<< "$dps")
289
290 if [ -z "$error" ]; then
291 echo "${result}"
292 fi
293 }
294
295 # Converts the argument (e.g. bridge/port/interface/dp name) to
296 # the corresponding keywords.
297 # Returns empty string if could not map the arg to any keyword.
298 arg_to_kwords() {
299 local arg="$1"
300 local possible_kwords=($2)
301 local non_parsables=()
302 local match=
303 local kword
304
305 for kword in ${possible_kwords[@]}; do
306 case "$kword" in
307 bridge|switch)
308 match="$(complete_bridge "$arg")"
309 ;;
310 port)
311 match="$(complete_port "$arg")"
312 ;;
313 interface|iface)
314 match="$(complete_iface "$arg")"
315 ;;
316 dp_name|dp)
317 match="$(complete_dp "$arg")"
318 ;;
319 *)
320 if [ "$arg" = "$kword" ]; then
321 match="$kword"
322 else
323 non_parsables+=("$kword")
324 continue
325 fi
326 ;;
327 esac
328
329 if [ -n "$match" ]; then
330 echo "$kword"
331 return
332 fi
333 done
334
335 # If there is only one non-parsable kword,
336 # just assumes the user input it.
337 if [ "${#non_parsables[@]}" -eq "1" ]; then
338 echo "$non_parsables"
339 return
340 fi
341 }
342
343 # Expands the keywords to the corresponding instance names.
344 kwords_to_args() {
345 local possible_kwords=($@)
346 local args=()
347 local printf_expand_once=
348 local kword
349
350 for kword in ${possible_kwords[@]}; do
351 local match=
352
353 case "${kword}" in
354 bridge|switch)
355 match="$(complete_bridge "")"
356 ;;
357 port)
358 match="$(complete_port "")"
359 ;;
360 interface|iface)
361 match="$(complete_iface "")"
362 ;;
363 dp_name|dp)
364 match="$(complete_dp "")"
365 ;;
366 -*)
367 # Treats option as kword as well.
368 match="$kword"
369 ;;
370 *)
371 match=
372 ;;
373 esac
374 match=$(echo "$match" | tr '\n' ' ' | tr -s ' ' | sed -e 's/^[ \t]*//')
375 args+=( $match )
376 if [ -n "$_PRINTF_ENABLE" ]; then
377 local output_stderr=
378
379 if [ -z "$printf_expand_once" ]; then
380 printf_expand_once="once"
381 printf -v output_stderr "\nArgument expansion:\n"
382 fi
383 printf -v output_stderr "$output_stderr available completions \
384 for keyword \"%s\": %s " "$kword" "$match"
385
386 printf_stderr "$output_stderr"
387 fi
388 done
389
390 echo "${args[@]}"
391 }
392
393
394
395
396 # Parse and Compgen
397 # =================
398 #
399 #
400 #
401 # This function takes the current command line arguments as input,
402 # finds the command format and returns the possible completions.
403 parse_and_compgen() {
404 local command=$_COMMAND
405 local subcmd_line=($@)
406 local subcmd=${subcmd_line[0]}
407 local target=
408 local subcmd_format=
409 local comp_keywords=
410 local comp_wordlist=
411
412 if [ -n "$_APPCTL_TARGET" ]; then
413 target="--target $_APPCTL_TARGET"
414 fi
415
416 # Extracts the subcommand format.
417 subcmd_format="$($command $target list-commands 2>/dev/null | tail -n +2 | cut -c3- \
418 | awk -v opt=$subcmd '$1 == opt {print $0}' | tr -s ' ' )"
419
420 # Finds the possible completions based on input argument.
421 comp_keyword="$(subcmd_find_keyword_based_on_input "$subcmd_format" \
422 "${subcmd_line[@]}")"
423
424 # Prints subcommand format and expands the keywords if 'comp_keyword'
425 # is not empty.
426 if [ -n "$comp_keyword" ]; then
427 printf_stderr "$(printf "\nCommand format:\n%s" "$subcmd_format")"
428 comp_wordlist="$(kwords_to_args "$comp_keyword")"
429 # If there is no expanded completions, returns "NO_EXPAN" to
430 # distinguish from the case of no available completions.
431 if [ -z "$comp_wordlist" ]; then
432 echo "NO_EXPAN"
433 else
434 echo "$comp_wordlist"
435 fi
436 fi
437 }
438
439
440
441 # Compgen Helper
442 # ==============
443 #
444 #
445 #
446 # Takes the current command line arguments and returns the possible
447 # completions.
448 #
449 # At the beginning, the options are checked and completed. For ovs-appctl
450 # completion, The function looks for the --target option which gives the
451 # target daemon name. If it is not provided, by default, 'ovs-vswitchd'
452 # is used.
453 #
454 # Then, tries to locate and complete the subcommand. If the subcommand
455 # is provided, the following arguments are passed to the 'parse_and_compgen'
456 # function to figure out the corresponding completion of the subcommand.
457 #
458 # Returns the completion arguments on success.
459 ovs_comp_helper() {
460 local cmd_line_so_far=($@)
461 local comp_wordlist _subcmd options i
462 local j=-1
463
464 # Parse the command-line args till we find the subcommand.
465 for i in "${!cmd_line_so_far[@]}"; do
466 # if $i is not greater than $j, it means the previous iteration
467 # skips not-visited args. so, do nothing and catch up.
468 if [ $i -le $j ]; then continue; fi
469 j=$i
470 if [[ "${cmd_line_so_far[i]}" =~ ^--* ]]; then
471 # If --target is found, locate the target daemon.
472 # Else, it is an option command, fill the comp_wordlist with
473 # all options.
474 if [ "$_COMMAND" = "ovs-appctl" ] \
475 && [[ "${cmd_line_so_far[i]}" =~ ^--target$ ]]; then
476 _APPCTL_TARGET="ovs-vswitchd"
477
478 if [ -n "${cmd_line_so_far[j+1]}" ]; then
479 local daemon
480
481 for daemon in $_POSSIBLE_TARGETS; do
482 # Greps "$daemon" in argument, since the argument may
483 # be the path to the pid file.
484 if [ "$daemon" = "${cmd_line_so_far[j+1]}" ]; then
485 _APPCTL_TARGET="$daemon"
486 ((j++))
487 break
488 fi
489 done
490 continue
491 else
492 comp_wordlist="$_POSSIBLE_TARGETS"
493 break
494 fi
495 else
496 options="$(extract_options $_COMMAND)"
497 # See if we could find the exact option.
498 if [ "${cmd_line_so_far[i]}" = "$(grep -- "${cmd_line_so_far[i]}" <<< "$options")" ]; then
499 # If an argument is required and next argument is non-empty,
500 # skip it. Else, return directly.
501 if [ -n "$(option_require_arg "${cmd_line_so_far[i]}")" ]; then
502 ((j++))
503 if [ -z "${cmd_line_so_far[j]}" ]; then
504 printf_stderr "\nOption requires an arugment."
505 return
506 fi
507 fi
508 continue
509 # Else, need to keep completing on option.
510 else
511 comp_wordlist="$options"
512 break
513 fi
514 fi
515 fi
516 # Takes the first non-option argument as subcmd.
517 _subcmd="${cmd_line_so_far[i]}"
518 break
519 done
520
521 if [ -z "$comp_wordlist" ]; then
522 # If the subcommand is not found, provides all subcmds and options.
523 if [ -z "$_subcmd" ]; then
524 comp_wordlist="$(extract_subcmds) $(extract_options)"
525 # Else parses the current arguments and finds the possible completions.
526 else
527 # $j stores the index of the subcmd in cmd_line_so_far.
528 comp_wordlist="$(parse_and_compgen "${cmd_line_so_far[@]:$j}")"
529 fi
530 fi
531
532 echo "$comp_wordlist"
533 }
534
535 # Compgen
536 # =======
537 #
538 #
539 #
540 # The compgen function.
541 _ovs_command_complete() {
542 local cur prev
543
544 _COMMAND=${COMP_WORDS} # element 0 is the command.
545 COMPREPLY=()
546 cur=${COMP_WORDS[COMP_CWORD]}
547
548 # Do not print anything at first [TAB] execution.
549 if [ "$COMP_TYPE" -eq "9" ]; then
550 _PRINTF_ENABLE=
551 else
552 _PRINTF_ENABLE="enabled"
553 fi
554
555 # Extracts bash prompt PS1.
556 if [ "$1" != "debug" ]; then
557 extract_bash_prompt
558 fi
559
560 # Invokes the helper function to get all available completions.
561 # Always not input the 'COMP_WORD' at 'COMP_CWORD', since it is
562 # the one to be completed.
563 _COMP_WORDLIST="$(ovs_comp_helper \
564 ${COMP_WORDS[@]:1:COMP_CWORD-1})"
565
566 # This is a hack to prevent autocompleting when there is only one
567 # available completion and printf disabled.
568 if [ -z "$_PRINTF_ENABLE" ] && [ -n "$_COMP_WORDLIST" ]; then
569 _COMP_WORDLIST="$_COMP_WORDLIST none void no-op"
570 fi
571
572 if [ -n "$_PRINTF_ENABLE" ] && [ -n "$_COMP_WORDLIST" ]; then
573 if [ -n "$(echo $_COMP_WORDLIST | tr ' ' '\n' | sed -e '/NO_EXPAN/d' | grep -- "^$cur")" ]; then
574 printf_stderr "\nAvailable completions:\n"
575 else
576 if [ "$1" != "debug" ]; then
577 # If there is no match between '$cur' and the '$_COMP_WORDLIST'
578 # prints a bash prompt since the 'complete' will not print it.
579 printf_stderr "\n$_BASH_PROMPT${COMP_WORDS[@]}"
580 fi
581 fi
582 fi
583
584 if [ "$1" = "debug" ]; then
585 printf_stderr "$(echo $_COMP_WORDLIST | tr ' ' '\n' | sort -u | sed -e '/NO_EXPAN/d' | grep -- "$cur")\n"
586 else
587 if [ -n "$_COMP_WORDLIST" ]; then
588 COMPREPLY=( $(compgen -W "$(echo $_COMP_WORDLIST | tr ' ' '\n' \
589 | sort -u | sed -e '/NO_EXPAN/d')" -- $cur) )
590 else
591 compopt -o nospace
592 # If there is no completions, just complete on file path.
593 _filedir
594 fi
595 fi
596
597 return 0
598 }
599
600 # Debug mode.
601 if [ "$1" = "debug" ]; then
602 shift
603 COMP_TYPE=0
604 COMP_WORDS=($@)
605 COMP_CWORD="$(expr $# - 1)"
606
607 # If the last argument is TAB, it means that the previous
608 # argument is already complete and script should complete
609 # next argument which is not input yet. This hack is for
610 # compromising the fact that bash cannot take unquoted
611 # empty argument.
612 if [ "${COMP_WORDS[$COMP_CWORD]}" = "TAB" ]; then
613 COMP_WORDS[$COMP_CWORD]=""
614 fi
615
616 _ovs_command_complete "debug"
617 # Normal compgen mode.
618 else
619 complete -F _ovs_command_complete ovs-appctl
620 complete -F _ovs_command_complete ovs-ofctl
621 complete -F _ovs_command_complete ovs-dpctl
622 complete -F _ovs_command_complete ovsdb-tool
623 fi