gsu: Initial completion support.
authorAndre Noll <maan@systemlinux.org>
Fri, 23 Sep 2011 15:21:58 +0000 (17:21 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Fri, 29 Aug 2014 19:39:58 +0000 (21:39 +0200)
This adds the framework for command completion of scripts which make
use of the gsu subcommand library. The idea is to implement as much
as possible as generic helpers within gsu in order to minimize code
duplication in the scripts which source the gsu library.

The new "complete" subcommand is automatically included in each
application, just like the help, man and prefs commands we already
have. This subommand is meant to be used as the completer which calls
the custom subcommand completers included in the application.

Two new public helper functions, gsu_complete_options() and
gsu_cword_is_option_parameter() are introduced in this commit.

The former function takes a usual getopt string and completes according
to all options in this string. The latter is useful for completers
which need to determine whether the current word is the parameter to
some option in order to find out how to complete the current word.

For example, the -s option of cmt's vmem command takes a mandatory
parameter which is a number. So the completer for this command must
complete differently depending on whether the previous word is "-s".

misc/gsu/subcommand

index f09c66d..f5f4745 100644 (file)
@@ -18,7 +18,7 @@ export gsu_command_regex='^com_\([-a-zA-Z_0-9]\+\)()'
 _gsu_available_commands()
 {
        result="$({
-               printf "help\nman\nprefs\n"
+               printf "help\nman\nprefs\ncomplete\n"
                sed -ne "s/$gsu_command_regex/\1/g;T;p" $0
                } | sort | tr '\n' ' ')"
 }
@@ -43,6 +43,26 @@ _gsu_print_available_commands()
 ) 2>&1
 }
 
+gsu_complete_options()
+{
+       local opts="$1" cword="$2" cur
+       local -a words
+
+       shift 2
+       words=("$@")
+       cur="${words[$cword]}"
+       ret=0
+       [[ ! "$cur" == -* ]] && return
+
+       ret=0
+       for ((i=0; i < ${#opts}; i++)); do
+               opt="${opts:$i:1}"
+               [[ "$opt" == ":" ]] && continue
+               printf "%s" "-$opt "
+               let ret++
+       done
+}
+
 export gsu_prefs_txt="
 Print the current preferences.
 
@@ -93,6 +113,11 @@ _com_prefs()
        done
 }
 
+complete_prefs()
+{
+       gsu_complete_options "e" "$@"
+}
+
 export gsu_man_txt="
 Print the manual.
 
@@ -155,6 +180,18 @@ Usage: help [command]
 Without arguments, print the list of available commands. Otherwise,
 print the help text for the given command."
 
+export gsu_complete_txt="
+Command line completion.
+
+Usage: complete [<cword> <word>...]
+
+In the first form, the command prints all possible completions to stdout.
+This can be used from the completion function of the shell.
+
+Completion code suitable to be evaled is written to stdout if no argument
+was given.
+"
+
 _com_help()
 {
        local a b
@@ -165,6 +202,7 @@ _com_help()
                        printf "com_help()\n$gsu_help_txt" | head -n 4; echo "--"
                        printf "com_man()\n$gsu_man_txt" | head -n 4; echo "--"
                        printf "com_prefs()\n$gsu_prefs_txt" | head -n 4; echo "--"
+                       printf "com_complete()\n$gsu_complete_txt" | head -n 4; echo "--"
                        grep -A 2 "$gsu_command_regex" $0
                } | grep -v -- '--' \
                        | sed -e "/$gsu_command_regex/bs" \
@@ -200,6 +238,11 @@ _com_help()
                ret=$GSU_SUCCESS
                return
        fi
+       if test "$1" = "complete"; then
+               echo "$gsu_complete_txt"
+               ret=$GSU_SUCCESS
+               return
+       fi
        ret=$GSU_SUCCESS
        if grep -q "^com_$1()" $0; then
                sed -e "1,/^com_$1()$/d" -e '/^{/,$d' -e 's/^## *//' $0
@@ -210,6 +253,12 @@ _com_help()
        ret=-$E_GSU_BAD_COMMAND
 }
 
+complete_help()
+{
+       _gsu_available_commands
+       echo "$result"
+}
+
 # Wrapper for bash's getopts.
 #
 # Aborts on programming errors such as missing or invalid option string.  On
@@ -296,6 +345,64 @@ gsu_getopts()
        ret=$GSU_SUCCESS
 }
 
+_com_complete()
+{
+       local cmd n cword="$1"
+       local -a words
+
+       if (($# == 0)); then
+               cat <<EOF
+               local cur="\${COMP_WORDS[\$COMP_CWORD]}";
+               local -a candidates;
+
+               candidates=(\$($0 complete "\$COMP_CWORD" "\${COMP_WORDS[@]}"));
+               COMPREPLY=(\$(compgen -W "\${candidates[*]}" -- "\$cur"));
+EOF
+       fi
+
+       [[ -z "$cword" ]] && return
+       if (($cword <= 1)); then
+               _gsu_available_commands
+               echo "${result}"
+               ret=$GSU_SUCCESS
+               return
+       fi
+       shift
+       words=("$@")
+       cmd="${words[1]}"
+       ret=$GSU_SUCCESS # It's not an error if no completer was defined
+       [[ "$(type -t complete_$cmd)" != "function" ]] && return
+       complete_$cmd "$cword" "${words[@]}"
+       # ignore errors, they would only clutter the completion output
+       ret=$GSU_SUCCESS
+}
+
+gsu_cword_is_option_parameter()
+{
+       local opts="$1" cword="$2" prev i n
+       local -a words
+
+       result=
+       (($cword == 0)) && return
+       ((${#opts} < 2)) && return
+
+       shift 2
+       words=("$@")
+       prev="${words[$(($cword - 1))]}"
+       [[ ! "$prev" == -* ]] && return
+
+       n=$((${#opts} - 1))
+       for ((i=0; i < $n; i++)); do
+               opt="${opts:$i:1}"
+               [[ "${opts:$(($i + 1)):1}" != ":" ]] && continue
+               let i++
+               [[ "$prev" != "-$opt" ]] && continue
+               result="$opt"
+               return
+       done
+       ret=0
+}
+
 gsu()
 {
        local i
@@ -310,7 +417,7 @@ gsu()
        arg="$1"
        shift
        # check internal commands
-       if [[ "$arg" = "help" || "$arg" = "man" || "$arg" = "prefs" ]]; then
+       if [[ "$arg" = "help" || "$arg" = "man" || "$arg" = "prefs" || "$arg" = "complete" ]]; then
                _com_$arg "$@"
                if [[ "$ret" -lt 0 ]]; then
                        gsu_err_msg