Commit ce086810 authored by Mark Olesen's avatar Mark Olesen
Browse files

ENH: use bash associative array for on-the-fly completion (issue #551)

- this reduces the number of functions and allows lazy loading of
  completion options, which makes it easy to quickly add any other
  OpenFOAM application in completion.

  The generic '_of_complete_' function handles (bash) completion for
  any OpenFOAM application. On the first call for any particular
  application, it retrieves the available options from the application
  help output and adds this information to its environmental cache for
  subsequent use.

- Tcsh completion uses the same function via a bash wrapper.
  But since its wrapper is transient, the on-the-fly generation would
  be less efficient. For this case, a pre-generated completion_cache
  can be used, which is generated with

      bin/tools/foamCreateCompletionCache
parent e0ebc8e9
#!/bin/sh
#!/bin/bash
#------------------------------------------------------------------------------
# ========= |
# \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
......@@ -23,12 +23,15 @@
# along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
#
# Script
# foamCreateBashCompletions
# foamCreateCompletionCache
#
# Description
# Create bash completions for OpenFOAM applications
# Create cache of bash completion values for OpenFOAM applications
# The cached values are typically used by the tcsh completion wrapper.
#
#------------------------------------------------------------------------------
defaultOutputFile="$WM_PROJECT_DIR/etc/config.sh/completion_cache"
usage() {
exec 1>&2
while [ "$#" -ge 1 ]; do echo "$1"; shift; done
......@@ -36,141 +39,107 @@ usage() {
Usage: ${0##*/} [OPTION] [appName .. [appNameN]]
options:
-d dir | -dir dir Directory to process
-u | -user Add \$FOAM_USER_APPBIN to the search directories
-head | -header Generate header
-h | -help Print the usage
-d dir | -dir dir Directory to process
-u | -user Add \$FOAM_USER_APPBIN to the search directories
-no-header Suppress header generation
-o FILE Write to alternative output
-h | -help Print the usage
Create cache of bash completion values for OpenFOAM applications.
The cached values are typically used by the tcsh completion wrapper.
Default search: \$FOAM_APPBIN only.
Default output: $defaultOutputFile
Create bash completions for OpenFOAM applications and write to stdout.
By default searches \$FOAM_APPBIN only.
Alternatively, scan the output from individual applications for single completion
commands (using the '_of_complete_' backend).
Uses the search directory if applications are specified.
USAGE
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
}
#-------------------------------------------------------------------------------
searchDirs="$FOAM_APPBIN"
unset optHeader
optHeader=true
unset outputFile
while [ "$#" -gt 0 ]
do
case "$1" in
-h | -help)
usage
;;
-d | -dir)
searchDirs="$2"
[ -d $searchDirs ] || usage "directory not found '$searchDirs'"
shift
;;
-u | -user)
searchDirs="$searchDirs $FOAM_USER_APPBIN"
;;
-head | -header)
optHeader=true
;;
-*)
usage "unknown option: '$1'"
;;
*)
break
;;
-h | -help)
usage
;;
-d | -dir)
[ "$#" -ge 2 ] || die "'$1' option requires an argument"
searchDirs="$2"
[ -d "$searchDirs" ] || die "directory not found '$searchDirs'"
shift
;;
-u | -user)
searchDirs="$searchDirs $FOAM_USER_APPBIN"
;;
-no-head*)
optHeader=false
;;
-o | -output)
[ "$#" -ge 2 ] || die "'$1' option requires an argument"
outputFile="$2"
shift
;;
-*)
usage "unknown option: '$1'"
;;
*)
break
;;
esac
shift
done
# No applications given, then always generate a header
if [ "$#" -eq 0 ]
: ${outputFile:=$defaultOutputFile}
# Verify that output is writeable
if [ -e "$outputFile" ]
then
optHeader=true
[ -f "$outputFile" ] || \
die "Cannot overwrite $outputFile" "Not a file"
[ -w "$outputFile" ] || \
die "Cannot overwrite $outputFile" "No permission?"
else
[ -w "$(dirname $outputFile)" ] || \
die "Cannot write $outputFile" "directory is not writeble"
fi
# Header requested or required
exec 1>| $outputFile || exit $?
echo "Writing $outputFile" 1>&2
echo 1>&2
# Header not disabled
[ "$optHeader" = true ] && cat << HEADER
#----------------------------------*-sh-*--------------------------------------
# Bash completions for OpenFOAM applications
# Recreate with "${0##*/}"
# Cached options for bash completion of OpenFOAM applications.
# These are the values expected by the '_of_complete_' function
#
# Formatted as "complete ... -F _of_APPNAME APPNAME
# Recreate with "${0##*/}"
#
# Generic completion handler for OpenFOAM applications
# - arg1 = command-name
# - arg2 = current word
# - arg3 = previous word
# - arg4 = options with args
# - arg5 = boolean options
#
unset -f _of_complete_ 2>/dev/null
_of_complete_()
{
# Unused: local cmd=\$1
local cur=\$2
local prev=\$3
local optsWithArgs="\$4 " # Trailing space added for easier matching
local opts="\$5 "
local choices
# Global associative array (cached options for OpenFOAM applications)
declare -gA _of_complete_cache_;
case \${prev} in
-help|-doc|-srcDoc)
# These options are usage and we can stop here.
COMPREPLY=()
return 0
;;
-case)
COMPREPLY=(\$(compgen -d -- \${cur}))
;;
-time)
# Could use "foamListTimes -withZero", but still doesn't address ranges
COMPREPLY=(\$(compgen -d -X '![-0-9]*' -- \${cur}))
;;
-region)
choices=\$(\ls -d system/*/ 2>/dev/null | sed -e 's#/\$##' -e 's#^.*/##')
COMPREPLY=(\$(compgen -W "\$choices" -- \${cur}))
;;
-fileHandler)
choices="collated uncollated masterUncollated"
COMPREPLY=(\$(compgen -W "\$choices" -- \${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}))
elif [ -n "\$cur" -a "\${cur#-}" = "\${cur}" ]
then
# Already started a (non-empty) word that isn't an option,
# use files in which case revert to filenames.
COMPREPLY=(\$(compgen -f -- \${cur}))
else
# Catchall
# - Present remaining options (not already seen in \$COMP_LINE)
choices=\$(
for o in \${opts} \${optsWithArgs}
do
[ "\${COMP_LINE/\$o/}" = "\${COMP_LINE}" ] && echo \$o
done
)
COMPREPLY=(\$(compgen -W "\$choices" -- \${cur}))
fi
;;
esac
return 0
}
# Clear existing cache.
_of_complete_cache_=()
#------------------------------------------------------------------------------
HEADER
#-------------------------------------------------------------------------------
......@@ -178,92 +147,48 @@ HEADER
# Scans the output of the application -help to detect options with/without
# arguments. Dispatch via _of_complete_
#
generateCompletion()
extractOptions()
{
local fullName="$1"
local appName="${1##*/}"
local appHelp
[ -f "$fullName" -a -x "$fullName" ] || {
echo "skip $fullName" 1>&2
return 1
}
if [ "$appName" = "complete_" ]
then
echo "skip $appName ... reserved name?" 1>&2
return 1
fi
local appName="$1"
local helpText=$($appName -help 2>/dev/null | sed -ne '/^ *-/p')
appHelp=$($fullName -help) || {
echo "error calling $fullName" 1>&2
[ -n "$helpText" ] || {
echo "Error calling $appName" 1>&2
return 1
}
# Options with args - as array
local optsWithArgs=($(awk '/^ {0,4}-[a-z]/ && /</ {print $1}' <<< "$appHelp"))
# Array of options with args
local argOpts=($(awk '/^ {0,4}-[a-z]/ && /</ {print $1}' <<< "$helpText"))
# Options without args - as array
local opts=($(awk '/^ {0,4}-[a-z]/ && !/</ {print $1}' <<< "$appHelp"))
# See bash(1) for some details. Completion functions are called with
# arg1 = command-name, arg2 = current word, arg3 = previous word
#
# Append known option types and dispatch to _of_complete_
echo " $appName" 1>&2
cat << COMPLETION
# [$appName]
unset -f _of_${appName} 2>/dev/null
_of_${appName}() {
_of_complete_ "\$@" \\
"${optsWithArgs[@]}" \\
"${opts[@]}"
}
complete -o filenames -F _of_${appName} $appName
# Array of options without args
local boolOpts=($(awk '/^ {0,4}-[a-z]/ && !/</ {print $1}' <<< "$helpText"))
COMPLETION
appName="${appName##*/}"
echo "$appName" 1>&2
echo "_of_complete_cache_[${appName}]=\"${argOpts[@]} | ${boolOpts[@]}\""
}
#------------------------------------------------------------------------------
[ "$#" -gt 0 ] || set -- ${searchDirs}
if [ "$#" -eq 0 ]
then
for dir in ${searchDirs}
do
if [ -d "$dir" ]
then
echo "Processing directory $dir" 1>&2
else
echo "No such directory: $dir" 1>&2
continue
fi
# Sort with ignore-case
set -- $(\ls $dir | sort -f)
for appName
for item
do
if [ -d "$item" ]
then
# Process directory for applications - sort with ignore-case
echo "[directory] $item" 1>&2
choices="$(find $item -maxdepth 1 -executable -type f | sort -f 2>/dev/null)"
for appName in $choices
do
generateCompletion "$dir/$appName"
extractOptions $appName
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
elif command -v "$item" > /dev/null 2>&1
then
extractOptions $item
else
echo "No such file or directory: $item" 1>&2
fi
done
# Generate footer
[ "$optHeader" = true ] && cat << FOOTER
......
#!bash
# A bash -*- sh -*- adapter for re-using OpenFOAM bash completions with tcsh
#----------------------------------*-sh-*--------------------------------------
# ========= |
# \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
# \\ / O peration |
# \\ / A nd | Copyright (C) 2017 OpenCFD Ltd.
# \\/ M anipulation |
#------------------------------------------------------------------------------
# This file is part of OpenFOAM, licensed under the GNU General Public License
# <http://www.gnu.org/licenses/>.
#
# File
# etc/config.csh/complete-wrapper
#
# Description
# A wrapper for using OpenFOAM bash completions with tcsh.
#
# Called with appName and COMMAND_LINE
# Arguments
# appName = the application name
#
# Source the bash completions
. $WM_PROJECT_DIR/etc/config.sh/bash_completion
# Environment
# The tcsh COMMAND_LINE is passed in via the environment.
# This corresponds to the bash COMP_LINE variable
#
#------------------------------------------------------------------------------
[ "$#" -ge 1 ] || exit 1
# Preload completion cache
if [ -f $WM_PROJECT_DIR/etc/config.sh/completion_cache ]
then . $WM_PROJECT_DIR/etc/config.sh/completion_cache
fi
# Use the bash completion function, but retain cache etc.
_of_complete_tcsh=true
if [ -f $WM_PROJECT_DIR/etc/config.sh/bash_completion ]
then . $WM_PROJECT_DIR/etc/config.sh/bash_completion
else
# Could warn about missing file, or treat silently
echo
exit 1
fi
appName=$1
......@@ -38,11 +72,11 @@ else
COMP_CWORD=$((${#COMP_WORDS[@]}-1))
fi
# bash completions are "complete ... -F _of_APPNAME APPNAME
_of_${appName} \
# Call _of_complete_ APPNAME Current Previous
_of_complete_ \
"$appName" "${COMP_WORDS[COMP_CWORD]}" "${COMP_WORDS[COMP_CWORD-1]}"
# Need slash on the end of directories for tcsh
# Tcsh needs slash on the end of directories
reply=($(for i in ${COMPREPLY[@]}
do
if [ -d "$i" -a "${i#/}" = "$i" ]
......
......@@ -3,15 +3,23 @@
# Using bash_completion functions for the hard work
if ($?tcsh) then # tcsh only
if ( -f $WM_PROJECT_DIR/etc/config.sh/bash_completion \
&& -f $WM_PROJECT_DIR/etc/config.csh/complete) then
foreach appName (`sed -ne 's/^complete.* //p' $WM_PROJECT_DIR/etc/config.sh/bash_completion`)
# Remove old completions, which look like:
# complete APPNAME 'p,*,`bash $WM_PROJECT_DIR/etc/ ...
foreach appName (`complete | sed -ne '/WM_PROJECT/s/\t.*$//p'`)
uncomplete $cleaned
end
# Generate completions for predefined directories
foreach dirName ("$FOAM_APPBIN")
if ( ! -d $dirName || ! -f $WM_PROJECT_DIR/etc/config.csh/complete-wrapper ) continue
foreach appName (`find $dirName -maxdepth 1 -executable -type f`)
# Pass explicitly
## complete $appName 'p,*,`bash $WM_PROJECT_DIR/etc/config.csh/complete '$appName' "${COMMAND_LINE}"`,'
## complete $appName:t 'p,*,`bash $WM_PROJECT_DIR/etc/config.csh/complete-wrapper '$appName:t' "${COMMAND_LINE}"`,'
# Pass via environment
complete $appName 'p,*,`bash $WM_PROJECT_DIR/etc/config.csh/complete '$appName'`,'
complete $appName:t 'p,*,`bash $WM_PROJECT_DIR/etc/config.csh/complete-wrapper '$appName:t'`,'
end
endif
end
endif
#------------------------------------------------------------------------------
......@@ -196,11 +196,10 @@ unalias wmRefresh
unalias foamVersion
unalias foamPV
# Cleanup completions, which look like this:
# complete blockMesh 'p,*,`bash $WM_PROJECT_DIR/etc/ ...
# Remove old completions, which look like:
# complete APPNAME 'p,*,`bash $WM_PROJECT_DIR/etc/ ...
if ($?prompt && $?tcsh) then # Interactive tcsh only
foreach cleaned (`complete | sed -n -e '/WM_PROJECT/s/\t.*$//p'`)
foreach cleaned (`complete | sed -ne '/WM_PROJECT/s/\t.*$//p'`)
uncomplete $cleaned
end
endif
......
This diff is collapsed.
This diff is collapsed.
......@@ -181,16 +181,21 @@ unset -f wmRefresh 2>/dev/null
unset -f foamVersion 2>/dev/null
unset -f foamPV 2>/dev/null
# Cleanup bash completions, which look like this:
# "complete ... -F _of_APPNAME APPNAME
# 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')"
for cleaned in $foamClean complete_
# Remove old completions, which look like
# "complete ... -F _of_complete_ APPNAME
# For economy, obtain list first
foamOldDirs="$(complete 2>/dev/null | sed -ne 's/^.*-F _of_.* \(..*\)$/\1/p')"
for cleaned in $foamOldDirs
do
unset -f _of_$cleaned 2>/dev/null
complete -r $cleaned 2>/dev/null
complete -r $cleaned 2>/dev/null
done
# Completion functions
unset -f foamAddCompletion 2>/dev/null
unset -f _of_complete_ 2>/dev/null
# Completion cache
unset _of_complete_cache_
#------------------------------------------------------------------------------
# Intermediate variables (do as last for a clean exit code)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment