modulecmd.in 34.4 KB
Newer Older
1
#!@PMODULES_HOME@/bin/bash
2 3
#

4
#set -o nounset
5 6
# we have to unset CDPATH, otherwise 'cd' prints the directoy!
unset CDPATH
7

gsell's avatar
gsell committed
8
# used inside output text only
9 10 11 12 13 14 15 16
declare -r  CMD='module'

declare -r  bindir=$(cd $(dirname "$0") && pwd)
declare -r  prefix=$(dirname "${bindir}")
declare -r  libdir="${prefix}/lib"

source "${libdir}/libpmodules.bash"

17
declare -r  version='@PMODULES_VERSION@'
18
declare -r  modulecmd="${bindir}/modulecmd.tcl"
19

20
declare -rx TCL_LIBRARY="${libdir}/tcl8.6"
21

gsell's avatar
gsell committed
22 23
# required by pre 0.99.3 modulefiles
declare -rx PSI_LIBMODULES="${TCL_LIBRARY}/libmodules.tcl"
24 25

# :FIXME: this is not save, if a component contains spaces.
26 27
declare -ra modulepath=( ${MODULEPATH//:/ } )

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

30
shopt -s nullglob
gsell's avatar
gsell committed
31 32
declare -a Groups='()'
declare -A HierarchyDepths='()'
33

34 35 36 37 38 39
save_env() {
	local s=''
	while (( $# > 0 )); do
		s+="$( typeset -p $1 );"
		shift
	done
40
	echo export PMODULES_ENV=$( "${PMODULES_HOME}/bin/base64" --wrap=0 <<< "$s" )	
41
}
42

43 44 45 46 47 48 49 50
export_env() {
	local s=''
	while (( $# > 0 )); do
		echo -n "export $1=${!1};"
		shift
	done
}

gsell's avatar
gsell committed
51
trap 'save_env Groups HierarchyDepths UsedReleases PMODULES_DEFAULT_GROUPS PMODULES_DEFINED_RELEASES PMODULES_DEFAULT_RELEASES' EXIT
52 53 54 55 56 57 58 59 60 61 62

print_version() {
	echo "
Pmodules ${version} using Tcl Environment Modules @MODULES_VERSION@
Copyright GNU GPL v2
" 1>&2
}

usage() {
	print_version
	echo "
63 64
USAGE:
        module [ switches ] [ subcommand ] [subcommand-args ]
65

66 67
SWITCHES:
        -h|-H|-?|--help         this usage info
68
        -V|--version            modules version & configuration options
69 70

SUBCOMMANDS:
71
        + add|load [switches ]  modulefile [modulefile ...]
72 73 74
        + rm|unload             modulefile [modulefile ...]
        + switch|swap           [modulefile1] modulefile2
        + display|show          modulefile [modulefile ...]
75
        + avail [ switches ]    [modulefile [modulefile ...]]
76
        + search [ switches ]   [ args ]
gsell's avatar
gsell committed
77 78
        + use [ switches ]      [dir|group|release ...]
        + unuse                 dir|group|release [dir|group|release ...]
79 80
        + refresh
        + purge
81
        + list [ switches ]
82 83 84 85 86 87 88 89 90 91 92
        + 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
93
	die 1
94 95 96 97
}

subcommand_help_add() {
	echo "
98 99 100
USAGE:
        module add     modulefile...
        module load    modulefile...
101
                Load modulefile(s) into the shell environment. Loading a
gsell's avatar
gsell committed
102
		'group-head' will extend the MODULEPATH. E.g.: loading a 
103 104 105
		compiler makes additional modules like openmpi and libraries
		compiled with this compiler available.
" 1>&2
106
	die 1
107 108 109 110 111 112 113 114
}

subcommand_help_load() {
	subcommand_help_add
}

subcommand_help_rm() {
	echo "
115 116 117
USAGE:
        module rm      modulefile...
        moudle unload  modulefile...
118
                Remove modulefile(s) from the shell environment. Removing
gsell's avatar
gsell committed
119 120
		a 'group-head' will also unload all modules belonging to
		this group.
121
" 1>&2
122
	die 1
123 124 125 126 127 128 129 130
}

subcommand_help_unload() {
	subcommand_help_rm
}

subcommand_help_switch() {
	echo "
131 132 133
USAGE:
        module switch  [modulefile1] modulefile2
        module swap    [modulefile1] modulefile2
134 135 136 137
		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
138
	die 1
139 140 141 142 143 144 145 146
}

subcommand_help_swap() {
	subcommand_help_switch
}

subcommand_help_display() {
	echo "
147 148 149
USAGE:
        module display modulefile...
        module show    modulefile...
150 151 152 153 154 155
		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
156
	die 1
157 158 159 160 161 162 163 164
}

subcommand_help_show() {
	subcommand_help_display
}

subcommand_help_apropos() {
	echo "
165 166 167
USAGE:
        module apropos string
        module keyword string	Seeks through the 'whatis' informations of all modulefiles for
168 169 170 171
		the specified string.  All module-whatis informations matching
		the string will be displayed.
		
" 1>&2
172
	die 1
173 174 175 176 177 178 179 180 181
}

subcommand_help_keyword() {
	subcommand_help_apropos
}


subcommand_help_avail() {
	echo "
182 183 184
USAGE:
        module avail string
		List all available modulefiles in the current MODULEPATH. If
185 186 187 188 189 190 191 192
		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'.
" 1>&2
193
	die 1
194 195 196 197
}

subcommand_help_search() {
	echo "
198
USAGE:
199
        module search [switches] string...
200 201 202 203
		Search installed modules. If an argument is given, search
		for modules whose name match the argument.

SWITCHES: 
204 205
        --no-header
		Suppress output of a header.
206

207
        --release=RELEASE
208 209 210 211
		Search for modules within this release. You can specify this
		switch multiple times.  Without this switch, the used releases
		will be searched.

212
        -a|--all-releases
213 214
		Search within all releases.
		
215
        --with=STRING
216 217 218 219 220 221 222
		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
223
	die 1
224 225 226 227
}

subcommand_help_use() {
	echo "
228
USAGE:
gsell's avatar
gsell committed
229
        module use [-a|--append|-p|--prepend] [directory|group|release...]
230 231 232 233 234 235 236 237 238
		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.

gsell's avatar
gsell committed
239
		With a group as argument, the modules in this group will 
240 241 242
		be made available.

		With a release as argument, this modules with this release
243 244 245 246 247 248
		will be made available.

SWITCHES:
        -a | --append -p | --prepend )
                Append/prepend agrument to module search path or list of to be
                searched releases.
249
" 1>&2
250
	die 1
251 252 253 254
}

subcommand_help_unuse() {
	echo "
gsell's avatar
gsell committed
255 256
unuse directory|group|release...
		Remove the given directory, group or release from the search
257 258
		path.
" 1>&2
259
	die 1
260 261 262
}
subcommand_help_update() {
	echo "
263 264 265
USAGE:
        module update
                Attempt  to  reload  all  loaded  modulefiles.
266
" 1>&2
267
	die 1
268 269 270 271
}

subcommand_help_refresh() {
	echo "
272 273 274
USAGE:
        module refresh
                Force a refresh of all non-persistent components of currently
275 276 277 278
		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
279
	die 1
280 281 282 283
}

subcommand_help_purge() {
	echo "
284 285 286
USAGE:
        module purge
		Unload all loaded modulefiles.
287
" 1>&2
288
	die 1
289 290 291 292
}

subcommand_help_list() {
	echo "
293 294 295
USAGE:
        module list
		List loaded modules.
296
" 1>&2
297
	die 1
298 299 300 301
}

subcommand_help_clear() {
	echo "
302 303 304
USAGE:
        module clear
		Force the Modules package to believe that no modules are
305 306
		currently loaded.
" 1>&2
307
	die 1
308 309 310 311
}

subcommand_help_whatis() {
	echo "
312 313
USAGE:
        module whatis [modulefile...]
314 315 316 317
                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
318
	die 1
319 320 321 322
}

subcommand_help_initadd() {
	echo "
323 324
USAGE:
        module initadd	modulefile...
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
		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
347
	die 1
348 349 350 351
}

subcommand_help_initprepend() {
	echo "
352 353
USAGE:
        module initprepend modulefile...
354 355 356
		Does the same as initadd but prepends the given modules to
		the beginning of the list.
" 1>&2
357
	die 1
358 359 360 361
}

subcommand_help_initrm() {
	echo "
362 363
USAGE:
        module initrm modulefile...
364 365
		Remove modulefile(s) from the shell's initialization files.
" 1>&2
366
	die 1
367 368 369 370
}

subcommand_help_initswitch() {
	echo "
371 372
USAGE:
        module initswitch modulefile1 modulefile2
373 374
		Switch modulefile1 with modulefile2 in the shell's initialization files.
" 1>&2
375
	die 1
376 377 378 379
}

subcommand_help_initlist() {
	echo "
380 381 382
USAGE:
        module initlist
		List all of the modulefiles loaded from the shell's initialization file.
383
" 1>&2
384
	die 1
385 386 387 388
}

subcommand_help_initclear() {
	echo "
389 390 391
USAGE:
	module initclear
		Clear all of the modulefiles from the shell's initialization files.
392
" 1>&2
393
	die 1
394 395
}

396 397 398
#
# get release of module
# Note:
gsell's avatar
gsell committed
399 400
# - the release of a modulefile outside ${PMODULES_ROOT} is 'stable'
# - the release of a modulefile inside ${PMODULES_ROOT} without a 
401 402 403 404 405
#   coresponding release file is 'unstable'
#
# Args:
# 	$1: absolute modulefile name
#
406 407
get_release() {
	local -r modulefile=$1
408

gsell's avatar
gsell committed
409 410
	# is modulefile outside ${PMODULES_ROOT}?
	if [[ ! ${modulefile} =~ ${PMODULES_ROOT} ]]; then
411 412 413 414
		echo 'stable'
		return 0
	fi

gsell's avatar
gsell committed
415
	# we are inside ${PMODULES_ROOT}
416 417
	local -r releasefile="${modulefile%/*}/.release-${modulefile##*/}"
	if [[ -r ${releasefile} ]]; then
418
		# read releasefile, remove empty lines, spaces etc
419
		local -r data=$( < "${releasefile}" )
420
		echo ${data}
421
	else	
422
		echo 'unstable'
423
	fi
424
	return 0
425 426
}

gsell's avatar
gsell committed
427
: ${PMODULES_DEFINED_RELEASES:=':unstable:stable:deprecated:'}
428

429
is_release() {
gsell's avatar
gsell committed
430
	[[ ${PMODULES_DEFINED_RELEASES} =~ :$1: ]]
431 432 433
}

is_used_release() {
gsell's avatar
gsell committed
434
	[[ ":${UsedReleases}:" =~ :$1: ]]
435 436
}

gsell's avatar
gsell committed
437
declare used_groups=":${PMODULES_USED_GROUPS}:"
438

gsell's avatar
gsell committed
439 440
is_used_group() {
	[[ ${used_groups} =~ :$1: ]]
441 442 443 444 445 446
}

module_is_loaded() {
	[[ :${LOADEDMODULES}: =~ :$1: ]]
}

447 448 449 450 451 452 453 454 455 456 457
#
# 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}" == "#%Module1.0" ]]
}

458 459 460
subcommand_generic0() {
	local -r subcommand=$1
	shift
461 462 463 464 465 466 467 468 469 470 471 472 473
	local opts=''
	opts=$(get_options -- '' "$@") || subcommand_help_${subcommand}
	eval set --  "${opts}"
	while (( $# > 0 )); do
		case $1 in
			-- )
				shift
				;;
			* )
				die 3 "${CMD} ${subcommand}: illegal argument -- $1"
				;;
		esac
	done
474 475 476
	"${modulecmd}" "${shell}" "${subcommand}"
}

477
subcommand_generic1() {
478 479
	local -r subcommand=$1
	shift
480 481 482 483 484 485 486
	local opts=''
	opts=$(get_options -- '' "$@") || subcommand_help_${subcommand}
	eval set --  "${opts}"
	local args=()
	while (( $# > 0 )); do
		case $1 in
			-- )
487
					;;
488 489 490 491 492 493 494 495
			* )
				if (( ${#args[@]} == 0 )); then
				        args+=( "$1" )
				else
					die 3 "${CMD} ${subcommand}: only one argument allowed"
				fi
				;;
		esac
496
		shift
497 498 499 500 501
	done
	if (( ${#args[@]} == 0 )); then
	        die 3 "${CMD} ${subcommand}: missing argument"
	fi
	"${modulecmd}" "${shell}" "${subcommand}" "${args[@]}"
502 503
}

504
subcommand_generic1plus() {
505 506
	local -r subcommand=$1
	shift
507 508 509 510 511 512 513 514 515 516 517 518
	local opts=''
	opts=$(get_options -- '' "$@") || subcommand_help_${subcommand}
	eval set --  "${opts}"
	local args=()
	while (( $# > 0 )); do
		case $1 in
			-- )
				;;
			* )
				args+=( "$1" )
				;;
		esac
519
		shift
520 521 522
	done
	if (( ${#args[@]} == 0 )); then
	        die 3 "${CMD} ${subcommand}: missing argument"
523
	fi
524
	"${modulecmd}" "${shell}" "${subcommand}" "${args[@]}"
525 526
}

527
subcommand_generic1or2() {
528 529
	local -r subcommand=$1
	shift
530 531 532 533 534 535 536 537 538
	local opts=''
	opts=$(get_options -- '' "$@") || subcommand_help_${subcommand}
	eval set --  "${opts}"
	local args=()
	while (( $# > 0 )); do
		case $1 in
			-- )
				;;
			* )
539
				if (( ${#args[@]} > 2 )); then
540 541
					die 3 "${CMD} ${subcommand}: only one or two arguments are allowed"
				fi
542
				args+=( "$1" )
543 544
				;;
		esac
545
		shift
546 547 548
	done
	if (( ${#args[@]} == 0 )); then
	        die 3 "${CMD} ${subcommand}: missing argument"
549
	fi
550
	"${modulecmd}" "${shell}" "${subcommand}" "${args[@]}"
551 552
}

553
#
554
# load [-fsvw] <module>
555 556 557
# 
# $1: module to load
#
558
subcommand_load() {
559 560 561
	local release='undef'
	local moduledir=''
	local m=''
562 563 564 565 566

	#
	# Test whether a given module can be loaded according to the 
	# accepted releases.
	#
567 568 569
	# Notes:
	#	The variable 'release' in function 'subcommand_load()' will be set.
	#	The release of a modulefile outsite our hierarchy is 'stable'.
570 571 572 573 574
	#
	# $1: absolute name of modulefile
	#
	is_loadable() {
		release=$( get_release "$1" )			
gsell's avatar
gsell committed
575
		[[ :${UsedReleases}: =~ ${release} ]] && return 0
576 577 578 579 580
		return 1
	}

	#
	# Test whether a given module is available.
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596
	# Possible cases:
	# - absolute file- or link-name in- or outside our hierarchy
	# - relative file- or link-name in- or outside out hierarchy
	# - full module name in- or outside our hierarchy
	# - module name without version 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: is a loadable module
	# 	1: nothing found
	# 	2: wrong shebang
	# 	3: has unused release
	#	4: inside our hierarchy but not loadable
597 598
	#
	# Notes:
599 600
	#	The variable 'release' in function 'subcommand_load()' will be set.
	#	The variable 'm' in function 'subcommand_load()' may be set.
601
	#
602
	is_available() {
gsell's avatar
gsell committed
603
		local  m=$1
604 605 606 607 608 609 610 611 612

		# handle the case of an absolute or relative file- or link-name
		if [[ -f ${m} ]]; then
			if [[ "${m:0:1}" != "/" ]]; then
				# convert to absolte path if relative
				m=$(get_abspath "${m}")
			fi
			is_modulefile "${m}" || return 2
			is_loadable "${m}"   || return 3
gsell's avatar
gsell committed
613
			if [[ "${m}" =~ "${PMODULES_ROOT}" ]]; then
614 615 616 617 618 619 620 621
				for dir in "${modulepath[@]}"; do
					[[ "${m}" =~ "${dir}" ]] && return 0
				done
				return 4
			else
				return 0
			fi
		fi
622

623
		# check whether $m is in our modulepath
624 625 626 627
		for dir in "${modulepath[@]}"; do
			if [[ -d ${dir}/$1 ]]; then
				# module specified without version, like 'hdf5'
				while read fname; do
628 629 630 631 632 633
					is_modulefile "${fname}" || return 2
					if is_loadable "${fname}"; then
						moduledir="${dir}"
						return 0
					fi
				done < <(find "${dir}/$1" -mindepth 1 -maxdepth 1 -type l -o -type f  \! -name ".*")
634 635 636 637
			else
				# module specified with name/version, like 'hdf5/1.8.14'
				[[ -f ${dir}/$1 ]] || continue
 				[[ -r ${dir}/$1 ]] || continue
638 639 640 641 642
				is_modulefile "${dir}/$1" || return 2
		        	if is_loadable "${dir}/$1"; then
					moduledir="${dir}"
					return 0
				fi
643 644 645 646 647 648 649 650 651 652 653 654 655 656
			fi
		done
		return 1
	}

	#
	# output load 'hints'
	#
	# Note:
	#	The variable 'm' from the parent function will be used
	#	but not changed.
	#
	# Args:
	#	none
657
	output_load_hints() {
gsell's avatar
gsell committed
658
		local -ra rels=( ${PMODULES_DEFINED_RELEASES//:/ } )
659 660
		for rel in "${rels[@]}"; do
			eval $( subcommand_use "${rel}" )
661 662 663 664 665
			if is_available "${m}"; then
				info "${m}: is ${rel}! If you want to load this module, run"
				info "\tmodule use ${rel}"
				info "before running"
				info  "\tmodule load ${m}"
666 667 668 669 670 671 672 673 674 675 676 677
				exit 42
			fi
		done
		local something_found='no'
		local -a output=()
		local -a release=()
		local -a loadable=()
		local -i i=0
		local -i n=0
		while read -a line; do
			output[n]="module load ${line[@]:3} ${line[0]}"
			release[n]=${line[1]}
gsell's avatar
gsell committed
678
			if [[ ":${UsedReleases}:" =~ "${release[n]}" ]]; then
679 680 681 682 683 684
				loadable[n]='yes'
			else
				loadable[n]='no'
			fi
			n+=1
		done < <(subcommand_search  "${m}" -a --no-header 2>&1)
685
		info "${CMD} load: module unavailable -- ${m}"
686
		if (( n > 0 )); then
687
			info "\nBut the following modules chain(s) are available in the hierarchy:"
688 689
			for ((i=n-1; i >=0; i--)); do
				if [[ "${loadable[i]}" == "no" ]]; then
690
 				        info "${output[i]}\t# ${release[i]}"
691
				else
692
					info "${output[i]}"
693 694 695 696
				fi
			done
		fi
	}
697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731

	local opts
	opts=$(get_options -o fsvw -l force -l silent -l verbose -l warn -- "$@") || \
	    subcommand_help_load
	eval set --  "${opts}"
	local args=()
	opts=''
	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'
				;;
			-- )
				;;
			* )
				args+=( $1 )
				;;
			esac
			shift
	done
	if (( ${#args[@]} == 0 )); then
		die 2 "${CMD} load: No module specified."
	fi
	for m in "${args[@]}"; do		
		if is_available "${m}"; then
		        if [[ ${verbosity_lvl} != silent ]] && [[ ${release} != stable ]]; then
732
			        info "Warning: the ${release} module '${m}' has been loaded."
733 734
			fi
			"${modulecmd}" "${shell}" ${opts} load "${m}"
735
		else
736 737 738 739 740
			if [[ ${verbosity_lvl} == 'verbose' ]]; then
			        output_load_hints
			else
				die 3 "${CMD} load: module unavailable -- ${m}"
			fi
741
		fi
742
	done
743 744
}

745 746 747
#
# unload <module>
#
748 749 750
subcommand_unload() {
	# :FIXME: add dependency tests: don't unload if module is required be
	#	  another module
751 752 753 754
	while (( $# > 0 )); do
		subcommand_generic1 unload "$1"
		shift
	done
755 756
}

757 758 759
#
# swap <module> [<module>]
#
760
subcommand_swap() {
761
	subcommand_generic1or2 swap "$@"
762 763
}

764 765 766
#
# show <module>
#
767
subcommand_show() {
768 769 770 771
	while (( $# > 0 )); do
		subcommand_generic1 show "$1"
		shift
	done
772 773 774 775 776 777 778 779 780 781
}

#
# get all available modules in given directory.
# return list like
#	modulename1 release1 modulename2 release2 ...
#
get_available_modules() {
	local -r dir=$1
	local -r module=$2
gsell's avatar
gsell committed
782
	local -r use_releases=${3:-${UsedReleases}}
783 784 785 786 787
	local -a mods=()
	while read mod; do
		local release=$( get_release "${dir}/${mod}" )

		if [[ :${use_releases}: =~ :${release}: ]]; then
788
		        mods+=( "${mod}" ${release} )
789 790 791 792 793
		fi
	done < <(MODULEPATH="${dir}" "${modulecmd}" bash -t avail "${module}" 2>&1 | tail -n +2)
	echo "${mods[@]}"
}

794 795 796
#
# avail [-hlt] [<module-pattern>...]
#
797 798 799 800 801 802
subcommand_avail() {
	# use this variable in the output functions
	local -a mods=()
	local dir=''
	
	# get number of columns of terminal
gsell's avatar
gsell committed
803
	cols=$(tput cols)
804 805

	output_header() {
gsell's avatar
gsell committed
806 807 808
		local caption=${dir/${PMODULES_ROOT}\/}
		local caption=${caption/\/${PMODULES_MODULEFILES_DIR}/: }
		local caption=${caption/: \//: }
gsell's avatar
gsell committed
809
		let i=($cols-${#caption})/2-2
810
		printf -- "%0.s-" $(seq 1 $i) 1>&2
gsell's avatar
gsell committed
811
		printf -- " %s " "${caption}" 1>&2
812 813 814 815 816 817 818 819 820 821 822 823 824 825
		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=''
					;;
				* )
826
					out="${release}"
827 828 829 830
					;;
			esac
			printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2
		done
831
		info ""
832 833
	}

834 835
	#
	# :FIXME: for the time being, this is the same as terse_output!
836 837 838 839 840 841 842 843 844 845 846 847 848 849 850
	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
851
		info ""
852 853 854 855 856
	}

	human_readable_output() {
		output_header

gsell's avatar
gsell committed
857
		local -i column=$cols
858 859
		local -i colsize=16
		for ((i=0; i<${#mods[@]}; i+=2)); do
860
			if [[ ${verbosity_lvl} == 'verbose' ]]; then
861 862 863 864 865 866 867 868 869 870 871 872 873 874 875
			        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 ))
gsell's avatar
gsell committed
876
			if (( column+len >= cols )); then
877 878 879
			        printf -- "\n" 1>&2
				column=0
			fi
gsell's avatar
gsell committed
880
			if (( column+colsize < cols )); then
881 882 883 884 885 886 887 888 889
			        fmt="%-${colsize}s"
			else
				fmt="%-s"
			fi
			printf "${fmt}" "${mod}" 1>&2
			column+=colsize
		done
		printf -- "\n\n" 1>&2
	}
890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918
	local opts=''
	opts=$(get_options -o hlt -l human -l long -l terse -- "$@") || subcommand_help_avail
	eval set --  "${opts}"
	local pattern=()
	local output_function=''
	local opts=''
	while (($# > 0)); do
		case $1 in
			-h | --human )
				[[ -z ${opts} ]] || \
				    die 1 "${CMD} list: you cannot set both options: '$1' and '${opts}'."
				opts=$1
				output_function='human_readable_output'
				;;
			-l | --long )
				[[ -z ${opts} ]] || \
				    die 1 "${CMD} list: you cannot set both options: '$1' and '${opts}'."
				opts=$1
				output_function='long_output'
				;;
			-t | --terse )
				[[ -z ${opts} ]] || \
				    die 1 "${CMD} list: you cannot set both options: '$1' and '${opts}'."
				opts=$1
				output_function='terse_output'
				;;
			-- )
				;;
			* )
919
				pattern+=( "$1" )
920 921 922 923 924 925 926
				;;
		esac
		shift
	done
	output_function=${output_function:-human_readable_output}
	if (( ${#pattern[@]} == 0 )); then
	        pattern+=( '' )
927
	fi
928
	for string in "${pattern[@]}"; do
929
		for dir in "${modulepath[@]}"; do
930
			mods=( $( get_available_modules "${dir}" "${string}" ) )
931 932 933 934 935 936 937
			[[ ${#mods[@]} == 0 ]] && continue

			${output_function}
		done
	done
}

gsell's avatar
gsell committed
938
# get available groups
939 940
# $1: root of modulefile hierarchy
#
gsell's avatar
gsell committed
941 942 943 944 945 946 947 948
get_groups () {
	local -r root="$1"
	{
		cd "${root}" 
		for f in [A-Z]*; do
			Groups+=( $f )
		done
	};
949 950
}

951
#
952
# $1: root of modulefile hierarchy
953
get_hierarchy_depth () {
gsell's avatar
gsell committed
954
	local -r root="$1"
955
	{
gsell's avatar
gsell committed
956 957 958 959
		cd "${root}"
		local _group
		for _group in "${Groups[@]}"; do
			local  tmp=$(find "${_group}/modulefiles" -depth -type f -o -type l | head -1)
960 961
			local -a tmp2=( ${tmp//\// } )
			local depth=${#tmp2[@]}
gsell's avatar
gsell committed
962 963
			let depth-=4
			HierarchyDepths[$_group]=${depth} 
964
		done
965 966 967
	};
}

968
#
gsell's avatar
gsell committed
969
# use [-a|--append|-p|--prepend] [directory|group|release...]
970
#
971
subcommand_use() {
gsell's avatar
gsell committed
972 973 974
	if (( ${#Groups[@]} == 0 )); then
	        get_groups "${PMODULES_ROOT}"
		get_hierarchy_depth "${PMODULES_ROOT}"
gsell's avatar
gsell committed
975
	fi
976
	print_info() {
977 978
		local f
		local r
gsell's avatar
gsell committed
979
		info "Used groups:"
gsell's avatar
gsell committed
980
		for f in ${used_groups//:/ }; do
981
			info "\t${f}"
982
		done
gsell's avatar
gsell committed
983
		info "\nUnused groups:"
gsell's avatar
gsell committed
984 985 986 987 988
		local _group
		for _group in "${Groups[@]}"; do
			local -i depth=${HierarchyDepths[${_group}]}
			if ! is_used_group "${_group}" && (( depth == 0 )); then
			    info "\t${_group}"
989 990
			fi
		done
991
		
992
		info "\nUsed releases:"
gsell's avatar
gsell committed
993
		for r in ${UsedReleases//:/ }; do
994
			info "\t${r}"
995
		done
gsell's avatar
gsell committed
996 997
		info "\nUnused releases:"
		for r in ${PMODULES_DEFINED_RELEASES//:/ }; do
998
			if ! is_used_release $r; then
999
				info "\t${r}"
1000 1001 1002
			fi
		done

1003 1004
		info "\nAdditonal directories in MODULEPATH:"
		let n=0
1005
		for (( i=0; i<${#modulepath[@]}; i++)); do
gsell's avatar
gsell committed
1006
			if [[ ! ${modulepath[i]} =~ ${PMODULES_ROOT} ]]; then
1007 1008
			        info "\t${modulepath[i]}"
			        let n+=1
1009 1010
			fi
		done
1011 1012 1013 1014 1015
		if (( n == 0 )); then
		        info "\tnone"
		fi
		info "\n"
	}
1016

1017 1018
	use () {
		
1019 1020 1021 1022
		local dirs_to_add=()
		local subcommand_switches=''
		while (( $# > 0)); do
			arg=$1
1023 1024 1025 1026 1027 1028 1029 1030
			# if is release
			# ...
			# elif is group
			# ...
			# elif matches modulepath root
			# ...
			# elif is directory
			# ...
1031
			local modulefiles_dir="${PMODULES_ROOT}/${arg}/${PMODULES_MODULEFILES_DIR}"
1032 1033 1034 1035 1036 1037
			if [[ ${arg} == -a ]] || [[ ${arg} == --append ]]; then
			        subcommand_switches='--append'
			elif [[ ${arg} == -p ]] || [[ ${arg} == --prepend ]]; then
			        subcommand_switches=''
			elif is_release "${arg}"; then
			        # releases are always *appended*
gsell's avatar
gsell committed
1038 1039 1040
			        append_path UsedReleases "${arg}"
			elif [[ ! ${arg} =~ */* ]] && [[ -d ${modulefiles_dir} ]]; then
				if (( ${HierarchyDepths[$arg]} != 0 )); then
1041
					die 3 "${CMD} ${0##_}: cannot add group ${arg} to module path"
1042
				fi
gsell's avatar
gsell committed
1043
				append_path PMODULES_USED_GROUPS "${arg}"
gsell's avatar
gsell committed
1044 1045
			       	dirs_to_add+=( ${modulefiles_dir} )
			elif [[ ${arg} =~ ^${PMODULES_ROOT} ]]; then
1046
				die 3 "${CMD} ${0##_}: illegal directory: ${arg}"
1047 1048 1049 1050
			elif [[ -d ${arg} ]]; then
			        local normalized_dir=$(cd "${arg}" && pwd)
				dirs_to_add+=( ${normalized_dir} )
			elif [[ ${arg} =~ "-*" ]]; then
1051
				die 3 "${CMD} ${0##_}: illegal switch: ${arg}"
1052
			else
1053
				die 3 "${CMD} ${0##_}: neither a directory, release or group: ${arg}"
1054 1055 1056
			fi
			shift
		done
gsell's avatar
gsell committed
1057
		echo "export PMODULES_USED_GROUPS=