Hi,

I'm using the great bash-completion package for quite a while now,
but I find it lacks a good support of aliases. (Yes, I know they are
deprecated in favour of shell functions, but there are many people
out there which use them a lot.)

Therefore, I'd like to propose the attached code to be included in a
future release. It basically rebinds the completion of the aliased
command once completion of an alias is attempted.

However, there are some caveats:
(a) An aliased command must not contain whitespace in its name, as
    it is used to split words.
(b) Does not support semantically broken aliases, like:
    alias foo='SomeCommand --user'
    because the rebound completion handler of SomeCommand cannot see
    that the previous word would be --user to propose user names.
(c) Currently, handles sudo and the _longopt completion function specially.
    There might be other commands/functions where that could be necessary.

If you are interested/have further questions/suggestions, drop me an email.

ciao,
Mario
#
# vim: set tabstop=4 shiftwidth=4 noexpandtab filetype=sh:
#
# File:
#   GenericAliases.bash
#
# Description:
#   Bash-completion helpers to handle aliases.
#
# Author(s):
#   (c) 2012-2014 Mario Schwalbe ([email protected])
#

#
# Get aliased command for alias ($1).
# Returns: 0 on success, 1 otherwise.
#
__get_aliased_command()
{
        local aliasFor=$(alias "$1" 2> /dev/null)
        [ -z "$aliasFor" ] && return 1

        # strip alias specification stuff
        aliasFor=${aliasFor#*\'}
        aliasFor=${aliasFor%\'}

        # remove leading and trailing spaces
        aliasFor=${aliasFor##+( )}
        aliasFor=${aliasFor%%+( )}

        # strip first word of command unless like `alias so=sudo -E'
        if [[ "$aliasFor" == "sudo "* ]] ; then
                local w
                for w in ${aliasFor#* } ; do
                        if [[ "$w" != -* ]] ; then
                                aliasFor=$w
                                break
                        fi
                done
        fi

        # get first word of command
        aliasFor=${aliasFor%% *}

        # stip path to avoid endless recursion due to `alias ps=/bin/ps fax'
        aliasFor=${aliasFor##*/}

        echo $aliasFor
}

#
# Rebind previously existing completion of a command ($2) to another command 
($1).
# Returns: 0 on success, 1 otherwise.
#
__rebind_completion()
{
        if [ $# -lt 2 ] ; then
                echo "__rebind_completion: Missing arument(s): $@" > /dev/stderr
                return 1
        fi

        local cmd=$1 aliasFor=$2

        # get completion
        local oldComp=$(complete -p "$aliasFor" 2> /dev/null)

        if [ -n "$oldComp" ] ; then
                # replace trailing command part
                local newComp=${oldComp/% $aliasFor/ $cmd}

                if [ "$newComp" != "$oldComp" ] ; then
                        if [[ "$newComp" == *"-F _longopt "* ]] ; then
                                eval "
                                        __${cmd}_longopt()
                                        {
                                                _longopt $aliasFor \${@:2}
                                        }
                                "
                                newComp=${newComp/_longopt/__${cmd}_longopt}
                        fi

                        $newComp > /dev/null 2>&1
                        return $?
                fi
        fi

        return 1
}

#
# Lookup and rebind previously existing completion of a command ($2) to another 
command ($1).
# Returns: 0 on success, 1 otherwise.
#
__lookup_completion()
{
        if [ $# -lt 2 ] ; then
                echo "__lookup_completion: Missing arument(s): $@" > /dev/stderr
                return 1
        fi

        local cmd=$1 aliasFor=$2

        if complete -p "$aliasFor" > /dev/null 2>&1 ; then
                # have completion: rebind
                __rebind_completion $cmd "$aliasFor"
                return $?
        elif [ "$aliasFor" != "${aliasFor##*/}" ] ; then
                # aliased command contained a path: retry without
                __lookup_completion $cmd "${aliasFor##*/}"
                return $?
        else
                # did not find anything: try to expand one more time
                local nextAliasFor=$(__get_aliased_command "$aliasFor")

                # break recursion in case of `alias ls=ls --color=auto' or end 
of list
                if [ -n "$nextAliasFor" -a "$nextAliasFor" != "$aliasFor" ] ; 
then
                        __lookup_completion $cmd "$nextAliasFor"
                        return $?
                else
                        _completion_loader "$aliasFor"
                        __rebind_completion $cmd "$aliasFor"
                        return $?
                fi
        fi

        echo "__lookup_completion: FIXME: unexpectedly reached end" > 
/dev/stderr
        return 1
}

#
# Default completion handler to rebind aliases on demand.
# Parameters:
#     1. command/alias ... command/alias to resolve completion for
#     2. current word  ... (see BASH completions)
#     3. previous word ... (see BASH completions)
# Returns:
#     0 on success, 124 retry completion, 1 otherwise.
#
__lookup_completion_handler()
{
        local cmd=$1 aliasFor=$(__get_aliased_command "$1")

        if [ -n "$aliasFor" ] ; then
                # try to resolve chains of aliases and rebind completion
                __lookup_completion $cmd "$aliasFor" && return 124
                return $?
        else
                # not an alias: call default implementation
                _completion_loader $cmd
                return $?
        fi
}

# overwrite default completion handler to also lookup aliases
complete -F __lookup_completion_handler -D

# ***** end of source *****

_______________________________________________
Bash-completion-devel mailing list
[email protected]
http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/bash-completion-devel

Reply via email to