utilities: Add bash command-line completion script.
authorAlex Wang <alexw@nicira.com>
Mon, 20 Oct 2014 04:36:58 +0000 (21:36 -0700)
committerAlex Wang <alexw@nicira.com>
Thu, 30 Oct 2014 18:22:26 +0000 (11:22 -0700)
This patch adds bash command-line completion script for ovs-appctl,
ovs-dpctl, ovs-ofctl and ovsdb-tool command.  Right now, the script
can do the following:

- display available completion or complete on unfinished user input
  (long option, subcommand, and argument).

- 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 limitations are:

- only support small set of important keywords
  (dp, datapath, bridge, switch, port, interface, iface).

- does not support parsing of nested options
  (e.g. ovsdb-tool create [db [schema]]).

- does not support expansion on repeatitive argument
  (e.g. ovs-dpctl show [dp...]).

- only support matching on long options, and only in the format
  (--option [arg], i.e. should not use --option=[arg]).

To use the script, either copy it inside /etc/bash_completion.d/
or manually run it via . ovs-command-compgen.bash.

Also, a unit testsuite is provided as ovs-command-compgen-test.bash.
It is suggested that this test script be run only inside
tutorial/sandbox directory.

For more info please refer to utilities/ovs-command-compgen.INSTALL.md.

Signed-off-by: Alex Wang <alexw@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
NEWS
utilities/automake.mk
utilities/ovs-command-compgen-test.bash [new file with mode: 0755]
utilities/ovs-command-compgen.INSTALL.md [new file with mode: 0644]
utilities/ovs-command-compgen.bash [new file with mode: 0755]

diff --git a/NEWS b/NEWS
index 66e57bc..d271188 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,8 @@
 Post-v2.3.0
 ---------------------
+   - Add bash command-line completion support for ovs-appctl/ovs-dpctl/
+     ovs-ofctl/ovsdb-tool commands.  Please check
+     utilities/ovs-command-compgen.INSTALL.md for how to use.
    - The "learn" action supports a new flag "delete_learned" that causes
      the learned flows to be deleted when the flow with the "learn" action
      is deleted.
index 9ddbbe2..ccdd4dc 100644 (file)
@@ -26,6 +26,9 @@ utilities/ovs-lib: $(top_builddir)/config.status
 
 EXTRA_DIST += \
        utilities/ovs-check-dead-ifs.in \
+       utilities/ovs-command-compgen.bash \
+       utilities/ovs-command-compgen-test.bash \
+       utilities/ovs-command-compgen.INSTALL.md \
        utilities/ovs-ctl.in \
        utilities/ovs-dev.py \
        utilities/ovs-docker \
