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