scop / bash-completion

Programmable completion functions for bash
GNU General Public License v2.0
2.92k stars 380 forks source link

Additional helper functions #432

Open qtc-de opened 4 years ago

qtc-de commented 4 years ago

Hey :)

I'm new to bash-completion and wrote my completion scripts from scratch so far. Now I'm really happy that I can use some helper functions from your scripts. However, certain helper functions I used in the past are missing and I wanted to ask if you are interested in implementing them or if you can tell me alternative solution how these cases can be covered by your script (I would create a pull request, but as you will see my bash skills are really poor. Furthermore, I'm not sure if the functions are already implemented somewhere and I'm missing them).

1. Get n-th Parameter

As I can see, you support a _get_first_arg function. This is nice, but in the past I also need to obtain the n-th parameter from the command line. I used the following function to accomplish this:

function _comp_get_parameter() {
    # Returns the n-th parameter from the command line (not counting options
    # or the command name).
    #
    # Parameters
    #   number          (int)           Parameter to select
    #
    # Returns
    #   retval          (int)           Error / Success
    #
    # Side effects
    #   result is saved in $arg variable
    #
    local COUNT

    COUNT=0
    arg=

    for var in ${COMP_LINE}; do

    # exclude options and the toolname
    if [[ "$var" == -* ]] || [[ "$var" == "${COMP_WORDS[0]}" ]]; then
        continue;
    fi

        COUNT=$((COUNT+1))
        if [[ $COUNT -eq $1 ]]; then
            arg="$var"
            return 0
        fi

    done

    return 1
}

2. List Contains

This is not really completion specific, but it is that useful that I use it in nearly all my completion scripts. The idea is to compare two strings that contain space separated words with each other. If one word matches, it returns true, otherwise false.

function _comp_contains() {
    # Takes two strings containing space seperated words and checks if one of the
    # words in the second list matches one in the first.
    # E.g.: `_comp_contains "test test2" "no nope test"` returns true.
    #
    # Parameters
    #   list_lookup     (string)        Space seperated words to search in
    #   list_search     (string)        Space seperated words to search
    #
    # Returns
    #   retval          (int)           Error / Success
    #
    # Side effects
    #   None
    #
    for word in $2; do

        if [[ $1 =~ (^|[[:space:]])${word}($|[[:space:]]) ]]; then
            return 0
        fi

    done;

    return 1
}

Sometimes you have parameter groups for a command. E.g. you supply the argument --decrypt that can be combined with --username and --password, but not with other options that would be allowed otherwise. In this cases I check if --decrypt, --username or --password are present on the command line. If this is the case, I only complete the corresponding set. This can be done with: if _comp_contains "${COMP_LINE}" "--decrypt --username --password";

3. Remove Already Used Arguments

The curl command is a good example. Let's look at its completions:

[tneitzel@analysis-tn ~]$ curl --ntlm
--ntlm     --ntlm-wb  
[tneitzel@analysis-tn ~]$ curl --ntlm --ntlm
--ntlm     --ntlm-wb  

After -ntlm is specified once, it is not useful to complete this option a second time. To prevent this, I usually use the following function:

function _comp_filter() {
    # Takes the name of a variable that contains the current option list as a string.
    # Iterates over the current command line and removes all options that are already
    # present.
    #
    # A second string of space seperated words can be passed
    # that are excluded from filtering. This is useful, when certain options are allowed
    # to appear multiple times.
    #
    # Parameters
    #   opts            (string)        Space seperated words to filter (call by ref)
    #   exclude         (string)        Space seperated words to exclude
    #
    # Returns
    #   reval           (int)           Error / Succes
    #
    # Side effects
    #   filtered option list is stored in first variable (passwd by ref)
    #
    local Opts=$1 filter exclude cur

    cur="${COMP_WORDS[COMP_CWORD]}"
    filter=" ${!Opts} "
    exclude=$2

    # iterate over each word inside the current command line
    for var in ${COMP_LINE}; do

        # exclude the current word to allow full specified options to be space completed
    if [[ "$var" == "$cur" ]]; then
        continue;

        # exclude words from the exclusion list
        elif _comp_contains "$exclude" $var; then
            continue

        # otherwise remove the word from the filter list
        else
            # remove actual option
            filter=( "${filter/ ${var} / }" )
        fi

    done

    _upvars -v $Opts "$filter" 
}

After using _comp_filter opts, duplicate options are filtered from my completion list.

4. Removing Short Arguments

I agree with your guidelines that short options should not be completed. However, for the completion logic it is sometimes useful to define a list with a certain set of arguments, e.g. -o --output -m --merge .... Moreover, this list could sometimes be used again to define the completion list. In these cases it is useful when you have a function that filters the short options.

function _comp_filter_shorts() {
    # Takes a string of space seperated words and removes all short options from it.
    # Completion of short options is a matter of taste. The maintainers of bash-completion
    # do not recommend it. Instead, completions should only handle completion for arguments
    # that are required by short options, but to not complete short options themselfs.
    #
    # Parameters
    #   opts            (string)        Space seperated option list (call by ref)
    #
    # Returns
    #   reval           (int)           Error / Succes
    #
    # Side effects
    #   filtered option list is stored in first variable (passwd by ref)
    #
    local Opts=$1 filter

    filter=" ${!Opts} "

    for word in $filter; do

        if [[ $word =~ ^-[^-]+$ ]]; then
            filter=( "${filter/ ${word} / }" )
        fi

    done

    _upvars -v $Opts "$filter" 
}

After using _comp_filter_shorts opts, the options list does no longer contain short options.

Summarized

Like already mentioned, the above shown functions are implemented rather poorly and probably contain some bugs for situations that I have not thought of. However, I think that the actual functionalities are quite useful and could be used for many completion scripts. Therefore, I would be happy if they are present in the default completion toolset for bash.

It would be great if you could supply some feedback for each function. I would be interested whether:

  1. You think that the functionality is generally useful.
  2. The corresponding task could be done using the functions exposed by your script.
  3. You are willing to implement the functionality in your script.

Best Regards Tobias

scop commented 4 years ago

Thanks! 1-3 are useful, no doubt, and I think I have some private prototypes for those stashed somewhere. Will take a look when I find time.

4) on the other hand I don't think I'm a fan of -- general the idea of using it for option arg completions and then filtering out shorts for completion suggestions is ok I think, however when implemented properly, short option arg completions should also take option bundling into account, which makes things somewhat more complicated. But if we can make them work nicely with something like the suggestion, no problem here. But as with all functions, we want to have actual uses for them within bash-completion -- to dogfood them if nothing else -- so some example implementations would be needed.

Processwise I think it would be wise to approach getting these in in some order, one at a time, and opening separate issues/PR's for each.

qtc-de commented 4 years ago

on the other hand I don't think I'm a fan of -- ...

Fair point. I should probably read some more of your completion scripts from this repo to understand how certain things can be implemented. I used the function e.g. in this script kutil, but as already said there are maybe better ways to get the job done.

Concerning the other functions I agree that separate issues/PR's make sense. What do your prefer? I guess Pull requests containing my poorly written bash functions creates a lot of overhead as you probably want to modify many things (especially since you have already some private implementations). So may you just close this issue and I create 3 new one?