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.
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).
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…