modulecmd.bash.in 52.3 KB
Newer Older
1
#!@PMODULES_HOME@/sbin/bash --noprofile
gsell's avatar
gsell committed
2 3 4 5 6 7 8 9 10 11
#

#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)
gsell's avatar
gsell committed
12
declare     prefix=$(dirname "${mydir}")
13
declare -r  sbindir="${prefix}/sbin"
gsell's avatar
gsell committed
14 15 16
declare -r  libdir="${prefix}/lib"
declare -r  libexecdir="${prefix}/libexec"

gsell's avatar
gsell committed
17 18 19
declare -r base64="${sbindir}/base64"
declare -r mktemp="${sbindir}/mktemp"
declare -r sort="${sbindir}/sort"
20
declare -r getopt="${sbindir}/getopt"
gsell's avatar
gsell committed
21

gsell's avatar
gsell committed
22
source "${libdir}/libstd.bash"
gsell's avatar
gsell committed
23 24
source "${libdir}/libpmodules.bash"

25 26
: ${PMODULES_DEFINED_RELEASES:=':unstable:stable:deprecated:'}

gsell's avatar
gsell committed
27
declare -r  version='@PMODULES_VERSION@'
gsell's avatar
gsell committed
28 29 30 31

if [[ ${PMODULES_PURETCL} == yes ]]; then
	declare -r  modulecmd="${libexecdir}/modulecmd.tcl"
else
gsell's avatar
gsell committed
32
	declare -rx TCLLIBPATH="${PMODULES_HOME}/lib/Pmodules"
gsell's avatar
gsell committed
33 34
	declare -r  modulecmd="${libexecdir}/modulecmd.bin"
fi
gsell's avatar
gsell committed
35 36 37 38

declare verbosity_lvl=${PMODULES_VERBOSITY:-'verbose'}

shopt -s nullglob
gsell's avatar
gsell committed
39

gsell's avatar
gsell committed
40
declare -A GroupDepths='()'
gsell's avatar
gsell committed
41
declare current_modulefile=''
gsell's avatar
gsell committed
42
declare g_shell=''
gsell's avatar
gsell committed
43

44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
declare -A Subcommands
declare -A Options
declare -A Help

Help['version']="
Pmodules ${version} using Tcl Environment Modules @MODULES_VERSION@
Copyright GNU GPL v2
"

print_help() {
        echo -e "${Help[$1]}" 1>&2
        std::die 1
}

export_env() {
59
	local -r shell="$1"
gsell's avatar
gsell committed
60
	shift
gsell's avatar
gsell committed
61 62
	case "${shell}" in
	bash | zsh )
gsell's avatar
gsell committed
63
		local -r fmt="export %s=\"%s\"; "
gsell's avatar
gsell committed
64
		;;
gsell's avatar
gsell committed
65 66
	csh | tcsh )
		local -r fmt="setenv %s \"%s\"; "
gsell's avatar
gsell committed
67 68
		;;
	* )
69
		std::die 1 "Unsupported shell -- ${shell}\n"
