This patch is a RFC for the bash command-line completion script for ovs-appctl command. Right now, the script can do the following:
- accept user specified '--target' and query available completions from the corresponding deamons. - once the subcommand (e.g. ofproto/trace) has been given, the script will print the subcommand format. - the script can convert between keywords like 'bridge/port/interface/dp' and the available record in ovsdb. - the script can print and auto-complete the half input argument. To use the script, either copy it inside /etc/bash_completion.d/ or manually run it via . ovs-appctl-compgen.bash. Also, a unit testsuite is provided as ovs-appctl-compgen-test.bash. It is suggested that this testsuit be run inside ovs-sandbox. Signed-off-by: Alex Wang <al...@nicira.com> --- utilities/ovs-appctl-compgen-test.bash | 586 ++++++++++++++++++++++++++++++++ utilities/ovs-appctl-compgen.bash | 516 ++++++++++++++++++++++++++++ 2 files changed, 1102 insertions(+) create mode 100755 utilities/ovs-appctl-compgen-test.bash create mode 100755 utilities/ovs-appctl-compgen.bash diff --git a/utilities/ovs-appctl-compgen-test.bash b/utilities/ovs-appctl-compgen-test.bash new file mode 100755 index 0000000..a20788f --- /dev/null +++ b/utilities/ovs-appctl-compgen-test.bash @@ -0,0 +1,586 @@ +#!/bin/bash +# +# Tests for the ovs-appctl-compgen.bash +# +# Please run this with ovs-appctl-compgen.bash script inside +# ovs-sandbox, under the same directory. +# +# For information about running the ovs-sandbox, please refer to +# the tutorial directory. +# +# +# +COMP_OUTPUT= +TMP= +EXPECT= +TEST_RESULT= + +TEST_COUNTER=0 +TEST_TARGETS=(ovs-vswitchd ovsdb-server ovs-ofctl) + +# +# Helper functions. +# +get_command_format() { + local input="$@" + + echo "$(grep -A 1 "Command format" <<< "$input" | tail -n+2)" +} + +get_argument_expansion() { + local input="$@" + + echo "$(grep -- "argument keyword .* is expanded to" <<< "$input" | sed -e 's/^[ \t]*//')" +} + +get_available_completions() { + local input="$@" + + echo "$(sed -e '1,/Available/d' <<< "$input" | tail -n+2)" +} + +reset_globals() { + COMP_OUTPUT= + TMP= + EXPECT= + TEST_RESULT= +} + +# +# $1: Test name. +# $2: ok or fail. +# +print_result() { + (( TEST_COUNTER++ )) + printf "%2d: %-70s %s\n" "$TEST_COUNTER" "$1" "$2" +} + +# +# Sub-tests. +# +ovs_apptcl_TAB() { + local target="$1" + local target_line= + local comp_output tmp expect + + if [ -n "$target" ]; then + target_line="--target $target" + fi + comp_output="$(bash ovs-appctl-compgen.bash debug ovs-appctl $target_line TAB 2>&1)" + tmp="$(get_available_completions "$comp_output")" + expect="$(ovs-appctl --option | sort | sed -n '/^--.*/p' | cut -d '=' -f1) +$(ovs-appctl $target_line help | tail -n +2 | cut -c3- | cut -d ' ' -f1)" + if [ "$tmp" = "$expect" ]; then + echo "ok" + else + echo "fail" + fi +} + +# +# Test preparation. +# +ovs-vsctl add-br br0 +ovs-vsctl add-port br0 p1 + + +# +# Begin the test. +# +cat <<EOF + +## ------------------------------ ## +## ovs-appctl-compgen unit tests. ## +## ------------------------------ ## + +EOF + + +# complete ovs-appctl --tar[TAB] + +reset_globals + +COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl --tar 2>&1)" +TMP="$(get_available_completions "$COMP_OUTPUT")" +EXPECT="--target" +if [ "$TMP" = "$EXPECT" ]; then + TEST_RESULT=ok +else + TEST_RESULT=fail +fi + +print_result "complete ovs-appctl --targ[TAB]" "$TEST_RESULT" + + +# complete ovs-appctl --target [TAB] + +reset_globals + +COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl --target TAB 2>&1)" +TMP="$(get_available_completions "$COMP_OUTPUT")" +EXPECT="$(echo ${TEST_TARGETS[@]} | tr ' ' '\n' | sort)" +if [ "$TMP" = "$EXPECT" ]; then + TEST_RESULT=ok +else + TEST_RESULT=fail +fi + +print_result "complete ovs-appctl --target [TAB]" "$TEST_RESULT" + + +# complete ovs-appctl [TAB] +# complete ovs-appctl --target ovs-vswitchd [TAB] +# complete ovs-appctl --target ovsdb-server [TAB] +# complete ovs-appctl --target ovs-ofctl [TAB] + +reset_globals + +for i in NONE ${TEST_TARGETS[@]}; do + input= + test_target= + + if [ "$i" != "NONE" ]; then + input="$i" + test_target="--target $i " + fi + + if [ "$i" = "ovs-ofctl" ]; then + ovs-ofctl monitor br0 --detach --no-chdir --pidfile + fi + + TEST_RESULT="$(ovs_apptcl_TAB $input)" + + print_result "complete ovs-appctl ${test_target}[TAB]" "$TEST_RESULT" + + if [ "$i" = "ovs-ofctl" ]; then + ovs-appctl --target ovs-ofctl exit + fi +done + + +# check all subcommand formats + +reset_globals + +TMP="$(ovs-appctl help | tail -n +2 | cut -c3- | cut -d ' ' -f1)" + +# for each subcmd, check the print of subcmd format +for i in $TMP; do + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl $i TAB 2>&1)" + tmp="$(get_command_format "$COMP_OUTPUT")" + EXPECT="$(ovs-appctl help | tail -n+2 | cut -c3- | grep -- "^$i " | tr -s ' ')" + if [ "$tmp" = "$EXPECT" ]; then + TEST_RESULT=ok + else + TEST_RESULT=fail + break + fi +done + +print_result "check all subcommand format" "$TEST_RESULT" + + +# complex completion check - bfd/set-forwarding +# bfd/set-forwarding [interface] normal|false|true +# test expansion of 'interface' + +reset_globals + +for i in loop_once; do + # check the top level completion. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl bfd/set-forwarding TAB 2>&1)" + TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')" + EXPECT="argument keyword \"normal\" is expanded to: normal +argument keyword \"false\" is expanded to: false +argument keyword \"true\" is expanded to: true +argument keyword \"interface\" is expanded to: p1" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # check the available completions. + TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')" + EXPECT="false normal p1 true" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # set argument to 'true', there should be no more completions. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl bfd/set-forwarding true TAB 2>&1)" + TMP="$(sed -e '/./,$!d' <<< "$COMP_OUTPUT")" + EXPECT="Command format: +bfd/set-forwarding [interface] normal|false|true" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # set argument to 'p1', there should still be the completion for booleans. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl bfd/set-forwarding p1 TAB 2>&1)" + TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')" + EXPECT="argument keyword \"normal\" is expanded to: normal +argument keyword \"false\" is expanded to: false +argument keyword \"true\" is expanded to: true" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # check the available completions. + TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')" + EXPECT="false normal true" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # set argument to 'p1 false', there should still no more completions. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl bfd/set-forwarding p1 false TAB 2>&1)" + TMP="$(sed -e '/./,$!d' <<< "$COMP_OUTPUT")" + EXPECT="Command format: +bfd/set-forwarding [interface] normal|false|true" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + TEST_RESULT=ok +done + +print_result "complex completion check - bfd/set-forwarding" "$TEST_RESULT" + + +# complex completion check - lacp/show +# lacp/show [port] +# test expansion on 'port' + +reset_globals + +for i in loop_once; do + # check the top level completion. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl lacp/show TAB 2>&1)" + TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')" + EXPECT="argument keyword \"port\" is expanded to: br0 p1" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # check the available completions. + TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')" + EXPECT="br0 p1" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # set argument to 'p1', there should be no more completions. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl lacp/show p1 TAB 2>&1)" + TMP="$(sed -e '/./,$!d' <<< "$COMP_OUTPUT")" + EXPECT="Command format: +lacp/show [port]" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + TEST_RESULT=ok +done + +print_result "complex completion check - lacp/show" "$TEST_RESULT" + + +# complex completion check - ofproto/trace +# ofproto/trace {[dp_name] odp_flow | bridge br_flow} [-generate|packet] +# test expansion on 'dp|dp_name' and 'bridge' + +reset_globals + +for i in loop_once; do + # check the top level completion. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl ofproto/trace TAB 2>&1)" + TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')" + EXPECT="argument keyword \"bridge\" is expanded to: br0 +argument keyword \"odp_flow\" is expanded to: odp_flow +argument keyword \"dp_name\" is expanded to: ovs-system" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # check the available completions. + TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')" + EXPECT="br0 odp_flow ovs-system" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # set argument to 'ovs-system', should go to the dp-name path. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl ofproto/trace ovs-system TAB 2>&1)" + TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')" + EXPECT="argument keyword \"odp_flow\" is expanded to: odp_flow" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # check the available completions. + TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')" + EXPECT="odp_flow" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # set odp_flow to some random string, should go to the next level. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl ofproto/trace ovs-system "in_port(123),mac(),ip,tcp" TAB 2>&1)" + TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')" + EXPECT="argument keyword \"-generate\" is expanded to: -generate +argument keyword \"packet\" is expanded to: packet" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # check the available completions. + TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')" + EXPECT="-generate packet" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # set packet to some random string, there should be no more completions. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl ofproto/trace ovs-system "in_port(123),mac(),ip,tcp" "ABSJDFLSDJFOIWEQR" TAB 2>&1)" + TMP="$(sed -e '/./,$!d' <<< "$COMP_OUTPUT")" + EXPECT="Command format: +ofproto/trace {[dp_name] odp_flow | bridge br_flow} [-generate|packet]" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # set argument to 'br0', should go to the bridge path. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl ofproto/trace br0 TAB 2>&1)" + TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')" + EXPECT="argument keyword \"br_flow\" is expanded to: br_flow" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # check the available completions. + TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')" + EXPECT="br_flow" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # set argument to some random string, should go to the odp_flow path. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl ofproto/trace "in_port(123),mac(),ip,tcp" TAB 2>&1)" + TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')" + EXPECT="argument keyword \"-generate\" is expanded to: -generate +argument keyword \"packet\" is expanded to: packet" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # check the available completions. + TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')" + EXPECT="-generate packet" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + TEST_RESULT=ok +done + +print_result "complex completion check - ofproto/trace" "$TEST_RESULT" + + +# complex completion check - vlog/set +# vlog/set {spec | PATTERN:facility:pattern} +# test non expandable arguments + +reset_globals + +for i in loop_once; do + # check the top level completion. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl vlog/set TAB 2>&1)" + TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')" + EXPECT="argument keyword \"PATTERN:facility:pattern\" is expanded to: PATTERN:facility:pattern +argument keyword \"spec\" is expanded to: spec" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # check the available completions. + TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')" + EXPECT="PATTERN:facility:pattern spec" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # set argument to random 'abcd', there should be no more completions. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl vlog/set abcd TAB 2>&1)" + TMP="$(sed -e '/./,$!d' <<< "$COMP_OUTPUT")" + EXPECT="Command format: +vlog/set {spec | PATTERN:facility:pattern}" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + TEST_RESULT=ok +done + +print_result "complex completion check - vlog/set" "$TEST_RESULT" + + +# complete after delete port + +reset_globals +ovs-vsctl del-port p1 + +for i in loop_once; do + # check match on interface, there should be no available interface expansion. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl bfd/set-forwarding TAB 2>&1)" + TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')" + EXPECT="argument keyword \"normal\" is expanded to: normal +argument keyword \"false\" is expanded to: false +argument keyword \"true\" is expanded to: true +argument keyword \"interface\" is expanded to:" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # check the available completions. + TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')" + EXPECT="false normal true" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # check match on port, there should be no p1 as port. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl lacp/show TAB 2>&1)" + TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')" + EXPECT="argument keyword \"port\" is expanded to: br0" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # check the available completions. + TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')" + EXPECT="br0" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + TEST_RESULT=ok +done + +print_result "complete after delete port" "$TEST_RESULT" + + +# complete after delete bridge + +reset_globals +ovs-vsctl del-br br0 +for i in loop_once; do + # check match on port, there should be no p1 as port. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl bridge/dump-flows TAB 2>&1)" + TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')" + EXPECT="argument keyword \"bridge\" is expanded to:" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # check the available completions. + TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')" + EXPECT= + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + TEST_RESULT=ok +done + +print_result "complete after delete bridge" "$TEST_RESULT" + + +# negative test - incorrect subcommand + +reset_globals + +for i in loop_once; do + # incorrect subcommand + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl ERROR 2>&1)" + TMP="$(echo "$COMP_OUTPUT" | sed -e 's/[ \t]*$//' | sed -e '/./,$!d')" + EXPECT= + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl ERROR TAB 2>&1)" + TMP="$(echo "$COMP_OUTPUT" | sed -e 's/[ \t]*$//' | sed -e '/./!d')" + EXPECT="Command format:" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + TEST_RESULT=ok +done + +print_result "negative test - incorrect subcommand" "$TEST_RESULT" + + +# negative test - no ovs-vswitchd +# negative test - no ovsdb-server +# negative test - no ovs-ofctl +# should not see any error. + +killall ovs-vswitchd ovsdb-server + +for i in ${TEST_TARGETS[@]}; do + for j in loop_once; do + reset_globals + + daemon="$i" + + # should show no avaiable subcommands. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl --target $daemon TAB 2>&1)" + TMP="$(get_available_completions "$COMP_OUTPUT")" + EXPECT="$(ovs-appctl --option | sort | sed -n '/^--.*/p' | cut -d '=' -f1)" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + # should not match any input. + COMP_OUTPUT="$(bash ovs-appctl-compgen.bash debug ovs-appctl --target $daemon ERROR SUBCMD TAB 2>&1)" + TMP="$(echo "$COMP_OUTPUT" | sed -e 's/[ \t]*$//' | sed -e '/./!d')" + EXPECT="Command format:" + if [ "$TMP" != "$EXPECT" ]; then + TEST_RESULT=fail + break + fi + + TEST_RESULT=ok + done + print_result "negative test - no $daemon" "$TEST_RESULT" +done \ No newline at end of file diff --git a/utilities/ovs-appctl-compgen.bash b/utilities/ovs-appctl-compgen.bash new file mode 100755 index 0000000..4c4a4e5 --- /dev/null +++ b/utilities/ovs-appctl-compgen.bash @@ -0,0 +1,516 @@ +#!/bin/bash +# +# A bash command completion script for ovs-appctl. +# +# Keywords +# ======== +# +# +# +# Expandable keywords. +_KWORDS=(bridge port interface dp_name dp) +# Printf enabler. +_PRINTF_ENABLE= +# Bash prompt. +_BASH_PROMPT= +# Target in the current completion, default ovs-vswitchd. +_APPCTL_TARGET="ovs-vswitchd" +# Output to the compgen. +_APPCTL_COMP_WORDLIST= +# Possible targets. +_POSSIBLE_TARGETS="ovs-vswitchd ovsdb-server ovs-ofctl" + +# Command Extraction +# ================== +# +# +# +# Extracts all subcommands of target. +# If cannot find target, returns nothing. +extract_subcmds() { + local target=$_APPCTL_TARGET + local subcmds= + local error + + subcmds="$(ovs-appctl --target $target help 2>/dev/null | tail -n +2 | cut -c3- \ + | cut -d ' ' -f1)" || error="TRUE" + + if [ -z "$error" ]; then + echo "$subcmds" + fi +} + +# Extracts all long options of ovs-appctl. +extract_options() { + echo "$(ovs-appctl --option | sort | sed -n '/^--.*/p' | cut -d '=' -f1)" +} + + + +# Combination Discovery +# ===================== +# +# +# +# Given the subcommand formats, finds all possible completions +# at current completion level. +find_possible_comps() { + local combs="$@" + local comps= + local line + + while read line; do + local arg= + + for arg in $line; do + # If it is an optional argument, gets all completions, + # and continues. + if [ ! -z "$(grep -- "\[*\]" <<< "$arg")" ]; then + local opt_arg="$(sed -e 's/^\[\(.*\)\]$/\1/' <<< "$arg")" + local opt_args=() + + IFS='|' read -a opt_args <<< "$opt_arg" + comps="${opt_args[@]} $comps" + # If it is a compulsory argument, adds it to the comps + # and break, since all following args are for next stage. + else + local args=() + + IFS='|' read -a args <<< "$arg" + comps="${args[@]} $comps" + break; + fi + done + done <<< "$combs" + + echo "$comps" +} + +# Given the subcommand format, and the current command line input, +# finds all possible completions. +subcmd_find_comp_based_on_input() { + local format="$1" + local cmd_line=($2) + local mult= + local combs= + local comps= + local arg line + + # finds all combinations by searching for '{}'. + # there should only be one '{}', otherwise, the + # command format should be changed to multiple commands. + mult="$(sed -n 's/^.*{\(.*\)}.*$/ \1/p' <<< "$format" | tr '|' '\n' | cut -c1-)" + if [ -n "$mult" ]; then + while read line; do + local tmp= + + tmp="$(sed -e "s@{\(.*\)}@$line@" <<< "$format")" + combs="$combs@$tmp" + done <<< "$mult" + combs="$(tr '@' '\n' <<< "$combs")" + else + combs="$format" + fi + + # Now, starts from the first argument, narrows down the + # subcommand format combinations. + for arg in "${subcmd_line[@]}"; do + local kword possible_comps + + # Finds next level possible comps. + possible_comps=$(find_possible_comps "$combs") + # Finds the kword. + kword="$(arg_to_kwords "$arg" "$possible_comps")" + # Trims the 'combs', keeps context only after 'kword'. + if [ -n "$combs" ]; then + combs="$(sed -n "s@^.*\[\?$kword|\?[a-z_]*\]\? @@p" <<< "$combs")" + fi + done + comps="$(find_possible_comps "$combs")" + + echo "$(kwords_to_args "$comps")" +} + + + +# Helper +# ====== +# +# +# +# Prints the input to stderr. $_PRINTF_ENABLE must be filled. +printf_stderr() { + local stderr_out="$@" + + if [ -n "$_PRINTF_ENABLE" ]; then + printf "\n$stderr_out" 1>&2 + fi +} + +# Extracts the bash prompt PS1, outputs it with the input argument +# via 'printf_stderr'. +# +# Idea inspired by: +# http://stackoverflow.com/questions/10060500/bash-how-to-evaluate-ps1-ps2 +extract_bash_prompt() { + if [ -z "$_BASH_PROMPT" ]; then + _BASH_PROMPT="$(echo want_bash_prompt_PS1 | bash -i 2>&1 \ + | tail -1 | sed 's/ exit//g')" + fi +} + + + +# Keyword Conversion +# ================== +# +# +# +# All completion functions. +complete_bridge () { + local result error + + result=$(ovs-vsctl list-br 2>/dev/null | grep -- "^$1") || error="TRUE" + + if [ -z "$error" ]; then + echo "${result}" + fi +} + +complete_port () { + local ports result error + local all_ports + + all_ports=$(ovs-vsctl --format=table \ + --no-headings \ + --columns=name \ + list Port 2>/dev/null) || error="TRUE" + ports=$(printf "$all_ports" | sort | tr -d '"' | uniq -u) + result=$(grep -- "^$1" <<< "$ports") + + if [ -z "$error" ]; then + echo "${result}" + fi +} + +complete_iface () { + local bridge bridges result error + + bridges=$(ovs-vsctl list-br 2>/dev/null) || error="TRUE" + for bridge in $bridges; do + local ifaces + + ifaces=$(ovs-vsctl list-ifaces "${bridge}" 2>/dev/null) || error="TRUE" + result="${result} ${ifaces}" + done + + if [ -z "$error" ]; then + echo "${result}" + fi +} + +complete_dp () { + local dps result error + + dps=$(ovs-appctl dpctl/dump-dps 2>/dev/null | cut -d '@' -f2) || error="TRUE" + result=$(grep -- "^$1" <<< "$dps") + + if [ -z "$error" ]; then + echo "${result}" + fi +} + +# Converts the argument (e.g. bridge/port/interface/dp name) to +# the corresponding keywords. +# Returns empty string if could not map the arg to any keyword. +arg_to_kwords() { + local arg="$1" + local possible_kwords=($2) + local non_parsables=() + local match= + local kword + + for kword in ${possible_kwords[@]}; do + case "$kword" in + bridge) + match="$(complete_bridge "$arg")" + ;; + port) + match="$(complete_port "$arg")" + ;; + interface) + match="$(complete_iface "$arg")" + ;; + dp_name|dp) + match="$(complete_dp "$arg")" + ;; + *) + if [ "$arg" = "$kword" ]; then + match="$kword" + else + non_parsables+=("$kword") + continue + fi + ;; + esac + + if [ -n "$match" ]; then + echo "$kword" + return + fi + done + + # If there is only one non-parsable kword, + # just assumes the user input it. + if [ "${#non_parsables[@]}" -eq "1" ]; then + echo "$non_parsables" + return + fi +} + +# Expands the keywords to the corresponding instance names. +kwords_to_args() { + local possible_kwords=($@) + local args=() + local kword + + for kword in ${possible_kwords[@]}; do + local match= + + case "${kword}" in + bridge) + match="$(complete_bridge "")" + ;; + port) + match="$(complete_port "")" + ;; + interface) + match="$(complete_iface "")" + ;; + dp_name|dp) + match="$(complete_dp "")" + ;; + -*) + # Treats option as kword as well. + match="$kword" + ;; + *) + match=($kword) + ;; + esac + match=$(echo "$match" | tr '\n' ' ') + args+=( $match ) + if [ -n "$_PRINTF_ENABLE" ]; then + local output_stderr= + + if [ -z "$printf_expand_once" ]; then + printf_expand_once="once" + printf -v output_stderr "\nArgument expansion:\n" + fi + printf -v output_stderr "$output_stderr argument keyword \ +\"%s\" is expanded to: %s " "$kword" "$match" + + printf_stderr "$output_stderr" + fi + done + + echo "${args[@]}" +} + + + + +# Parse and Compgen +# ================= +# +# +# +# This function takes the current command line arguments as input, +# finds the command format and returns the possible completions. +parse_and_compgen() { + local subcmd_line=($@) + local subcmd=${subcmd_line[0]} + local target=$_APPCTL_TARGET + local subcmd_format= + local comp_wordlist= + + # Extracts the subcommand format. + subcmd_format="$(ovs-appctl --target $target help 2>/dev/null | tail -n +2 | cut -c3- \ + | awk -v opt=$subcmd '$1 == opt {print $0}' | tr -s ' ' )" + + # Prints subcommand format. + printf_stderr "$(printf "\nCommand format:\n%s" "$subcmd_format")" + + # Finds the possible completions based on input argument. + comp_wordlist="$(subcmd_find_comp_based_on_input "$subcmd_format" \ + "${subcmd_line[@]}")" + + echo "$comp_wordlist" +} + + + +# Compgen Helper +# ============== +# +# +# +# Takes the current command line arguments and returns the possible +# completions. +# +# At the beginning, the options are checked and completed. The function +# looks for the --target option which gives the target daemon name. +# If it is not provided, by default, 'ovs-vswitchd' is used. +# +# Then, tries to locate and complete the subcommand. If the subcommand +# is provided, the following arguments are passed to the 'parse_and_compgen' +# function to figure out the corresponding completion of the subcommand. +# +# Returns the completion arguments on success. +ovs_appctl_comp_helper() { + local cmd_line_so_far=($@) + local comp_wordlist appctl_subcmd i + local j=-1 + + for i in "${!cmd_line_so_far[@]}"; do + # if $i is not greater than $j, it means the previous iteration + # skips not-visited args. so, do nothing and catch up. + if [ $i -le $j ]; then continue; fi + j=$i + if [[ "${cmd_line_so_far[i]}" =~ ^--* ]]; then + # If --target is found, locate the target daemon. + # Else, it is an option command, fill the comp_wordlist with + # all options. + if [[ "${cmd_line_so_far[i]}" =~ ^--target$ ]]; then + if [ -n "${cmd_line_so_far[j+1]}" ]; then + local daemon + + for daemon in $_POSSIBLE_TARGETS; do + # Greps "$daemon" in argument, since the argument may + # be the path to the pid file. + if [ "$daemon" = "${cmd_line_so_far[j+1]}" ]; then + _APPCTL_TARGET="$daemon" + ((j++)) + break + fi + done + continue + else + comp_wordlist="$_POSSIBLE_TARGETS" + break + fi + else + comp_wordlist="$(extract_options)" + break + fi + fi + # Takes the first non-option argument as subcmd. + appctl_subcmd="${cmd_line_so_far[i]}" + break + done + + if [ -z "$comp_wordlist" ]; then + # If the subcommand is not found, provides all subcmds and options. + if [ -z "$appctl_subcmd" ]; then + comp_wordlist="$(extract_subcmds) $(extract_options)" + # Else parses the current arguments and finds the possible completions. + else + # $j stores the index of the subcmd in cmd_line_so_far. + comp_wordlist="$(parse_and_compgen "${cmd_line_so_far[@]:$j}")" + fi + fi + + echo "$comp_wordlist" +} + + + +export LC_ALL=C + +# Compgen +# ======= +# +# +# +# The compgen function. +_ovs_appctl_complete() { + local cur prev + + COMPREPLY=() + cur=${COMP_WORDS[COMP_CWORD]} + + # Do not print anything at first [TAB] execution. + if [ "$COMP_TYPE" -eq "9" ]; then + _PRINTF_ENABLE= + else + _PRINTF_ENABLE="enabled" + fi + + # Extracts bash prompt PS1. + if [ "$1" != "debug" ]; then + extract_bash_prompt + fi + + # Invokes the helper function to get all available completions. + # Always not input the 'COMP_WORD' at 'COMP_CWORD', since it is + # the one to be completed. + _APPCTL_COMP_WORDLIST="$(ovs_appctl_comp_helper \ + ${COMP_WORDS[@]:1:COMP_CWORD-1})" + + # This is a hack to prevent autocompleting when there is only one + # available completion and printf disabled. + if [ -z "$_PRINTF_ENABLE" ] && [ -n "$_APPCTL_COMP_WORDLIST" ]; then + _APPCTL_COMP_WORDLIST="$_APPCTL_COMP_WORDLIST void" + fi + + # Prints all available completions to stderr. If there is only one matched + # completion, do nothing. + if [ -n "$_PRINTF_ENABLE" ] \ + && [ -n "$(echo $_APPCTL_COMP_WORDLIST | tr ' ' '\n' | \ + grep -- "^$cur")" ]; then + printf_stderr "\nAvailable completions:\n" + fi + + # If there is no match between '$cur' and the '$_APPCTL_COMP_WORDLIST' + # prints a bash prompt since the 'complete' will not print it. + if [ -n "$_PRINTF_ENABLE" ] \ + && [ -z "$(echo $_APPCTL_COMP_WORDLIST | tr ' ' '\n' | grep -- "^$cur")" ] \ + && [ "$1" != "debug" ] ; then + printf_stderr "\n$_BASH_PROMPT ${COMP_WORDS[@]}" + fi + + if [ "$1" = "debug" ] ; then + if [ -n "$cur" ]; then + printf_stderr "$(echo $_APPCTL_COMP_WORDLIST | tr ' ' '\n' | sort -u | grep -- "$cur")\n" + else + printf_stderr "$(echo $_APPCTL_COMP_WORDLIST | tr ' ' '\n' | sort -u | grep -- "$cur")\n" + fi + else + COMPREPLY=( $(compgen -W "$(echo $_APPCTL_COMP_WORDLIST | tr ' ' '\n' \ + | sort -u)" -- $cur) ) + fi + + return 0 +} + +# Debug mode. +if [ "$1" = "debug" ] ; then + shift + COMP_TYPE=0 + COMP_WORDS=($@) + COMP_CWORD="$(expr $# - 1)" + + # If the last argument is TAB, it means that the previous + # argument is already complete and script should complete + # next argument which is not input yet. This is a hack + # since compgen will break the input whitespace even + # though there is no input after it but bash cannot. + if [ "${COMP_WORDS[-1]}" = "TAB" ]; then + COMP_WORDS[${#COMP_WORDS[@]}-1]="" + fi + + _ovs_appctl_complete "debug" +# Normal compgen mode. +else + complete -F _ovs_appctl_complete ovs-appctl +fi \ No newline at end of file -- 1.7.9.5 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev