#####
#To: chet@po.cwru.edu, sarahmckenna@lucent.com
#Message-Id: <slrn8mqioc.msb.ian@lovelorn.linuxcare.com>
#Posted-To: comp.unix.shell, gnu.bash.bug
#Subject: bash 2.04 programmable completion examples
#Reply-To: ian@linuxcare.com, ian@caliban.org
#Summary: examples of programmable completion for bash 2.04
#Date: Thu, 13 Jul 2000 00:52:33 -0400 (EDT)
#From: ianmacd@linuxcare.com (Ian Macdonald)
#####

#########################################################################
# Turn on extended globbing
shopt -s extglob

# A lot of the following one-liners were taken directly from the
# completion examples provided with the bash 2.04 source distribution

# Make directory commands see only directories
complete -d cd mkdir rmdir pushd

# Make file commands see only files
complete -f cat less more chown ln strip
complete -f -X '*.gz' gzip
complete -f -X '*.Z' compress
complete -f -X '!*.+(Z|gz|tgz|Gz)' gunzip zcat zmore
complete -f -X '!*.Z' uncompress zmore zcat
complete -f -X '!*.+(gif|jpg|jpeg|GIF|JPG|bmp)' ee xv
complete -f -X '!*.+(ps|PS|ps.gz)' gv
complete -f -X '!*.+(dvi|DVI)' dvips xdvi dviselect dvitype
complete -f -X '!*.+(pdf|PDF)' acroread xpdf
complete -f -X '!*.texi*' makeinfo texi2dvi texi2html
complete -f -X '!*.+(tex|TEX)' tex latex slitex
complete -f -X '!*.+(mp3|MP3)' mpg123

# kill sees only signals
complete -A signal kill -P '%'

# user commands see only users
complete -u finger su usermod userdel passwd

# bg completes with stopped jobs
complete -A stopped -P '%' bg

# other job commands
complete -j -P '%' fg jobs disown

# network commands complete with hostname
complete -A hostname ssh rsh telnet rlogin ftp ping fping host traceroute \
	    nslookup

# export and others complete with shell variables
complete -v export local readonly unset

# set completes with set options
complete -A setopt set

# shopt completes with shopt options
complete -A shopt shopt

# helptopics
complete -A helptopic help

# unalias completes with aliases
complete -a unalias

# various commands complete with commands
complete -c command type nohup exec nice eval strace gdb

# bind completes with readline bindings (make this more intelligent)
complete -A binding bind

# Now we get to the meat of the file, the functions themselves. Some
# of these are works in progress. Most assume GNU versions of the
# tools in question and may require modifications for use on vanilla
# UNIX systems.
#
# A couple of functions may have non-portable, Linux specific code in
# them, but this will be noted where applicable


