[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:48 UTC 2014


depends on this patch:

http://openvswitch.org/pipermail/dev/2014-September/045617.html

On Fri, Sep 12, 2014 at 12:14 AM, Alex Wang <alexw at 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 <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