A bit of mbfl development and the Video of the Day

Posted on Sep 29, 2020

It’s been a while since I last touched mbfl’s code base; today I tagged development revision ‘v3.0.0-devel.1’. The latest added feature is the ability to automatically generate a gnu Bash command–line completion script, using the gnu Readline facilities, for a shell script using the ‘actions’ module.

We can inspect the generated code from the build directory of the mbfl package by running the included template script as follows:

$ bash examples/template-actions.sh help print-completions-script

the process prints to ‘stdout’ a sequence of Bash commands and functions implementing automatic command–line completion for the actions of the script template-actions.sh itself; there is a function for each action, both node actions and leaf actions.

We can store the generated script in a file to be sourced upon launching the terminal, or we can evaluate it with the command:

eval "$(my-script help print-completions-script)"

For a script named my-script, it is a good idea to install a file with pathname:

/etc/bash_completions.d/my-script.bash

with contents:

# my-script.bash --
#
# Completions for the "my-script" script.

eval "$(my-script help print-completions-script)"

### end of file

The documentation of the ‘actions’ module describes the implemented api. At present, only command–line actions–completion is automatically generated; if we want automatic completion for the arguments of a leaf action: we must redefine the automatically–generated completion–function of that leaf action.

I would like to do something for command line options too (the arguments starting with a dash or double–dash) but it is more complicated because, until now, scripts are allowed to add options at the last moment with the script_before_parsing_options_* functions; so the available options are not statically declared like the tree of actions. I do not know if it is a good idea to change that; probably not.

What I could do is to implement a new predefined mbfl option, something like --print-options-completions-script, that generates completion functions for the options after the script_before_parsing_options_* function of a leaf action has been called. To use such a feature we should call the command script itself once for every leaf action, handing to it the new option; such sequence of script invocations can be automatically generated. I will think of it.

Pluggable completion scripts

The automatically–generated completion–scripts use a very simple handling of indices in the COMP_WORDS array that makes the completion facilities “pluggable”. For example: I have a private package on my Slackware system called mmux-system-administration; this package installs a script named admin. Its completion script starts as follows:

complete -F p-mmux-system-administration-completion-admin -o default admin

function p-mmux-system-administration-completion-admin () {
  local -r word_to_be_completed=${COMP_WORDS[${COMP_CWORD}]}
  p-mmux-system-administration-dispatch-completion-admin 0
}

function p-mmux-system-administration-dispatch-completion-admin () {
  local -ir index_of_command=${1:?'missing argument: index of command'}
  local -ir index_of_subcommand=$((1 + index_of_command))

  ...
}

The function p-mmux-system-administration-completion-admin() is just a trampoline, the function p-mmux-system-administration-dispatch-completion-admin() is the one that actually starts the command–line completions generation.

I can plug these facilities into an independent function, let’s call it menu(); it can be implemented as follows:

complete -F p-menu-completion-menu -o default menu

function menu () {
    "$@"
}

function p-menu-compgen-menu () {
    local -r candidate_completions=${1:?"missing candidate completions argument to '${FUNCNAME}'"}
    COMPREPLY=(`compgen -W "$candidate_completions" -- "$word_to_be_completed"`)
}

function p-menu-completion-menu () {
    local -r word_to_be_completed=${COMP_WORDS[${COMP_CWORD}]}
    p-menu-dispatch-completion-menu 0
}

function p-menu-dispatch-completion-menu () {
    local -ir index_of_command=${1:?"missing argument: index of command to '${FUNCNAME}'"}
    local -ir index_of_next_arg=$((1 + index_of_command))

    if ((index_of_next_arg == COMP_CWORD))
    then p-menu-compgen-menu 'admin'
    elif ((index_of_next_arg < COMP_CWORD))
    then
        case "${COMP_WORDS[$index_of_next_arg]}" in
            'admin')
                p-mmux-system-administration-dispatch-completion-admin $index_of_next_arg
                ;;
        esac
    else
        printf 'error: command-line completion function invoked incorrectly: %s\n' "$FUNCNAME" >&2
        return 1
    fi
}

if we evaluate this code we can call the script admin as a subcommand of menu() as follows:

menu admin this that

with automatically–generated command–line completion of the actions of admin. I am testing this in my terminals; by plugging the completions into the one of menu() I do not need to remember which names have the scripts I installed (I have a rarely used acer-aspire-v5 that I do not always remember).

Video of the day

I enjoyed this live. Fortunately there are still new songs that I like, even though they are maybe 1% of the ones I try to listen to…