Hello,
today was a bit rainy, a perfect weather
to try tc and to begin the first percent
of it's completion (300 lines)
enjoy

Raph
#!/bin/bash
## completion (attempt) for tc(8) language^Wcommand
## Raphaël Droz <gibbo...@gmail.com>

have tc && {
    _tc_has_args() {
        [ $# -ne 2 ] && return 1
        local i j opt argnb
        opt=$1 && shift
        let argnb=$1
        let i=1
        # first search for this option
        while [ $i -le $COMP_CWORD ]; do
            # found, let's have a look to its arguments now
            if [ ${COMP_WORDS[$i]} = $opt ]; then
                let j=$i+1
                while [[ $j -le $((i+$argnb)) ]]; do
                        # this one is null, returns 1
                        [[ -z "${COMP_WORDS[$j]}" ]] && return 1
                        ((j++))
                done
                # last but not least :
                # either the next argument should exists
                # either $COMP_CWORDS should be $j+1 to
                # be sure a space as been appended
                # won't work with -o nospace
                # or it will if -o nospace does increment
                # $COMP_CWORD as does the default behavior (untested)

                # echo -e "\n$opt, 
j=$j\nCOMP_WORDS[$((j-1))]=${COMP_WORDS[$j-1]}
                # echo -e 
"COMP_WORDS[$j]=${COMP_WORDS[$j]}\nCOMP_CWORD=$COMP_CWORD passed" > /dev/stderr
                [ -n "${COMP_WORDS[$j]}" ] && return 0

                # notice here that j had been given a final
                # while-increment so it should be equal
                # to the new CWORD
                [ $j -eq $COMP_CWORD ] && return 0
                return 1
            fi
            ((i++))
        done
        return 1
    }

    # echoes argument matching the regexp in COMP_WORDS between
    # optionnal start and end position
    # return the position or -1 if not found
    _tc_get_arg_matching() {
        [ -z "$1" ] && return -1
        local i aarg limit start a
        limit=$COMP_CWORD
        start=1
        aarg=$1 && shift
        [ -n "$1" ] && {
                let start=$1; shift;
        }
        [ -n "$1" ] && {
                let limit=$1; shift;
        }
        [ $limit -gt $COMP_CWORD ] && limit=$COMP_CWORD
        [ $start -lt 1 ] && start=1
        [ $start -gt $limit ] && {
                a=$limit
                limit=$start
                start=$a
        }

        let i=$start
        while [ $i -lt $limit ]; do
            [[ ${COMP_WORDS[$i]} =~ $aarg ]] &&
            echo ${COMP_WORDS[$i]} &&
            return $i
            ((i++))
        done
        return -1
    }

        # returns 0 if the argument is found in the cmdline
        # if several argument are given, returns 0 if one of them
        # is found in the cmdline
    _tc_in_cmd() {
        [ -z "$1" ] && return 1
        local i aarg
        let i=0
        #echo -e "\n\n\n=====$@"
        aarg=$1 && shift
        #echo -e "$aarg :"
        while [ $((i++)) -lt $((COMP_CWORD-1)) ]; do
            [[ ${COMP_WORDS[$i]} = "$aarg" ]] && {
                #echo "$aarg match ${COMP_WORDS[$i]} ($i)"
                return 0
            }
        done

        # a bit tricky :
        # if args left, process them and returns (recursive)
        # otherwise, with only one argument, the test is false
        # so returns still return 1
        [[ -n "$@" ]] && _tc_in_cmd $@
        return $?
    }

        # list available kernel schedulers|actions|filters
    _tc_getsched() {
        echo $(ls /lib/modules/$(uname -r)/kernel/net/sched/$1_*.ko|\
                sed "s/^.*$1_\([a-z0-9-]*\)\.ko/\1/")
    }

   # generate COMPREPLY being given
   # some options candidate for completion
   # some other options candidate for completion
   #   which won't be checked for duplication
   # some options mutually exclusive
    _tc-makecomp() {
        # argument and kernel variable argument
        local arg varg excl
        local strleft=()
        arg="$1" && shift
        varg="$1" && shift
        excl="$1" && shift
        #echo -e "1=$arg\n2=$varg\n3=$excl" > /dev/stderr
        for i in $arg; do
        # if $i already in cmdline : skip it from compgen
        # if :
        #     $i is part of the exclusive arguments
        #     and
        #     one of the exclusive arguments is already in the cmdline
        # --> skip it from compgen
                _tc_in_cmd $i || {
                    [[ $i =~ ${excl// /|} ]] && _tc_in_cmd $excl
                } && continue

                strleft[${#strleft[*]}]=$i;
        done
        COMPREPLY=( $(compgen -W "${strleft[*]} $varg" -- $cur ) )
    }

        # short dream about completing the action OBJECT of tc
    _tc-action() {
        if [ -z "$cmd" ]; then
            COMPREPLY=( $(compgen -W "add change replace get delete \
                                     ls list flush action help" -- $cur ) )
        else
            local ACTSPECOP
            case $cmd in
                add|change|replace)
                    ACTSPECOP=ACR
                    ;;
                get|delete)
                    ACTSPECOP=GD
                    ;;
                ls|list|flush)
                    ACTSPECOP=FL
                    ;;
                action)
                    ACTSPECOP=x
                    ;;
            esac
            #_tc_getsched act
            COMPREPLY=( )
        fi
        return 0
    }

        # filtering is a bit more advanced
    _tc-filter() {
        # TODO: non-exhaustive list, as no local documentation available right 
now
        local proto='ipv{4,6} irda {r,}arp wan_ppp ppp{ses,disc}'
        local cls=$(_tc_getsched cls)
        local filtertype filterpos

        if ! _tc_in_cmd protocol; then
            _tc_in_cmd pref && COMPREPLY=( $(compgen -W 'protocol' -- $cur ) ) 
||
            COMPREPLY=( $(compgen -W 'pref protocol' -- $cur ) )
            return 0
        else
                if ! _tc_has_args protocol 1; then
                        COMPREPLY=( $(compgen -W "$proto" -- $cur ) )
                        return 0
                else
                        if ! _tc_in_cmd $cls; then
                                _tc_in_cmd estimator && ! _tc_has_args 
estimator 2 && return 0
                                _tc-makecomp 'estimator root classid handle 
help' "$cls" 'root classid'
                                return 0
                        else
                                filtertype=$(_tc_get_arg_matching ${cls// /|})
                                filterpos=$?

                                # giving up here : complexity 1, developper 0
                                [ $COMP_CWORD -gt $((filterpos+1)) ] && return 0

                                case $filtertype in
                                        basic)
                                                COMPREPLY=( $(compgen -W 'match 
police action classid' -- $cur ) )
                                                return 0
                                                ;;
                                        flow)
                                                COMPREPLY=( $(compgen -W 'map 
hash' -- $cur ) )
                                                return 0
                                                ;;
                                        u32)
                                                COMPREPLY=( $(compgen -W 'match 
link classid police offset \
                                                                ht hashkey 
sample divisor help' -- $cur ) )
                                                return 0
                                                ;;
                                        fw)
                                                COMPREPLY=( $(compgen -W 
'classid police help' -- $cur ) )
                                                return 0
                                                ;;
                                        route)
                                                COMPREPLY=( $(compgen -W 
'from{,if} to flowid police' -- $cur ) )
                                                return 0
                                                ;;
                                        rsvp?(6))
                                                COMPREPLY=( $(compgen -W 
'ipproto' -- $cur ) )
                                                return 0
                                                ;;
                                        tcindex)
                                                COMPREPLY=( $(compgen -W 'hash 
mask shift pass_on fall_through \
                                                                        classid 
police' -- $cur ) )
                                                return 0
                                                ;;
                                        *)
                                                # TODO shouldn't happens
                                                COMPREPLY=( 
_tc_error_$filtertype )
                                                return 0
                                                ;;
                                esac
                        fi
                fi
        fi
        # TODO shouldn't happens
        COMPREPLY=( _tc_error3 )
        return 0

    }

    # here comes the completion root
    _tc() {
        local cur object cmd objpos
        COMPREPLY=()
        cur=`_get_cword`
        prev=${COMP_WORDS[COMP_CWORD-1]}

        # the OBJECTS
        case $prev in
            qdisc)
                COMPREPLY=( $(compgen -W 'add del replace change link help 
show' -- $cur ) )
                return 0
                ;;
            @(class|filter))
                COMPREPLY=( $(compgen -W 'add del replace change show help' -- 
$cur ))
                return 0
                ;;
            action?(s))
                _tc-action
                return 0
                ;;
            monitor)
                return 0
                ;;
            -batch)
                _filedir
                return 0
                ;;

            #### appends 'dev' if ${prev} is one of the following
            #### as compgen -W '...' -S ' dev' doesn't appear to work
            #### 'show' case is handled separatly later
            @(add|del|replace|change|link))
                COMPREPLY=( $(compgen -W 'dev' -- $cur ) )
                return 0
                ;;
            # 'dev' keyword is always followed by an interface name
            dev)
                _available_interfaces
                return 0
                ;;
        esac

        # we be soon useful
        object=$(_tc_get_arg_matching 'qdisc|class|filter|actions?|monitor' 0 5)
        objpos=$?
        cmd=${COMP_WORDS[$objpos+1]}

    # to avoid (possible|futur|imaginary) confusion in the tc ocean
    # in case we find some other dashed switches,
    # so limit the impact of these tests to the case
    # where the OBJECT is not yet defined
    # (at the beginning of the command line)

    # TODO: look for mutually exclusive option (are they all ?)
        if [[ "$cur" == -* ]] && [[ -z $object ]]; then
            COMPREPLY=( $( compgen -W '-s -stats -statistics -d -details -r 
-raw -p -pretty -i -iec -batch -force' -- $cur ) )
            return 0
    # no case matched until now, we are at the very beginning of the cmdline
    # maybe a duplicate of [ -z "$object" ] below
        elif [[ $COMP_CWORD -eq 1 ]]; then
            COMPREPLY=( $( compgen -W 'qdisc class filter action monitor' -- 
$cur ) )
            return 0
        fi


    # here comes the hard stuff, no more $prev to dig around
    # no more stones petit Poucet
    # but hopefully, we just grabbed :
    # the OBJECT
    # its position
    # and, as we go, ...
    # the following command
        [ -z "$object" ] && {
            # TODO : OBJECT completion according to given switches
            # because no OBJECT means a switch
            COMPREPLY=( $( compgen -W 'qdisc class filter action monitor' -- 
$cur ) )
            return 0
        }
        [ -z "$cmd" ] && COMPREPLY=( _tc_error5 ) && return

    # grabs some common option which need an argument
    # (they have a priority over the other cases)
    _tc_in_cmd parent && ! _tc_has_args parent 1 &&
        COMPREPLY=( $(compgen -W "$(tc qdisc show |\
                sed 's/^.* .* \([0-9a-f]\{1,4\}\):.*/\1/')" -- $cur ) ) &&
        return 0

    _tc_in_cmd classid && ! _tc_has_args classid 1 &&
        return 0

    _tc_in_cmd handle && ! _tc_has_args handle 1 &&
        return 0

    ## factor that stuff here rather than below
        if [[ $object =~ qdisc|class ]] && [ $cmd != show ]; then
        # argument and kernel variable argument (see _tc-makecomp())
            local arg varg excl
            local strleft=()
            [ $object = qdisc ] && {
                arg='handle root ingress parent estimator stab help'
                varg='{p,b}fifo{,_fast} htb tbf prio cbq red sfq'
                excl='root ingress parent'
            }
            [ $object = class ] && {
                arg='classid parent root help'
                varg=$(_tc_getsched sch)
                excl='root parent'
            }
            _tc-makecomp "$arg" "$varg" "$excl"
            return 0
        fi

     ## others cases no treated before
        case $object in
            qdisc)
                # show [ dev IF ] [ ingress ]
                if [ $cmd = show ]; then
                        ## would have been really nice
                        ## but the really principles of spaces law
                        #_available_interfaces
                        # a=${COMPREPLY[*]}
                        #COMPREPLY=()
                        #b=${a// /\\ ,}
                        #COMPREPLY=( $(compgen -S test -W "{dev\ {$b\ 
},}{ingress,}" -- $cur ) )
                        ## bug even without "space bug" above
                        #COMPREPLY=( $(compgen -S test -W 
"{dev_{$b},}{ingress,}" -- $cur ) )

                        # old solution
                        if _tc_in_cmd dev; then
                                ! _tc_has_args dev 1 && _available_interfaces ||
                                COMPREPLY=( $(compgen -W 'ingress' -- $cur ) )
                        elif ! _tc_in_cmd ingress; then
                                COMPREPLY=( $(compgen -W 'dev ingress' -- $cur 
) )
                        fi
                else
                    COMPREPLY=( _tc_error1 )
                fi
                return 0
                ;;
            class)
                if [ $cmd = show ]; then
                    COMPREPLY=( $(compgen -W 'dev root parent' -- $cur ) )
                else
                    COMPREPLY=( _tc_error2 )
                fi
                return 0
                ;;
            filter)
                _tc-filter
                return 0
                ;;
            action?(s))
                _tc-action
                return 0
                ;;
        esac
}
} && complete -F _tc tc
_______________________________________________
Bash-completion-devel mailing list
Bash-completion-devel@lists.alioth.debian.org
http://lists.alioth.debian.org/mailman/listinfo/bash-completion-devel

Reply via email to