Files
Pmodules/Pmodules/modulecmd.bash.in
T

2034 lines
51 KiB
Bash
Executable File

#!@PMODULES_HOME@/sbin/bash --noprofile
#
#set -o nounset
# we have to unset CDPATH, otherwise 'cd' prints the directoy!
unset CDPATH
# used for some output only
declare -r CMD='module'
declare -r mydir=$(cd $(dirname "$0") && pwd)
declare prefix=$(dirname "${mydir}")
declare -r sbindir="${prefix}/sbin"
declare -r libdir="${prefix}/lib"
declare -r libexecdir="${prefix}/libexec"
declare -r base64="${sbindir}/base64"
declare -r mktemp="${sbindir}/mktemp"
declare -r sort="${sbindir}/sort"
source "${libdir}/libstd.bash"
source "${libdir}/libpmodules.bash"
declare -r version='@PMODULES_VERSION@'
if [[ ${PMODULES_PURETCL} == yes ]]; then
declare -r modulecmd="${libexecdir}/modulecmd.tcl"
else
declare -rx TCLLIBPATH="${PMODULES_HOME}/lib/Pmodules"
declare -r modulecmd="${libexecdir}/modulecmd.bin"
fi
# required for pre 0.99.3 modulefiles
declare -rx PSI_LIBMODULES="${TCLLIBPATH}/libmodules.tcl"
declare verbosity_lvl=${PMODULES_VERBOSITY:-'verbose'}
shopt -s nullglob
declare -A GroupDepths='()'
declare current_modulefile=''
declare g_shell=''
pbuild::export_env() {
local -r shell="$1"
shift
case "${shell}" in
bash | zsh )
local -r fmt="export %s=\"%s\"; "
;;
csh | tcsh )
local -r fmt="setenv %s \"%s\"; "
;;
* )
std::die 1 "Unsupported shell -- ${shell}\n"
;;
esac
while (( $# > 0 )); do
printf "${fmt}" "$1" "${!1}"
shift
done
}
pbuild::save_env() {
local -r shell="$1"
shift
local s=''
local tmp
while (( $# > 0 )); do
tmp="$( typeset -p $1 2> /dev/null)"
[[ -n "${tmp}" ]] && s+="${tmp};"
shift
done
declare -g PMODULES_ENV=$( "${base64}" --wrap=0 <<< "$s" )
pbuild::export_env ${shell} PMODULES_ENV
}
trap 'pbuild::save_env ${g_shell} Overlays PMODULES_OVERLAYS GroupDepths UsedReleases UseFlags UsedGroups PMODULES_DEFAULT_GROUPS PMODULES_DEFINED_RELEASES PMODULES_DEFAULT_RELEASES' EXIT
print_version() {
echo "
Pmodules ${version} using Tcl Environment Modules @MODULES_VERSION@
Copyright GNU GPL v2
" 1>&2
}
usage() {
print_version
echo "
USAGE:
module [ switches ] [ subcommand ] [subcommand-args ]
SWITCHES:
-h|-H|-?|--help this usage info
-V|--version modules version & configuration options
SUBCOMMANDS:
+ add|load [switches] modulefile [modulefile ...]
+ rm|unload modulefile [modulefile ...]
+ switch|swap [modulefile1] modulefile2
+ display|show modulefile [modulefile ...]
+ avail [switches] [modulefile [modulefile ...]]
+ search [switches] [args]
+ use [switches] [dir|group|release ...]
+ unuse dir|group|release [dir|group|release ...]
+ refresh
+ purge
+ list [switches]
+ clear
+ help [modulefile|subcommand]
+ whatis [modulefile [modulefile ...]]
+ apropos|keyword string
+ initadd modulefile [modulefile ...]
+ initprepend modulefile [modulefile ...]
+ initrm modulefile [modulefile ...]
+ initswitch modulefile1 modulefile2
+ initlist
+ initclear
" 1>&2
std::die 1
}
subcommand_help_add() {
echo "
USAGE:
module add modulefile...
module load modulefile...
Load modulefile(s) into the shell environment. Loading a
'group-head' will extend the MODULEPATH. E.g.: loading a
compiler makes additional modules like openmpi and libraries
compiled with this compiler available.
" 1>&2
std::die 1
}
subcommand_help_load() {
subcommand_help_add
}
subcommand_help_rm() {
echo "
USAGE:
module rm modulefile...
moudle unload modulefile...
Remove modulefile(s) from the shell environment. Removing
a 'group-head' will also unload all modules belonging to
this group.
" 1>&2
std::die 1
}
subcommand_help_unload() {
subcommand_help_rm
}
subcommand_help_switch() {
echo "
USAGE:
module switch [modulefile1] modulefile2
module swap [modulefile1] modulefile2
Switch loaded modulefile1 with modulefile2. If modulefile1
is not specified, then it is assumed to be the currently
loaded module with the same root name as modulefile2.
" 1>&2
std::die 1
}
subcommand_help_swap() {
subcommand_help_switch
}
subcommand_help_display() {
echo "
USAGE:
module display modulefile...
module show modulefile...
Display information about one or more modulefiles. The
display sub-command will list the full path of the
modulefile(s) and all (or most) of the environment changes
the modulefile(s) will make if loaded. It will not display
any environment changes found within conditional statements.
" 1>&2
std::die 1
}
subcommand_help_show() {
subcommand_help_display
}
subcommand_help_apropos() {
echo "
USAGE:
module apropos string
module keyword string Seeks through the 'whatis' informations of all modulefiles for
the specified string. All module-whatis informations matching
the string will be displayed.
" 1>&2
std::die 1
}
subcommand_help_keyword() {
subcommand_help_apropos
}
subcommand_help_avail() {
echo "
USAGE:
module avail [switches] string
List all available modulefiles in the current MODULEPATH. If
an argument is given, then each directory in the MODULEPATH
is searched for modulefiles whose pathname match the argument.
This command does *not* display all installed modules on the
system. Only *loadable* modules are listed. The list of
available modules may change either by loading other modules,
e.g. a compiler, or with the sub-command 'use'.
SWITCHES:
-a|--all||--all-releases
List all available modules independend of the release.
-t|--terse
Output in short format.
-l|--long
Output in long format.
-h|--human
Output in human readable format.
-m|--machine
Output in machine readable format
" 1>&2
std::die 1
}
subcommand_help_search() {
echo "
USAGE:
module search [switches] string...
Search installed modules. If an argument is given, search
for modules whose name match the argument.
SWITCHES:
--no-header
Suppress output of a header.
--release=RELEASE
Search for modules within this release. You can specify this
switch multiple times. Without this switch, the used releases
will be searched.
-a|--all-releases
Search within all releases.
--with=STRING
Search for modules compiled with modules matching string. The
command
module search --with=gcc/4.8.3
lists all modules in the hierarchy compiled with gcc 4.8.3.
" 1>&2
std::die 1
}
subcommand_help_use() {
echo "
USAGE:
module use [-a|--append|-p|--prepend] [directory|group|release...]
Without arguments this sub-command displays information about
the module search path, used families and releases. You can
use this sub-command to get a list of available families and
releases.
With a directory as argument, this directory will either be
prepended or appended to the module search path. The default
is to prepend the directory.
With a group as argument, the modules in this group will
be made available.
With a release as argument, this modules with this release
will be made available.
SWITCHES:
-a | --append -p | --prepend )
Append/prepend agrument to module search path or list of to be
searched releases.
" 1>&2
std::die 1
}
subcommand_help_unuse() {
echo "
unuse directory|group|release...
Remove the given directory, group or release from the search
path.
" 1>&2
std::die 1
}
subcommand_help_update() {
echo "
USAGE:
module update
Attempt to reload all loaded modulefiles.
" 1>&2
std::die 1
}
subcommand_help_refresh() {
echo "
USAGE:
module refresh
Force a refresh of all non-persistent components of currently
loaded modules. This should be used on derived shells where
aliases need to be reinitialized but the environment variables
have already been set by the currently loaded modules.
" 1>&2
std::die 1
}
subcommand_help_purge() {
echo "
USAGE:
module purge
Unload all loaded modulefiles.
" 1>&2
std::die 1
}
subcommand_help_list() {
echo "
USAGE:
module list
List loaded modules.
" 1>&2
std::die 1
}
subcommand_help_clear() {
echo "
USAGE:
module clear
Force the Modules package to believe that no modules are
currently loaded.
" 1>&2
std::die 1
}
subcommand_help_whatis() {
echo "
USAGE:
module whatis [modulefile...]
Display the information set up by the module-whatis commands
inside the specified modulefile(s). If no modulefile is
specified, all 'whatis' lines will be shown.
" 1>&2
std::die 1
}
subcommand_help_initadd() {
echo "
USAGE:
module initadd modulefile...
Add modulefile(s) to the shell's initialization file in the
user's home directory. The startup files checked (in order)
are:
csh - .modules, .cshrc(.ext), .csh_variables, and
.login(.ext)
tcsh - .modules, .tcshrc, .cshrc(.ext), .csh_variables,
and .login(.ext)
(k)sh - .modules, .profile(.ext), and .kshenv(.ext)
bash - .modules, .bash_profile, .bash_login,
.profile(.ext) and .bashrc(.ext)
zsh - .modules, .zcshrc(.ext), .zshenv(.ext), and
.zlogin(.ext)
If a 'module load' line is found in any of these files, the
modulefile(s) is(are) appended to any existing list of
modulefiles. The 'module load' line must be located in at
least one of the files listed above for any of the 'init'
sub-commands to work properly. If the 'module load' line
line is found in multiple shell initialization files, all
of the lines are changed.
" 1>&2
std::die 1
}
subcommand_help_initprepend() {
echo "
USAGE:
module initprepend modulefile...
Does the same as initadd but prepends the given modules to
the beginning of the list.
" 1>&2
std::die 1
}
subcommand_help_initrm() {
echo "
USAGE:
module initrm modulefile...
Remove modulefile(s) from the shell's initialization files.
" 1>&2
std::die 1
}
subcommand_help_initswitch() {
echo "
USAGE:
module initswitch modulefile1 modulefile2
Switch modulefile1 with modulefile2 in the shell's initialization files.
" 1>&2
std::die 1
}
subcommand_help_initlist() {
echo "
USAGE:
module initlist
List all of the modulefiles loaded from the shell's initialization file.
" 1>&2
std::die 1
}
subcommand_help_initclear() {
echo "
USAGE:
module initclear
Clear all of the modulefiles from the shell's initialization files.
" 1>&2
std::die 1
}
#
# get release of module
# Note:
# - the release of a module outside ${Overlays[@]} is always 'stable'
# - the release of a module inside ${Overlays[@]} without a
# coresponding release file is always 'unstable'
#
# Args:
# $1 upvar for returned release
# $2 absolute modulefile name
# $3 colon seperated list of accepted releases
#
get_release() {
local "$1"
local -r modulefile=$2
local -r releases=$3
# is modulefile in a used overlay?
for root in "${!Overlays[@]}" 'ZZZZZZZ'; do
[[ ${modulefile} =~ ${root} ]] && break
done
if [[ "${root}" == 'ZZZZZZZ' ]]; then
std::upvar $1 'stable'
return 0
fi
# we are inside the used overlays
local -r releasefile="${modulefile%/*}/.release-${modulefile##*/}"
if [[ -r ${releasefile} ]]; then
# read releasefile, remove empty lines, spaces etc
local -r data=$( < "${releasefile}" )
std::upvar $1 "${data}"
else
std::upvar $1 'unstable'
fi
[[ :${releases}: =~ ${release} ]]
}
: ${PMODULES_DEFINED_RELEASES:=':unstable:stable:deprecated:'}
is_release() {
[[ ${PMODULES_DEFINED_RELEASES} =~ :$1: ]]
}
is_used_release() {
[[ ":${UsedReleases}:" =~ :$1: ]]
}
get_overlay_of_group () {
local "$1"
local -r group="$2"
for overlay in "${!Overlays[@]}"; do
if [[ -d "${overlay}/${group}/${PMODULES_MODULEFILES_DIR}" ]]; then
std::upvar $1 "${overlay}"
return 0
fi
done
return 1
}
is_group () {
local -r group="$1"
# arg isn't emtpy and group already in cache
[[ -n ${group} ]] && [[ -n ${GroupDepths[${group}]} ]] && return 0
# not yet cached or not a group
local overlay=''
get_overlay_of_group overlay "${group}" || return 1
get_group_depths "${overlay}" "${group}"
}
#
# Check whether a given path is in an overlay.
# If yes, return 0 and the overlay with upvar of first argument
# otherwise return 1
#
# $1 upvar to return overlay
# $2 upvar to return group
# $3 path to check
#
find_overlay () {
local "$1"
local "$2"
local -r path=$3
for overlay in "${!Overlays[@]}"; do
if [[ "${path}" =~ "${overlay}" ]]; then
std::upvar $1 "${overlay}"
local group="${path#${overlay}/}"
group=${group%%/*}
std::upvar $2 "${group}"
return 0
fi
done
return 1
}
is_used_group() {
[[ :${UsedGroups}: =~ :$1: ]]
}
module_is_loaded() {
[[ :${LOADEDMODULES}: =~ :$1: ]]
}
#
# check shebang
# $1: file name to test
is_modulefile() {
local -r fname="$1"
local shebang
[[ -r ${fname} ]] || return 1
read -n 11 shebang < "${fname}"
[[ "${shebang:0:8}" == '#%Module' ]] || [[ "${shebang:0:9}" == '#%Pmodule' ]]
}
subcommand_generic0() {
local -r subcommand="$1"
shift
local opts=()
pmodules::get_options opts -- '' "$@" || subcommand_help_${subcommand}
eval set -- "${opts[@]}"
while (( $# > 0 )); do
case $1 in
-- )
shift
;;
* )
std::die 3 "%s %s: illegal argument -- %s\n" \
"${CMD}" "${subcommand}" "$1"
;;
esac
done
"${modulecmd}" "${g_shell}" "${subcommand}"
}
subcommand_generic1() {
local -r subcommand="$1"
shift
local opts=()
pmodules::get_options opts -- '' "$@" || subcommand_help_${subcommand}
eval set -- "${opts[@]}"
local args=()
while (( $# > 0 )); do
case $1 in
-- )
;;
* )
if (( ${#args[@]} == 0 )); then
args+=( "$1" )
else
std::die 3 "%s %s: only one argument allowed\n" \
"${CMD}" "${subcommand}"
fi
;;
esac
shift
done
if (( ${#args[@]} == 0 )); then
std::die 3 "%s %s: missing argument\n" \
"${CMD}" "${subcommand}"
fi
"${modulecmd}" "${g_shell}" "${subcommand}" "${args[@]}"
}
subcommand_generic1plus() {
local -r subcommand="$1"
shift
local opts=()
pmodules::get_options opts -- '' "$@" || subcommand_help_${subcommand}
eval set -- "${opts[@]}"
local args=()
while (( $# > 0 )); do
case $1 in
-- )
;;
* )
args+=( "$1" )
;;
esac
shift
done
if (( ${#args[@]} == 0 )); then
std::die 3 "%s %s: missing argument\n" \
"${CMD}" "${subcommand}"
fi
"${modulecmd}" "${g_shell}" "${subcommand}" "${args[@]}"
}
subcommand_generic1or2() {
local -r subcommand="$1"
shift
local opts=()
pmodules::get_options opts -- '' "$@" || subcommand_help_${subcommand}
eval set -- "${opts[@]}"
local args=()
while (( $# > 0 )); do
case $1 in
-- )
;;
* )
if (( ${#args[@]} > 2 )); then
std::die 3 "%s %s: only one or two arguments are allowed\n" \
"${CMD}" "${subcommand}"
fi
args+=( "$1" )
;;
esac
shift
done
if (( ${#args[@]} == 0 )); then
std::die 3 "%s %s: missing argument\n" \
"${CMD}" "${subcommand}"
fi
"${modulecmd}" "${g_shell}" "${subcommand}" "${args[@]}"
}
#
# load [-fsvw] <module>
#
# $1: module to load
#
subcommand_load() {
local release='undef'
local current_modulefile=''
local prefix=''
local m=''
local saved_IFS="${IFS}";
IFS=':'
local -a modulepath=(${MODULEPATH})
IFS=${saved_IFS}
#
# Test whether a given module is available.
# The passed module-name can be
#
# - an absolute file- or link-name.
# The module can be either in- or outside our hierarchy.
#
# - a relative file- or link-name.
# The module can be either in- or outside out hierarchy.
#
# - specified with name and version (like gcc/5.2.0).
# The module can be either in- or outside our hierarchy.
#
# - specified with name only (without version, like gcc).
# The module can be either in- or outside our hierarchy.
#
# - directory in- or outsite our hierarchy (not supported by modulecmd.tcl!)
#
# arguments:
# $1: module name or file
#
# possible return values:
# 0: module is loadable
# 1: either not a modulefile or unsused release
#
# Notes:
# The variable 'release' in function 'subcommand_load()' will be set.
#
is_available() {
local m=$1
local -a array
#
# the next command assigns the absolute modulefile path
# to ${arry[0]} and - if the module is in our root - the
# prefix of the module to ${array[1]}.
#
# The trick with the first line matching "_PREFIX" is not
# 100% reliable: One of the Pmodules extensions must be
# called before something like
# setenv FOO_PREFIX bar
# can be used.
#
mapfile -t array < <("${modulecmd}" "${shell}" show "$m" 2>&1 | \
awk 'NR == 2 {print substr($0, 1, length($0)-1)}; /_PREFIX |_HOME / {print $3; exit}')
current_modulefile="${array[0]}"
prefix="${array[1]}"
test -n "${current_modulefile}" || return 1
local release=''
get_release release "${current_modulefile}" "${UsedReleases}"
}
#
# output load 'hints'
#
# Note:
# The variable 'm' from the parent function will be used
# but not changed.
#
# Args:
# none
output_load_hints() {
local output=''
local release=''
while read -a line; do
release=${line[1]}
if [[ ! ":${UsedReleases}:" =~ "${release}" ]]; then
output+="module use ${release}; "
fi
output+="module load ${line[@]:3} ${line[0]}\n"
done < <(subcommand_search "${m}" -a --no-header 2>&1)
if [[ -n "${output}" ]]; then
std::info "\nTry with one of the following command(s):\n"
std::die 3 "${output}\n"
fi
}
load_dependencies() {
local -r fname="$1"
while read dep; do
[[ -z ${dep} ]] && continue
[[ ${dep:0:1} == \# ]] && continue
module_is_loaded "${dep}" && continue
local output=$( subcommand_load --internal "${dep}")
echo ${output}
eval ${output}
done < "${fname}"
}
local opts=()
pmodules::get_options opts \
-o fsvwi -l force -l silent -l verbose -l warn -l internal \
-- "$@" || subcommand_help_load
eval set -- "${opts[@]}"
local args=()
opts=()
local shell="${g_shell}"
while (($# > 0)); do
case $1 in
-f | --force )
opts+=(' -f')
;;
-s | --silent )
verbosity_lvl='silent'
;;
-v | --verbose )
verbosity_lvl='verbose'
;;
-w | --warn )
verbosity_lvl='warn'
;;
-i | --internal )
shell='bash'
;;
-- )
;;
* )
args+=( $1 )
;;
esac
shift
done
if (( ${#args[@]} == 0 )); then
std::die 2 "${CMD} load: No module specified\n"
fi
for m in "${args[@]}"; do
if [[ "$m" =~ ":" ]]; then
# $m is an extendet module
# the format is one of
# - group:name
# - group:name:release
# - release:name
# - release:group:name
# - name:release
local save_ifs=${IFS}
IFS=':'
local -a toks=($m)
IFS=${save_ifs}
local group=''
local release=''
if is_group "${toks[0]}"; then
group=${toks[0]}
m=${toks[1]}
release=${toks[2]}
elif is_release "${toks[0]}"; then
release=${toks[0]}
if is_group "${toks[1]}"; then
group=${toks[1]}
m=${toks[2]}
else
m=${toks[1]}
group=${toks[2]}
fi
else
m=${toks[0]}
if is_group "${toks[1]}"; then
group=${toks[1]}
release=${toks[2]}
else
release=${toks[1]}
group=${toks[2]}
fi
fi
if [[ -n ${group} ]]; then
is_group "${group}" || \
std::die 3 "%s %s: illegal group name -- %s\n" \
"${CMD}" 'load' "${group}"
local -i depth=${GroupDepths[${group}]}
(( depth != 0 )) && \
std::die 3 "%s %s: illegal group name -- %s\n" \
"${CMD}" 'load' "${group}"
MODULEPATH=""
modulepath=()
for root in "${!Overlays[@]}"; do
MODULEPATH+="${root}/${group}/${PMODULES_MODULEFILES_DIR}:"
modulepath+=( ${MODULEPATH} )
done
fi
if [[ -n ${release} ]]; then
is_release "${release}" || \
std::die 3 "%s %s: illegal release name -- %s\n" \
"${CMD}" 'load' "${release}"
std::append_path UsedReleases "${release}"
fi
fi
local found=''
for flag in "${UseFlags[@]/#/_}" ""; do
if is_available "${m}${flag}"; then
m+="${flag}"
found=':'
break
fi
done
if [[ ! "${found}" ]]; then
std::info "%s %s: module unavailable -- %s\n" \
"${CMD}" 'load' "${m}"
[[ ${verbosity_lvl} == 'verbose' ]] && output_load_hints
std::die 3 ""
fi
if [[ ":${LOADEDMODULES}:" =~ ":${m}:" ]]; then
std::die 3 "%s %s: module already loaded -- %s\n" \
"${CMD}" 'load' "${m}"
fi
for root in "${!Overlays[@]}"; do
if [[ ${current_modulefile} =~ ${root} ]]; then
# modulefile is in our hierarchy
# ${prefix} was set in is_available()!
test -r "${prefix}/.dependencies" && load_dependencies "$_"
break
fi
done
local -r tmpfile=$( "${mktemp}" /tmp/Pmodules.XXXXXX ) \
|| std::die 1 "Oops: unable to create tmp file!\n"
local output=$("${modulecmd}" "${shell}" ${opts} load "${current_modulefile}" 2> "${tmpfile}")
echo "${output}"
eval "${output}"
local error=$( < "${tmpfile}")
# :FIXME: Not sure whether this is now correct!
# The idea is to supress the error messages from the Tcl modulecmd, but not
# the output to stderr coded in a modulefile.
if [[ "${error}" =~ ":ERROR:" ]]; then
std::info "%s %s: failed -- %s\n" \
"${CMD}" 'load' "${m}"
elif [[ -n ${error} ]]; then
echo "${error}" 1>&2
fi
if [[ ${verbosity_lvl} != silent ]] && [[ ${release} != stable ]]; then
std::info "%s %s: a %s module has been loaded -- %s\n" \
"${CMD}" 'load' ${release} "${m}"
fi
done
# fix LOADEDMODULES
LOADEDMODULES="${_LMFILES_}"
while read dir; do
[[ "${dir: -1}" == "/" ]] || dir+="/"
LOADEDMODULES="${LOADEDMODULES//${dir}}"
done <<< "${MODULEPATH//:/$'\n'}"
pbuild::export_env "${g_shell}" LOADEDMODULES
}
#
# unload <module>
#
subcommand_unload() {
# :FIXME: add dependency tests: don't unload if module is required be
# another module.
# For the time being the modules requiring this module will be
# unloaded too.
while (( $# > 0 )); do
subcommand_generic1 unload "$1"
shift
done
}
#
# swap <module> [<module>]
#
subcommand_swap() {
subcommand_generic1or2 swap "$@"
}
#
# show <module>
#
subcommand_show() {
while (( $# > 0 )); do
subcommand_generic1 show "$1"
shift
done
}
#
# Get all available modules in given modulepath. Whereby modulepath is
# a colon separated list.
# return list like
# modulename1 release1 modulename2 release2 ...
#
get_available_modules() {
local saved_IFS=${IFS};
IFS=':'
local -a dirs=($1)
IFS=${saved_IFS}
local -r module="$2"
local -r releases="${3:-${UsedReleases}}"
local -a mods=()
local release
local -A dict
for dir in "${dirs[@]}"; do
test -d "${dir}" || continue
{
cd "${dir}"
while read mod; do
get_release release "${dir}/${mod}" "${releases}" || continue
if [[ -z ${dict[${mod}]} ]]; then
mods+=( "${mod}" ${release} )
dict[${mod}]=1
fi
done < <(find * \( -type f -o -type l \) -not -name ".*" -ipath "${module}*")
}
done
echo "${mods[@]}"
}
#
# avail [-hlt] [<module-pattern>...]
#
subcommand_avail() {
# use this variable in the output functions
local -a mods=()
local dir=''
# get number of columns of terminal
cols=$(tput cols)
output_header() {
local caption=$1
let i=($cols-${#caption})/2-2
printf -- "%0.s-" $(seq 1 $i) 1>&2
printf -- " %s " "${caption}" 1>&2
printf -- "%0.s-" $(seq 1 $i) 1>&2
printf -- "\n" 1>&2
}
terse_output() {
output_header "$1"
for (( i=0; i<${#mods[@]}; i+=2 )); do
local mod=${mods[i]}
local release=${mods[i+1]}
case $release in
stable )
out=''
;;
* )
out="${release}"
;;
esac
printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2
done
std::info "\n"
}
machine_output() {
for (( i=0; i<${#mods[@]}; i+=2 )); do
printf "%-20s\t%s\n" "${mods[i]}" "${mods[i+1]}" 1>&2
done
}
#
# :FIXME: for the time being, this is the same as terse_output!
long_output() {
output_header "$1"
for (( i=0; i<${#mods[@]}; i+=2 )); do
local mod=${mods[i]}
local release=${mods[i+1]}
case $release in
stable )
out=''
;;
* )
out=${release}
;;
esac
printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2
done
std::info "\n"
}
human_readable_output() {
output_header "$1"
local -i column=$cols
local -i colsize=16
for ((i=0; i<${#mods[@]}; i+=2)); do
if [[ ${verbosity_lvl} == 'verbose' ]]; then
local release=${mods[i+1]}
case ${mods[i+1]} in
stable )
mod=${mods[i]}
;;
* )
mod="${mods[i]}(${release:0:1})"
;;
esac
else
mod=${mods[i]}
fi
local -i len=${#mod}
local -i span=$(( len / 16 + 1 ))
local -i colsize=$(( span * 16 ))
if (( column+len >= cols )); then
printf -- "\n" 1>&2
column=0
fi
if (( column+colsize < cols )); then
fmt="%-${colsize}s"
else
fmt="%-s"
fi
printf "${fmt}" "${mod}" 1>&2
column+=colsize
done
printf -- "\n\n" 1>&2
}
local opts=()
pmodules::get_options opts -o ahlmt \
-l all -l all-releases \
-l human -l long -l machine -l terse -- "$@" || subcommand_help_avail
eval set -- "${opts[@]}"
local pattern=()
local output_function='human_readable_output'
local opt_all_groups='no'
local opt_use_releases="${UsedReleases}"
while (($# > 0)); do
case $1 in
-a | --all )
opt_all_groups='yes'
opt_use_releases="${PMODULES_DEFINED_RELEASES}"
;;
--all-releases )
opt_use_releases="${PMODULES_DEFINED_RELEASES}"
;;
-h | --human )
output_function='human_readable_output'
;;
-l | --long )
output_function='long_output'
;;
-t | --terse )
output_function='terse_output'
;;
-m | --machine )
output_function='machine_output'
;;
-- )
;;
* )
pattern+=( "$1" )
;;
esac
shift
done
if [[ "${opt_all_groups}" = 'yes' ]]; then
rescan_groups "${!Overlays[@]}"
fi
if (( ${#pattern[@]} == 0 )); then
pattern+=( '' )
fi
local -a modulepath
local saved_IFS=${IFS}
IFS=':'
modulepath=(${MODULEPATH})
IFS=${saved_IFS}
local overlay=''
local group=''
local -A modulepath_of_group
local -a groups=()
for dir in "${modulepath[@]}"; do
group='other'
find_overlay overlay group "${dir}"
if [[ ${modulepath_of_group[${group}]} ]]; then
modulepath_of_group[${group}]+=:${dir}
else
modulepath_of_group[${group}]=${dir}
groups+=( "${group}" )
fi
done
groups+=( 'other' )
for string in "${pattern[@]}"; do
for group in "${groups[@]}"; do
mods=( $( get_available_modules \
"${modulepath_of_group[${group}]}" \
"${string}" \
"${opt_use_releases}" ) )
[[ ${#mods[@]} == 0 ]] && continue
${output_function} "${group}"
done
done
}
# compute depths or passed group
# Note: cwd must be Pmodules root directory
# $1: group
#
get_group_depth () {
local -r group="$1"
local -r dir="${group}/${PMODULES_MODULEFILES_DIR}"
test -d "${dir}" || return 1
local tmp=$(find "${dir}" -depth -type f -o -type l 2>/dev/null| head -1)
local -a tmp2=( ${tmp//\// } )
local depth=${#tmp2[@]}
(( depth-=4 ))
# if a group doesn't contain a module, the above computed depth is < 0.
# We set it to 0 (even this might be wrong).
# :FIXME: better solution?
(( depth < 0 )) && (( depth = 0 ))
GroupDepths[$group]=${depth}
}
#
# Compute depth for all known groups
# $1: root of modulefile hierarchy
get_group_depths () {
local -r roots=( "$@" )
local root
for root in "${roots[@]}"; do
{
cd "${root}"
local group
# for some unknown reason [A-Z]* doesn't work
# on (some?) SL6 systems
for group in [ABCDEFGHIJKLMNOPQRSTUVWXYZ]*; do
get_group_depth "${group}"
done
};
done
}
# re-scan available groups.
#
# Note:
# Removing groups is not supported for the time being. Be aware, that
# a user might have a module loaded from this group. This cannot be checked.
#
# $1: root of modulefile hierarchy
rescan_groups() {
local -r roots="$@"
local root
for root in "${roots[@]}"; do
{
cd "${root}"
# for some unknown reason [A-Z]* doesn't work with
# some bash versions
for group in [ABCDEFGHIJKLMNOPQRSTUVWXYZ]*; do
if [[ -z "${GroupDepths[${group}]}" ]]; then
get_group_depth "${group}"
fi
done
};
done
}
#
# use [-a|--append|-p|--prepend] [directory|group|release...]
#
subcommand_use() {
if (( ${#GroupDepths[@]} == 0 )); then
get_group_depths "${!Overlays[@]}"
fi
local saved_IFS=${IFS}
IFS=':'
local -a modulepath=(${MODULEPATH})
IFS=${saved_IFS}
local add2path_func='std::append_path'
print_info() {
local f
local r
std::info "Used groups:\n"
for f in ${UsedGroups//:/ }; do
std::info "\t${f}\n"
done
std::info "\nUnused groups:\n"
local _group
for _group in "${!GroupDepths[@]}"; do
local -i depth=${GroupDepths[${_group}]}
if ! is_used_group "${_group}" && (( depth == 0 )); then
std::info "\t${_group}\n"
fi
done
std::info "\nUsed releases:\n"
for r in ${UsedReleases//:/ }; do
std::info "\t${r}\n"
done
std::info "\nUnused releases:\n"
for r in ${PMODULES_DEFINED_RELEASES//:/ }; do
if ! is_used_release $r; then
std::info "\t${r}\n"
fi
done
std::info "\nUsed flags:\n"
for flag in "${UseFlags//:/ }"; do
std::info "\t${flag}\n"
done
local overlay
std::info "\nUsed overlays:\n"
for overlay in "${!Overlays[@]}"; do
std::info "\t${overlay}\n"
done
std::info "\nAdditonal directories in MODULEPATH:\n"
let n=0
local group
for (( i=0; i<${#modulepath[@]}; i++)); do
if ! find_overlay overlay group "${modulepath[i]}"; then
std::info "\t${modulepath[i]}\n"
let n+=1
fi
done
if (( n == 0 )); then
std::info "\tnone\n"
fi
std::info "\n"
}
use () {
declare -g UsedGroups
declare -g MODULEPATH
local dirs_to_add=()
while (( $# > 0)); do
arg="$1"
# if is release
# ...
# elif is group
# ...
# elif matches modulepath root
# ...
# elif is directory
# ...
# else
# error
#
if is_release "${arg}"; then
# releases are always *appended*
std::append_path UsedReleases "${arg}"
elif [[ "${arg}" =~ "flag=" ]]; then
std::append_path UseFlags "${arg/flag=}"
elif [[ "${arg}" =~ "overlay=" ]]; then
local overlay="${arg/overlay=}"
[[ -d "${overlay}" ]] || \
std:die 3 "%s %s: is not a directory -- %s\n" \
"${CMD}" "${FUNCNAME[0]##*_}" "${overlay}"
if [[ ! ${Overlays[${overlay}]} ]]; then
Overlays[${overlay}]=1
PMODULES_OVERLAYS="${PMODULES_OVERLAYS}:${overlay}"
get_group_depths "${!Overlays[@]}"
for group in ${UsedGroups//:/ }; do
local dir="${overlay}/${group}/${PMODULES_MODULEFILES_DIR}"
if [[ -d "${dir}" ]]; then
std::prepend_path MODULEPATH "${dir}"
fi
done
fi
elif [[ ! ${arg} =~ */* ]] && is_group "${arg}"; then
if (( ${GroupDepths[$arg]} != 0 )); then
std::die 3 "%s %s: cannot add group to module path -- %s\n" \
"${CMD}" "${FUNCNAME[0]##*_}" "${arg}"
fi
std::append_path UsedGroups "${arg}"
for overlay in "${!Overlays[@]}"; do
for group in ${UsedGroups//:/ }; do
local dir="${overlay}/"
dir+="${group}/${PMODULES_MODULEFILES_DIR}"
if [[ -d "${dir}" ]]; then
std::prepend_path MODULEPATH "${dir}"
fi
done
done
elif [[ ${arg} =~ ^${PMODULES_ROOT} ]]; then
std::die 3 "%s %s: illegal directory -- %s\n" \
"${CMD}" "${FUNCNAME[0]##*_}" "${arg}"
elif [[ -d ${arg} ]]; then
${add2path_func} "$(cd "${arg}" && pwd)"
else
std::die 3 "%s %s: neither a directory, release or group -- %s\n" \
"${CMD}" "${FUNCNAME[0]##*_}" "${arg}"
fi
shift
done
pbuild::export_env ${g_shell} MODULEPATH UsedGroups
}
local opts=()
pmodules::get_options opts -o 'ap' -l 'append' -l 'prepend' -- "$@" || subcommand_help_use
eval set -- "${opts[@]}"
local -a args=()
while (( $# > 0)); do
case "$1" in
-a | --append )
add2path_func='std::append_path'
;;
-p | --prepend )
add2path_func='std::prepend_path'
;;
-- )
;;
* )
args+=( "$1" )
;;
esac
shift
done
if (( ${#args[@]} == 0 )); then
print_info
else
use "${args[@]}"
fi
}
#
# unuse directory|group|release...
#
subcommand_unuse() {
local opts=()
local saved_IFS=${IFS}
IFS=':'
local -a modulepath=(${MODULEPATH})
IFS=${saved_IFS}
pmodules::get_options opts -o '' -- "$@" || subcommand_help_unuse
eval set -- "${opts[@]}"
local dirs_to_remove=()
while (( $# > 0)); do
if [[ "$1" == "--" ]]; then
shift
continue
fi
arg=$1
# if is release
# ...
# elif is group
# ...
# elif matches modulepath root
# ...
# elif is directory
# ...
local modulefiles_dir="${PMODULES_ROOT}/${arg}/${PMODULES_MODULEFILES_DIR}"
if is_release "${arg}"; then
std::remove_path UsedReleases "${arg}"
elif [[ "${arg}" =~ "flag=" ]]; then
std::remove_path UseFlags "${arg/flag=}"
elif [[ "${arg}" =~ "overlay=" ]]; then
local overlay="${arg/overlay=}"
[[ -d "${overlay}" ]] || \
std::die 3 "%s %s: is not a directory -- %s\n" \
"${CMD}" "${FUNCNAME[0]##*_}" "${overlay}"
[[ "${overlay}" == "${PMODULES_ROOT}" ]] && \
std::die 3 "%s %s: cannot remove root overlay -- %s\n" \
"${CMD}" "${FUNCNAME[0]##*_}" "${overlay}"
if [[ ${Overlays[${overlay}]} ]]; then
[[ "${_LMFILES_}" =~ "${overlay}" ]] && \
std::die 3 "%s %s: cannot remove overlay -- %s\n" \
"${CMD}" "${FUNCNAME[0]##*_}" "${overlay}"
unset Overlays[${overlay}]
std::remove_path PMODULES_OVERLAYS "${overlay}"
for dir in "${modulepath[@]}"; do
if [[ "${dir}" =~ "${overlay}" ]]; then
std::remove_path MODULEPATH "${dir}"
fi
done
fi
elif [[ ! ${arg} =~ */* ]] && [[ -d ${modulefiles_dir} ]]; then
if (( ${GroupDepths[$arg]} != 0 )); then
std::die 3 "%s %s: cannot remove group from module path -- %s\n" \
"${CMD}" "${FUNCNAME[0]##*_}" "${arg}"
fi
std::remove_path UsedGroups "${arg}"
dirs_to_remove+=( ${modulefiles_dir} )
elif [[ -d ${arg} ]]; then
local normalized_dir=$(cd "${arg}" && pwd)
dirs_to_remove+=( ${normalized_dir} )
elif [[ ${arg} =~ ^${PMODULES_ROOT} ]]; then
std::die 3 "%s %s: illegal directory -- %s\n." \
"${CMD}" "${FUNCNAME[0]##*_}" "${arg}"
else
std::die 3 "%s %s: not a directory -- %s\n" \
"${CMD}" "${FUNCNAME[0]##*_}" "${arg}"
fi
shift
done
declare -gx UsedGroups
pbuild::export_env ${g_shell} UsedGroups
[[ ${#dirs_to_remove[@]} == 0 ]] && return
for dir in "${dirs_to_remove[@]}"; do
subcommand_generic1 unuse "${dir}"
done
}
#
# update
#
# :FIXME: either compile Modules with --enable-beginenv or remove the sub-command
#
subcommand_update() {
subcommand_generic0 update "$@"
}
#
# refresh
#
subcommand_refresh() {
subcommand_generic0 refresh "$@"
}
reset_modulepath() {
MODULEPATH=''
local group
local root
for root in "${!Overlays[@]}"; do
for group in ${PMODULES_DEFAULT_GROUPS}; do
local dir="${root}/${group}/${PMODULES_MODULEFILES_DIR}"
[[ -d "${dir}" ]] && std::prepend_path MODULEPATH "${dir}"
done
done
}
reset_used_groups() {
UsedGroups=''
local group
for group in ${PMODULES_DEFAULT_GROUPS}; do
std::append_path UsedGroups "${group}"
done
}
reset_used_releases() {
declare -g UsedReleases=''
for r in ${PMODULES_DEFAULT_RELEASES//:/ }; do
std::append_path UsedReleases "${r}"
done
}
##############################################################################
#
# purge
#
subcommand_purge() {
subcommand_generic0 purge "$@"
reset_modulepath
reset_used_groups
pbuild::export_env ${g_shell} MODULEPATH UsedGroups
}
##############################################################################
#
# list [-hlt]
#
subcommand_list() {
local opts=()
pmodules::get_options opts -o hlt -l human -l long -l terse -- "$@" || \
subcommand_help_list
eval set -- "${opts[@]}"
local opts=()
while (( $# > 0 )); do
case $1 in
-h | --human )
opts+=( '-h' )
;;
-l | --long )
opts+=( '-l' )
;;
-t | --terse )
opts+=( '-t' )
;;
-- )
;;
* )
std::die 1 "%s %s: invalid argument -- %s" \
"${CMD}" "list" "$1"
;;
esac
shift
done
"${modulecmd}" "${g_shell}" list "${opts[@]}"
}
init_path() {
std::replace_path PATH "${PMODULES_HOME%/*}/.*"
std::prepend_path PATH "${PMODULES_HOME}/bin"
}
init_manpath() {
std::replace_path MANPATH "${PMODULES_HOME%/*}/.*"
if [[ -r /etc/man.config ]]; then
declare _manconf='/etc/man.config'
elif [[ -r /etc/man.conf ]]; then
declare _manconf='/etc/man.conf'
fi
if [[ -n ${_manconf} ]]; then
while read name value rest; do
std::append_path MANPATH "${value}"
done < <(grep "^MANPATH\s" "${_manconf}")
unset _manconf
else
std::append_path MANPATH "${PMODULES_HOME}/share/man"
std::append_path MANPATH "/usr/share/man"
fi
}
##############################################################################
pmodules_init() {
declare -gx LOADEDMODULES=''
declare -gx _LMFILES_=''
declare -Ag Overlays=([${PMODULES_ROOT}]="1")
declare -gx PMODULES_OVERLAYS="${PMODULES_ROOT}"
declare -g UsedGroups=''
declare -gx MODULEPATH=''
declare -Ag GroupDepths='()'
declare -g UseFlags=()
reset_modulepath
reset_used_groups
reset_used_releases
init_path
init_manpath
pbuild::export_env "${g_shell}" \
LOADEDMODULES \
_LMFILES_ \
MODULEPATH \
PATH \
MANPATH
}
##############################################################################
#
# clear
#
subcommand_clear() {
local -r subcommand="${FUNCNAME##*_}"
local opts=()
pmodules::get_options opts -- '' "$@" || subcommand_help_${subcommand}
eval set -- "${opts[@]}"
while (( $# > 0 )); do
case $1 in
-- )
shift
;;
* )
std::die 3 "%s %s: illegal argument -- %s" \
"${CMD}" "${subcommand}" "$1"
;;
esac
done
pmodules_init
pbuild::export_env ${g_shell} LOADEDMODULES MODULEPATH _LMFILES_
}
##############################################################################
#
# search [switches] [STRING...]
#
subcommand_search() {
local -r subcommand="${FUNCNAME##*_}"
local modules=()
local with_modules='//'
local src_prefix=()
local opt_print_header='yes'
local opt_print_modulefiles='no'
local opt_print_csv='no'
local opt_use_releases=':'
local -r fmt="%-20s %-10s %-12s %-s\n"
# no args
print_header() {
printf '\n' 1>&1
printf "${fmt}" "Module" "Release" "Group" "Requires" 1>&2
printf -- '-%.0s' {1..60} 1>&2
printf '\n' 1>&2
}
#.....................................................................
#
# output result of search
# Args:
# $1: tmp file
#
# variables used from enclosing function:
# opt_print_header
# opt_print_modulefiles
# with_modules
#
print_result() {
local -r tmpfile=$1
[[ "${opt_print_header}" == "yes" ]] && print_header
if [[ "${opt_print_modulefiles}" == "yes" ]]; then
while read -a line; do
# group first
local out="${line[2]}/"
# add directory of modulefiles
out+="${PMODULES_MODULEFILES_DIR}/"
for d in "${line[@]:3}"; do
out+="$d/"
done
out+="${line[0]}"
std::info "${out}\n"
done < <("${sort}" -k 1,1 -k 4,4 -k 5,5 "${tmpfile}" | awk "${with_modules}")
elif [[ "${opt_print_csv}" == "yes" ]]; then
while read -a toks; do
:
done < <("${sort}" -k 1,1 -k 4,4 -k 5,5 "${tmpfile}" | awk "${with_modules}")
else
"${sort}" -k 1,1 -k 4,4 -k 5,5 "${tmpfile}" | awk "${with_modules}" 1>&2
fi
}
#.....................................................................
#
# search modules
# Args:
# $1: module name pattern
#
# Variables used from enclosing function
# :FIXME:
#
search () {
local -r module=$1
# write results to a temporary file for later processing
local -r tmpfile=$( "${mktemp}" /tmp/Pmodules.XXXXXX ) \
|| std::die 1 "Oops: unable to create tmp file!\n"
local group
# loop over all groups
for group in "${!GroupDepths[@]}"; do
# loop over all directories which can be added to
# MODULEPATH inside current group
local depth=${GroupDepths[${group}]}
local mpaths=( $(find \
"${src_prefix[@]/%//${group}/modulefiles}" \
-type d \
-mindepth ${depth} -maxdepth ${depth} \
2>/dev/null))
local mpath
local overlay
local unused
for mpath in "${mpaths[@]}"; do
# get dependencies encoded in directory name
find_overlay overlay unused "${mpath}"
local p="${mpath/${overlay}}"
p=( ${p//\// } )
local deps=()
local -i i
for ((i=2; i < ${#p[@]}; i+=2)); do
deps+=( ${p[i]}/${p[i+1]} )
done
local requires=${deps[@]}
# get and print all available modules in $mpath
# with respect to the requested releases
# tmpfile: module/version release group group-dependencies...
local mods=( $( get_available_modules \
"${mpath}" \
"${module}" \
"${opt_use_releases}" ) )
[[ ${#mods[@]} == 0 ]] && continue
for (( i=0; i<${#mods[@]}; i+=2 )); do
printf "${fmt}" ${mods[i]} "${mods[i+1]}" \
${group} "${requires}" >> "${tmpfile}"
done
done
done
print_result "${tmpfile}"
rm -f "${tmpfile}"
}
local opts=()
pmodules::get_options opts \
-o 'ahH?' \
-l help \
-l no-header \
-l print-modulefiles \
-l release: \
-l with: \
-l all-releases \
-l src: \
-l print-csv \
-- "$@" || subcommand_help_${subcommand}
eval set -- "${opts[@]}"
while (( $# > 0 )); do
case $1 in
--no-header )
opt_print_header='no'
;;
--print-modulefiles )
opt_print_modulefiles='yes'
opt_print_header='no'
;;
--print-csv )
opt_print_csv='yes'
opt_print_header='no'
;;
--release )
is_release "$2" || \
std::die 1 "%s %s: illegal release name -- %s\n" \
"${CMD}" 'search' "$2"
opt_use_releases+="$2:"
shift
;;
--with )
if [[ -z $2 ]] || [[ "$2" =~ "-*" ]]; then
std::die 1 "%s %s: illegal value for --with option -- %s\n" \
"${CMD}" 'search' "$2"
fi
with_modules+=" && / ${2//\//\\/}/"
shift
;;
-a | --all-releases )
opt_use_releases="${PMODULES_DEFINED_RELEASES}"
;;
--src )
src_prefix=$2
pmodules::check_directories "${src_prefix}"
shift
;;
-\? | -h | -H | --help )
usage
;;
-- )
;;
* )
modules+=( "$1" )
;;
esac
shift
done
if [[ -z "${src_prefix}" ]]; then
src_prefix=( "${!Overlays[@]}" )
fi
if [[ "${opt_use_releases}" == ":" ]]; then
opt_use_releases=":${UsedReleases}:"
fi
if [[ ${#modules[@]} == 0 ]]; then
modules+=( '' )
fi
if (( ${#GroupDepths[@]} == 0 )) || [[ ${src_prefix} != ${PMODULES_ROOT} ]]; then
get_group_depths "${src_prefix}"
fi
for module in "${modules[@]}"; do
search "${module}"
done
}
#
# help [module|sub-command]
#
subcommand_help() {
local opts=()
pmodules::get_options opts -o HV\? -l version -l help -- "$@" || usage
eval set -- "${opts[@]}"
local arg=''
while (( $# > 0 )); do
case $1 in
-[hH] | -\? | --help )
usage
;;
-V | --version )
print_version
std::die 1
;;
-- )
:
;;
* )
[[ -z ${arg} ]] || \
std::die 1 "${CMD} help: only one argument allowed.\n"
arg="$1"
;;
esac
shift
done
if [[ -z ${arg} ]]; then
usage
elif typeset -F subcommand_help_${arg} > /dev/null 2>&1 ; then
# help for sub-command
subcommand_help_${arg}
else
# :FIXME: print help of newest *available* module
# (respecting UsedReleases)
subcommand_generic1plus help "${arg}"
fi
}
#
# whatis [module]
#
subcommand_whatis() {
if (( $# == 0 )); then
subcommand_generic0 whatis
else
subcommand_generic1plus whatis "$@"
fi
}
#
# apropos string
#
subcommand_apropos() {
subcommand_generic1 apropos "$@"
}
#
# initadd module...
#
subcommand_initadd() {
subcommand_generic1plus initadd "$@"
}
#
# initprepend module...
#
subcommand_initprepend() {
subcommand_generic1plus initprepend "$@"
}
#
# initrm module...
#
subcommand_initrm() {
subcommand_generic1plus initrm "$@"
}
#
# initswitch module1 module2
#
subcommand_initswitch() {
subcommand_generic1or2 initswitch "$@"
}
#
# initlist
#
subcommand_initlist() {
subcommand_generic0 initlist "$@"
}
#
# initclear
#
subcommand_initclear() {
subcommand_generic0 initclear "$@"
}
case "$1" in
bash | zsh )
declare g_shell="$1"
;;
csh | tcsh )
declare g_shell='csh'
;;
* )
std::die 1 "${CMD}: unsupported shell -- $1\n"
;;
esac
shift
export PMODULES_OVERLAYS
if [[ -n ${PMODULES_ENV} ]]; then
eval "$("${base64}" -d <<< "${PMODULES_ENV}" 2>/dev/null)"
else
pmodules_init
fi
declare -A Subcommands=(\
[add]="subcommand_load" \
[load]="subcommand_load" \
[rm]="subcommand_unload" \
[unload]="subcommand_unload" \
[switch]="subcommand_swap" \
[swap]="subcommand_swap" \
[display]="subcommand_show" \
[show]="subcommand_show" \
[apropos]="subcommand_apropos" \
[keyword]="subcommand_apropos" \
[avail]="subcommand_avail" \
[search]="subcommand_search" \
[use]="subcommand_use" \
[unuse]="subcommand_unuse" \
[update]="subcommand_update" \
[refresh]="subcommand_refresh" \
[purge]="subcommand_purge" \
[list]="subcommand_list" \
[clear]="subcommand_clear" \
[whatis]="subcommand_whatis" \
[initadd]="subcommand_initadd" \
[initprepend]="subcommand_initprepend" \
[initrm]="subcommand_initrm" \
[initswitch]="subcommand_initswitch" \
[initlist]="subcommand_initlist" \
[initclear]="subcommand_initclear" \
[help]="subcommand_help" \
)
declare -a opts=()
while (( $# > 0 )); do
case $1 in
-H | -\? | --help | -help )
usage
;;
-V | --version )
print_version
std::die 1
;;
--debug )
set -x
;;
'' )
;;
-* )
opts+=( "$1" )
;;
* )
if [[ -z "${Subcommands[$1]}" ]]; then
std::die 1 "${CMD}: unknown sub-command -- $1\n"
fi
subcommand="$1"
shift
break
;;
esac
shift
done
while (( $# > 0 )); do
case "$1" in
-- )
;;
* )
opts+=( "$1" )
;;
esac
shift
done
if [[ -z "${subcommand}" ]]; then
std::die 1 "${CMD}: no sub-command specified.\n"
fi
if (( ${#GroupDepths[@]} == 0 )); then
get_group_depths "${!Overlays[@]}"
fi
${Subcommands[$subcommand]} "${opts[@]}"
# Local Variables:
# mode: sh
# sh-basic-offset: 8
# tab-width: 8
# End: