]> git.proxmox.com Git - ovs.git/blame - utilities/ovs-appctl-bashcomp.bash
dpctl: Fix dpctl process command parameter error.
[ovs.git] / utilities / ovs-appctl-bashcomp.bash
CommitLineData
423ede18
AW
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.
63extract_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.
82extract_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.
96option_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.
116find_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,
b8905bfa
AW
153# finds keywords of all possible completions.
154subcmd_find_keyword_based_on_input() {
423ede18
AW
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
1fdbfd4e 193 combs="$(sed -n "s@^.*\[\{0,1\}$kword|\{0,1\}[a-z_]*\]\{0,1\} @@p" <<< "$combs")"
423ede18
AW
194 fi
195 done
196 comps="$(find_possible_comps "$combs")"
197
b8905bfa 198 echo "$comps"
423ede18
AW
199}
200
201
202
203# Helper
204# ======
205#
206#
207#
208# Prints the input to stderr. $_PRINTF_ENABLE must be filled.
209printf_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.
225extract_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.
242complete_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
252complete_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
268complete_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
284complete_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.
298arg_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.
344kwords_to_args() {
345 local possible_kwords=($@)
346 local args=()
b8905bfa 347 local printf_expand_once=
423ede18
AW
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=
b8905bfa 409 local comp_keywords=
423ede18
AW
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
423ede18 420 # Finds the possible completions based on input argument.
b8905bfa 421 comp_keyword="$(subcmd_find_keyword_based_on_input "$subcmd_format" \
423ede18
AW
422 "${subcmd_line[@]}")"
423
b8905bfa
AW
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
423ede18
AW
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.
459ovs_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
b8905bfa
AW
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
423ede18
AW
582 fi
583
584 if [ "$1" = "debug" ]; then
b8905bfa 585 printf_stderr "$(echo $_COMP_WORDLIST | tr ' ' '\n' | sort -u | sed -e '/NO_EXPAN/d' | grep -- "$cur")\n"
423ede18 586 else
b8905bfa
AW
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
b6b0e049 591 compopt -o nospace
b8905bfa 592 # If there is no completions, just complete on file path.
b6b0e049 593 _filedir
b8905bfa 594 fi
423ede18
AW
595 fi
596
597 return 0
598}
599
423ede18
AW
600# Debug mode.
601if [ "$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.
1b8c7b96
AW
612 if [ "${COMP_WORDS[$COMP_CWORD]}" = "TAB" ]; then
613 COMP_WORDS[$COMP_CWORD]=""
423ede18
AW
614 fi
615
616 _ovs_command_complete "debug"
617# Normal compgen mode.
618else
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
53d9f383 623fi