gsell's avatar
gsell committed
70 71 72 73 74 75 76
		;;
	esac

	while (( $# > 0 )); do
		printf "${fmt}" "$1" "${!1}"
		shift
	done
gsell's avatar
gsell committed
77 78
}

79 80 81 82 83 84 85
#
# Save/cache some variables.
# This function is called on exit via a trap handler.
#
# Args;
#       none
#
86 87
declare g_env_must_be_saved='no'

88
save_env() {
89
        [[ ${g_env_must_be_saved} == 'no' ]] && return 0
90 91 92
        local vars=( GroupDepths UsedReleases UseFlags UsedGroups )
        vars+=( PMODULES_DEFAULT_GROUPS PMODULES_DEFINED_RELEASES )
        vars+=( PMODULES_DEFAULT_RELEASES )
93

94
	local s=$(typeset -p ${vars[@]})
gsell's avatar
gsell committed
95
	declare -g PMODULES_ENV=$( "${base64}" --wrap=0 <<< "$s" )
96
	export_env ${g_shell} PMODULES_ENV
gsell's avatar
gsell committed
97 98
}

99
trap 'save_env ' EXIT
gsell's avatar
gsell committed
100 101 102 103 104

#
# get release of module
# Note:
# - the release of a modulefile outside ${PMODULES_ROOT} is 'stable'
105
# - the release of a modulefile inside ${PMODULES_ROOT} without a
gsell's avatar
gsell committed
106 107 108 109 110 111
#   coresponding release file is 'unstable'
#
# Args:
# 	$1: absolute modulefile name
#
get_release() {
112 113
	local "$1"
	local -r modulefile="$2"
gsell's avatar
gsell committed
114 115 116

	# is modulefile outside ${PMODULES_ROOT}?
	if [[ ! ${modulefile} =~ ${PMODULES_ROOT} ]]; then
117
		std::upvar $1 'stable'
gsell's avatar
gsell committed
118 119 120 121 122 123 124 125
		return 0
	fi

	# we are inside ${PMODULES_ROOT}
	local -r releasefile="${modulefile%/*}/.release-${modulefile##*/}"
	if [[ -r ${releasefile} ]]; then
		# read releasefile, remove empty lines, spaces etc
		local -r data=$( < "${releasefile}" )
126
		std::upvar $1 "${data}"
127
	else
128
		std::upvar $1 'unstable'
gsell's avatar
gsell committed
129 130 131 132 133 134 135 136
	fi
	return 0
}

is_release() {
	[[ ${PMODULES_DEFINED_RELEASES} =~ :$1: ]]
}

137 138 139 140 141 142
#
# Check whether argument is a group
#
# Args:
#       $1: string
#
gsell's avatar
gsell committed
143
is_group () {
gsell's avatar
gsell committed
144 145 146
	local -r group="$1"
	# arg isn't emtpy and group already in cache
	[[ -n ${group} ]] && [[ -n ${GroupDepths[${group}]} ]] && return 0
147 148 149
        local moduledir="${PMODULES_ROOT}/${group}/${PMODULES_MODULEFILES_DIR}"
        [[ -d "${moduledir}" ]] || return 1
	compute_group_depth "${moduledir}"
gsell's avatar
gsell committed
150 151 152 153 154 155
}

#
# check shebang
# $1: file name to test
is_modulefile() {
156
	local -r fname="$1"
gsell's avatar
gsell committed
157 158 159
	local shebang
	[[ -r ${fname} ]] || return 1
	read -n 11 shebang < "${fname}"
160
	[[ "${shebang:0:8}" == '#%Module' ]] || [[ "${shebang:0:9}" == '#%Pmodule' ]]
gsell's avatar
gsell committed
161 162 163
}

subcommand_generic0() {
164
	local -r subcommand="$1"
gsell's avatar
gsell committed
165
	shift
166
        local -a args=()
gsell's avatar
gsell committed
167 168
	while (( $# > 0 )); do
		case $1 in
169 170 171
			-H | --help )
				print_help "${subcommand}"
				;;
gsell's avatar
gsell committed
172
			-- )
173
				:
gsell's avatar
gsell committed
174 175
				;;
			* )
176
				args+=( "$1" )
gsell's avatar
gsell committed
177 178 179
				;;
		esac
	done
180
        if (( ${#args[@]} > 0 )); then
gsell's avatar
gsell committed
181 182 183
	        std::die 3 "%s %s: %s\n" \
                         "${CMD}" "${subcommand}" \
                         "no arguments allowed"
184
        fi
gsell's avatar
gsell committed
185
	"${modulecmd}" "${g_shell}" "${subcommand}"
gsell's avatar
gsell committed
186 187 188
}

subcommand_generic1() {
189
	local -r subcommand="$1"
gsell's avatar
gsell committed
190
	shift
191
	local -a args=()
gsell's avatar
gsell committed
192 193
	while (( $# > 0 )); do
		case $1 in
194 195
			-H | --help )
				print_help "${subcommand}"
gsell's avatar
gsell committed
196 197
				;;
			-- )
198
				:
gsell's avatar
gsell committed
199 200 201 202 203 204 205 206
				;;
			* )
				args+=( "$1" )
				;;
		esac
		shift
	done
	if (( ${#args[@]} == 0 )); then
gsell's avatar
gsell committed
207 208 209
	        std::die 3 "%s %s: %s\n" \
                         "${CMD}" "${subcommand}" \
                         "missing argument"
210
	elif (( ${#args[@]} > 1 )); then
gsell's avatar
gsell committed
211 212 213
	        std::die 3 "%s %s: %s\n" \
                         "${CMD}" "${subcommand}" \
                         "only one argument allowed"
gsell's avatar
gsell committed
214
	fi
gsell's avatar
gsell committed
215
	"${modulecmd}" "${g_shell}" "${subcommand}" "${args[@]}"
gsell's avatar
gsell committed
216 217
}

218
subcommand_generic1plus() {
219
	local -r subcommand="$1"
gsell's avatar
gsell committed
220 221 222 223
	shift
	local args=()
	while (( $# > 0 )); do
		case $1 in
224 225 226
			-H | --help )
				print_help "${subcommand}"
				;;
gsell's avatar
gsell committed
227
			-- )
228
                                :
gsell's avatar
gsell committed
229 230 231 232 233 234 235 236
				;;
			* )
				args+=( "$1" )
				;;
		esac
		shift
	done
	if (( ${#args[@]} == 0 )); then
gsell's avatar
gsell committed
237 238 239
	        std::die 3 "%s %s: %s\n" \
                         "${CMD}" "${subcommand}" \
                         "missing argument"
gsell's avatar
gsell committed
240
	fi
gsell's avatar
gsell committed
241
	"${modulecmd}" "${g_shell}" "${subcommand}" "${args[@]}"
gsell's avatar
gsell committed
242 243
}

244
##############################################################################
gsell's avatar
gsell committed
245 246
#
# load [-fsvw] <module>
247
#
gsell's avatar
gsell committed
248 249
# $1: module to load
#
250 251 252 253 254 255 256 257
Subcommands[add]='load'
Subcommands[load]='load'
Options['load']='-l help -o Hfsvwi -l force -l silent -l verbose -l warn -l internal'
Help[load]='
USAGE:
        module add     modulefile...
        module load    modulefile...
                Load modulefile(s) into the shell environment. Loading a
258
		'group-head' will extend the MODULEPATH. E.g.: loading a
259 260 261 262
		compiler makes additional modules like openmpi and libraries
		compiled with this compiler available.
'

gsell's avatar
gsell committed
263
subcommand_load() {
264
        local -r subcommand='load'
gsell's avatar
gsell committed
265
	local release='undef'
266 267
	local current_modulefile=''
	local prefix=''
gsell's avatar
gsell committed
268 269
	local m=''

270
	local saved_IFS="${IFS}";
gsell's avatar
gsell committed
271 272 273 274 275 276
	IFS=':'
	local -a modulepath=(${MODULEPATH})
	IFS=${saved_IFS}

	#
	# Test whether a given module is available.
gsell's avatar
gsell committed
277 278 279 280 281 282 283 284 285 286 287 288 289 290
	# 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.
	#
291
	# - directory in- or outsite our hierarchy (not supported by modulecmd.tcl!)
gsell's avatar
gsell committed
292 293 294 295 296
	#
	# arguments:
	# 	$1: module name or file
	#
	# possible return values:
gsell's avatar
gsell committed
297
	# 	0: module is loadable
298
	# 	1: either not a modulefile or unsused release
gsell's avatar
gsell committed
299 300 301 302 303 304
	#
	# Notes:
	#	The variable 'release' in function 'subcommand_load()' will be set.
	#
	is_available() {
		local  m=$1
305 306 307 308 309 310 311
		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
312 313
		# 100% reliable: One of the Pmodules extensions must be
		# called before something like
314 315 316 317
		#   setenv FOO_PREFIX bar
		# can be used.
		#
		mapfile -t array  < <("${modulecmd}" "${shell}" show "$m" 2>&1 | \
318
			awk 'NR == 2 {print substr($0, 1, length($0)-1)}; /_PREFIX |_HOME / {print $3; exit}')
319 320 321
		current_modulefile="${array[0]}"
		prefix="${array[1]}"
		test -n "${current_modulefile}" || return 1
gsell's avatar
fixes  
gsell committed
322
		get_release release "${current_modulefile}" "${UsedReleases}"
gsell's avatar
gsell committed
323 324 325 326 327 328 329 330 331 332 333 334
	}

	#
	# output load 'hints'
	#
	# Note:
	#	The variable 'm' from the parent function will be used
	#	but not changed.
	#
	# Args:
	#	none
	output_load_hints() {
gsell's avatar
gsell committed
335
		local output=''
gsell's avatar
gsell committed
336
		local release=''
gsell's avatar
gsell committed
337
		while read -a line; do
gsell's avatar
gsell committed
338 339 340
			release=${line[1]}
			if [[ ! ":${UsedReleases}:" =~ "${release}" ]]; then
				output+="module use ${release}; "
gsell's avatar
gsell committed
341
			fi
gsell's avatar
gsell committed
342
			output+="module load ${line[@]:3} ${line[0]}\n"
gsell's avatar
gsell committed
343
		done < <(subcommand_search  "${m}" -a --no-header 2>&1)
gsell's avatar
gsell committed
344 345
		if [[ -n "${output}" ]]; then
			std::info "\nTry with one of the following command(s):\n"
346
 			std::die 3 "${output}\n"
gsell's avatar
gsell committed
347 348 349
		fi
	}

350 351 352 353
        module_is_loaded() {
	        [[ :${LOADEDMODULES}: =~ :$1: ]]
        }

gsell's avatar
gsell committed
354 355 356 357 358
	load_dependencies() {
		local -r fname="$1"
		while read dep; do
			[[ -z ${dep} ]] && continue
			[[ ${dep:0:1} == \# ]] && continue
gsell's avatar
gsell committed
359
			module_is_loaded "${dep}" && continue
gsell's avatar
gsell committed
360
			local output=$( subcommand_load --internal "${dep}")
361
			echo ${output}
gsell's avatar
gsell committed
362 363 364 365
			eval ${output}
		done < "${fname}"
	}

gsell's avatar
gsell committed
366
	local args=()
367
	opts=()
gsell's avatar
gsell committed
368
	local shell="${g_shell}"
gsell's avatar
gsell committed
369 370
	while (($# > 0)); do
		case $1 in
371 372 373
                        -H | --help )
                                print_help "${subcommand_load}"
                                ;;
gsell's avatar
gsell committed
374
			-f | --force )
375
				opts+=(' -f')
gsell's avatar
gsell committed
376 377 378 379 380 381 382 383 384 385
				;;
			-s | --silent )
				verbosity_lvl='silent'
				;;
			-v | --verbose )
				verbosity_lvl='verbose'
				;;
			-w | --warn )
				verbosity_lvl='warn'
				;;
gsell's avatar
gsell committed
386 387 388
			-i | --internal )
				shell='bash'
				;;
gsell's avatar
gsell committed
389 390 391 392 393 394 395 396 397
			-- )
				;;
			* )
				args+=( $1 )
				;;
			esac
			shift
	done
	if (( ${#args[@]} == 0 )); then
gsell's avatar
gsell committed
398 399 400
                std::die 2 "%s %s: %s\n" \
                         "${CMD}" "${subcommand}" \
		         "No module specified"
gsell's avatar
gsell committed
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
	fi
	for m in "${args[@]}"; do
		if [[ "$m" =~ ":" ]]; then

			# extendet module name is either
			# - group:name or
			# - group:name:release or
			# - release:name or
			# - release:group:name or
			# - 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
442
				is_group "${group}" || \
gsell's avatar
gsell committed
443 444 445 446
                                        std::die 3 "%s %s: %s -- %s\n" \
                                                 "${CMD}" "${subcommand}" \
                                                 "illegal group name" \
                                                 "${group}"
gsell's avatar
gsell committed
447
				local -i depth=${GroupDepths[${group}]}
448
				(( depth != 0 )) && \
gsell's avatar
gsell committed
449 450 451 452 453 454
                                        std::die 3 "%s %s: %s -- %s\n" \
                                                 "${CMD}" "${subcommand}" \
                                                 "illegal group name" \
                                                 "${group}"
				MODULEPATH="${PMODULES_ROOT}/${group}/"
                                MODULEPATH+="${PMODULES_MODULEFILES_DIR}"
gsell's avatar
gsell committed
455 456 457
				modulepath=( ${MODULEPATH} )
			fi
			if [[ -n ${release} ]]; then
gsell's avatar
gsell committed
458
			        is_release "${release}" || \
gsell's avatar
gsell committed
459 460 461 462
                                        std::die 3 "%s %s: %s -- %s\n" \
                                                 "${CMD}" "${subcommand}" \
                                                 "illegal release name"
                                                 "${release}"
gsell's avatar
gsell committed
463
				std::append_path UsedReleases "${release}"
464
                                g_env_must_be_saved='yes'
gsell's avatar
gsell committed
465
			fi
gsell's avatar
gsell committed
466
		fi
gsell's avatar
gsell committed
467 468 469 470 471 472
		local found=''
		for flag in "${UseFlags[@]/#/_}" ""; do
			if is_available "${m}${flag}"; then
				m+="${flag}"
				found=':'
				break
gsell's avatar
gsell committed
473 474
			fi
		done
gsell's avatar
gsell committed
475
		if [[ ! "${found}" ]]; then
476 477
			std::info "%s %s: module unavailable -- %s\n" \
                                  "${CMD}" 'load' "${m}"
gsell's avatar
gsell committed
478 479
			[[ ${verbosity_lvl} == 'verbose' ]] && output_load_hints
			std::die 3 ""
gsell's avatar
gsell committed
480
		fi
481
		if [[ ":${LOADEDMODULES}:" =~ ":${m}:" ]]; then
gsell's avatar
gsell committed
482 483 484 485
			std::die 3 "%s %s: %s -- %s\n" \
                                 "${CMD}" "${subcommand}" \
                                 "module conflicts with already loaded module" \
                                 "${m}"
486
		fi
gsell's avatar
gsell committed
487
		if [[ ${current_modulefile} =~ ${PMODULES_ROOT} ]]; then
488 489
			# modulefile is in our hierarchy
			# ${prefix} was set in is_available()!
490
                        test -r "${prefix}/.info" && cat "$_" 1>&2
491
			test -r "${prefix}/.dependencies" && load_dependencies "$_"
gsell's avatar
gsell committed
492
		fi
493
		local tmpfile=$( "${mktemp}" /tmp/Pmodules.XXXXXX ) \
494
                    || std::die 1 "Oops: unable to create tmp file!\n"
gsell's avatar
gsell committed
495 496
		local output=$("${modulecmd}" "${shell}" ${opts} 'load' \
                                              "${current_modulefile}" 2> "${tmpfile}")
gsell's avatar
gsell committed
497 498
		echo "${output}"
		eval "${output}"
gsell's avatar
gsell committed
499

gsell's avatar
gsell committed
500 501
                # we do not want to print the error message we got from
                # modulecmd, they are a bit ugly
gsell's avatar
gsell committed
502
                # :FIXME: Not sure whether this is now correct!
gsell's avatar
gsell committed
503 504 505
                # The idea is to supress the error messages from the Tcl
                # modulecmd, but not the output to stderr coded in a
                # modulefile.
506

gsell's avatar
gsell committed
507
		local error=$( < "${tmpfile}")
gsell's avatar
gsell committed
508
		if [[ "${error}" =~ ":ERROR:" ]]; then
gsell's avatar
gsell committed
509 510 511 512 513 514
                        local s=${error%%$'\n'*}
                        local error_txt='failed'
                        if [[ "$s" =~ ' conflicts ' ]]; then
                                error_txt='conflicts with already loaded modules'
                        fi
			std::die 3 "%s %s: %s -- %s\n" \
gsell's avatar
gsell committed
515 516 517
                                 "${CMD}" "${subcommand}" \
                                 "${error_txt}" \
                                 "${m}"
gsell's avatar
gsell committed
518 519
		elif [[ -n ${error} ]]; then
                        echo "${error}" 1>&2
520
		fi
gsell's avatar
gsell committed
521 522 523 524 525 526
		if [[ ${verbosity_lvl} != silent ]] && \
                           [[ ${release} != stable ]]; then
			std::info "%s %s: %s -- %s\n" \
                                  "${CMD}" 'load' \
                                  "${release} module has been loaded"
                                  "${m}"
gsell's avatar
gsell committed
527 528
		fi
	done
529 530
	# fix LOADEDMODULES
	LOADEDMODULES="${_LMFILES_}"
531
        local dir
gsell's avatar
gsell committed
532
	while read dir; do
533 534
		[[ "${dir: -1}" == "/" ]] || dir+="/"
		LOADEDMODULES="${LOADEDMODULES//${dir}}"
gsell's avatar
gsell committed
535
	done <<< "${MODULEPATH//:/$'\n'}"
536
	export_env "${g_shell}" LOADEDMODULES
gsell's avatar
gsell committed
537 538
}

539
##############################################################################
gsell's avatar
gsell committed
540
#
541
# unload
gsell's avatar
gsell committed
542
#
543 544 545 546 547 548 549 550 551 552 553 554
Subcommands[rm]='unload'
Subcommands[unload]='unload'
Options[unload]='-o H -l help'
Help[unload]="
USAGE:
        module rm      modulefile...
        module unload  modulefile...
                Remove modulefile(s) from the shell environment. Removing
		a 'group-head' will also unload all modules belonging to
		this group.
"

gsell's avatar
gsell committed
555
subcommand_unload() {
556
        local -r subcommand='unload'
gsell's avatar
gsell committed
557 558 559 560
	# :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.
561
	local args=()
gsell's avatar
gsell committed
562
	while (( $# > 0 )); do
563
		case $1 in
564 565 566
                        -H | --help )
                                print_help "${subcommand}"
                                ;;
567 568 569 570 571 572
			-- )
				;;
			* )
				args+=( "$1" )
				;;
		esac
gsell's avatar
gsell committed
573 574
		shift
	done
575
	if (( ${#args[@]} == 0 )); then
gsell's avatar
gsell committed
576 577 578
	        std::die 3 "%s %s: %s\n" \
                         "${CMD}" "${subcommand}" \
                         "missing argument"
579 580
	fi

581
        local arg
582
	for arg in "${args[@]}"; do
583
	        local output=$("${modulecmd}" "${g_shell}" 'unload' "${arg}")
584 585 586
                echo "${output}"
                eval "${output}"
	done
gsell's avatar
gsell committed
587 588
}

589
##############################################################################
gsell's avatar
gsell committed
590 591 592
#
# swap <module> [<module>]
#
593 594 595 596 597 598 599 600 601 602 603 604
Subcommands[switch]='swap'
Subcommands[swap]='swap'
Options[swap]='-o H -l help'
Help[swap]="
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.
"

gsell's avatar
gsell committed
605
subcommand_swap() {
606
        local -r subcommand='swap'
607 608 609
	local args=()
	while (( $# > 0 )); do
		case $1 in
610 611 612
                        -H | --help )
                                print_help "${subcommand}"
                                ;;
613 614 615 616 617 618 619 620 621
			-- )
				;;
			* )
				args+=( "$1" )
				;;
		esac
		shift
	done
	if (( ${#args[@]} == 0 )); then
gsell's avatar
gsell committed
622 623 624
	        std::die 3 "%s %s: %s\n" \
                         "${CMD}" "${subcommand}" \
                         "missing argument"
625
        elif (( ${#args[@]} > 2 )); then
gsell's avatar
gsell committed
626 627 628
                std::die 3 "%s %s: %s\n" \
                         "${CMD}" "${subcommand}" \
                         "too many arguments"
629 630
	fi
        if (( ${#args[@]} == 1 )); then
631 632
                local -r module_to_load=${args[0]}
                local -r module_to_unload=${module_to_load%/*}
633
        else
634 635
                local -r module_to_unload=${args[0]}
                local -r module_to_load=${args[1]}
636 637 638
        fi
	subcommand_unload "${module_to_unload}"
        subcommand_load   "${module_to_load}"
gsell's avatar
gsell committed
639 640
}

641
##############################################################################
gsell's avatar
gsell committed
642 643 644
#
# show <module>
#
645 646 647 648 649 650 651 652 653 654 655 656 657 658
Subcommands[display]='show'
Subcommands[show]='show'
Options[show]='-o H -l help'
Help[show]='
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.
'

gsell's avatar
gsell committed
659
subcommand_show() {
660 661
        local -r subcommand='show'
	local args=()
gsell's avatar
gsell committed
662
	while (( $# > 0 )); do
663 664 665 666 667 668 669 670 671 672
		case $1 in
                        -H | --help )
                                print_help "${subcommand}"
                                ;;
			-- )
				;;
			* )
				args+=( "$1" )
				;;
		esac
gsell's avatar
gsell committed
673 674
		shift
	done
675
        if (( ${#args[@]} == 0 )); then
gsell's avatar
gsell committed
676 677 678
	        std::die 3 "%s %s: %s\n" \
                         "${CMD}" "${subcommand}" \
                         "missing argument"
679 680 681 682 683 684
	fi

        local arg
	for arg in "${args[@]}"; do
	        "${modulecmd}" "${g_shell}" "${subcommand}" "${arg}"
	done
gsell's avatar
gsell committed
685 686 687 688 689 690 691 692
}

#
# get all available modules in given directory.
# return list like
#	modulename1 release1 modulename2 release2 ...
#
get_available_modules() {
693 694 695
	local -r dir="$1"
	local -r module="$2"
	local -r use_releases="${3:-${UsedReleases}}"
Achim Gsell's avatar
Achim Gsell committed
696
	local -a mods=()
697
	local    release
gsell's avatar
gsell committed
698
	test -d "${dir}" || return 0
Achim Gsell's avatar
Achim Gsell committed
699 700 701
	{
		cd "${dir}"
		while read mod; do
702
			get_release release "${dir}/${mod}"
Achim Gsell's avatar
Achim Gsell committed
703 704 705 706 707 708 709 710

			if [[ :${use_releases}: =~ :${release}: ]]; then
			        mods+=( "${mod}" ${release} )
			fi
		done < <(find * \( -type f -o -type l \) -not -name ".*" -ipath "${module}*")
	}
	echo "${mods[@]}"
}
711 712

##############################################################################
gsell's avatar
gsell committed
713 714 715
#
# avail [-hlt] [<module-pattern>...]
#
716 717 718 719 720 721 722 723 724 725
Subcommands[avail]='avail'
Options[avail]='-l help -o Hahlmt -l all -l all-releases -l human -l long -l machine -l terse'
Help[avail]="
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
726
		system. Only *loadable* modules are listed.  The list of
727 728 729 730 731 732
		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.
733

734 735 736 737 738 739 740 741 742 743 744 745
	-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
"

gsell's avatar
gsell committed
746
subcommand_avail() {
747
        local -r subcommand='avail'
gsell's avatar
gsell committed
748 749 750
	# use this variable in the output functions
	local -a mods=()
	local dir=''
751

gsell's avatar
gsell committed
752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780
	# get number of columns of terminal
	cols=$(tput cols)

	output_header() {
		local caption=${dir/${PMODULES_ROOT}\/}
		local caption=${caption/\/${PMODULES_MODULEFILES_DIR}/: }
		local caption=${caption/: \//: }
		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
		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
781
		std::info "\n"
gsell's avatar
gsell committed
782 783
	}

784 785 786 787 788 789
	machine_output() {
		for (( i=0; i<${#mods[@]}; i+=2 )); do
			printf "%-20s\t%s\n" "${mods[i]}" "${mods[i+1]}" 1>&2
		done
	}

gsell's avatar
gsell committed
790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806
	#
	# :FIXME: for the time being, this is the same as terse_output!
	long_output() {
		output_header
		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
807
		std::info "\n"
gsell's avatar
gsell committed
808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846
	}

	human_readable_output() {
		output_header

		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 pattern=()
847
	local output_function='human_readable_output'
gsell's avatar
gsell committed
848
	local opt_all_groups='no'
gsell's avatar
gsell committed
849
	local opt_use_releases="${UsedReleases}"
gsell's avatar
gsell committed
850 851
	while (($# > 0)); do
		case $1 in
852 853 854
                        -H | --help )
                                print_help "${subcommand}"
                                ;;
gsell's avatar
gsell committed
855 856 857 858 859
			-a | --all )
				opt_all_groups='yes'
				opt_use_releases="${PMODULES_DEFINED_RELEASES}"
				;;
			--all-releases )
gsell's avatar
gsell committed
860
				opt_use_releases="${PMODULES_DEFINED_RELEASES}"
861
				;;
gsell's avatar
gsell committed
862 863 864 865 866 867 868 869 870
			-h | --human )
				output_function='human_readable_output'
				;;
			-l | --long )
				output_function='long_output'
				;;
			-t | --terse )
				output_function='terse_output'
				;;
871 872 873
			-m | --machine )
				output_function='machine_output'
				;;
gsell's avatar
gsell committed
874
			-- | '' )
gsell's avatar
gsell committed
875 876 877 878 879 880 881
				;;
			* )
				pattern+=( "$1" )
				;;
		esac
		shift
	done
gsell's avatar
gsell committed
882
	if [[ "${opt_all_groups}" = 'yes' ]]; then
gsell's avatar
gsell committed
883
		rescan_groups "${PMODULES_ROOT}"
gsell's avatar
gsell committed
884
	fi
gsell's avatar
gsell committed
885 886 887
	if (( ${#pattern[@]} == 0 )); then
	        pattern+=( '' )
	fi
888
	local saved_IFS=${IFS};
gsell's avatar
gsell committed
889 890 891
	IFS=':'
	local -a modulepath=(${MODULEPATH})
	IFS=${saved_IFS}
892
        local string
gsell's avatar
gsell committed
893 894
	for string in "${pattern[@]}"; do
		for dir in "${modulepath[@]}"; do
895 896 897
			mods=( $( get_available_modules \
                                          "${dir}" "${string}" \
                                          "${opt_use_releases}" ) )
gsell's avatar
gsell committed
898 899 900 901 902 903
			[[ ${#mods[@]} == 0 ]] && continue
			${output_function}
		done
	done
}

gsell's avatar
gsell committed
904
#
905
# compute depth of modulefile directory.
gsell's avatar
gsell committed
906
#
907 908 909 910 911
# Args:
#       $1: absolute path of a modulefile directory
#
compute_group_depth () {
        local -r dir=$1
gsell's avatar
gsell committed
912
	test -d "${dir}" || return 1
913 914 915 916 917 918
        local group=${dir%/*}
        local group=${group##*/}
	local -i depth=$(find "${dir}" -depth \( -type f -o -type l \) \
                          -printf "%d" -quit 2>/dev/null)
	(( depth-=2 ))
	# if a group doesn't contain a modulefile, depth is negativ
gsell's avatar
gsell committed
919
	# :FIXME: better solution?
920
	(( depth < 0 )) && (( depth = 0 ))
921 922
	GroupDepths[$group]=${depth}
        g_env_must_be_saved='yes'
gsell's avatar
gsell committed
923 924
}

gsell's avatar
gsell committed
925
#
926 927 928 929 930 931
# (Re-)Scan available groups in given root and compute group depth's
#
# Args:
#       $1: root of modulefile hierarchy
#
scan_groups () {
gsell's avatar
gsell committed
932
	local -r root="$1"
933 934 935 936
	local moduledir
	for moduledir in ${root}/*/${PMODULES_MODULEFILES_DIR}; do
		compute_group_depth "${moduledir}"
	done
gsell's avatar
gsell committed
937 938 939 940
}

rescan_groups() {
	local -r root="$1"
941 942 943 944 945 946
	local moduledir
	for moduledir in ${root}/*/${PMODULES_MODULEFILES_DIR}; do
		if [[ -z "${GroupDepths[${group}]}" ]]; then
		        compute_group_depth "${moduledir}"
                fi
	done
gsell's avatar
gsell committed
947 948
}

949
##############################################################################
gsell's avatar
gsell committed
950 951 952
#
# use [-a|--append|-p|--prepend] [directory|group|release...]
#
953 954 955 956 957 958 959 960 961 962 963 964 965 966
Subcommands[use]='use'
Options[use]='-l help -o Hap -l append -l prepend'
Help[use]="
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.

967
		With a group as argument, the modules in this group will
968 969 970 971 972 973 974 975 976 977 978
		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.
"

gsell's avatar
gsell committed
979
subcommand_use() {
980
        local -r subcommand='use'
981
	local saved_IFS=${IFS};
gsell's avatar
gsell committed
982 983 984
	IFS=':'
	local -a modulepath=(${MODULEPATH})
	IFS=${saved_IFS}
985
        local add2path_func='std::append_path'
gsell's avatar
gsell committed
986

987 988 989 990 991 992 993 994
        group_is_used() {
	        [[ :${UsedGroups}: =~ :$1: ]]
        }

        release_is_used() {
	        [[ ":${UsedReleases}:" =~ :$1: ]]
        }

gsell's avatar
gsell committed
995 996 997
	print_info() {
		local f
		local r
998
		std::info "Used groups:\n"
gsell's avatar
gsell committed
999
		for f in ${UsedGroups//:/ }; do
1000
			std::info "\t${f}\n"
gsell's avatar
gsell committed
1001
		done
1002
		std::info "\nUnused groups:\n"
gsell's avatar
gsell committed
1003
		local _group
gsell's avatar
gsell committed
1004
		for _group in "${!GroupDepths[@]}"; do
gsell's avatar
gsell committed
1005
			local -i depth=${GroupDepths[${_group}]}
1006
			if ! group_is_used "${_group}" && (( depth == 0 )); then
1007
			    std::info "\t${_group}\n"
gsell's avatar
gsell committed
1008 1009
			fi
		done
1010

1011
		std::info "\nUsed releases:\n"
gsell's avatar
gsell committed
1012
		for r in ${UsedReleases//:/ }; do
1013
			std::info "\t${r}\n"
gsell's avatar
gsell committed
1014
		done
1015
		std::info "\nUnused releases:\n"
gsell's avatar
gsell committed
1016
		for r in ${PMODULES_DEFINED_RELEASES//:/ }; do
1017
			if ! release_is_used $r; then
1018
				std::info "\t${r}\n"
gsell's avatar
gsell committed
1019 1020 1021
			fi
		done

1022
		std::info "\nUsed flags:\n"
gsell's avatar
gsell committed
1023
		for flag in "${UseFlags//:/ }"; do
1024
			std::info "\t${flag}\n"
gsell's avatar
gsell committed
1025 1026
		done

1027
		std::info "\nAdditonal directories in MODULEPATH:\n"
gsell's avatar
gsell committed
1028 1029 1030
		let n=0
		for (( i=0; i<${#modulepath[@]}; i++)); do
			if [[ ! ${modulepath[i]} =~ ${PMODULES_ROOT} ]]; then
1031
			        std::info "\t${modulepath[i]}\n"
gsell's avatar
gsell committed
1032 1033 1034 1035
			        let n+=1
			fi
		done
		if (( n == 0 )); then
1036
		        std::info "\tnone\n"
gsell's avatar
gsell committed
1037 1038 1039 1040 1041
		fi
		std::info "\n"
	}

	use () {
1042
		local arg=$1
1043

1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
		if is_release "${arg}"; then
                        # argument is release
			std::append_path UsedReleases "${arg}"
                        return
                fi
		if [[ "${arg}" =~ "flag=" ]]; then
                        # argument is flag
			std::append_path UseFlags "${arg/flag=}"
                        return
                fi
                if [[ -n ${GroupDepths[${arg}]} ]] &&
                           (( ${GroupDepths[${arg}]} == 0 )); then
                        # argument is group in our root with depth 0
			std::append_path UsedGroups "${arg}"
                        ${add2path_func} MODULEPATH "${modulefiles_dir}"
                        return
                fi
                if [[ -n ${GroupDepths[${arg}]} ]] &&
                           (( ${GroupDepths[${arg}]} > 0 )); then
                        # argument is a hierarchical group in our root
			std::die 3 "%s %s: %s -- %s\n" \
                                 "${CMD}" "${subcommand}" \
                                 "illegal group" \
                                 "${arg}"
                        return
                fi
                # arg must be a directory!
		if [[ ! -d ${arg} ]]; then
			std::die 3 "%s %s: %s -- %s\n" \
                                 "${CMD}" "${subcommand}" \
                                 "illegal argument" \
                                 "${arg}"
                        return
                fi
1078

1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
                dir="$(cd "${arg}" && pwd)"
                if [[ ${dir} =~ ^${PMODULES_ROOT} ]]; then
                        # argument is somehing in our root
			std::die 3 "%s %s: %s -- %s\n" \
                                 "${CMD}" "${subcommand}" \
                                 "illegal argument" \
                                 "${arg}"
                        return
                fi
                # argument is a modulepath
                ${add2path_func} MODULEPATH "$(cd "${arg}" && pwd)"
gsell's avatar
gsell committed
1090
	}
gsell's avatar
gsell committed
1091

gsell's avatar
gsell committed
1092 1093 1094
	local -a args=()
	while (( $# > 0)); do
		case "$1" in
1095 1096 1097 1098
                        -H | --help )
                                print_help "${subcommand}"
                                ;;
		        -a | --append )