[Bash-completion-devel] tc completion attempt

Raph gibboris at gmail.com
Sun May 31 01:42:26 UTC 2009


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
-------------- next part --------------
#!/bin/bash
## completion (attempt) for tc(8) language^Wcommand
## Rapha?l Droz <gibboris at 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


More information about the Bash-completion-devel mailing list