depends on this patch: http://openvswitch.org/pipermail/dev/2014-September/045617.html
On Fri, Sep 12, 2014 at 12:14 AM, Alex Wang <al...@nicira.com> wrote: > 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