diff --git a/utilities/ovs-command-compgen-test.bash b/utilities/ovs-command-compgen-test.bash
new file mode 100755 (executable)
index 0000000..9de0d12
--- /dev/null
@@ -0,0 +1,688 @@
+#!/bin/bash
+#
+# Tests for the ovs-command-compgen.bash
+#
+# Please run this with ovs-command-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_COMMANDS=(ovs-appctl ovs-ofctl ovs-dpctl ovsdb-tool)
+TEST_APPCTL_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 -- "available completions for keyword" <<< "$input" | sed -e 's/^[ \t]*//')"
+}
+
+get_available_completions() {
+    local input="$@"
+
+    echo "$(sed -e '1,/Available/d' <<< "$input" | tail -n+2)"
+}
+
+generate_expect_completions() {
+    local keyword="$1"
+    local completions="$2"
+
+    echo "available completions for keyword \"$keyword\": $completions" \
+        | sed -e 's/[ \t]*$//'
+}
+
+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"
+}
+
+#
+# $1: test stage
+# $2: actual
+# $3: expect
+#
+print_error() {
+    local stage="$1"
+    local actual="$2"
+    local expect="$3"
+
+    printf "failed at stage_%s:\n" "$stage"
+    printf "actual output: %s\n" "$actual"
+    printf "expect output: %s\n" "$expect"
+}
+
+#
+# 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-command-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 list-commands | tail -n +2 | cut -c3- | cut -d ' ' -f1 | sort)"
+    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-command-compgen unit tests. ##
+## ------------------------------- ##
+
+EOF
+
+
+# complete ovs-appctl [TAB]
+# complete ovs-dpctl  [TAB]
+# complete ovs-ofctl  [TAB]
+# complete ovsdb-tool [TAB]
+
+for test_command in ${TEST_COMMANDS[@]}; do
+    reset_globals
+
+    COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ${test_command} TAB 2>&1)"
+    TMP="$(get_available_completions "$COMP_OUTPUT")"
+    EXPECT="$(${test_command} --option | sort | sed -n '/^--.*/p' | cut -d '=' -f1)
+$(${test_command} list-commands | tail -n +2 | cut -c3- | cut -d ' ' -f1 | sort)"
+    if [ "$TMP" = "$EXPECT" ]; then
+        TEST_RESULT=ok
+    else
+        TEST_RESULT=fail
+    fi
+
+    print_result "complete ${test_command} [TAB]" "$TEST_RESULT"
+done
+
+
+# complete ovs-appctl --tar[TAB]
+
+reset_globals
+
+COMP_OUTPUT="$(bash ovs-command-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-command-compgen.bash debug ovs-appctl --target TAB 2>&1)"
+TMP="$(get_available_completions "$COMP_OUTPUT")"
+EXPECT="$(echo ${TEST_APPCTL_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 --target ovs-vswitchd [TAB]
+# complete ovs-appctl --target ovsdb-server [TAB]
+# complete ovs-appctl --target ovs-ofctl    [TAB]
+
+reset_globals
+
+for target in ${TEST_APPCTL_TARGETS[@]}; do
+    target_field="--target $i "
+
+    if [ "$target" = "ovs-ofctl" ]; then
+        ovs-ofctl monitor br0 --detach --no-chdir --pidfile
+    fi
+
+    TEST_RESULT="$(ovs_apptcl_TAB $target)"
+
+    print_result "complete ovs-appctl ${target_field}[TAB]" "$TEST_RESULT"
+
+    if [ "$target" = "ovs-ofctl" ]; then
+        ovs-appctl --target ovs-ofctl exit
+    fi
+done
+
+
+# check all subcommand formats
+
+reset_globals
+
+TMP="$(ovs-appctl list-commands | tail -n +2 | cut -c3- | cut -d ' ' -f1 | sort)"
+
+# for each subcmd, check the print of subcmd format
+for i in $TMP; do
+    COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl $i TAB 2>&1)"
+    tmp="$(get_command_format "$COMP_OUTPUT")"
+    EXPECT="$(ovs-appctl list-commands | tail -n+2 | cut -c3- | grep -- "^$i " | tr -s ' ' | sort)"
+    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-command-compgen.bash debug ovs-appctl bfd/set-forwarding TAB 2>&1)"
+    TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
+    EXPECT="$(generate_expect_completions "normal" "")
+$(generate_expect_completions "false" "")
+$(generate_expect_completions "true" "")
+$(generate_expect_completions "interface" "p1")"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "1" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # check the available completions.
+    TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
+    EXPECT="p1"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "2" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # set argument to 'true', there should be no more completions.
+    COMP_OUTPUT="$(bash ovs-command-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
+        print_error "3" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # set argument to 'p1', there should still be the completion for booleans.
+    COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl bfd/set-forwarding p1 TAB 2>&1)"
+    TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
+    EXPECT="$(generate_expect_completions "normal" "")
+$(generate_expect_completions "false" "")
+$(generate_expect_completions "true" "")"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "4" "$TMP" "$EXPECT"
+        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
+        print_error "5" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # set argument to 'p1 false', there should still no more completions.
+    COMP_OUTPUT="$(bash ovs-command-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
+        print_error "6" "$TMP" "$EXPECT"
+        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-command-compgen.bash debug ovs-appctl lacp/show TAB 2>&1)"
+    TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
+    EXPECT="$(generate_expect_completions "port" "br0 p1")"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "1" "$TMP" "$EXPECT"
+        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
+        print_error "2" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # set argument to 'p1', there should be no more completions.
+    COMP_OUTPUT="$(bash ovs-command-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
+        print_error "3" "$TMP" "$EXPECT"
+        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-command-compgen.bash debug ovs-appctl ofproto/trace TAB 2>&1)"
+    TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
+    EXPECT="$(generate_expect_completions "bridge" "br0")
+$(generate_expect_completions "odp_flow" "")
+$(generate_expect_completions "dp_name" "ovs-system")"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "1" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # check the available completions.
+    TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
+    EXPECT="br0 ovs-system"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "2" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # set argument to 'ovs-system', should go to the dp-name path.
+    COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl ofproto/trace ovs-system TAB 2>&1)"
+    TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
+    EXPECT="$(generate_expect_completions "odp_flow" "")"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "3" "$TMP" "$EXPECT"
+        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
+        print_error "4" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # set odp_flow to some random string, should go to the next level.
+    COMP_OUTPUT="$(bash ovs-command-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="$(generate_expect_completions "-generate" "-generate")
+$(generate_expect_completions "packet" "")"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "5" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # check the available completions.
+    TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
+    EXPECT="-generate"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "6" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # set packet to some random string, there should be no more completions.
+    COMP_OUTPUT="$(bash ovs-command-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
+        print_error "7" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # set argument to 'br0', should go to the bridge path.
+    COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl ofproto/trace br0 TAB 2>&1)"
+    TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
+    EXPECT="$(generate_expect_completions "br_flow" "")"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "8" "$TMP" "$EXPECT"
+        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
+        print_error "9" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # set argument to some random string, should go to the odp_flow path.
+    COMP_OUTPUT="$(bash ovs-command-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="$(generate_expect_completions "-generate" "-generate")
+$(generate_expect_completions "packet" "")"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "10" "$TMP" "$EXPEC"T
+        TEST_RESULT=fail
+        break
+    fi
+
+    # check the available completions.
+    TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
+    EXPECT="-generate"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "11" "$TMP" "$EXPECT"
+        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-command-compgen.bash debug ovs-appctl vlog/set TAB 2>&1)"
+    TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
+    EXPECT="$(generate_expect_completions "PATTERN:facility:pattern" "")
+$(generate_expect_completions "spec" "")"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "1" "$TMP" "$EXPECT"
+        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
+        print_error "2" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # set argument to random 'abcd', there should be no more completions.
+    COMP_OUTPUT="$(bash ovs-command-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
+        print_error "3" "$TMP" "$EXPECT"
+        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-command-compgen.bash debug ovs-appctl bfd/set-forwarding TAB 2>&1)"
+    TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
+    EXPECT="$(generate_expect_completions "normal" "")
+$(generate_expect_completions "false" "")
+$(generate_expect_completions "true" "")
+$(generate_expect_completions "interface" "")"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "1" "$TMP" "$EXPECT"
+        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
+        print_error "2" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # check match on port, there should be no p1 as port.
+    COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl lacp/show TAB 2>&1)"
+    TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
+    EXPECT="$(generate_expect_completions "port" "br0")"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "3" "$TMP" "$EXPECT"
+        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
+        print_error "4" "$TMP" "$EXPECT"
+        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-command-compgen.bash debug ovs-appctl bridge/dump-flows TAB 2>&1)"
+    TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
+    EXPECT="$(generate_expect_completions "bridge" "")"
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "1" "$TMP" "$EXPECT"
+        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
+        print_error "2" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    # check 'ovs-ofctl monitor [misslen] [invalid_ttl] [watch:[...]]', should
+    # not show any available completion.
+    COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-ofctl monitor non_exist_br TAB 2>&1)"
+    TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
+    EXPECT=
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "3" "$TMP" "$EXPECT"
+        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-command-compgen.bash debug ovs-appctl ERROR 2>&1)"
+    TMP="$(echo "$COMP_OUTPUT" | sed -e 's/[ \t]*$//' | sed -e '/./,$!d')"
+    EXPECT=
+    if [ "$TMP" != "$EXPECT" ]; then
+        print_error "1" "$TMP" "$EXPECT"
+        TEST_RESULT=fail
+        break
+    fi
+
+    COMP_OUTPUT="$(bash ovs-command-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
+        print_error "2" "$TMP" "$EXPECT"
+        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.
+
+reset_globals
+killall ovs-vswitchd ovsdb-server
+
+for i in ${TEST_APPCTL_TARGETS[@]}; do
+    for j in loop_once; do
+        reset_globals
+
+        daemon="$i"
+
+        # should show no avaiable subcommands.
+        COMP_OUTPUT="$(bash ovs-command-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-command-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
+
+
+# negative test - do not match on nested option
+
+reset_globals
+
+for i in loop_once; do
+    COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovsdb-tool create TAB 2>&1)"
+    TMP="$(get_available_completions "$COMP_OUTPUT")"
+    EXPECT=
+    if [ "$TMP" != "$EXPECT" ]; then
+        TEST_RESULT=fail
+        break
+    fi
+
+    TEST_RESULT=ok
+done
+
+print_result "negative test - do not match on nested option" "$TEST_RESULT"
\ No newline at end of file
diff --git a/utilities/ovs-command-compgen.INSTALL.md b/utilities/ovs-command-compgen.INSTALL.md
new file mode 100644 (file)
index 0000000..e77ead5
--- /dev/null
@@ -0,0 +1,57 @@
+Using bash command-line completion script
+-----------------------------------------
+
+ovs-command-compgen.bash adds bash command-line completion support
+for ovs-appctl, ovs-dpctl, ovs-ofctl and ovsdb-tool commands.
+
+Features:
+---------
+
+   display available completion or complete on unfinished user input
+   (long option, subcommand, and argument).
+
+   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.
+
+Limitations:
+------------
+
+   only support small set of important keywords
+   (dp, datapath, bridge, switch, port, interface, iface).
+
+   does not support parsing of nested options
+   (e.g. ovsdb-tool create [db [schema]]).
+
+   does not support expansion on repeatitive argument
+   (e.g. ovs-dpctl show [dp...]).
+
+   only support matching on long options, and only in the format
+   (--option [arg], i.e. should not use --option=[arg]).
+
+
+How to use:
+-----------
+
+   To use the script, either copy it inside /etc/bash_completion.d/
+   or manually run it via . ovs-command-compgen.bash.
+
+Test:
+-----
+
+   An unit testsuite is provided as ovs-command-compgen-test.bash.
+   To run the test, first enter ovs sandbox via:
+
+        make sandbox && cd sandbox
+
+   Then copy both ovs-command-compgen-test.bash and ovs-command-compgen.bash
+   to the current directory.  Finally, run the test via:
+
+        bash ovs-command-compgen-test.bash
+
+Bug Reporting:
+--------------
+
+Please report problems to bugs@openvswitch.org.
\ No newline at end of file
diff --git a/utilities/ovs-command-compgen.bash b/utilities/ovs-command-compgen.bash
new file mode 100755 (executable)
index 0000000..c87445a
--- /dev/null
@@ -0,0 +1,614 @@
+#!/bin/bash
+#
+# A bash command completion script for ovs-appctl.
+#
+#
+# Right now, the script can do the following:
+#
+#    - display available completion or complete on unfinished user input
+#      (long option, subcommand, and argument).
+#
+#    - 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 limitation are:
+#
+#    - only support small set of important keywords
+#      (dp, datapath, bridge, switch, port, interface, iface).
+#
+#    - does not support parsing of nested option
+#      (e.g. ovsdb-tool create [db [schema]]).
+#
+#    - does not support expansion on repeatitive argument
+#      (e.g. ovs-dpctl show [dp...]).
+#
+#    - only support matching on long options, and only in the format
+#      (--option [arg], i.e. should not use --option=[arg]).
+#
+#
+#
+# Keywords
+# ========
+#
+#
+#
+# Expandable keywords.
+_KWORDS=(bridge switch port interface iface dp_name dp)
+# Command name.
+_COMMAND=
+# Printf enabler.
+_PRINTF_ENABLE=
+# Bash prompt.
+_BASH_PROMPT=
+# Output to the compgen.
+_COMP_WORDLIST=
+
+#
+# For ovs-appctl command only.
+#
+# Target in the current completion, default ovs-vswitchd.
+_APPCTL_TARGET=
+# Possible targets.
+_POSSIBLE_TARGETS="ovs-vswitchd ovsdb-server ovs-ofctl"
+
+# Command Extraction
+# ==================
+#
+#
+#
+# Extracts all subcommands of 'command'.
+# If fails, returns nothing.
+extract_subcmds() {
+    local command=$_COMMAND
+    local target=
+    local subcmds error
+
+    if [ -n "$_APPCTL_TARGET" ]; then
+        target="--target $_APPCTL_TARGET"
+    fi
+
+    subcmds="$($command $target list-commands 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.
+# If fails, returns nothing.
+extract_options() {
+    local command=$_COMMAND
+    local options error
+
+    options="$($command --option 2>/dev/null | sort | sed -n '/^--.*/p' | cut -d '=' -f1)" \
+        || error="TRUE"
+
+    if [ -z "$error" ]; then
+        echo "$options"
+    fi
+}
+
+# Returns the option format, if the option asks for an argument.
+# If fails, returns nothing.
+option_require_arg() {
+    local command=$_COMMAND
+    local option=$1
+    local require_arg error
+
+    require_arg="$($command --option | sort | sed -n '/^--.*/p' | grep -- "$option" | grep -- "=")" \
+        || error="TRUE"
+
+    if [ -z "$error" ]; then
+        echo "$require_arg"
+    fi
+}
+
+# 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 [ -n "$(sed -n '/^\[.*\]$/p' <<< "$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 in format "\[*", it is a start of nested
+            # option, do not parse.
+            elif [ -n "$(sed -n "/^\[.*$/p" <<< "$arg")" ]; then
+                break;
+            # 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")"
+        # Returns if could not find 'kword'
+        if [ -z "$kword" ]; then
+            return
+        fi
+        # 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'.
+#
+# Original idea inspired by:
+# http://stackoverflow.com/questions/10060500/bash-how-to-evaluate-ps1-ps2
+#
+# The code below is taken from Peter Amidon.  His change makes it more
+# robust.
+extract_bash_prompt() {
+    local myPS1 v
+
+    myPS1="$(sed 's/Begin prompt/\\Begin prompt/; s/End prompt/\\End prompt/' <<< "$PS1")"
+    v="$(bash --norc --noprofile -i 2>&1 <<< $'PS1=\"'"$myPS1"$'\" \n# Begin prompt\n# End prompt')"
+    v="${v##*# Begin prompt}"
+    _BASH_PROMPT="$(tail -n +2 <<< "${v%# End prompt*}" | sed 's/\\Begin prompt/Begin prompt/; s/\\End prompt/End prompt/')"
+}
+
+
+
+# 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|switch)
+                match="$(complete_bridge "$arg")"
+                ;;
+            port)
+                match="$(complete_port "$arg")"
+                ;;
+            interface|iface)
+                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|switch)
+                match="$(complete_bridge "")"
+                ;;
+            port)
+                match="$(complete_port "")"
+                ;;
+            interface|iface)
+                match="$(complete_iface "")"
+                ;;
+            dp_name|dp)
+                match="$(complete_dp "")"
+                ;;
+            -*)
+                # Treats option as kword as well.
+                match="$kword"
+                ;;
+            *)
+                match=
+                ;;
+        esac
+        match=$(echo "$match" | tr '\n' ' ' | sed -e 's/^[ \t]*//')
+        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     available completions \
+for keyword \"%s\": %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 command=$_COMMAND
+    local subcmd_line=($@)
+    local subcmd=${subcmd_line[0]}
+    local target=
+    local subcmd_format=
+    local comp_wordlist=
+
+    if [ -n "$_APPCTL_TARGET" ]; then
+        target="--target $_APPCTL_TARGET"
+    fi
+
+    # Extracts the subcommand format.
+    subcmd_format="$($command $target list-commands 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.  For ovs-appctl
+# completion, 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_comp_helper() {
+    local cmd_line_so_far=($@)
+    local comp_wordlist _subcmd options i
+    local j=-1
+
+    # Parse the command-line args till we find the subcommand.
+    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 [ "$_COMMAND" = "ovs-appctl" ] \
+                && [[ "${cmd_line_so_far[i]}" =~ ^--target$ ]]; then
+                _APPCTL_TARGET="ovs-vswitchd"
+
+                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
+                options="$(extract_options $_COMMAND)"
+                # See if we could find the exact option.
+                if [ "${cmd_line_so_far[i]}" = "$(grep -- "${cmd_line_so_far[i]}" <<< "$options")" ]; then
+                    # If an argument is required and next argument is non-empty,
+                    # skip it.  Else, return directly.
+                    if [ -n "$(option_require_arg "${cmd_line_so_far[i]}")" ]; then
+                        ((j++))
+                        if [ -z "${cmd_line_so_far[j]}" ]; then
+                            printf_stderr "\nOption requires an arugment."
+                            return
+                        fi
+                    fi
+                    continue
+                # Else, need to keep completing on option.
+                else
+                    comp_wordlist="$options"
+                    break
+                fi
+            fi
+        fi
+        # Takes the first non-option argument as subcmd.
+        _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 "$_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"
+}
+
+# Compgen
+# =======
+#
+#
+#
+# The compgen function.
+_ovs_command_complete() {
+  local cur prev
+
+  _COMMAND=${COMP_WORDS} # element 0 is the command.
+  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.
+  _COMP_WORDLIST="$(ovs_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 "$_COMP_WORDLIST" ]; then
+      _COMP_WORDLIST="$_COMP_WORDLIST none void no-op"
+  fi
+
+  # Prints all available completions to stderr.  If there is only one matched
+  # completion, do nothing.
+  if [ -n "$_PRINTF_ENABLE" ] \
+      && [ -n "$(echo $_COMP_WORDLIST | tr ' ' '\n' | \
+                grep -- "^$cur")" ]; then
+      printf_stderr "\nAvailable completions:\n"
+  fi
+
+  # If there is no match between '$cur' and the '$_COMP_WORDLIST'
+  # prints a bash prompt since the 'complete' will not print it.
+  if [ -n "$_PRINTF_ENABLE" ] \
+      && [ -z "$(echo $_COMP_WORDLIST | tr ' ' '\n' | grep -- "^$cur")" ] \
+      && [ "$1" != "debug" ]; then
+      printf_stderr "\n$_BASH_PROMPT${COMP_WORDS[@]}"
+  fi
+
+  if [ "$1" = "debug" ]; then
+      printf_stderr "$(echo $_COMP_WORDLIST | tr ' ' '\n' | sort -u | grep -- "$cur")\n"
+  else
+      COMPREPLY=( $(compgen -W "$(echo $_COMP_WORDLIST | tr ' ' '\n' \
+                                 | sort -u)" -- $cur) )
+  fi
+
+  return 0
+}
+
+# Needed for the sorting of completions in display.
+export LC_ALL=C
+
+# 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 hack is for
+    # compromising the fact that bash cannot take unquoted
+    # empty argument.
+    if [ "${COMP_WORDS[-1]}" = "TAB" ]; then
+        COMP_WORDS[${#COMP_WORDS[@]}-1]=""
+    fi
+
+    _ovs_command_complete "debug"
+# Normal compgen mode.
+else
+    complete -F _ovs_command_complete ovs-appctl
+    complete -F _ovs_command_complete ovs-ofctl
+    complete -F _ovs_command_complete ovs-dpctl
+    complete -F _ovs_command_complete ovsdb-tool
+fi
\ No newline at end of file