ENH: improve bash completion functionality (issue #551)

- use complete -o filenames, dropped -o nospace to make it more responsive.

- restructure completion code to use a unified backend, which makes it easier
  understand, maintain and re-use.

- foamCreateBashCompletions now simply outputs to a stdout, and allows
  quick generation of completion of single applications.

- add -fileHandler completion in anticipation of future changes there.

- relocated as etc/config.s/bash_completion to prevent inadvertently
  having two versions (.com, .org) installed at the same time.
This commit is contained in:
Mark Olesen
2017-07-31 15:35:40 +02:00
parent 797155f862
commit c614110d46
5 changed files with 2421 additions and 8671 deletions

View File

@ -34,37 +34,27 @@ usage() {
while [ "$#" -ge 1 ]; do echo "$1"; shift; done while [ "$#" -ge 1 ]; do echo "$1"; shift; done
cat<<USAGE cat<<USAGE
Usage: ${0##*/} [OPTION] <outputFile> Usage: ${0##*/} [OPTION] [appName .. [appNameN]]
options: options:
-d dir | -dir dir Directory to process -d dir | -dir dir Directory to process
-u | -user Add \$FOAM_USER_APPBIN to the search directories -u | -user Add \$FOAM_USER_APPBIN to the search directories
-head | -header Generate header
-h | -help Print the usage -h | -help Print the usage
Create bash completions for OpenFOAM applications and write to <outputFile>. Create bash completions for OpenFOAM applications and write to stdout.
By default searches \$FOAM_APPBIN only. By default searches \$FOAM_APPBIN only.
Alternatively, scan the output from individual applications for single completion
commands (using the '_of_complete_' backend).
USAGE USAGE
exit 1 exit 1
} }
# Report error and exit
die()
{
exec 1>&2
echo
echo "Error encountered:"
while [ "$#" -ge 1 ]; do echo " $1"; shift; done
echo
echo "See '${0##*/} -help' for usage"
echo
exit 1
}
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
#set -x
unset outFile
searchDirs="$FOAM_APPBIN" searchDirs="$FOAM_APPBIN"
unset optHeader
while [ "$#" -gt 0 ] while [ "$#" -gt 0 ]
do do
case "$1" in case "$1" in
@ -79,88 +69,159 @@ do
-u | -user) -u | -user)
searchDirs="$searchDirs $FOAM_USER_APPBIN" searchDirs="$searchDirs $FOAM_USER_APPBIN"
;; ;;
-head | -header)
optHeader=true
;;
-*) -*)
usage "unknown option: '$1'" usage "unknown option: '$1'"
;; ;;
*) *)
outFile=$1
break break
;; ;;
esac esac
shift shift
done done
[ -n "$outFile" ] || usage "No output file specified" # No applications given, then always generate a header
if [ "$#" -eq 0 ]
then
optHeader=true
fi
# Header requested or required
# Generate header [ "$optHeader" = true ] && cat << HEADER
cat << HEADER > $outFile
#----------------------------------*-sh-*-------------------------------------- #----------------------------------*-sh-*--------------------------------------
# Bash completions for OpenFOAM applications # Bash completions for OpenFOAM applications
# Recreate with "${0##*/}"
#
# Formatted as "complete ... -F _of_APPNAME APPNAME # Formatted as "complete ... -F _of_APPNAME APPNAME
unset -f _of_filter_opts 2>/dev/null #
_of_filter_opts() # Generic completion handler for OpenFOAM applications
# - arg1 = command-name
# - arg2 = current word
# - arg3 = previous word
# - arg4 = options with args
# - arg5 = options without args
#
unset -f _of_complete_ 2>/dev/null
_of_complete_()
{ {
local allOpts=\$1 # Unused: local cmd=\$1
local applied=\$2 local cur=\$2
for o in \${allOpts}; do local prev=\$3
[ "\${applied/\$o/}" == "\${applied}" ] && echo \$o local optsWithArgs="\$4 " # Trailing space added for easier matching
local opts="\$5 "
case \${prev} in
-help|-doc|-srcDoc)
COMPREPLY=()
;;
-case)
COMPREPLY=(\$(compgen -d -- \${cur}))
;;
-time)
# Could use "foamListTimes -withZero", but still doesn't address ranges
COMPREPLY=(\$(compgen -d -X '![-0-9]*' -- \${cur}))
;;
-region)
local list=\$(\ls -d system/*/ 2>/dev/null | sed -e 's#/\$##' -e 's#^.*/##')
COMPREPLY=(\$(compgen -W "\$list" -- \${cur}))
;;
-fileHandler)
COMPREPLY=(\$(compgen -W "collated uncollated masterUncollated" -- \${cur}))
;;
*Dict)
# local dirs=\$(\ls -d s*/)
# local files=\$(\ls -f | grep Dict)
# COMPREPLY=(\$(compgen -W \"\$dirs \$files\" -- \${cur}))
COMPREPLY=(\$(compgen -f -- \${cur}))
;;
*)
if [ "\${optsWithArgs/\${prev} /}" != "\${optsWithArgs}" ]
then
# Option with unknown type of arg - set to files.
# Not always correct but can still navigate path if needed...
COMPREPLY=(\$(compgen -f -- \${cur}))
else
# Catchall
# - Present remaining options (not already seen in $COMP_LINE)
opts=\$(
for o in \${opts} \${optsWithArgs}
do
[ "\${COMP_LINE/\$o/}" = "\${COMP_LINE}" ] && echo \$o
done done
)
COMPREPLY=(\$(compgen -W "\${opts}" -- \${cur}))
fi
;;
esac
return 0
} }
#------------------------------------------------------------------------------
HEADER HEADER
#------------------------------------------------------------------------------ #-------------------------------------------------------------------------------
# Scans the output of the application -help to detect options with/without
# arguments. Dispatch via _of_complete_
# #
# Produce contents for switch for common options generateCompletion()
#
commonOptions()
{ {
local indent1=" " local fullName="$1"
local indent2=" " local appName="${1##*/}"
for opt local appHelp
do
case $opt in [ -f "$fullName" -a -x "$fullName" ] || {
-case) echo "skip $fullName" 1>&2
echo "${indent1}-case)" return 1
echo "${indent2}COMPREPLY=(\$(compgen -d -- \${cur}))" }
echo "${indent2};;" if [ "$appName" = "complete_" ]
;; then
-srcDoc|-help) echo "skip $appName ... reserved name?" 1>&2
echo "${indent1}-srcDoc|-help)" return 1
echo "${indent2}COMPREPLY=()" fi
echo "${indent2};;"
;; appHelp=$($fullName -help) || {
-time) echo "error calling $fullName" 1>&2
echo "${indent1}-time)" return 1
echo "${indent2}COMPREPLY=(\$(compgen -d -X '![-0-9]*' -- \${cur}))" }
echo "${indent2};;"
;; echo " $appName" 1>&2
-region)
echo "${indent1}-region)" # Options with args - as array
echo "${indent2}local regions=\$(sed 's#/##g' <<< \$([ -d system ] && (\cd system && (\ls -d */ 2>/dev/null))))" local optsWithArgs=($(awk '/^ {0,4}-[a-z]/ && /</ {print $1}' <<< "$appHelp"))
echo "${indent2}COMPREPLY=(\$(compgen -W \"\$regions\" -- \${cur}))"
echo "${indent2};;" # Options without args - as array
;; local opts=($(awk '/^ {0,4}-[a-z]/ && !/</ {print $1}' <<< "$appHelp"))
*Dict)
echo "${indent1}*Dict)" # See bash(1) for some details. Completion functions are called with
# echo "${indent2}local dirs=\$(\ls -d s*/)" # arg1 = command-name, arg2 = current word, arg3 = previous word
# echo "${indent2}local files=\$(\ls -f | grep Dict)" #
# echo "${indent2}COMPREPLY=(\$(compgen -W \"\$dirs \$files\" -- \${cur}))" # Append known option types and dispatch to _of_complete_
echo "${indent2}COMPREPLY=(\$(compgen -f -- \${cur}))" cat << COMPLETION
echo "${indent2};;"
;; # $appName
esac unset -f _of_${appName} 2>/dev/null
done _of_${appName}() {
_of_complete_ "\$@" \\
"${optsWithArgs[@]}" \\
"${opts[@]}"
} && complete -o filenames -F _of_${appName} $appName
COMPLETION
} }
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
for dir in ${searchDirs}
do if [ "$#" -eq 0 ]
then
for dir in ${searchDirs}
do
if [ -d "$dir" ] if [ -d "$dir" ]
then then
echo "Processing directory $dir" 1>&2 echo "Processing directory $dir" 1>&2
@ -173,55 +234,30 @@ do
set -- $(\ls $dir | sort -f) set -- $(\ls $dir | sort -f)
for appName for appName
do do
[ -f "$dir/$appName" -a -x "$dir/$appName" ] || continue generateCompletion "$dir/$appName"
appHelp=$($appName -help)
echo " $appName" 1>&2
# Options with args
optsWithArgs=($(awk '/^ {0,4}-[a-z]/ && /</ {print $1}' <<< "$appHelp"))
# Options without args
opts=($(awk '/^ {0,4}-[a-z]/ && !/</ {print $1}' <<< "$appHelp"))
cat << WRITECOMPLETION >> $outFile
unset -f _of_${appName} 2>/dev/null
_of_${appName}()
{
local cur="\${COMP_WORDS[COMP_CWORD]}"
local prev="\${COMP_WORDS[COMP_CWORD-1]}"
local opts="${opts[@]} "
local optsWithArgs="${optsWithArgs[@]} "
case \${prev} in
$(commonOptions ${optsWithArgs[@]})
*)
if [ "\${optsWithArgs/\${prev} /}" != "\${optsWithArgs}" ]
then
# Unknown type of arg follows - set to files.
# Not always correct but can still navigate path if needed...
COMPREPLY=(\$(compgen -f -- \${cur}))
else
# Catch-all - present all remaining options
opts=\$(_of_filter_opts "\${opts}" "\${COMP_LINE}")
optsWithArgs=\$(_of_filter_opts "\${optsWithArgs}" "\${COMP_LINE}")
COMPREPLY=(\$(compgen -W "\${opts} \${optsWithArgs}" -- \${cur}))
fi
;;
esac
return 0
}
complete -o nospace -F _of_${appName} $appName
WRITECOMPLETION
done done
done done
else
for appName
do
if [ -f "$appName" -a -x "$appName" ]
then
generateCompletion "$appName"
elif fullName=$(command -v $appName 2>/dev/null)
then
generateCompletion "$fullName"
else
echo "No application found: $appName" 1>&2
fi
done
fi
# Generate footer # Generate footer
cat << FOOTER >> $outFile [ "$optHeader" = true ] && cat << FOOTER
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
FOOTER FOOTER

View File

@ -177,7 +177,7 @@ then
# Bash completions # Bash completions
if command -v complete > /dev/null 2>&1 if command -v complete > /dev/null 2>&1
then then
_foamEtc config.sh/bashcompletion _foamEtc config.sh/bash_completion
fi fi
fi fi

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -183,9 +183,9 @@ unset -f foamPV 2>/dev/null
# Cleanup bash completions, which look like this: # Cleanup bash completions, which look like this:
# "complete ... -F _of_APPNAME APPNAME # "complete ... -F _of_APPNAME APPNAME
# For economy, obtain list first but also add in 'filter_opts' helper # For economy, obtain list first but also remove the 'of_complete_' backend
foamClean="$(complete 2>/dev/null | sed -n -e 's/complete.*-F _of_.* \(..*\)$/\1/p')" foamClean="$(complete 2>/dev/null | sed -n -e 's/complete.*-F _of_.* \(..*\)$/\1/p')"
for cleaned in $foamClean filter_opts for cleaned in $foamClean complete_
do do
unset -f _of_$cleaned 2>/dev/null unset -f _of_$cleaned 2>/dev/null
complete -r $cleaned 2>/dev/null complete -r $cleaned 2>/dev/null