# GNU chown(1) completion. This should be expanded to allow the use of
# ':' as well as '.' as the user.group separator.
#
_chown ()
{
	local cur prev user group

        COMPREPLY=()
        cur=${COMP_WORDS[COMP_CWORD]}
        prev=${COMP_WORDS[COMP_CWORD-1]}

	# do not attempt completion if we're specifying an option
	if [ "${cur:0:1}" = "-" ]; then return 0; fi

	# first parameter on line or first since an option?
	if [ $COMP_CWORD -eq 1 ] || [ "${prev:0:1}" = "-" ]; then
		case "$cur" in
		[a-zA-Z]*.*)
			user=${cur%.*}
			group=${cur#*.}
			COMPREPLY=( $( awk 'BEGIN {FS=":"} \
					{if ($1 ~ /^'$group'/) print $1}' \
					/etc/group ) )
			for (( i=0; i < ${#COMPREPLY[@]}; i++ )); do
				COMPREPLY[i]=$user.${COMPREPLY[i]}
			done
			return 0
			;;
		*)
			COMPREPLY=( $( compgen -u $cur -S '.' ) )
			return 0
			;;
		esac
	else
		COMPREPLY=( $( compgen -f $cur ) )
	fi

	return 0
}
complete -F _chown chown

# umount(8) completion. This relies on the mount point being the third
# space-delimited field in the output of mount(8)
#
_umount ()
{
	local cur

        COMPREPLY=()
        cur=${COMP_WORDS[COMP_CWORD]}

	# could rewrite the cut | grep to be a sed command, but this is
	# clearer and doesn't result in much overhead
	COMPREPLY=( $( mount | cut -d' ' -f 3 | grep ^$cur) )
	return 0
}
complete -F _umount umount

# GID completion. This will get a list of all valid group names from
# /etc/group and should work anywhere.
#
_gid_func ()
{
	local cur

        COMPREPLY=()
        cur=${COMP_WORDS[COMP_CWORD]}
	COMPREPLY=( $( awk 'BEGIN {FS=":"} {if ($1 ~ /^'$cur'/) print $1}' \
			   /etc/group ) )
	return 0
}
complete -F _gid_func groupdel groupmod

# mount(8) completion. This will pull a list of possible mounts out of
# /etc/fstab, unless the word being completed contains a ':', which
# would indicate the specification of an NFS server. In that case, we
# query the server for a list of all available exports and complete on
# that instead.
#
_mount ()

{       local cur

        COMPREPLY=()
        cur=${COMP_WORDS[COMP_CWORD]}

	case "$cur" in
	*:*)
	      COMPREPLY=( $( /usr/sbin/showmount -e --no-headers ${cur%%:*} |\
			       grep ^${cur#*:} | awk '{print $1}'))
		return 0
		;;
	*)
		COMPREPLY=( $( awk '{if ($2 ~ /\//) print $2}' /etc/fstab | \
			       grep ^$cur ))
		return 0
		;;
	esac
}
complete -F _mount mount

# Linux rmmod(1) completion. This completes on a list of all currently
# installed kernel modules.
#
_rmmod ()
{
	local cur

        COMPREPLY=()
        cur=${COMP_WORDS[COMP_CWORD]}

	COMPREPLY=($( lsmod | awk '{if (NR != 1 && $1 ~ /^'$cur'/) print $1}'))
	return 0
}
complete -F _rmmod rmmod

# Linux insmod(1) completion. This completes on a list of all
# available modules for the version of the kernel currently running.
#
_insmod ()
{
	local cur modpath

        COMPREPLY=()
        cur=${COMP_WORDS[COMP_CWORD]}
	modpath=/lib/modules/`uname -r`

	COMPREPLY=($( ls -R $modpath | sed -ne 's/^\('$cur'.*\)\.o$/\1/p'))
	return 0
}
complete -F _insmod insmod depmod modprobe

# man(1) completion. This relies on the security enhanced version of
# GNU locate(1). UNIX variants having non-numeric man page sections
# other than l, m and n should add the appropriate sections to the
# first clause of the case statement.
#
# This is Linux specific, in that 'man <section> <page>' is the
# expected syntax. This allows one to do something like
# 'man 3 str<tab>' to obtain a list of all string handling syscalls on
# the system.
#
_man ()
{
	local cur prev

        COMPREPLY=()
        cur=${COMP_WORDS[COMP_CWORD]}
        prev=${COMP_WORDS[COMP_CWORD-1]}

	case "$prev" in
	[0-9lmn])
		COMPREPLY=($( slocate -ql 0 -r '/man/man'$prev'/'$cur | \
		      sed -ne 's/^.*\/\('$cur'[^.\/]*\)\..*$/\1/p' ))
		return 0
		;;
	*)
		COMPREPLY=($( slocate -ql 0 -r '/man/man./'$cur | \
		      sed -ne 's/^.*\/\('$cur'[^.\/]*\)\..*$/\1/p' ))
		return 0
		;;
	esac
}
complete -F _man man

# Linux killall(1) completion. This wouldn't be much use on, say,
# Solaris, where killall does exactly that: kills ALL processes.
#
# This could be improved. For example, it currently doesn't take
# command line options into account
#
_killall ()
{
	local cur prev

        COMPREPLY=()
        cur=${COMP_WORDS[COMP_CWORD]}
        prev=${COMP_WORDS[COMP_CWORD-1]}

	case "$prev" in
	-[A-Z0-9]*)
		# get a list of processes (the first sed evaluation
		# takes care of swapped out processes, the second
		# takes care of getting the basename of the process)
	       COMPREPLY=( $( ps ahx | awk '{if ($5 ~ /^'$cur'/) print $5}' | \
			       sed -e 's#[]\[]##g' -e 's#^.*/##' ))
		return 0
		;;
	esac

	# first parameter can be either a signal or a process
	if [ $COMP_CWORD -eq 1 ]; then
		# standard signal completion is rather braindead, so we need
		# to hack around to get what we want here, which is to
		# complete on a dash, followed by the signal name minus
		# the SIG prefix
		COMPREPLY=( $( compgen -A signal SIG${cur#-} ))
		for (( i=0; i < ${#COMPREPLY[@]}; i++ )); do
			COMPREPLY[i]=-${COMPREPLY[i]#SIG}
		done
	fi

	# get processes, adding to signals if applicable
	COMPREPLY=( ${COMPREPLY[*]} $( ps ahx | \
		                       awk '{if ($5 ~ /^'$cur'/) print $5}' | \
				       sed -e 's#[]\[]##g' -e 's#^.*/##' ))
	return 0
}
complete -F _killall killall

# GNU find(1) completion. This makes heavy use of ksh style extended
# globs and contains Linux specific code for completing the parameter
# to the -fstype option.
#
_find ()
{
	local cur prev

        COMPREPLY=()
        cur=${COMP_WORDS[COMP_CWORD]#-}
        prev=${COMP_WORDS[COMP_CWORD-1]}

	case "$prev" in
	-@(max|min)depth)
		COMPREPLY=( $( compgen -W '0 1 2 3 4 5 6 7 8 9' ) )
		return 0
		;;
	-?(a)newer|-fls|-fprint?(0|f))
		COMPREPLY=( $( compgen -f $cur ) )
		return 0
		;;
	-fstype)
		# this is highly non-portable (the option to -d is a tab)
		COMPREPLY=( $( cut -d'	' -f 2 /proc/filesystems | grep ^$cur ) )
		return 0
		;;
	-gid)
		COMPREPLY=( $( awk 'BEGIN {FS=":"} \
				{if ($3 ~ /^'$cur'/) print $3}' /etc/group ) )
		return 0
		;;
	-group)
		COMPREPLY=( $( awk 'BEGIN {FS=":"} \
				{if ($1 ~ /^'$cur'/) print $1}' /etc/group ) )
		return 0
		;;
	-?(x)type)
		COMPREPLY=( $( compgen -W 'b c d p f l s' $cur ) )
		return 0
		;;
	-uid)
		COMPREPLY=( $( awk 'BEGIN {FS=":"} \
				{if ($3 ~ /^'$cur'/) print $3}' /etc/passwd ) )
		return 0
		;;
	-user)
		COMPREPLY=( $( compgen -u $cur ) )
		return 0
		;;
	-[acm]min|-[acm]time|-?(i)?(l)name|-inum|-?(i)path|-?(i)regex| \
	-links|-perm|-size|-used|-exec|-ok|-printf)
		# do nothing, just wait for a parameter to be given
		return 0
		;;
	esac

	# complete using basic options ($cur has had its dash removed here,
	# as otherwise compgen will bomb out with an error, since it thinks
	# the dash is an option to itself)
	COMPREPLY=( $( compgen -W 'daystart depth follow help maxdepth \
			mindepth mount noleaf version xdev amin anewer atime \
			cmin cnewer ctime empty false fstype gid group ilname \
			iname inum ipath iregex links lname mmin mtime name \
			newer nouser nogroup perm regex size true type uid \
			used user xtype exec fls fprint fprint0 fprintf ok \
			print print0 printf prune ls' $cur ) )

	# this removes any options from the list of completions that have
	# already been specified somewhere on the command line.
	COMPREPLY=( $( echo "${COMP_WORDS[@]}-" | \
		       (while read -d '-' i; do
			    [ "$i" == "" ] && continue
			    # flatten array with spaces on either side,
			    # otherwise we cannot grep on word boundaries of
			    # first and last word
			    COMPREPLY=" ${COMPREPLY[@]} "
			    # remove word from list of completions
			    COMPREPLY=( ${COMPREPLY/ ${i%% *} / } )
		        done
		        echo ${COMPREPLY[@]})
		  ) )
	
	# put dashes back
	for (( i=0; i < ${#COMPREPLY[@]}; i++ )); do
		COMPREPLY[i]=-${COMPREPLY[i]}
	done
		
	return 0
}
complete -F _find find

# Linux ifconfig(8) completion
#
_ifconfig ()
{
	local cur

        COMPREPLY=()
        cur=${COMP_WORDS[COMP_CWORD]}

	case "${COMP_WORDS[1]}" in
	-|*[0-9]*)
		COMPREPLY=( $( compgen -W '-a up down arp promisc allmulti \
					   metric mtu dstaddr netmask add del \
					   tunnel irq io_addr mem_start media \
					   broadcast pointopoint hw multicast \
					   address txqueuelen' $cur ))
		COMPREPLY=( $( echo " ${COMP_WORDS[@]}" | \
			       (while read -d ' ' i; do
				   [ "$i" == "" ] && continue
				   # flatten array with spaces on either side,
				   # otherwise we cannot grep on word
				   # boundaries of first and last word
				   COMPREPLY=" ${COMPREPLY[@]} "
				   # remove word from list of completions
				   COMPREPLY=( ${COMPREPLY/ $i / } )
				done
			        echo ${COMPREPLY[@]})
			  ) )
		return 0
		;;
	esac

	COMPREPLY=( $( ifconfig -a | sed -ne 's/^\('$cur'[^ ]*\).*$/\1/p' ))
}
complete -F _ifconfig ifconfig

# Linux ipsec(8) completion (for FreeS/WAN). Very basic.
#
_ipsec ()
{
	local cur

        COMPREPLY=()
        cur=${COMP_WORDS[COMP_CWORD]}

	COMPREPLY=( $( compgen -W 'auto barf eroute klipsdebug look manual \
				   pluto ranbits rsasigkey setup showdefaults \
				   showhostkey spi spigrp tncfg whack' $cur ))
}
complete -F _ipsec ipsec
#########################################################################
