[ovs-dev] [RFC ovs-appctl compgen] ovs-appctl: Add bash command-line completion script.
Alex Wang
alexw at nicira.com
Fri Sep 12 07:14:51 UTC 2014
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 <alexw at 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
More information about the dev
mailing list