#!@TCLSHDIR@/tclsh
#
# MODULECMD.TCL, a pure TCL implementation of the module command
# Copyright (C) 2002-2004 Mark Lakata
# Copyright (C) 2004-2017 Kent Mein
# Copyright (C) 2016-2018 Xavier Delaruelle
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

##########################################################################

#
# Some Global Variables.....
#
set g_debug 0;# Set to 1 to enable debugging
set error_count 0 ;# Start with 0 errors
set g_return_false 0 ;# False value is rendered if == 1
set g_autoInit 0
set g_inhibit_interp 0 ;# Modulefile interpretation disabled if == 1
set g_inhibit_errreport 0 ;# Non-critical error reporting disabled if == 1
set g_inhibit_dispreport 0 ;# Display-mode reporting disabled if == 1
set g_init_errreport 0 ;# Start with error report locked before opts parsed
set g_force 0 ;# Path element reference counting if == 0
set CSH_LIMIT 4000 ;# Workaround for commandline limits in csh
set flag_default_dir 1 ;# Report default directories
set flag_default_mf 1 ;# Report default modulefiles and version alias
set reportfd "stderr" ;# File descriptor to use to report messages

set g_pager "@pager@" ;# Default command to page into, empty=disable
set g_pager_opts "@pageropts@" ;# Options to pass to the pager command

set g_siteconfig "@etcdir@/siteconfig.tcl" ;# Site configuration

# BEGIN: Pmodules extension
set PREFIX "prefix"
set name "name"
set version "version"
set group "group"
set g_url "g_url"
set g_license "g_license"
set g_maintainer "g_maintainer"
set g_help "g_help"
set g_whatis "g_whatis"

########################################################################
#
# P m o d u l e s   procedures
#

#
# :TODO:
# switch/swap
# unload modules if parent removed
#

if {[info exists env(PMODULES_DEBUG)] && $env(PMODULES_DEBUG)} {
	proc debug {msg} {
		set level [expr [info level] -2] 
		set r [catch {info level ${level}} e]
		if {$r} {
			set caller ""
		} else {
			set caller [lindex [split [info level [expr [info level] - 3]]] 0]
		}
		puts -nonewline stderr "${caller}: "
		puts stderr ${msg}
	}
} else {
	proc debug {msg} {}
}

proc procExists p {
	return uplevel 1 [expr {[llength [info procs $p]] > 0}]
}

proc module-addgroup { group } {
	global env
	global name
	global version

	reportDebug "called with arg $group, mode is \"[module-info mode]\""
	set Implementation [file join {*}$::implementation]

	set	GROUP [string toupper $group]
	regsub -- "-" ${GROUP} "_" GROUP
	setenv	${GROUP}		$name
	setenv	${GROUP}_VERSION	$version

	set	::${group}		$name
	set	::${group}_version	$version

	if { [module-info mode load] } {
		reportDebug "mode is load"

		set dir $::PmodulesRoot/$group/$::PmodulesModulfilesDir/$Implementation
		append-path MODULEPATH $dir
		append-path PMODULES_USED_GROUPS $group
		reportDebug "mode=load: new MODULEPATH=$env(MODULEPATH)"
		reportDebug "mode=load: new PMODULES_USED_GROUPS=$env(PMODULES_USED_GROUPS)"
	} elseif { [module-info mode unload] } {
		set GROUP [string toupper $group]
		reportDebug "remove hierarchical group '${GROUP}'"
		
		if { [info exists env(PMODULES_LOADED_${GROUP})] } {
			reportDebug "unloading orphan modules"
			set modules [split $env(PMODULES_LOADED_${GROUP}) ":"]
			foreach m ${modules} {
				if { ${m} == "999999999" } {
					continue
				}
				if { [is-loaded ${m}] } {
					reportDebug "unloading: $m"
					module unload ${m}
				}
			}
		} else {
			reportDebug "no orphan modules to unload"
		}
		reportDebug "mode=remove: $env(MODULEPATH)"
		set dir $::PmodulesRoot/$group/$::PmodulesModulfilesDir/$Implementation
		remove-path MODULEPATH $dir
		remove-path PMODULES_USED_GROUPS $group
	}
	if { [module-info mode switch2] } {
		reportDebug "mode=switch2"
		set dir  $::PmodulesRoot/$group/$::PmodulesModulfilesDir/[module-info name]
		append-path MODULEPATH $dir
		append-path PMODULES_USED_GROUPS ${group}
	}
}

proc set-family { group } {
        module-addgroup $group
}

proc _pmodules_update_loaded_modules { group name version } {
	if { ${group} == "999999999" } {
		return
	}
	set GROUP [string toupper $group]
	reportDebug "${GROUP} $name/$version"
	append-path PMODULES_LOADED_${GROUP} "$name/$version"
	remove-path PMODULES_LOADED_${GROUP} "999999999"
}

#
# load dependencies, but do *not* unload dependencies
#
proc _pmodules_load_dependencies { fname } {
	if { ! [ file exists ${fname} ] } {
		return
	}
	if { ! [module-info mode load] } {
		return
	}
	reportDebug "load dependencies from: ${fname}"
	#  Slurp up the data file
	set fp [open ${fname} r]
	set file_data [read ${fp}]
	close ${fp}
	set data [split ${file_data} "\n"]
	foreach line ${data} {
		reportDebug "MODULEPATH=$::env(MODULEPATH)"
		set module_name [string trim $line]
		if { ${module_name} == "#" || ${module_name} == "" } {
			continue
		}
		if { [is-loaded ${module_name}] } {
			reportDebug "module already loaded: ${module_name}"
			continue
		}
		reportDebug "module load: ${module_name}"
		module load ${module_name}
     }
}

proc lreverse_n { list n } {
        set res {}
        set i [expr [llength $list] - $n]
        while {$i >= 0} {
                lappend res {*}[lrange $list $i [expr $i+$n-1]]
                incr i -$n
        }
        set res
}

#
# set standard environment variables
#
proc _pmodules_setenv { PREFIX name version } {
	#
	# Hack for supporting legacy modules
	if { "${::group}" == "Legacy" } {
		reportDebug "this is a legacy module..."
		return
	}

	reportDebug "PREFIX = $PREFIX"
	
	set		NAME			[string toupper $name]
	regsub -- "-" ${NAME} "_" NAME

	if { ! [info exist ::dont-setenv] } {
		set ::dont-setenv {}
	}

	if { ${version} != "" } {
		if { [lsearch ${::dont-setenv} "${NAME}_VERSION"] == -1 } {
			setenv		${NAME}_VERSION		$version
		}
	}

	if { [file isdirectory "$PREFIX"] } {
		if { [lsearch ${::dont-setenv} "${NAME}_PREFIX"] == -1 } {
			setenv		${NAME}_PREFIX		$PREFIX
		}
		if { [lsearch ${::dont-setenv} "${NAME}_DIR"] == -1 } {
			setenv		${NAME}_DIR		$PREFIX
		}
		if { [lsearch ${::dont-setenv} "${NAME}_HOME"] == -1 } {
			setenv		${NAME}_HOME		$PREFIX
		}
	} else {
		reportDebug "$PREFIX is not a directory"
	}

	if { [file isdirectory "$PREFIX/bin"] } {
		if { [lsearch ${::dont-setenv} "PATH"] == -1 } {
			prepend-path	PATH			$PREFIX/bin
		}
	}

	if { [file isdirectory "$PREFIX/sbin"] } {
		if { [lsearch ${::dont-setenv} "PATH"] == -1 } {
			prepend-path	PATH			$PREFIX/sbin
		}
	}

	if { [file isdirectory "$PREFIX/share/man"] } {
		if { [lsearch ${::dont-setenv} "MANPATH"] == -1 } {
			prepend-path	MANPATH			$PREFIX/share/man
		}
	}

	# set various environment variables - as long as they are not blacklisted
	reportDebug "prepend to include paths"
	if { [file isdirectory "$PREFIX/include"] } {
		if { [lsearch ${::dont-setenv} "C_INCLUDE_PATH"] == -1 } {
			prepend-path	C_INCLUDE_PATH		$PREFIX/include
		}
		if { [lsearch ${::dont-setenv} "CPLUS_INCLUDE_PATH"] == -1 } {
			prepend-path	CPLUS_INCLUDE_PATH	$PREFIX/include
		}
		if { [lsearch ${::dont-setenv} "${NAME}_INCLUDE_DIR"] == -1 } {
			setenv		${NAME}_INCLUDE_DIR	$PREFIX/include
		}
	}

	reportDebug "prepend to library paths"
	if { [file isdirectory "$PREFIX/lib"] } {
		if { [lsearch ${::dont-setenv} "LIBRARY_PATH"] == -1 } {
			prepend-path	LIBRARY_PATH		$PREFIX/lib
		}
		if { [lsearch ${::dont-setenv} "LD_LIBRARY_PATH"] == -1 } {
			prepend-path	LD_LIBRARY_PATH		$PREFIX/lib
		}
		if { [lsearch ${::dont-setenv} "${NAME}_LIBRARY_DIR"] == -1 } {
			setenv		${NAME}_LIBRARY_DIR	$PREFIX/lib
		}
	}

	reportDebug "prepend to library paths (64bit)"
	if { [file isdirectory "$PREFIX/lib64"] } {
		if { [lsearch ${::dont-setenv} "LIBRARY_PATH"] == -1 } {
			prepend-path	LIBRARY_PATH		$PREFIX/lib64
		}
		if { [lsearch ${::dont-setenv} "LD_LIBRARY_PATH"] == -1 } {
			prepend-path	LD_LIBRARY_PATH		$PREFIX/lib64
		}
		if { [lsearch ${::dont-setenv} "${NAME}_LIBRARY_DIR"] == -1 } {
			setenv		${NAME}_LIBRARY_DIR	$PREFIX/lib64
		}
	}
}

#
# intialize global vars
# Modulefile is something like
#
#   ${PMODULES_ROOT}/group/${PMODULES_MODULEFILES_DIR}/name/version
# or
#   ${PMODULES_ROOT}/group/${PMODULES_MODULEFILES_DIR}/X1/Y1/name/version
# or
#   ${PMODULES_ROOT}/group/${PMODULES_MODULEFILES_DIR}/X1/Y1//X2/Y2/name/version
#
proc _init_global_vars { prefixVN } {
	global	group
	global  name
	global  version
	global	implementation
	upvar 1	$prefixVN PREFIX		# prefix of package

	reportDebug	"$::ModulesCurrentModulefile"
	set	::PmodulesRoot		$::env(PMODULES_ROOT)
	set	::PmodulesModulfilesDir	$::env(PMODULES_MODULEFILES_DIR)
	set	modulefile		[file split $::ModulesCurrentModulefile]
	set	pmodules_root		[file split $::PmodulesRoot]
	set	pmodules_root_num_dirs	[llength $pmodules_root]

	set	modulefile_root	[file join {*}[lrange $modulefile 0 [expr $pmodules_root_num_dirs - 1]]]
	if { $::PmodulesRoot != $modulefile_root } {
		reportDebug "stop sourcing: ${::PmodulesRoot} != $modulefile_root"
		return
	} 
	reportDebug	"modulefile is inside our root"
	set	rel_modulefile	[lrange $modulefile [llength $pmodules_root] end]
	set	group		[lindex $rel_modulefile 0]
	set	name		[lindex $modulefile end-1]
	set	version		[lindex $modulefile end]
	set	implementation	[lrange $rel_modulefile 2 end]
	set	prefix		"$pmodules_root $group [lreverse_n $implementation 2]"
	set	PREFIX		[file join {*}$prefix]

	reportDebug "PREFIX=$PREFIX"
	reportDebug "group of module $name: $group"
}

proc _pmodules_output_message { fname } {
	if { [ file exists "${fname}" ] } {
		set fp [open "${fname}" r]
		set info_text [read $fp]
		close $fp
		puts stderr ${info_text}
	}
}

if { [info exists ::whatis] } {
	module-whatis	"$whatis"
}


#
# we cannot load another module with the same name
#
#conflict	$name

#if { [module-info mode load] } {
#	debug "${name}/${version}: loading ... "
#	_pmodules_output_message "${PREFIX}/.info"
#}


# END: Pmodules extension

# Used to tell if a machine is running Windows or not
proc isWin {} {
   global tcl_platform

   if { $tcl_platform(platform) eq "windows" } {
      return 1
   } else {
      return 0
   }
}

#
# Set Default Path separator
#
if { [isWin] } {
   set g_def_separator "\;"
} else {
   set g_def_separator ":"
}

# Detect if terminal is attached to stderr message channel
proc isStderrTty {} {
   global g_is_stderr_tty

   if {![info exists g_is_stderr_tty]} {
      set g_is_stderr_tty [expr {![catch {fconfigure stderr -mode}]}]
   }

   return $g_is_stderr_tty
}

# Provide columns number for output formatting
proc getTtyColumns {} {
   global g_tty_columns

   if {![info exists g_tty_columns]} {
      # determine col number from tty capabilites
      if {[catch {exec stty size} stty_size] == 0 && $stty_size ne ""} {
         set g_tty_columns [lindex $stty_size 1]
      } else {
         # default size if tty cols cannot be found
         set g_tty_columns 80
      }
   }

   return $g_tty_columns
}

# Use MODULECONTACT variable to set your support email address
if {[info exists env(MODULECONTACT)]} {
   set contact $env(MODULECONTACT)
} else {
   # Or change this to your support email address...
   set contact "root@localhost"
}

# Set some directories to ignore when looking for modules.
set ignoreDir(CVS) 1
set ignoreDir(RCS) 1
set ignoreDir(SCCS) 1
set ignoreDir(.svn) 1
set ignoreDir(.git) 1

global g_shellType
global g_shell

set show_oneperline 0 ;# Gets set if you do module list/avail -t
set show_modtimes 0 ;# Gets set if you do module list/avail -l
set show_filter "" ;# Gets set if you do module avail -d or -L

proc raiseErrorCount {} {
   global error_count
   incr error_count
}

proc renderFalse {} {
   global g_shellType g_false_rendered

   reportDebug "renderFalse: called."

   if {[info exists g_false_rendered]} {
      reportDebug "renderFalse: false already rendered"
   } elseif {[info exists g_shellType]} {
      # setup flag to render only once
      set g_false_rendered 1

      # render a false value most of the time through a variable assignement
      # that will be looked at in the shell module function calling
      # modulecmd.tcl to return in turns a boolean status. Except for python
      # and cmake, the value assigned to variable is also returned as the
      # entire rendering status
      switch -- $g_shellType {
         {sh} - {csh} - {fish} {
            # no need to set a variable on real shells as last statement
            # result can easily be checked
            puts stdout "test 0 = 1;"
         }
         {tcl} {
            puts stdout "set _mlstatus 0;"
         }
         {cmd} {
            # nothing needed, reserved for future cygwin, MKS, etc
         }
         {perl} {
            puts stdout "\$_mlstatus = 0;"
         }
         {python} {
            puts stdout "_mlstatus = False"
         }
         {ruby} {
            puts stdout "_mlstatus = false"
         }
         {lisp} {
            puts stdout "nil"
         }
         {cmake} {
            puts stdout "set(_mlstatus FALSE)"
         }
         {r} {
            puts stdout "mlstatus <- FALSE"
         }
      }
   }
}

proc renderTrue {} {
   global g_shellType

   reportDebug "renderTrue: called."

   # render a true value most of the time through a variable assignement that
   # will be looked at in the shell module function calling modulecmd.tcl to
   # return in turns a boolean status. Except for python and cmake, the
   # value assigned to variable is also returned as the full rendering status
   switch -- $g_shellType {
      {sh} - {csh} - {fish} {
         # no need to set a variable on real shells as last statement
         # result can easily be checked
         puts stdout "test 0;"
      }
      {tcl} {
         puts stdout "set _mlstatus 1;"
      }
      {perl} {
         puts stdout "\$_mlstatus = 1;"
      }
      {python} {
         puts stdout "_mlstatus = True"
      }
      {ruby} {
         puts stdout "_mlstatus = true"
      }
      {lisp} {
         puts stdout "t"
      }
      {cmake} {
         puts stdout "set(_mlstatus TRUE)"
      }
      {r} {
         puts stdout "mlstatus <- TRUE"
      }
   }
}

proc renderText {text} {
   global g_shellType

   reportDebug "renderText: called ($text)."

   # render a text value most of the time through a variable assignement that
   # will be looked at in the shell module function calling modulecmd.tcl to
   # return in turns a string value.
   switch -- $g_shellType {
      {sh} - {csh} - {fish} {
         foreach word $text {
            # no need to set a variable on real shells, echoing text will make
            # it available as result
            puts stdout "echo '$word';"
         }
      }
      {tcl} {
         puts stdout "set _mlstatus \"$text\";"
      }
      {perl} {
         puts stdout "\$_mlstatus = '$text';"
      }
      {python} {
         puts stdout "_mlstatus = '$text'"
      }
      {ruby} {
         puts stdout "_mlstatus = '$text'"
      }
      {lisp} {
         puts stdout "(message \"$text\")"
      }
      {cmake} {
         puts stdout "set(_mlstatus \"$text\")"
      }
      {r} {
         puts stdout "mlstatus <- '$text'"
      }
   }
}

#
# Debug, Info, Warnings and Error message handling.
#
proc reportDebug {message {nonewline ""}} {
   global g_debug

   if {$g_debug} {
      report "DEBUG $message" "$nonewline"
   } elseif {!$::g_init_errreport} {
      # save message if report is not currently initialized as we do not
      # know yet if debug mode is enabled or not
      lappend ::errreport_buffer [list "reportDebug" $message $nonewline]
   }
}

proc reportWarning {message {nonewline ""}} {
   global g_inhibit_errreport

   if {!$g_inhibit_errreport} {
      report "WARNING: $message" "$nonewline"
   }
}

proc reportError {message {nonewline ""}} {
   global g_inhibit_errreport

   # if report disabled, also disable error raise to get a coherent
   # behavior (if no message printed, no error code change)
   if {!$g_inhibit_errreport} {
      raiseErrorCount
      report "ERROR: $message" "$nonewline"
   }
}

proc reportErrorAndExit {message} {
   if {$::g_init_errreport} {
      raiseErrorCount
      renderFalse
      error "$message"
   } else {
      # save message if report is not yet initialized
      lappend ::errreport_buffer [list "reportErrorAndExit" $message]
   }
}

proc reportInternalBug {message modfile} {
   global contact g_inhibit_errreport

   # if report disabled, also disable error raise to get a coherent
   # behavior (if no message printed, no error code change)
   if {!$g_inhibit_errreport} {
      raiseErrorCount
      report "Module ERROR: $message\n  In '$modfile'\n  Please contact\
         <$contact>"
   }
}

proc report {message {nonewline ""}} {
   global reportfd

   if {$::g_init_errreport} {
      # protect from issue with fd, just ignore it
      catch {
         if {$nonewline ne ""} {
            puts -nonewline $reportfd "$message"
         } else {
            puts $reportfd "$message"
         }
      }
   } else {
      # save message if report is not yet initialized
      lappend ::errreport_buffer [list "report" $message $nonewline]
   }
}

# report error the correct way depending of its type
proc reportIssue {issuetype issuemsg {issuefile {}}} {
   switch -- $issuetype {
      {invalid} {
         reportInternalBug $issuemsg $issuefile
      }
      default {
         reportError $issuemsg
      }
   }
}

proc reportVersion {} {
   report "Modules Release @MODULES_RELEASE@@MODULES_BUILD@\
      (@MODULES_BUILD_DATE@)"
}

# disable error reporting (non-critical report only) unless debug enabled
proc inhibitErrorReport {} {
   global g_inhibit_errreport g_debug

   if {!$g_debug} {
      set g_inhibit_errreport 1
   }
}

proc reenableErrorReport {} {
   global g_inhibit_errreport

   set g_inhibit_errreport 0
}

proc isErrorReportInhibited {} {
   global g_inhibit_errreport

   return $g_inhibit_errreport
}

# init error report and output buffered messages
proc initErrorReport {} {
   # setup message paging if enabled
   initPager

   set ::g_init_errreport 1

   # now error report is init output every message saved in buffer
   foreach errreport $::errreport_buffer {
      eval $errreport
   }
}

# exit in a clean manner by closing interaction with external components
proc cleanupAndExit {code} {
   global reportfd

   # close pager if enabled
   if {$reportfd ne "stderr"} {
      catch {flush $reportfd}
      catch {close $reportfd}
   }

   exit $code
}

# init configuration for output paging then start paging if enabled
proc initPager {} {
   global env g_pager g_pager_opts asked_pager reportfd

   # default pager enablement depends of pager command value
   if {$g_pager eq "" || [file tail $g_pager] eq "cat"} {
      set use_pager 0
      set init_use_pager 0
   } else {
      set use_pager 1
      set init_use_pager 1
   }

   if {[file tail $g_pager] eq "less" && $g_pager_opts ne "" &&\
      [info exists env(LESS)]} {
      reportDebug "initPager: clear 'less' pager options as LESS variable\
         defined"
      set g_pager_opts ""
   }

   foreach var [list MODULES_PAGER PAGER] {
      if {[info exists env($var)]} {
         if {$env($var) ne ""} {
            # MODULES_PAGER env variable set means pager should be enabled
            if {!$use_pager && $var eq "MODULES_PAGER"} {
               set use_pager 1
            }
            # fetch pager command and option
            set g_pager [lindex $env($var) 0]
            set g_pager_opts [lrange $env($var) 1 end]

         # variable defined empty means no-pager
         } else {
            set use_pager 0
            set g_pager ""
            set g_pager_opts ""
         }

         reportDebug "initPager: configure pager from $var variable\
            (use_pager=$use_pager, cmd='$g_pager', opts='$g_pager_opts')"

         # if MODULES_PAGER set, no look at PAGER
         break
      }
   }

   # paging may have been enabled or disabled from the command-line
   if {[info exists asked_pager]} {
      # enable from command-line only if it is enabled in script config
      if {$asked_pager && !$use_pager && $init_use_pager} {
         set use_pager 1
      } elseif {!$asked_pager && $use_pager} {
         set use_pager 0
      }
      set asked $asked_pager
   } else {
      set asked "-"
   }

   # empty or 'cat' pager command means no-pager
   if {$use_pager && ($g_pager eq "" || [file tail $g_pager] eq "cat")} {
      set use_pager 0
   }

   # setup paging if enabled and if error stream is attached to a terminal
   set is_tty [isStderrTty]
   if {$is_tty && $use_pager} {
      reportDebug "initPager: start pager (asked_pager=$asked,\
         cmd='$g_pager', opts='$g_pager_opts')"
      if {[catch {
         set reportfd [open "|$g_pager $g_pager_opts >@stderr 2>@stderr" w]
         fconfigure $reportfd -buffering line -blocking 1 -buffersize 65536
      } errMsg]} {
         reportWarning $errMsg
      }
   } else {
      reportDebug "initPager: no pager start (is_tty=$is_tty,\
         use_pager=$use_pager, asked_pager=$asked, cmd='$g_pager',\
         opts='$g_pager_opts')"
   }
}

########################################################################
# Use a slave TCL interpreter to execute modulefiles
#

proc unset-env {var} {
   global env

   if {[info exists env($var)]} {
      reportDebug "unset-env:  $var"
      unset env($var)
   }
}

proc execute-modulefile {modfile {must_have_cookie 1}} {
   global g_debug g_inhibit_interp g_inhibit_errreport g_inhibit_dispreport
   global g_modfileUntrackVars g_modfileAliases
   # BEGIN Pmodules extensions
   global PREFIX
   global name
   global version
   global group
   global g_url
   global g_license
   global g_maintainer
   global g_help
   global g_whatis
   # END Pmodules extensions
   
   pushModuleFile $modfile

   # skip modulefile if interpretation has been inhibited
   if {$g_inhibit_interp} {
      reportDebug "execute-modulefile: Skipping $modfile"
      return 1
   }

   reportDebug "execute-modulefile:  Starting $modfile"
   
   if {![info exists g_modfileUntrackVars]} {
      # list variable that should not be tracked for saving
      array set g_modfileUntrackVars [list g_debug 1 g_inhibit_interp 1\
	 g_inhibit_errreport 1 g_inhibit_dispreport 1\
         ModulesCurrentModulefile 1 must_have_cookie 1 modcontent 1 env 1]

      # BEGIN Pmodules extensions
      array set g_modfileUntrackVars [list \
					      name 1 \
					      version 1 \
					      group 1 \
					      whatis 1 \
					      g_url 1 \
					      g_license 1 \
					      g_maintainer 1 \
					      g_help 1]
      # END Pmodules extensions

      # list interpreter alias commands to define
      array set g_modfileAliases [list setenv setenv unsetenv unsetenv getenv\
         getenv system system chdir chdir append-path append-path\
         prepend-path prepend-path remove-path remove-path prereq prereq\
         conflict conflict is-loaded is-loaded is-saved is-saved is-used\
         is-used is-avail is-avail module module module-info\
         module-info module-whatis module-whatis set-alias set-alias\
         unset-alias unset-alias uname uname x-resource x-resource exit\
         exitModfileCmd module-version module-version module-alias\
         module-alias module-virtual module-virtual module-trace module-trace\
         module-verbosity module-verbosity module-user module-user module-log\
         module-log reportInternalBug reportInternalBug reportWarning\
         reportWarning reportError reportError raiseErrorCount\
         raiseErrorCount report report isWin isWin puts putsModfileCmd\
         readModuleContent readModuleContent]

      # BEGIN Pmodules extensions
      array set g_modfileAliases [ list \
					   reportDebug reportDebug \
					  _init_global_vars _init_global_vars \
					  _pmodules_setenv _pmodules_setenv \
					  _pmodules_update_loaded_modules _pmodules_update_loaded_modules \
					   debug debug \
					   module-url module-url \
					   module-license module-license \
					   module-maintainer module-maintainer \
					   module-help module-help \
					   module-addgroup module-addgroup \
					   set-family set-family \
					   output-help output-help ]
      # END Pmodules extensions

   }

   # dedicate an interpreter per level of interpretation to have in case of
   # cascaded interpretations a specific interpreter per level
   set itrp "__modfile[info level]"

   # create modulefile interpreter at first interpretation
   if {![interp exists $itrp]} {
      interp create $itrp

      # dump initial interpreter state to restore it before each modulefile
      # interpreation
      dumpInterpState $itrp g_modfileVars g_modfileArrayVars\
         g_modfileUntrackVars g_modfileProcs
   }

   # reset interp state command before each interpretation
   resetInterpState $itrp g_modfileVars g_modfileArrayVars\
      g_modfileUntrackVars g_modfileProcs g_modfileAliases g_modfileCommands

   # reset modulefile-specific variable before each interpretation
   interp eval $itrp {global ModulesCurrentModulefile g_debug\
			      g_inhibit_interp g_inhibit_errreport g_inhibit_dispreport \
			      PREFIX, group, g_url, g_license, g_maintainer, g_help, g_whatis}
   interp eval $itrp set ModulesCurrentModulefile $modfile
   interp eval $itrp set g_debug $g_debug
   interp eval $itrp set g_inhibit_interp $g_inhibit_interp
   interp eval $itrp set g_inhibit_errreport $g_inhibit_errreport
   interp eval $itrp set g_inhibit_dispreport $g_inhibit_dispreport
   interp eval $itrp set must_have_cookie $must_have_cookie

   # BEGIN Pmodules extension
   reportDebug "set PREFIX"
   _init_global_vars PREFIX
   reportDebug "PREFIX after _init_global_vars: $PREFIX"
   interp eval $itrp set PREFIX $PREFIX
   interp eval $itrp set name $name
   interp eval $itrp set version $version
   interp eval $itrp set group $group
   interp eval $itrp set g_url $g_url
   interp eval $itrp set g_license $g_license
   interp eval $itrp set g_maintainer $g_maintainer
   interp eval $itrp set g_help $g_help
   interp eval $itrp set g_whatis $g_whatis

   set errorVal [interp eval $itrp {
      set modcontent [readModuleContent $ModulesCurrentModulefile 1\
         $must_have_cookie]
      if {$modcontent eq ""} {
         return 1
      }
      info script $ModulesCurrentModulefile

      # BEGIN Pmodules extensions
      _pmodules_setenv ${PREFIX} ${name} ${version}
      _pmodules_update_loaded_modules ${group} ${name} ${version}
      
      proc ModulesHelp { } {
	 output-help
      }
      # END Pmodules extensions
      
      # eval then call for specific proc depending mode under same catch
      set sourceFailed [catch {
         eval $modcontent
         switch -- [module-info mode] {
            {help} {
               if {[info procs "ModulesHelp"] eq "ModulesHelp"} {
                  ModulesHelp
               } else {
                  reportWarning "Unable to find ModulesHelp in\
                     $ModulesCurrentModulefile."
               }
            }
            {display} {
               if {[info procs "ModulesDisplay"] eq "ModulesDisplay"} {
                  ModulesDisplay
               }
            }
            {test} {
               if {[info procs "ModulesTest"] eq "ModulesTest"} {
                  if {[string is true -strict [ModulesTest]]} {
                     report "Test result: PASS"
                  } else {
                     report "Test result: FAIL"
                     raiseErrorCount
                  }
               } else {
                  reportWarning "Unable to find ModulesTest in\
                     $ModulesCurrentModulefile."
               }
            }
         }
      } errorMsg]
      if {$sourceFailed} {
         global errorInfo
         # no error in case of "continue" command
         # catch continue even if called outside of a loop
         if {$errorMsg eq "invoked \"continue\" outside of a loop"\
            || $sourceFailed == 4} {
            unset errorMsg
            return 0
         # catch break even if called outside of a loop
         } elseif {$errorMsg eq "invoked \"break\" outside of a loop"\
            || ($errorMsg eq "" && (![info exists errorInfo]\
            || $errorInfo eq ""))} {
            raiseErrorCount
            unset errorMsg
            return 1
         } elseif {$errorMsg eq "SUB_FAILED"} {
            # error counter and message already handled, just return error
            return 1
         } elseif [regexp "^WARNING" $errorMsg] {
            raiseErrorCount
            report $errorMsg
            return 1
         } else {
            reportInternalBug $errorMsg $ModulesCurrentModulefile
            return 1
         }
      } else {
         unset errorMsg
         return 0
      }
   }]

   popModuleFile

   reportDebug "Exiting $modfile"
   return $errorVal
}

# BEGIN Pmodules extensions
proc module-url { url } {
	set ::g_url ${url}
}

proc module-license { license } {
	set ::g_license ${license}
}

proc module-maintainer { maintainer } {
	set ::g_maintainer ${maintainer}
}

proc module-help { help } {
	set ::g_help ${help}
}

proc output-help { } {
	if { [info exists ::g_whatis] } {
		report "${::g_whatis}"
	}
	if { [info exists ::version] } {
		report "Version:    ${::version}"
	}
	if { [info exists ::g_url] } {
		report "Homepage:   ${::g_url}"
	}
	if { [info exists ::g_license] } {
		report "License:    ${::g_license}"
	}
	if { [info exists ::g_maintainer] } {
		report "Maintainer: ${::g_maintainer}"
	}
	if { [info exists ::g_help] } {
		report "${::g_help}\n"
	}
}
# END Pmodules extension
	
# Smaller subset than main module load... This function runs modulerc and
# .version files
proc execute-modulerc {modfile} {
   global g_rcfilesSourced ModulesVersion
   global g_debug g_inhibit_errreport g_inhibit_dispreport
   global g_modrcUntrackVars g_modrcAliases

   reportDebug "execute-modulerc: $modfile"

   pushModuleFile $modfile
   set ModulesVersion {}
   # does not report commands from rc file on display mode
   set g_inhibit_dispreport 1

   set modname [file dirname [currentModuleName]]

   if {![info exists g_rcfilesSourced($modfile)]} {
      if {![info exists g_modrcUntrackVars]} {
         # list variable that should not be tracked for saving
         array set g_modrcUntrackVars [list g_debug 1 g_inhibit_errreport 1\
            g_inhibit_dispreport 1 ModulesCurrentModulefile 1\
            ModulesVersion 1 modcontent 1 env 1]

         # list interpreter alias commands to define
         array set g_modrcAliases [list uname uname system system chdir\
            chdir module-version module-version module-alias module-alias\
            module-virtual module-virtual module module module-info\
            module-info module-trace module-trace module-verbosity\
            module-verbosity module-user module-user module-log module-log\
            reportInternalBug reportInternalBug setModulesVersion\
            setModulesVersion readModuleContent readModuleContent]
      }

      # dedicate an interpreter per level of interpretation to have in case of
      # cascaded interpretations a specific interpreter per level
      set itrp "__modrc[info level]"

      reportDebug "execute-modulerc: sourcing rc $modfile"
      # create modulerc interpreter at first interpretation
      if {![interp exists $itrp]} {
         interp create $itrp

         # dump initial interpreter state to restore it before each modulerc
         # interpreation
         dumpInterpState $itrp g_modrcVars g_modrcArrayVars\
            g_modrcUntrackVars g_modrcProcs
      }

      # reset interp state command before each interpretation
      resetInterpState $itrp g_modrcVars g_modrcArrayVars\
         g_modrcUntrackVars g_modrcProcs g_modrcAliases g_modrcCommands

      interp eval $itrp {global ModulesCurrentModulefile g_debug\
         g_inhibit_errreport g_inhibit_dispreport ModulesVersion}
      interp eval $itrp set ModulesCurrentModulefile $modfile
      interp eval $itrp set g_debug $g_debug
      interp eval $itrp set g_inhibit_errreport $g_inhibit_errreport
      interp eval $itrp set g_inhibit_dispreport $g_inhibit_dispreport
      interp eval $itrp {set ModulesVersion {}}

      set errorVal [interp eval $itrp {
         set modcontent [readModuleContent $ModulesCurrentModulefile]
         if {$modcontent eq ""} {
            # simply skip rc file, no exit on error here
            return 1
         }
         info script $ModulesCurrentModulefile
         if [catch {eval $modcontent} errorMsg] {
            reportInternalBug $errorMsg $ModulesCurrentModulefile
            return 1
         } else {
            # pass ModulesVersion value to master interp
            if {[info exists ModulesVersion]} {
               setModulesVersion $ModulesVersion
            }
            return 0
         }
      }]

      # default version set via ModulesVersion variable in .version file
      # override previously defined default version for modname
      if {[file tail $modfile] eq ".version" && $ModulesVersion ne ""} {
         setModuleResolution "$modname/default" $modname/$ModulesVersion\
            "default"
      }

      # Keep track of rc files we already sourced so we don't run them again
      set g_rcfilesSourced($modfile) $ModulesVersion
   }

   # re-enable command report on display mode
   set g_inhibit_dispreport 0

   popModuleFile

   return $g_rcfilesSourced($modfile)
}

# Save list of the defined procedure and the global variables with their
# associated values set in slave interpreter passed as argument. Global
# structures are used to save these information and the name of these
# structures are provided as argument.
proc dumpInterpState {itrp dumpVarsVN dumpArrayVarsVN untrackVarsVN\
   dumpProcsVN} {
   upvar #0 $dumpVarsVN dumpVars
   upvar #0 $dumpArrayVarsVN dumpArrayVars
   upvar #0 $untrackVarsVN untrackVars
   upvar #0 $dumpProcsVN dumpProcs

   # save name and value for any other global variables
   foreach var [$itrp eval {info globals}] {
      if {![info exists untrackVars($var)]} {
         reportDebug "dumpInterpState: saving for $itrp var $var"
         if {[$itrp eval array exists ::$var]} {
            set dumpVars($var) [$itrp eval array get ::$var]
            set dumpArrayVars($var) 1
         } else {
            set dumpVars($var) [$itrp eval set ::$var]
         }
      }
   }

   # save name of every defined procedures
   foreach var [$itrp eval {info procs}] {
      set dumpProcs($var) 1
   }
   reportDebug "dumpInterpState: saving for $itrp proc list [array names\
      dumpProcs]"
}

# Restore initial setup of slave interpreter passed as argument based on
# global structure previously filled with initial list of defined procedure
# and values of global variable.
proc resetInterpState {itrp dumpVarsVN dumpArrayVarsVN untrackVarsVN\
   dumpProcsVN aliasesVN dumpCommandsVN} {
   upvar #0 $dumpVarsVN dumpVars
   upvar #0 $dumpArrayVarsVN dumpArrayVars
   upvar #0 $untrackVarsVN untrackVars
   upvar #0 $dumpProcsVN dumpProcs
   upvar #0 $aliasesVN aliases
   upvar #0 $dumpCommandsVN dumpCommands

   # look at list of defined procedures and delete those not part of the
   # initial state list. do not check if they have been altered as no vital
   # procedures lied there. note that if a Tcl command has been overridden
   # by a proc, it will be removed here and command will also disappear
   foreach var [$itrp eval {info procs}] {
      if {![info exists dumpProcs($var)]} {
         reportDebug "resetInterpState: removing on $itrp proc $var"
         $itrp eval [list rename $var {}]
      }
   }

   # set interpreter alias commands each time to guaranty them being
   # defined and not overridden by modulefile or modulerc content
   foreach alias [array names aliases] {
      interp alias $itrp $alias {} $aliases($alias)
   }

   # dump interpreter command list here on first time as aliases should be
   # set prior to be found on this list for correct match
   if {![info exists dumpCommands]} {
      set dumpCommands [$itrp eval {info commands}]
      reportDebug "resetInterpState: saving for $itrp command list\
         $dumpCommands"
   # if current interpreter command list does not match initial list it
   # means that at least one command has been altered so we need to recreate
   # interpreter to guaranty proper functioning
   } elseif {$dumpCommands ne [$itrp eval {info commands}]} {
      reportDebug "resetInterpState: missing command(s), recreating $itrp"
      interp delete $itrp
      interp create $itrp
      # set aliases again on fresh interpreter
      foreach alias [array names aliases] {
         interp alias $itrp $alias {} $aliases($alias)
      }
   }

   # check every global variables currently set and correct them to restore
   # initial interpreter state. work on variables at the very end to ensure
   # procedures and commands are correctly defined
   foreach var [$itrp eval {info globals}] {
      if {![info exists untrackVars($var)]} {
         if {![info exists dumpVars($var)]} {
            reportDebug "resetInterpState: removing on $itrp var $var"
            $itrp eval unset ::$var
         } elseif {![info exists dumpArrayVars($var)]} {
            if {$dumpVars($var) ne [$itrp eval set ::$var]} {
               reportDebug "resetInterpState: restoring on $itrp var $var"
               if {[llength $dumpVars($var)] > 1} {
                  # restore value as list
                  $itrp eval set ::$var [list $dumpVars($var)]
               } else {
                  $itrp eval set ::$var $dumpVars($var)
               }
            }
         } else {
            if {$dumpVars($var) ne [$itrp eval array get ::$var]} {
               reportDebug "resetInterpState: restoring on $itrp var $var"
               $itrp eval array set ::$var [list $dumpVars($var)]
            }
         }
      }
   }
}

########################################################################
# commands run from inside a module file
#

# Dummy procedures for commands available on C-version but not
# implemented here. These dummy procedures enables support for
# modulefiles using these commands while warning users these
# commands have no effect.
proc module-log {args} {
   reportWarning "'module-log' command not implemented"
}

proc module-verbosity {args} {
   reportWarning "'module-verbosity' command not implemented"
}

proc module-user {args} {
   reportWarning "'module-user' command not implemented"
}

proc module-trace {args} {
   reportWarning "'module-trace' command not implemented"
}

proc module-info {what {more {}}} {
   global g_shellType g_shell tcl_platform

   set mode [currentMode]

   reportDebug "module-info: $what $more  mode=$mode"

   switch -- $what {
      {mode} {
         if {$more ne ""} {
            set command [currentCommandName]
            if {$mode eq $more || ($more eq "remove" && $mode eq "unload")\
               || ($more eq "switch" && $command eq "switch")} {
               return 1
            } else {
               return 0
            }
         } else {
            return $mode
         }
      }
      {command} {
         set command [currentCommandName]
         if {$more eq ""} {
            return $command
         } elseif {$command eq $more} {
            return 1
         } else {
            return 0
         }
      }
      {name} {
         return [currentModuleName]
      }
      {specified} {
         return [currentSpecifiedName]
      }
      {shell} {
         if {$more ne ""} {
            if {$g_shell eq $more} {
               return 1
            } else {
               return 0
            }
         } else {
            return $g_shell
         }
      }
      {flags} {
         # C-version specific option, not relevant for Tcl-version but return
         # a zero integer value to avoid breaking modulefiles using it
         return 0
      }
      {shelltype} {
         if {$more ne ""} {
            if {$g_shellType eq $more} {
               return 1
            } else {
               return 0
            }
         } else {
            return $g_shellType
         }
      }
      {user} {
         # C-version specific option, not relevant for Tcl-version but return
         # an empty value or false to avoid breaking modulefiles using it
         if {$more ne ""} {
            return 0
         } else {
            return {}
         }
      }
      {alias} {
         set ret [resolveModuleVersionOrAlias $more]
         if {$ret ne $more} {
            return $ret
         } else {
            return {}
         }
      }
      {trace} {
         return {}
      }
      {tracepat} {
         return {}
      }
      {type} {
         return "Tcl"
      }
      {symbols} {
         lassign [getModuleNameVersion $more 1] mod modname modversion
         set tag_list [getVersAliasList $mod]
         # if querying special symbol "default" but nothing found registered
         # on it, look at symbol registered on bare module name in case there
         # are symbols registered on it but no default symbol set yet to link
         # to them
         if {[llength $tag_list] == 0 && $modversion eq "default"} {
            set tag_list [getVersAliasList $modname]
         }
         return [join $tag_list ":"]
      }
      {version} {
         lassign [getModuleNameVersion $more 1] mod
         return [resolveModuleVersionOrAlias $mod]
      }
      {loaded} {
         lassign [getModuleNameVersion $more 1] mod
         return [getLoadedMatchingName $mod "returnall"]
      }
      default {
         error "module-info $what not supported"
         return {}
      }
   }
}

proc module-whatis {args} {
   global g_whatis
   set mode [currentMode]
   set message [join $args " "]

   reportDebug "module-whatis: $message  mode=$mode"

   if {$mode eq "display" && !$::g_inhibit_dispreport} {
      report "module-whatis\t$message"
   }\
   elseif {$mode eq "whatis"} {
      lappend g_whatis $message
   }
   return {}
}

# convert environment variable references in string to their values
# every local variable is prefixed by '0' to ensure they will not be
# overwritten through variable reference resolution process
proc resolvStringWithEnv {0str} {
   global env

   # fetch variable references in string
   set 0match_list [regexp -all -inline {\$[{]?([A-Za-z_][A-Za-z0-9_]*)[}]?}\
      ${0str}]
   if {[llength ${0match_list}] > 0} {
      # put in local scope every environment variable referred in string
      for {set 0i 1} {${0i} < [llength ${0match_list}]} {incr 0i 2} {
         set 0varname [lindex ${0match_list} ${0i}]
         if {![info exists ${0varname}]} {
            if {[info exists env(${0varname})]} {
               set ${0varname} $env(${0varname})
            } else {
            set ${0varname} ""
            }
         }
      }
      # resolv variable reference with values (now in local scope)
      set 0res [subst -nobackslashes -nocommands ${0str}]
   } else {
      set 0res ${0str}
   }

   reportDebug "resolvStringWithEnv: '${0str}' resolved to '${0res}'"

   return ${0res}
}

# deduce modulepath from modulefile and module name
proc getModulepathFromModuleName {modfile modname} {
   return [string range $modfile 0 end-[string length "/$modname"]]
}

# deduce module name from modulefile and modulepath
proc getModuleNameFromModulepath {modfile modpath} {
   return [string range $modfile [string length "$modpath/"] end]
}

# extract module name from modulefile and currently enabled modulepaths
proc findModuleNameFromModulefile {modfile} {
   set ret ""

   foreach modpath [getModulePathList] {
      if {[string first "$modpath/" "$modfile/"] == 0} {
         set ret [getModuleNameFromModulepath $modfile $modpath]
         break
      }
   }
   return $ret
}

# extract modulepath from modulefile and currently enabled modulepaths
proc findModulepathFromModulefile {modfile} {
   set ret ""

   foreach modpath [getModulePathList] {
      if {[string first "$modpath/" "$modfile/"] == 0} {
         set ret $modpath
         break
      }
   }
   return $ret
}

# Determine with a name provided as argument the corresponding module name,
# version and name/version. Module name is guessed from current module name
# when shorthand version notation is used. Both name and version are guessed
# from current module if name provided is empty. If 'name_relative_tocur' is
# enabled then name argument may be interpreted as a name relative to the
# current modulefile directory (useful for module-version and module-alias
# for instance).
proc getModuleNameVersion {{name {}} {name_relative_tocur 0}} {
   set curmod [currentModuleName]
   set curmodname [file dirname $curmod]
   set curmodversion [file tail $curmod]

   if {$name eq ""} {
      set name $curmodname
      set version $curmodversion
   # check for shorthand version notation like "/version" or "./version"
   # only if we are currently interpreting a modulefile or modulerc
   } elseif {$curmod ne "" && [regexp {^\.?\/(.*)$} $name match version]} {
      # if we cannot distinguish a module name, raise error when
      # shorthand version notation is used
      global ModulesCurrentModulefile
      if {$ModulesCurrentModulefile ne $curmod} {
         # name is the name of current module directory
         set name $curmodname
      } else {
         reportError "Invalid modulename '$name' found"
         return {}
      }
   } else {
      set name [string trimright $name "/"]
      set version [file tail $name]
      if {$name eq $version} {
         set version ""
      } else {
         set name [file dirname $name]
      }
      # name may correspond to last part of current module
      # if so name is replaced by current module name
      if {$name_relative_tocur && [file tail $curmodname] eq $name} {
         set name $curmodname
      }
   }

   if {$version eq ""} {
      set mod $name
   } else {
      set mod $name/$version
   }

   return [list $mod $name $version]
}

# Register alias or symbolic version deep resolution in a global array that
# can be used thereafter to get in one query the actual modulefile behind
# a virtual name. Also consolidate a global array that in the same manner
# list all the symbols held by modulefiles.
proc setModuleResolution {mod target {symver {}} {override_res_path 1}} {
   global g_moduleResolved g_resolvedHash g_resolvedPath
   global g_symbolHash g_moduleVersion g_sourceVersion
   global g_moduleAltName ModulesCurrentModulefile

   # find end-point module and register step-by-step path to get to it
   set res $target
   set res_path $res
   while {$mod ne $res && [info exists g_resolvedPath($res)]} {
      set res $g_resolvedPath($res)
      lappend res_path $res
   }

   # error if resolution end on initial module
   if {$mod eq $res} {
      reportError "Resolution loop on '$res' detected"
      return 0
   }

   # module name will be useful when registering symbol
   if {$symver ne ""} {
      lassign [getModuleNameVersion $mod] modfull modname
   }

   # change default symbol owner if previously given
   if {$symver eq "default"} {
      # alternative name "modname" is set when mod = "modname/default" both
      # names will be registered to be known for queries and resolution defs
      set modalt $modname

      if {[info exists g_moduleResolved($mod)]} {
         set prev $g_moduleResolved($mod)
         # no test needed, there must be a "default" in $prev symbol list
         set idx [lsearch -exact $g_symbolHash($prev) "default"]
         reportDebug "setModuleResolution: remove symbol 'default' from\
            '$prev'"
         set g_symbolHash($prev) [lreplace $g_symbolHash($prev) $idx $idx]
      }
   }

   # register end-point resolution
   reportDebug "setModuleResolution: $mod resolved to $res"
   set g_moduleResolved($mod) $res
   # set first element of resolution path only if not already set or
   # scratching enabled, no change when propagating symbol along res path
   if {$override_res_path || ![info exists g_resolvedPath($mod)]} {
      set g_resolvedPath($mod) $target
   }
   lappend g_resolvedHash($res) $mod

   # also register resolution on alternative name if any
   if {[info exists modalt]} {
      reportDebug "setModuleResolution: $modalt resolved to $res"
      set g_moduleResolved($modalt) $res
      if {$override_res_path || ![info exists g_resolvedPath($modalt)]} {
         set g_resolvedPath($modalt) $target
      }
      lappend g_resolvedHash($res) $modalt
      # register name alternative to know their existence
      set g_moduleAltName($modalt) $mod
      set g_moduleAltName($mod) $modalt
   }

   # if other modules were pointing to this one, adapt resolution end-point
   set relmod_list {}
   if {[info exists g_resolvedHash($mod)]} {
      set relmod_list $g_resolvedHash($mod)
      unset g_resolvedHash($mod)
   }
   # also adapt resolution for modules pointing to the alternative name
   if {[info exists modalt] && [info exists g_resolvedHash($modalt)]} {
      set relmod_list [concat $relmod_list $g_resolvedHash($modalt)]
      unset g_resolvedHash($modalt)
   }
   foreach relmod $relmod_list {
      set g_moduleResolved($relmod) $res
      reportDebug "setModuleResolution: $relmod now resolved to $res"
      lappend g_resolvedHash($res) $relmod
   }

   # register and propagate symbols to the resolution path
   if {[info exists g_symbolHash($mod)]} {
      set sym_list $g_symbolHash($mod)
   } else {
      set sym_list {}
   }
   if {$symver ne ""} {
      # merge symbol definitions in case of alternative name
      if {[info exists modalt] && [info exists g_symbolHash($modalt)]} {
         set sym_list [lsort -dictionary -unique [concat $sym_list\
            $g_symbolHash($modalt)]]
         reportDebug "setModuleResolution: set symbols '$sym_list' to $mod\
            and $modalt"
         set g_symbolHash($mod) $sym_list
         set g_symbolHash($modalt) $sym_list
      }

      # dictionary-sort symbols and remove eventual duplicates
      set sym_list [lsort -dictionary -unique [concat $sym_list\
         [list $symver]]]

      # propagate symbols in g_symbolHash and g_moduleVersion toward the
      # resolution path, handle that locally if we still work on same
      # modulename, call for a proper resolution as soon as we change of
      # module to get this new resolution registered
      foreach modres $res_path {
         lassign [getModuleNameVersion $modres] modfull modresname
         if {$modname eq $modresname} {
            if {[info exists g_symbolHash($modres)]} {
               set modres_sym_list [lsort -dictionary -unique [concat\
                  $g_symbolHash($modres) $sym_list]]
            } else {
               set modres_sym_list $sym_list
            }
            # sync symbols of alternative name if any
            if {[info exists g_moduleAltName($modres)]} {
               set altmodres $g_moduleAltName($modres)
               reportDebug "setModuleResolution: set symbols\
                  '$modres_sym_list' to $modres and $altmodres"
               set g_symbolHash($altmodres) $modres_sym_list
            } else {
               reportDebug "setModuleResolution: set symbols\
                  '$modres_sym_list' to $modres"
            }
            set g_symbolHash($modres) $modres_sym_list

            # register symbolic version for querying in g_moduleVersion
            foreach symelt $sym_list {
               set modvers "$modresname/$symelt"
               reportDebug "setModuleResolution: module-version $modvers =\
                  $modres"
               set g_moduleVersion($modvers) $modres
               set g_sourceVersion($modvers) $ModulesCurrentModulefile
            }
         # as we change of module name a proper resolution call should be
         # made (see below) and will handle the rest of the resolution path
         } else {
            set need_set_res 1
            break
         }
      }
   # when registering an alias, existing symbols on alias source name should
   # be broadcast along the resolution path with a proper resolution call
   # (see below)
   } else {
      lassign [getModuleNameVersion $target] modres modresname
      set need_set_res 1
   }

   # resolution needed to broadcast symbols along resolution path without
   # altering initial path already set for these symbols
   if {[info exists need_set_res]} {
      foreach symelt $sym_list {
         set modvers "$modresname/$symelt"
         reportDebug "setModuleResolution: set resolution for $modvers"
         setModuleResolution $modvers $modres $symelt 0
      }
   }

   return 1
}

# Specifies a default or alias version for a module that points to an 
# existing module version Note that aliases defaults are stored by the
# short module name (not the full path) so aliases and defaults from one
# directory will apply to modules of the same name found in other
# directories.
proc module-version {args} {
   global g_moduleVersion

   reportDebug "module-version: executing module-version $args"
   lassign [getModuleNameVersion [lindex $args 0] 1] mod modname modversion

   # go for registration only if valid modulename
   if {$mod ne ""} {
      foreach version [lrange $args 1 end] {
         set aliasversion "$modname/$version"
         # do not alter a previously defined alias version
         if {![info exists g_moduleVersion($aliasversion)]} {
            setModuleResolution $aliasversion $mod $version
         } else {
            reportWarning "Symbolic version '$aliasversion' already defined"
         }
      }
   }

   if {[currentMode] eq "display" && !$::g_inhibit_dispreport} {
      report "module-version\t$args"
   }
   return {}
}

proc module-alias {args} {
   global g_moduleAlias
   global g_sourceAlias ModulesCurrentModulefile

   lassign [getModuleNameVersion [lindex $args 0]] alias
   lassign [getModuleNameVersion [lindex $args 1] 1] mod

   reportDebug "module-alias: $alias = $mod"

   if {[setModuleResolution $alias $mod]} {
      set g_moduleAlias($alias) $mod
      set g_sourceAlias($alias) $ModulesCurrentModulefile
   }

   if {[currentMode] eq "display" && !$::g_inhibit_dispreport} {
      report "module-alias\t$args"
   }

   return {}
}

proc module-virtual {args} {
   global g_moduleVirtual
   global g_sourceVirtual ModulesCurrentModulefile

   lassign [getModuleNameVersion [lindex $args 0]] mod
   set modfile [getAbsolutePath [lindex $args 1]]

   reportDebug "module-virtual: $mod = $modfile"

   set g_moduleVirtual($mod) $modfile
   set g_sourceVirtual($mod) $ModulesCurrentModulefile

   if {[currentMode] eq "display" && !$::g_inhibit_dispreport} {
      report "module-virtual\t$args"
   }

   return {}
}

proc module {command args} {
   set mode [currentMode]

   # guess if called from top level
   set topcall [expr {[info level] == 1}]
   if {$topcall} {
      set msgprefix ""
   } else {
      set msgprefix "module: "
   }

   switch -regexp -- $command {
      {^(add|lo)} {
         # no error raised on empty argument list to cope with
         # initadd command that may expect this behavior
         if {[llength $args] > 0} {
            set ret 0
            pushCommandName "load"
            if {$topcall || $mode eq "load"} {
               set ret [eval cmdModuleLoad $args]
            }\
            elseif {$mode eq "unload"} {
               # on unload mode, unload mods in reverse order
               set ret [eval cmdModuleUnload "match" [lreverse $args]]
            }\
            elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
               report "module load\t$args"
            }
            popCommandName
            # sub-module interpretation failed, raise error
            if {$ret && !$topcall} {
               set errormsg "SUB_FAILED"
            }
         }
      }
      {^(rm|unlo)} {
         if {[llength $args] == 0} {
            set errormsg "Unexpected number of args for 'unload' command"
         } else {
            set ret 0
            pushCommandName "unload"
            if {$topcall || $mode eq "load"} {
               set ret [eval cmdModuleUnload "match" $args]
            }\
            elseif {$mode eq "unload"} {
               set ret [eval cmdModuleUnload "match" $args]
            }\
            elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
               report "module unload\t$args"
            }
            popCommandName
            # sub-module interpretation failed, raise error
            if {$ret && !$topcall} {
               set errormsg "SUB_FAILED"
            }
         }
      }
      {^(ref|rel)} {
         if {[llength $args] != 0} {
            set errormsg "Unexpected number of args for 'reload' command"
         } else {
            pushCommandName "reload"
            cmdModuleReload
            popCommandName
         }
      }
      {^use$} {
         if {$topcall || $mode eq "load"} {
            eval cmdModuleUse $args
         } elseif {$mode eq "unload"} {
            eval cmdModuleUnuse $args
         } elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
            report "module use\t$args"
         }
      }
      {^unuse$} {
         if {$topcall || $mode eq "load" || $mode eq "unload"} {
            eval cmdModuleUnuse $args
         } elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
            report "module unuse\t$args"
         }
      }
      {^source$} {
         if {[llength $args] == 0} {
            set errormsg "Unexpected number of args for 'source' command"
         } else {
            pushCommandName "source"
            if {$topcall || $mode eq "load"} {
               eval cmdModuleSource $args
            } elseif {$mode eq "unload"} {
               # on unload mode, unsource script in reverse order
               eval cmdModuleUnsource [lreverse $args]
            } elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
               report "module source\t$args"
            }
            popCommandName
         }
      }
      {^sw} {
         if {[llength $args] == 0 || [llength $args] > 2} {
            set errormsg "Unexpected number of args for 'switch' command"
         } else {
            pushCommandName "switch"
            eval cmdModuleSwitch $args
            popCommandName
         }
      }
      {^(di|show)} {
         if {[llength $args] == 0} {
            set errormsg "Unexpected number of args for 'show' command"
         } else {
            pushCommandName "display"
            eval cmdModuleDisplay $args
            popCommandName
         }
      }
      {^av} {
         pushCommandName "avail"
         if {$args ne ""} {
            foreach arg $args {
               cmdModuleAvail $arg
            }
         } else {
            cmdModuleAvail
         }
         popCommandName
      }
      {^al} {
         if {[llength $args] != 0} {
            set errormsg "Unexpected number of args for 'aliases' command"
         } else {
            pushCommandName "aliases"
            cmdModuleAliases
            popCommandName
         }
      }
      {^path$} {
         if {$topcall} {
            if {[llength $args] != 1} {
               set errormsg "Unexpected number of args for 'path' command"
            } else {
               eval cmdModulePath $args
            }
         } else {
            # no call other than from top level as it renders a result value
            set errormsg "${msgprefix}Command '$command' not supported"
         }
      }
      {^paths$} {
         if {$topcall} {
            if {[llength $args] != 1} {
               set errormsg "Unexpected number of args for 'paths' command"
            } else {
               eval cmdModulePaths $args
            }
         } else {
            # no call other than from top level as it renders a result value
            set errormsg "${msgprefix}Command '$command' not supported"
         }
      }
      {^li} {
         if {[llength $args] != 0} {
            set errormsg "Unexpected number of args for 'list' command"
         } else {
            pushCommandName "list"
            cmdModuleList
            popCommandName
         }
      }
      {^wh} {
         pushCommandName "whatis"
         if {$args ne ""} {
            foreach arg $args {
               cmdModuleWhatIs $arg
            }
         } else {
            cmdModuleWhatIs
         }
         popCommandName
      }
      {^(apropos|search|keyword)$} {
         if {[llength $args] > 1} {
            set errormsg "Unexpected number of args for '$command' command"
         } else {
            pushCommandName "search"
            eval cmdModuleApropos $args
            popCommandName
         }
      }
      {^pu} {
         if {[llength $args] != 0} {
            set errormsg "Unexpected number of args for 'purge' command"
         } else {
            pushCommandName "purge"
            eval cmdModulePurge
            popCommandName
         }
      }
      {^save$} {
         if {[llength $args] > 1} {
            set errormsg "Unexpected number of args for 'save' command"
         } else {
            eval cmdModuleSave $args
         }
      }
      {^restore$} {
         if {[llength $args] > 1} {
            set errormsg "Unexpected number of args for 'restore' command"
         } else {
            pushCommandName "restore"
            eval cmdModuleRestore $args
            popCommandName
         }
      }
      {^saverm$} {
         if {[llength $args] > 1} {
            set errormsg "Unexpected number of args for 'saverm' command"
         } else {
            eval cmdModuleSaverm $args
         }
      }
      {^saveshow$} {
         if {[llength $args] > 1} {
            set errormsg "Unexpected number of args for 'saveshow' command"
         } else {
            eval cmdModuleSaveshow $args
         }
      }
      {^savelist$} {
         if {[llength $args] != 0} {
            set errormsg "Unexpected number of args for 'savelist' command"
         } else {
            cmdModuleSavelist
         }
      }
      {^init(a|lo)} {
         if {[llength $args] == 0} {
            set errormsg "Unexpected number of args for 'initadd' command"
         } else {
            eval cmdModuleInit add $args
         }
      }
      {^initp} {
         if {[llength $args] == 0} {
            set errormsg "Unexpected number of args for 'initprepend' command"
         } else {
            eval cmdModuleInit prepend $args
         }
      }
      {^initsw} {
         if {[llength $args] != 2} {
            set errormsg "Unexpected number of args for 'initswitch' command"
         } else {
            eval cmdModuleInit switch $args
         }
      }
      {^init(rm|unlo)$} {
         if {[llength $args] == 0} {
            set errormsg "Unexpected number of args for 'initrm' command"
         } else {
            eval cmdModuleInit rm $args
         }
      }
      {^initl} {
         if {[llength $args] != 0} {
            set errormsg "Unexpected number of args for 'initlist' command"
         } else {
            eval cmdModuleInit list $args
         }
      }
      {^initclear$} {
         if {[llength $args] != 0} {
            set errormsg "Unexpected number of args for 'initclear' command"
         } else {
            eval cmdModuleInit clear $args
         }
      }
      {^autoinit$} {
         if {$topcall} {
            if {[llength $args] != 0} {
               set errormsg "Unexpected number of args for 'autoinit' command"
            } else {
               cmdModuleAutoinit
            }
         } else {
            # autoinit cannot be called elsewhere than from top level
            set errormsg "${msgprefix}Command '$command' not supported"
         }
      }
      {^($|help)} {
         if {$topcall} {
            pushCommandName "help"
            eval cmdModuleHelp $args
            popCommandName
            if {[llength $args] != 0} {
            }
         } else {
            # help cannot be called elsewhere than from top level
            set errormsg "${msgprefix}Command '$command' not supported"
         }
      }
      {^test$} {
         if {[llength $args] == 0} {
            set errormsg "Unexpected number of args for 'test' command"
         } else {
            pushCommandName "test"
            eval cmdModuleTest $args
            popCommandName
         }
      }
      {^(prepend|append|remove)-path$} {
         if {$topcall} {
            if {[llength $args] < 2} {
               set errormsg "Unexpected number of args for '$command' command"
            } else {
               eval cmdModuleResurface $command $args
            }
         } else {
            # no call other than from top level not to conflict with modulefile
            # specific Tcl commands
            set errormsg "${msgprefix}Command '$command' not supported"
         }
      }
      {^is-(loaded|saved|used)$} {
         if {$topcall} {
            eval cmdModuleResurface $command $args
         } else {
            # no call other than from top level not to conflict with modulefile
            # specific Tcl commands
            set errormsg "${msgprefix}Command '$command' not supported"
         }
      }
      {^is-avail$} {
         if {$topcall} {
            if {[llength $args] == 0} {
               set errormsg "Unexpected number of args for '$command' command"
            } else {
               eval cmdModuleResurface $command $args
            }
         } else {
            # no call other than from top level not to conflict with modulefile
            # specific Tcl commands
            set errormsg "${msgprefix}Command '$command' not supported"
         }
      }
      {info-loaded} {
         if {$topcall} {
            if {[llength $args] != 1} {
               set errormsg "Unexpected number of args for '$command' command"
            } else {
               eval cmdModuleResurface module-info loaded $args
            }
         } else {
            # no call other than from top level not to conflict with modulefile
            # specific Tcl commands
            set errormsg "${msgprefix}Command '$command' not supported"
         }
      }
      . {
         set errormsg "${msgprefix}Invalid command '$command'"
      }
   }

   # if an error need to be raised, proceed differently depending of
   # call level: if called from top level render errors then raise error
   # elsewhere call is made from a modulefile or modulerc and error
   # will be managed from execute-modulefile or execute-modulerc
   if {[info exists errormsg]} {
      if {$topcall} {
         reportErrorAndExit "$errormsg\nTry 'module --help'\
            for more information."
      } else {
         error "$errormsg"
      }
   # if called from top level render settings if any
   } elseif {$topcall} {
      renderSettings
   }

   return {}
}

proc setenv {var val} {
   global g_stateEnvVars env

   set mode [currentMode]

   reportDebug "setenv: ($var,$val) mode = $mode"

   if {$mode eq "load"} {
      set env($var) $val
      set g_stateEnvVars($var) "new"
      # clean any previously defined reference counter array
      set sharevar "${var}_modshare"
      if {[info exists env($sharevar)]} {
         unset-env $sharevar
         set g_stateEnvVars($sharevar) "del"
      }
   }\
   elseif {$mode eq "unload"} {
      # Don't unset-env here ... it breaks modulefiles
      # that use env(var) is later in the modulefile
      #unset-env $var
      set g_stateEnvVars($var) "del"
   }\
   elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
      # Let display set the variable for later use in the display
      # but don't commit it to the env
      set env($var) $val
      report "setenv\t\t$var\t$val"
   }
   return {}
}

proc getenv {var} {
   set mode [currentMode]

   reportDebug "getenv: ($var) mode = $mode"

   if {$mode eq "load" || $mode eq "unload"} {
      if {[info exists ::env($var)]} {
         return $::env($var)
      } else {
         return "_UNDEFINED_"
      }
   }\
   elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
      return "\$$var"
   }
   return {}
}

proc unsetenv {var {val {}}} {
   global g_stateEnvVars env

   set mode [currentMode]

   reportDebug "unsetenv: ($var,$val) mode = $mode"

   if {$mode eq "load"} {
      if {[info exists env($var)]} {
         unset-env $var
      }
      set g_stateEnvVars($var) "del"
      # clean any existing reference counter array
      set sharevar "${var}_modshare"
      if {[info exists env($sharevar)]} {
         unset-env $sharevar
         set g_stateEnvVars($sharevar) "del"
      }
   }\
   elseif {$mode eq "unload"} {
      if {$val ne ""} {
         set env($var) $val
         set g_stateEnvVars($var) "new"
      } else {
         set g_stateEnvVars($var) "del"
      }
   }\
   elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
      if {$val ne ""} {
         report "unsetenv\t$var\t$val"
      } else {
         report "unsetenv\t$var"
      }
   }
   return {}
}

proc chdir {dir} {
   global g_changeDir
   set mode [currentMode]
   set currentModule [currentModuleName]

   reportDebug "chdir: ($dir) mode = $mode"

   if {$mode eq "load"} {
      if {[file exists $dir] && [file isdirectory $dir]} {
         set g_changeDir $dir
      } else {
         # report issue but does not treat it as an error to have the
         # same behavior as C-version
         reportWarning "Cannot chdir to '$dir' for '$currentModule'"
      }
   } elseif {$mode eq "unload"} {
      # No operation here unable to undo a syscall.
   } elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
      report "chdir\t\t$dir"
   }

   return {}
}

# superseed exit command to handle it if called within a modulefile
# rather than exiting the whole process
proc exitModfileCmd {{code 0}} {
   global g_inhibit_interp
   set mode [currentMode]

   reportDebug "exit: ($code)"

   if {$mode eq "load"} {
      reportDebug "exit: Inhibit next modulefile interpretations"
      set g_inhibit_interp 1
   }

   # break to gently end interpretation of current modulefile
   return -code break
}

# enables slave interp to return ModulesVersion value to the master interp
proc setModulesVersion {val} {
   global ModulesVersion

   set ModulesVersion $val
}

# supersede puts command to catch content sent to stdout/stderr within
# modulefile in order to correctly send stderr content (if a pager has been
# enabled) or postpone content channel send after rendering on stdout the
# relative environment changes required by the modulefile
proc putsModfileCmd {args} {
   global g_stdoutPuts

   reportDebug "puts: ($args)"

   # determine if puts call targets the stdout or stderr channel
   switch -- [llength $args] {
      {1} {
         set deferPuts 1
      }
      {2} {
         switch -- [lindex $args 0] {
            {-nonewline} - {stdout} {
               set deferPuts 1
            }
            {stderr} {
               set reportArgs [list [lindex $args 1]]
            }
         }
      }
      {3} {
         if {[lindex $args 0] eq "-nonewline"} {
            switch -- [lindex $args 1] {
               {stdout} {
                  set deferPuts 1
               }
               {stderr} {
                  set reportArgs [list [lindex $args 2] 1]
               }
            }
         }
      }
   }

   # defer puts if it targets stdout (see renderSettings)
   if {[info exists deferPuts]} {
      lappend g_stdoutPuts $args
   # if it targets stderr call report, which knows what channel to use
   } elseif {[info exists reportArgs]} {
      eval report $reportArgs
   # pass to real puts command if not related to stdout or bad call
   } else {
      eval puts $args
   }
}

########################################################################
# path fiddling
#
proc getReferenceCountArray {var separator} {
   global env g_force g_def_separator g_debug

   set sharevar "${var}_modshare"
   set modshareok 1
   if {[info exists env($sharevar)]} {
      if {[info exists env($var)]} {
         set modsharelist [psplit $env($sharevar) $g_def_separator]
         set temp [expr {[llength $modsharelist] % 2}]

         if {$temp == 0} {
            array set countarr $modsharelist

            # sanity check the modshare list
            array set fixers {}
            array set usagearr {}

            # do not skip a bare empty path entry that can also be found in
            # reference counter array (sometimes var is cleared by setting it
            # empty not unsetting it, ignore var in this case)
            if {$env($var) eq "" && [info exists countarr()]} {
               set usagearr() 1
            } else {
               foreach dir [split $env($var) $separator] {
                  set usagearr($dir) 1
               }
            }
            foreach path [array names countarr] {
               if {! [info exists usagearr($path)]} {
                  unset countarr($path)
                  set fixers($path) 1
               }
            }

            foreach path [array names usagearr] {
               if {! [info exists countarr($path)]} {
                  # if no ref count found for a path, assume it has a ref
                  # count of 1 to be able to unload it easily if needed
                  set countarr($path) 1
               }
            }

            if {! $g_force} {
               if {[array size fixers]} {
                  reportWarning "\$$var does not agree with\
                     \$${var}_modshare counter. The following\
                     directories' usage counters were adjusted to match.\
                     Note that this may mean that module unloading may\
                     not work correctly."
                  foreach dir [array names fixers] {
                     report " $dir" -nonewline
                  }
                  report ""
               }
            }
         } else {
            # sharevar was corrupted, odd number of elements.
            set modshareok 0
         }
      } else {
         reportWarning "$sharevar exists ( $env($sharevar) ), but $var\
            doesn't. Environment is corrupted."
         set modshareok 0
      }
   } else {
      set modshareok 0
   }

   if {$modshareok == 0 && [info exists env($var)]} {
      array set countarr {}
      foreach dir [split $env($var) $separator] {
         set countarr($dir) 1
      }
   }

   set count_list [array get countarr]
   reportDebug "getReferenceCountArray: (var=$var, delim=$separator) got\
      '$count_list'"

   return $count_list
}


proc unload-path {args} {
   global g_stateEnvVars env g_force g_def_separator

   reportDebug "unload-path: ($args)"

   lassign [eval parsePathCommandArgs "unload-path" $args] separator\
      allow_dup idx_val var path_list

   array set countarr [getReferenceCountArray $var $separator]

   # Don't worry about dealing with this variable if it is already scheduled
   #  for deletion
   if {[info exists g_stateEnvVars($var)] && $g_stateEnvVars($var) eq "del"} {
      return {}
   }

   # save initial variable content to match index arguments
   if {[info exists env($var)]} {
      set dir_list [split $env($var) $separator]
      # detect if empty env value means empty path entry
      if {[llength $dir_list] == 0 && [info exists countarr()]} {
         lappend dir_list {}
      }
   } else {
      set dir_list [list]
   }

   # build list of index to remove from variable
   set del_idx_list [list]
   foreach dir $path_list {
      # retrieve dir value if working on an index list
      if {$idx_val} {
         set idx $dir
         # go to next index if this one is not part of the existing range
         # needed to distinguish an empty value to an out-of-bound value
         if {$idx < 0 || $idx >= [llength $dir_list]} {
            continue
         } else {
            set dir [lindex $dir_list $idx]
         }
      }

      # update reference counter array
      if {[info exists countarr($dir)]} {
         incr countarr($dir) -1
         set newcount $countarr($dir)
         if {$countarr($dir) <= 0} {
            unset countarr($dir)
         }
      } else {
         set newcount 0
      }

      # get all entry indexes corresponding to dir
      set found_idx_list [lsearch -all -exact $dir_list $dir]

      # remove all found entries
      if {$g_force || $newcount <= 0} {
         # only remove passed position in --index mode
         if {$idx_val} {
            lappend del_idx_list $idx
         } else {
            set del_idx_list [concat $del_idx_list $found_idx_list]
         }
      # if multiple entries found remove the extra entries compared to new
      # reference counter
      } elseif {[llength $found_idx_list] > $newcount} {
         # only remove passed position in --index mode
         if {$idx_val} {
            lappend del_idx_list $idx
         } else {
            # delete extra entries, starting from end of the list (on a path
            # variable, entries at the end have less priority than those at
            # the start)
            set del_idx_list [concat $del_idx_list [lrange $found_idx_list\
               $newcount end]]
         }
      }
   }

   # update variable if some element need to be removed
   if {[llength $del_idx_list] > 0} {
      set del_idx_list [lsort -integer -unique $del_idx_list]
      set newpath [list]
      set nbelem [llength $dir_list]
      # rebuild list of element without indexes set for deletion
      for {set i 0} {$i < $nbelem} {incr i} {
         if {[lsearch -exact $del_idx_list $i] == -1} {
            lappend newpath [lindex $dir_list $i]
         }
      }
   } else {
      set newpath $dir_list
   }

   # set env variable and corresponding reference counter in any case
   if {[llength $newpath] == 0} {
      unset-env $var
      set g_stateEnvVars($var) "del"
   } else {
      set env($var) [join $newpath $separator]
      set g_stateEnvVars($var) "new"
   }

   set sharevar "${var}_modshare"
   if {[array size countarr] > 0} {
      set env($sharevar) [pjoin [array get countarr] $g_def_separator]
      set g_stateEnvVars($sharevar) "new"
   } else {
      unset-env $sharevar
      set g_stateEnvVars($sharevar) "del"
   }
   return {}
}

proc add-path {pos args} {
   global env g_stateEnvVars g_def_separator

   reportDebug "add-path: ($args) pos=$pos"

   lassign [eval parsePathCommandArgs "add-path" $args] separator allow_dup\
      idx_val var path_list

   set sharevar "${var}_modshare"
   array set countarr [getReferenceCountArray $var $separator]

   if {$pos eq "prepend"} {
      set path_list [lreverse $path_list]
   }

   foreach dir $path_list {
      if {![info exists countarr($dir)] || $allow_dup} {
         # ignore env var set empty if no empty entry found in reference
         # counter array (sometimes var is cleared by setting it empty not
         # unsetting it)
         if {[info exists env($var)] && ($env($var) ne "" ||\
            [info exists countarr()])} {
            if {$pos eq "prepend"} {
               set env($var) "$dir$separator$env($var)"
            } else {
               set env($var) "$env($var)$separator$dir"
            }
         } else {
            set env($var) "$dir"
         }
      }
      if {[info exists countarr($dir)]} {
         incr countarr($dir)
      } else {
         set countarr($dir) 1
      }
      reportDebug "add-path: env($var) = $env($var)"
   }

   set env($sharevar) [pjoin [array get countarr] $g_def_separator]
   set g_stateEnvVars($var) "new"
   set g_stateEnvVars($sharevar) "new"
   return {}
}

# analyze argument list passed to a path command to set default value or raise
# error in case some attributes are missing
proc parsePathCommandArgs {cmd args} {
   global g_def_separator

   # parse argument list
   set next_is_delim 0
   set allow_dup 0
   set idx_val 0
   foreach arg $args {
      switch -glob -- $arg {
         {--index} {
            if {$cmd eq "add-path"} {
               reportWarning "--index option has no effect on $cmd"
            } else {
               set idx_val 1
            }
         }
         {--duplicates} {
            if {$cmd eq "unload-path"} {
               reportWarning "--duplicates option has no effect on $cmd"
            } else {
               set allow_dup 1
            }
         }
         {-d} - {-delim} - {--delim} {
            set next_is_delim 1
         }
         {--delim=*} {
            set delim [string range $arg 8 end]
         }
         default {
            if {$next_is_delim} {
               set delim $arg
               set next_is_delim 0
            } elseif {![info exists var]} {
               set var $arg
            } else {
               # set multiple passed values in a list
               lappend val_raw_list $arg
            }
         }
      }
   }

   # adapt with default value or raise error if some arguments are missing
   if {![info exists delim]} {
      set delim $g_def_separator
   } elseif {$delim eq ""} {
      error "$cmd should get a non-empty path delimiter"
   }
   if {![info exists var]} {
      error "$cmd should get an environment variable name"
   } elseif {$var eq ""} {
      error "$cmd should get a valid environment variable name"
   }
   if {![info exists val_raw_list]} {
      error "$cmd should get a value for environment variable $var"
   }

   # set list of value to add
   set val_list [list]
   foreach val $val_raw_list {
      # check passed indexes are numbers
      if {$idx_val && ![string is integer -strict $val]} {
         error "$cmd should get valid number as index value"
      }

      switch -- $val \
         {} {
            # add empty entry in list
            lappend val_list {}
         } \
         $delim {
            error "$cmd cannot handle path equals to separator string"
         } \
         default {
            # split passed value with delimiter
            set val_list [concat $val_list [split $val $delim]]
         }
   }

   reportDebug "parsePathCommandArgs: (delim=$delim, allow_dup=$allow_dup,\
      idx_val=$idx_val, var=$var, val=$val_list, nbval=[llength $val_list])"

   return [list $delim $allow_dup $idx_val $var $val_list]
}

proc prepend-path {args} {
   set mode [currentMode]

   reportDebug "prepend-path: ($args) mode=$mode"

   if {$mode eq "load"} {
      eval add-path "prepend" $args
   }\
   elseif {$mode eq "unload"} {
      eval unload-path $args
   }\
   elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
      report "prepend-path\t$args"
   }

   return {}
}

proc append-path {args} {
   set mode [currentMode]

   reportDebug "append-path: ($args) mode=$mode"

   if {$mode eq "load"} {
      eval add-path "append" $args
   }\
   elseif {$mode eq "unload"} {
      eval unload-path $args
   }\
   elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
      report "append-path\t$args"
   }

   return {}
}

proc remove-path {args} {
   set mode [currentMode]

   reportDebug "remove-path: ($args) mode=$mode"

   if {$mode eq "load"} {
      eval unload-path $args
   }\
   elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
      report "remove-path\t$args"
   }
   return {}
}

proc set-alias {alias what} {
   global g_Aliases g_stateAliases
   set mode [currentMode]

   reportDebug "set-alias: ($alias, $what) mode=$mode"
   if {$mode eq "load"} {
      set g_Aliases($alias) $what
      set g_stateAliases($alias) "new"
   }\
   elseif {$mode eq "unload"} {
      set g_Aliases($alias) {}
      set g_stateAliases($alias) "del"
   }\
   elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
      report "set-alias\t$alias\t$what"
   }

   return {}
}

proc unset-alias {alias} {
   global g_Aliases g_stateAliases

   set mode [currentMode]

   reportDebug "unset-alias: ($alias) mode=$mode"
   if {$mode eq "load"} {
      set g_Aliases($alias) {}
      set g_stateAliases($alias) "del"
   }\
   elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
      report "unset-alias\t$alias"
   }

   return {}
}

proc is-loaded {args} {
   reportDebug "is-loaded: $args"

   foreach mod $args {
      if {[getLoadedMatchingName $mod "returnfirst"] ne ""} {
         return 1
      }
   }
   # is something loaded whatever it is?
   if {[llength $args] == 0 && [llength [getLoadedModuleList]] > 0} {
      return 1
   } else {
      return 0
   }
}

proc conflict {args} {
   set mode [currentMode]
   set currentModule [currentModuleName]

   reportDebug "conflict: ($args) mode = $mode"

   if {$mode eq "load"} {
      foreach mod $args {
         # If the current module is already loaded, we can proceed
         if {![is-loaded $currentModule]} {
            # otherwise if the conflict module is loaded, we cannot
            if {[is-loaded $mod]} {
               set errMsg "WARNING: $currentModule cannot be loaded due\
                  to a conflict."
               set errMsg "$errMsg\nHINT: Might try \"module unload\
                  $mod\" first."
               error $errMsg
            }
         }
      }
   }\
   elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
      report "conflict\t$args"
   }

   return {}
}

proc prereq {args} {
   set mode [currentMode]
   set currentModule [currentModuleName]

   reportDebug "prereq: ($args) mode = $mode"

   if {$mode eq "load"} {
      if {![eval is-loaded $args]} {
         set errMsg "WARNING: $currentModule cannot be loaded due to\
             missing prereq."
         # adapt error message when multiple modules are specified
         if {[llength $args] > 1} {
            set errMsg "$errMsg\nHINT: at least one of the following\
               modules must be loaded first: $args"
         } else {
            set errMsg "$errMsg\nHINT: the following module must be\
               loaded first: $args"
         }
         error $errMsg
      }
   }\
   elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
      report "prereq\t\t$args"
   }

   return {}
}

proc x-resource {resource {value {}}} {
   global g_newXResources g_delXResources env

   set mode [currentMode]

   reportDebug "x-resource: ($resource, $value)"

   # sometimes x-resource value may be provided within resource name
   # as the "x-resource {Ileaf.popup.saveUnder: True}" example provided
   # in manpage. so here is an attempt to extract real resource name and
   # value from resource argument
   if {[string length $value] == 0 && ![file exists $resource]} {
      # look first for a space character as delimiter, then for a colon
      set sepapos [string first " " $resource]
      if { $sepapos == -1 } {
         set sepapos [string first ":" $resource]
      }

      if { $sepapos > -1 } {
         set value [string range $resource [expr {$sepapos + 1}] end]
         set resource [string range $resource 0 [expr {$sepapos - 1}]]
         reportDebug "x-resource: corrected ($resource, $value)"
      } else {
         # if not a file and no value provided x-resource cannot be
         # recorded as it will produce an error when passed to xrdb
         reportWarning "x-resource $resource is not a valid string or file"
         return {}
      }
   }

   # check current environment can handle X11 resource edition elsewhere exit
   if {($mode eq "load" || $mode eq "unload") &&\
      [catch {runCommand xrdb -query} errMsg]} {
      error "WARNING: X11 resources cannot be edited, issue spotted\n$errMsg"
   }

   # if a resource does hold an empty value in g_newXResources or
   # g_delXResources arrays, it means this is a resource file to parse
   if {$mode eq "load"} {
      set g_newXResources($resource) $value
   }\
   elseif {$mode eq "unload"} {
      set g_delXResources($resource) $value
   }\
   elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
      report "x-resource\t$resource\t$value"
   }

   return {}
}

proc uname {what} {
   global unameCache tcl_platform
   set result {}

   reportDebug "uname: called: $what"

   if {! [info exists unameCache($what)]} {
      switch -- $what {
         {sysname} {
            set result $tcl_platform(os)
         }
         {machine} {
            set result $tcl_platform(machine)
         }
         {nodename} - {node} {
            set result [runCommand uname -n]
         }
         {release} {
            set result $tcl_platform(osVersion)
         }
         {domain} {
            set result [runCommand domainname]
         }
         {version} {
            set result [runCommand uname -v]
         }
         default {
            error "uname $what not supported"
         }
      }
      set unameCache($what) $result
   }

   return $unameCache($what)
}

proc system {mycmd args} {
   reportDebug "system: $mycmd $args"

   set mode [currentMode]
   set status {}

   if {$mode eq "load" || $mode eq "unload"} {
      if {[catch {exec >&@stderr $mycmd $args}]} {
          # non-zero exit status, get it:
          set status [lindex $::errorCode 2]
      } else {
          # exit status was 0
          set status 0
      }
   } elseif {$mode eq "display" && !$::g_inhibit_dispreport} {
      if {[llength $args] == 0} {
         report "system\t\t$mycmd"
      } else {
         report "system\t\t$mycmd $args"
      }
   }

   return $status
}

# test at least one of the collections passed as argument exists
proc is-saved {args} {
   reportDebug "is-saved: $args"

   foreach coll $args {
      lassign [getCollectionFilename $coll] collfile colldesc
      if {[file exists $collfile]} {
         return 1
      }
   }
   # is something saved whatever it is?
   if {[llength $args] == 0 && [llength [findCollections]] > 0} {
      return 1
   } else {
      return 0
   }
}

# test at least one of the directories passed as argument is set in MODULEPATH
proc is-used {args} {
   reportDebug "is-used: $args"

   set modpathlist [getModulePathList]
   foreach path $args {
      # transform given path in an absolute path which should have been
      # registered in the MODULEPATH env var.
      set abspath [getAbsolutePath $path]
      if {[lsearch -exact $modpathlist $path] >= 0 ||\
         [lsearch -exact $modpathlist $abspath] >= 0} {
         return 1
      }
   }
   # is something used whatever it is?
   if {[llength $args] == 0 && [llength $modpathlist] > 0} {
      return 1
   } else {
      return 0
   }
}

# test at least one of the modulefiles passed as argument exists
proc is-avail {args} {
   reportDebug "is-avail: $args"
   set ret 0

   # disable error reporting to avoid modulefile errors
   # to pollute result. Only if not already inhibited
   set alreadyinhibit [isErrorReportInhibited]
   if {!$alreadyinhibit} {
      inhibitErrorReport
   }

   foreach mod $args {
      lassign [getPathToModule $mod] modfile modname
      if {$modfile ne ""} {
         set ret 1
         break
      }
   }

   # re-enable only is it was disabled from this procedure
   if {!$alreadyinhibit} {
      reenableErrorReport
   }
   return $ret
}

########################################################################
# internal module procedures
#
set g_modeStack {}

proc currentMode {} {
   global g_modeStack

   return [lindex $g_modeStack end]
}

proc pushMode {mode} {
   global g_modeStack

   lappend g_modeStack $mode
}

proc popMode {} {
   global g_modeStack

   set g_modeStack [lrange $g_modeStack 0 end-1]
}

set g_moduleNameStack {}

proc currentModuleName {} {
   global g_moduleNameStack

   return [lindex $g_moduleNameStack end]
}

proc pushModuleName {moduleName} {
   global g_moduleNameStack

   lappend g_moduleNameStack $moduleName
}

proc popModuleName {} {
   global g_moduleNameStack

   set g_moduleNameStack [lrange $g_moduleNameStack 0 end-1]
}

set g_moduleFileStack {}

proc pushModuleFile {modfile} {
   global g_moduleFileStack ModulesCurrentModulefile

   lappend g_moduleFileStack $modfile
   set ModulesCurrentModulefile $modfile
}

proc popModuleFile {} {
   global g_moduleFileStack ModulesCurrentModulefile

   set g_moduleFileStack [lrange $g_moduleFileStack 0 end-1]
   set ModulesCurrentModulefile [lindex $g_moduleFileStack end]
}

set g_specifiedNameStack {}

proc currentSpecifiedName {} {
   global g_specifiedNameStack

   return [lindex $g_specifiedNameStack end]
}

proc pushSpecifiedName {specifiedName} {
   global g_specifiedNameStack

   lappend g_specifiedNameStack $specifiedName
}

proc popSpecifiedName {} {
   global g_specifiedNameStack

   set g_specifiedNameStack [lrange $g_specifiedNameStack 0 end-1]
}

set g_commandNameStack {}

proc currentCommandName {} {
   global g_commandNameStack

   return [lindex $g_commandNameStack end]
}

proc pushCommandName {commandName} {
   global g_commandNameStack

   lappend g_commandNameStack $commandName
}

proc popCommandName {} {
   global g_commandNameStack

   set g_commandNameStack [lrange $g_commandNameStack 0 end-1]
}


# return list of loaded modules by parsing LOADEDMODULES env variable
proc getLoadedModuleList {} {
   global env g_def_separator

   if {[info exists env(LOADEDMODULES)]} {
      return [split $env(LOADEDMODULES) $g_def_separator]
   } else {
      return {}
   }
}

# return list of loaded module files by parsing _LMFILES_ env variable
proc getLoadedModuleFileList {} {
   global env g_def_separator

   if {[info exists env(_LMFILES_)]} {
      return [split $env(_LMFILES_) $g_def_separator]
   } else {
      return {}
   }
}

# return list of module paths by parsing MODULEPATH env variable
# behavior param enables to exit in error when no MODULEPATH env variable
# is set. by default an empty list is returned if no MODULEPATH set
# resolv_var param tells if environement variable references in path elements
# should be resolved or passed as-is in result list
proc getModulePathList {{behavior "returnempty"} {resolv_var 1}} {
   global env g_def_separator

   if {[info exists env(MODULEPATH)]} {
      set modpathlist [split $env(MODULEPATH) $g_def_separator]
      if {$resolv_var && [llength $modpathlist] > 0} {
         foreach modpath $modpathlist {
            lappend resmodpathlist [resolvStringWithEnv $modpath]
         }
         return $resmodpathlist
      } else {
         return $modpathlist
      }
   } elseif {$behavior eq "exiterronundef"} {
      reportErrorAndExit "No module path defined"
   } else {
      return {}
   }
}

# test if two modules share the same root name
proc isSameModuleRoot {mod1 mod2} {
   set mod1split [split $mod1 "/"]
   set mod2split [split $mod2 "/"]

   return [expr {[lindex $mod1split 0] eq [lindex $mod2split 0]}]
}

# test if one element in module name has a leading "dot" making this module
# a hidden module
proc isModuleHidden {mod} {
   foreach elt [split $mod "/"] {
      if {[string index $elt 0] eq "."} {
         return 1
      }
   }
   return 0
}

# check if module name is specified as a full pathname (not a name relative
# to a modulepath)
proc isModuleFullPath {mod} {
   if {[regexp {^(|\.|\.\.)/} $mod]} {
      return 1
   } else {
      return 0
   }
}

# check if a module corresponds to a virtual module (module name
# does not corresponds to end of the modulefile name)
proc isModuleVirtual {mod modfile} {
   if {[string first $mod $modfile end-[string length $mod]] == -1} {
      return 1
   } else {
      return 0
   }
}

# Return the full pathname and modulename to the module.  
# Resolve aliases and default versions if the module name is something like
# "name/version" or just "name" (find default version).
proc getPathToModule {mod {indir {}} {look_loaded "no"} {excdir {}}} {
   global g_loadedModules

   if {$mod eq ""} {
      return [list "" 0]
   }

   reportDebug "getPathToModule: finding '$mod' in '$indir' (excdir='')"

   # try first to look at loaded modules if enabled to find maching module
   # or to find a closest match (used when switching with single name arg)
   if {($look_loaded eq "match" && [set lm [getLoadedMatchingName\
      $mod]] ne "") || ($look_loaded eq "close" && [set lm\
      [getLoadedWithClosestName $mod]] ne "")} {
      set retlist [list $g_loadedModules($lm) $lm]
   # Check for $mod specified as a full pathname
   } elseif {[isModuleFullPath $mod]} {
      set mod [getAbsolutePath $mod]
      # note that a raw filename as an argument returns the full
      # path as the module name
      lassign [checkValidModule $mod] check_valid check_msg
      switch -- $check_valid {
         {true} {
            set retlist [list $mod $mod]
         }
         {invalid} - {accesserr} {
            set retlist [list "" $mod $check_valid $check_msg $mod]
         }
      }
   } else {
      if {$indir ne ""} {
         set dir_list $indir
      } else {
         set dir_list [getModulePathList "exiterronundef"]
      }
      # remove excluded directories (already searched)
      foreach dir $excdir {
         set dir_list [replaceFromList $dir_list $dir]
      }

      # modparent is the the modulename minus the module version.
      lassign [getModuleNameVersion $mod] mod modparent modversion
      set modroot [lindex [split $mod "/"] 0]
      # determine if we need to get hidden modules
      set fetch_hidden [isModuleHidden $mod]

      # Now search for $mod in module paths
      foreach dir $dir_list {
         # get list of modules related to the root of searched module to get
         # in one call a complete list of any module kind (file, alias, etc)
         # related to search to be able to then determine in this proc the
         # correct module to return without restarting new searches
         array unset mod_list
         array set mod_list [getModules $dir $modroot 0 "rc_defs_included"\
            $fetch_hidden]

         set prevmod ""
         set mod_res ""
         # loop to resolve correct modulefile in case specified mod is a
         # directory that should be analyzed to get default mod in it
         while {$prevmod ne $mod} {
            set prevmod $mod

            if {[info exists mod_list($mod)]} {
               switch -- [lindex $mod_list($mod) 0] {
                  {alias} - {version} {
                     set newmod [resolveModuleVersionOrAlias $mod]
                     # continue search on newmod if module from same root and
                     # not hidden (if hidden search disabled) as mod_list
                     # already contains everything related to this root module
                     if {[isSameModuleRoot $mod $newmod] && ($fetch_hidden ||\
                        ![isModuleHidden $newmod])} {
                        set mod $newmod
                        # indicate an alias or a symbol was solved
                        set mod_res $newmod
                     # elsewhere restart search on new modulename, constrained
                     # to specified dir if set
                     } else {
                        return [getPathToModule $newmod $indir]
                     }
                  }
                  {directory} {
                     # Move to default element in directory
                     set mod "$mod/[lindex $mod_list($mod) 1]"
                  }
                  {modulefile} {
                     # If mod was a file in this path, return that file
                     set retlist [list "$dir/$mod" $mod]
                  }
                  {virtual} {
                     # return virtual name with file it targets
                     set retlist [list [lindex $mod_list($mod) 2] $mod]
                  }
                  {invalid} - {accesserr} {
                     # may found mod but issue, so end search with error
                     set retlist [concat [list "" $mod] $mod_list($mod)]
                  }
               }
            }
         }
         # break loop if found something (valid or invalid module)
         # elsewhere go to next path
         if {[info exists retlist]} {
            break
         # found nothing after solving a matching alias or symbol
         } elseif {$mod_res eq $mod} {
            lappend excdir $dir
            # look for this name in the other module paths, so restart
            # directory search from first dir in list to ensure precedence
            return [getPathToModule $mod $indir "no" $excdir]
         }
      }
   }

   # set result if nothing found
   if {![info exists retlist]} {
      set retlist [list "" $mod "none" "Unable to locate a modulefile for\
         '$mod'"]
   }
   if {[lindex $retlist 0] ne ""} {
      reportDebug "getPathToModule: found '[lindex $retlist 0]' as\
         '[lindex $retlist 1]'"
   } else {
      eval reportIssue [lrange $retlist 2 4]
   }
   return $retlist
}

# return the currently loaded module whose name is the closest to the
# name passed as argument. if no loaded module match at least one part
# of the passed name, an empty string is returned.
proc getLoadedWithClosestName {name} {
   set ret ""
   set retmax 0

   if {[isModuleFullPath $name]} {
      set fullname [getAbsolutePath $name]
      # if module is passed as full modulefile path name, get corresponding
      # short name from used modulepaths
      if {[set shortname [findModuleNameFromModulefile $fullname]] ne ""} {
         set namesplit [split $shortname "/"]
      # or look at lmfile names to return the eventual exact match
      } else {
         global g_loadedModules g_loadedModuleFiles
         # module may be loaded with its full path name
         if {[info exists g_loadedModules($fullname)]} {
            set ret $fullname
         # or name corresponds to the _lmfiles_ entry of a virtual modules in
         # which case lastly loaded virtual module is returned
         } elseif {[info exists g_loadedModuleFiles($fullname)]} {
            set ret [lindex $g_loadedModuleFiles($fullname) end]
         }
      }
   } else {
      set namesplit [split $name "/"]
   }

   if {[info exists namesplit]} {
      # compare name to each currently loaded module name
      foreach mod [getLoadedModuleList] {
         # if module loaded as fullpath but test name not, try to get loaded
         # mod short name (with currently used modulepaths) to compare it
         if {[isModuleFullPath $mod] && [set modname\
            [findModuleNameFromModulefile $mod]] ne ""} {
            set modsplit [split $modname "/"]
         } else {
            set modsplit [split $mod "/"]
         }

         # min expr function is not supported in Tcl8.4 and earlier
         if {[llength $namesplit] < [llength $modsplit]} {
            set imax [llength $namesplit]
         } else {
            set imax [llength $modsplit]
         }

         # compare each element of the name to find closest answer
         # in case of equality, last loaded module will be returned as it
         # overwrites previously found value
         for {set i 0} {$i < $imax} {incr i} {
            if {[lindex $modsplit $i] eq [lindex $namesplit $i]} {
               if {$i >= $retmax} {
                  set retmax $i
                  set ret $mod
               }
            } else {
               # end of match, go next mod
               break
            }
         }
      }
   }

   reportDebug "getLoadedWithClosestName: '$ret' closest to '$name'"

   return $ret
}

# return the currently loaded module whose name is equal or include the name
# passed as argument. if no loaded module match, an empty string is returned.
proc getLoadedMatchingName {name {behavior "returnlast"}} {
   set ret {}
   set retmax 0

   # if module is passed as full modulefile path name, look at lmfile names
   # to return the eventual exact match
   if {[isModuleFullPath $name]} {
      global g_loadedModuleFiles
      set mod [getAbsolutePath $name]
      # if module is loaded with its full path name loadedmodules entry is
      # equivalent to _lmfiles_ corresponding entry so only check _lmfiles_
      if {[info exists g_loadedModuleFiles($mod)]} {
         # a loaded modfile may correspond to multiple loaded virtual modules
         switch -- $behavior {
            {returnlast} {
               # the last loaded module will be returned
               set ret [lindex $g_loadedModuleFiles($mod) end]
            }
            {returnfirst} {
               # the first loaded module will be returned
               set ret [lindex $g_loadedModuleFiles($mod) 0]
            }
            {returnall} {
               # all loaded modules will be returned
               set ret $g_loadedModuleFiles($mod)
            }
         }
      }
   } else {
      # compare name to each currently loaded module name, if multiple mod
      # match name:
      foreach mod [getLoadedModuleList] {
         # if module loaded as fullpath but test name not, try to get loaded
         # mod short name (with currently used modulepaths) to compare it
         if {[isModuleFullPath $mod] && [set modname\
            [findModuleNameFromModulefile $mod]] ne ""} {
            set matchmod "$modname/"
         } else {
            set matchmod $mod
         }
         if {[string first "$name/" "$matchmod/"] == 0} {
            switch -- $behavior {
               {returnlast} {
                  # the last loaded module will be returned
                  set ret $mod
               }
               {returnfirst} {
                  # the first loaded module will be returned
                  set ret $mod
                  break
               }
               {returnall} {
                  # all loaded modules will be returned
                  lappend ret $mod
               }
            }
         }
      }
   }

   reportDebug "getLoadedMatchingName: '$ret' matches '$name'"

   return $ret
}

proc runModulerc {} {
   # Runs the global RC files if they exist
   global env
   global g_moduleAlias g_rcAlias g_moduleVersion g_rcVersion
   global g_moduleVirtual g_rcVirtual
   set rclist {}

   reportDebug "runModulerc: running..."

   if {[info exists env(MODULERCFILE)]} {
      # if MODULERCFILE is a dir, look at a modulerc file in it
      if {[file isdirectory $env(MODULERCFILE)]\
         && [file isfile "$env(MODULERCFILE)/modulerc"]} {
         lappend rclist "$env(MODULERCFILE)/modulerc"
      } elseif {[file isfile $env(MODULERCFILE)]} {
         lappend rclist $env(MODULERCFILE)
      }
   }
   if {[file isfile "@prefix@/etc/rc"]} {
      lappend rclist "@prefix@/etc/rc"
   }
   if {[info exists env(HOME)] && [file isfile "$env(HOME)/.modulerc"]} {
      lappend rclist "$env(HOME)/.modulerc"
   }

   foreach rc $rclist {
      if {[file readable $rc]} {
         reportDebug "runModulerc: Executing $rc"
         cmdModuleSource "$rc"
      }
   }

   # identify alias or symbolic version set in these global RC files to be
   # able to include them or not in output or resolution processes
   array set g_rcAlias [array get g_moduleAlias]
   array set g_rcVersion [array get g_moduleVersion]
   array set g_rcVirtual [array get g_moduleVirtual]
}

# manage settings to save as a stack to have a separate set of settings
# for each module loaded or unloaded in order to be able to restore the
# correct set in case of failure
proc pushSettings {} {
   foreach var {env g_Aliases g_stateEnvVars g_stateAliases g_newXResource\
      g_delXResource} {
      eval "global g_SAVE_$var $var"
      eval "lappend g_SAVE_$var \[array get $var\]"
   }
}

proc popSettings {} {
   foreach var {env g_Aliases g_stateEnvVars g_stateAliases g_newXResource\
      g_delXResource} {
      eval "global g_SAVE_$var"
      eval "set g_SAVE_$var \[lrange \$g_SAVE_$var 0 end-1\]"
   }
}

proc restoreSettings {} {
   foreach var {env g_Aliases g_stateEnvVars g_stateAliases g_newXResource\
      g_delXResource} {
      eval "global g_SAVE_$var $var"
      # clear current $var arrays
      if {[info exists $var]} {
         eval "unset $var; array set $var {}"
      }
      eval "array set $var \[lindex \$g_SAVE_$var end\]"
   }
}

proc renderSettings {} {
   global env g_Aliases g_shellType g_shell
   global g_stateEnvVars g_stateAliases
   global g_newXResources g_delXResources
   global g_changeDir g_stdoutPuts error_count g_return_false g_return_text
   global g_autoInit CSH_LIMIT

   reportDebug "renderSettings: called."

   # required to work on cygwin, shouldn't hurt real linux
   fconfigure stdout -translation lf

   # preliminaries if there is stuff to render
   if {$g_autoInit || [array size g_stateEnvVars] > 0 ||\
      [array size g_stateAliases] > 0 || [array size g_newXResources] > 0 ||\
      [array size g_delXResources] > 0 || [info exists g_changeDir] ||\
      [info exists g_stdoutPuts] || [info exists g_return_text]} {
      switch -- $g_shellType {
         {python} {
            puts stdout "import os"
         }
      }
      set has_rendered 1
   } else {
      set has_rendered 0
   }

   if {$g_autoInit} {
      renderAutoinit
   }

   # new environment variables
   foreach var [array names g_stateEnvVars] {
      if {$g_stateEnvVars($var) eq "new"} {
         switch -- $g_shellType {
            {csh} {
               set val [charEscaped $env($var)]
               # csh barfs on long env vars
               if {$g_shell eq "csh" && [string length $val] >\
                  $CSH_LIMIT} {
                  if {$var eq "PATH"} {
                     reportWarning "PATH exceeds $CSH_LIMIT characters,\
                        truncating and appending /usr/bin:/bin ..."
                     set val [string range $val 0 [expr {$CSH_LIMIT\
                        - 1}]]:/usr/bin:/bin
                  } else {
                      reportWarning "$var exceeds $CSH_LIMIT characters,\
                         truncating..."
                      set val [string range $val 0 [expr {$CSH_LIMIT\
                         - 1}]]
                  }
               }
               puts stdout "setenv $var $val;"
            }
            {sh} {
               puts stdout "$var=[charEscaped $env($var)];\
                  export $var;"
            }
            {fish} {
               set val [charEscaped $env($var)]
               # fish shell has special treatment for PATH variable
               # so its value should be provided as a list separated
               # by spaces not by semi-colons
               if {$var eq "PATH"} {
                  regsub -all ":" $val " " val
               }
               puts stdout "set -xg $var $val;"
            }
            {tcl} {
               set val $env($var)
               puts stdout "set env($var) {$val};"
            }
            {perl} {
               set val [charEscaped $env($var) \']
               puts stdout "\$ENV{'$var'} = '$val';"
            }
            {python} {
               set val [charEscaped $env($var) \']
               puts stdout "os.environ\['$var'\] = '$val'"
            }
            {ruby} {
               set val [charEscaped $env($var) \']
               puts stdout "ENV\['$var'\] = '$val'"
            }
            {lisp} {
               set val [charEscaped $env($var) \"]
               puts stdout "(setenv \"$var\" \"$val\")"
            }
            {cmake} {
               set val [charEscaped $env($var) \"]
               puts stdout "set(ENV{$var} \"$val\")"
            }
            {r} {
               set val [charEscaped $env($var) \']
               puts stdout "Sys.setenv('$var'='$val')"
            }
            {cmd} {
               set val $env($var)
               puts stdout "set $var=$val"
            }
         }
      } elseif {$g_stateEnvVars($var) eq "del"} {
         switch -- $g_shellType {
            {csh} {
               puts stdout "unsetenv $var;"
            }
            {sh} {
               puts stdout "unset $var;"
            }
            {fish} {
               puts stdout "set -e $var;"
            }
            {tcl} {
               puts stdout "catch {unset env($var)};"
            }
            {cmd} {
               puts stdout "set $var="
            }
            {perl} {
               puts stdout "delete \$ENV{'$var'};"
            }
            {python} {
               puts stdout "os.environ\['$var'\] = ''"
               puts stdout "del os.environ\['$var'\]"
            }
            {ruby} {
               puts stdout "ENV\['$var'\] = nil"
            }
            {lisp} {
               puts stdout "(setenv \"$var\" nil)"
            }
            {cmake} {
               puts stdout "unset(ENV{$var})"
            }
            {r} {
               puts stdout "Sys.unsetenv('$var')"
            }
         }
      }
   }

   foreach var [array names g_stateAliases] {
      if {$g_stateAliases($var) eq "new"} {
         switch -- $g_shellType {
            {csh} {
               # set val [charEscaped $g_Aliases($var)]
               set val $g_Aliases($var)
               # Convert $n -> \!\!:n
               regsub -all {\$([0-9]+)} $val {\\!\\!:\1} val
               # Convert $* -> \!*
               regsub -all {\$\*} $val {\\!*} val
               puts stdout "alias $var '$val';"
            }
            {sh} {
               set val $g_Aliases($var)
               puts stdout "alias $var='$val';"
            }
            {fish} {
               set val $g_Aliases($var)
               puts stdout "alias $var '$val';"
            }
         }
      } elseif {$g_stateAliases($var) eq "del"} {
         switch -- $g_shellType {
            {csh} {
               puts stdout "unalias $var;"
            }
            {sh} {
               puts stdout "unalias $var;"
            }
            {fish} {
               puts stdout "functions -e $var;"
            }
         }
      }
   }

   # preliminaries for x-resources stuff
   if {[array size g_newXResources] > 0 || [array size g_delXResources] > 0} {
      switch -- $g_shellType {
         {python} {
            puts stdout "import subprocess"
         }
         {ruby} {
            puts stdout "require 'open3'"
         }
      }
   }

   # new x resources
   if {[array size g_newXResources] > 0} {
      # xrdb executable has already be verified in x-resource
      set xrdb [getCommandPath "xrdb"]
      foreach var [array names g_newXResources] {
         set val $g_newXResources($var)
         # empty val means that var is a file to parse
         if {$val eq ""} {
            switch -- $g_shellType {
               {sh} - {csh} - {fish} {
                  puts stdout "$xrdb -merge $var;"
               }
               {tcl} {
                  puts stdout "exec $xrdb -merge $var;"
               }
               {perl} {
                  puts stdout "system(\"$xrdb -merge $var\");"
               }
               {python} {
                  set var [charEscaped $var \']
                  puts stdout "subprocess.Popen(\['$xrdb',\
                     '-merge', '$var'\])"
               }
               {ruby} {
                  set var [charEscaped $var \']
                  puts stdout "Open3.popen2('$xrdb -merge $var')"
               }
               {lisp} {
                  puts stdout "(shell-command-to-string \"$xrdb\
                     -merge $var\")"
               }
               {cmake} {
                  puts stdout "execute_process(COMMAND $xrdb -merge $var)"
               }
               {r} {
                  set var [charEscaped $var \']
                  puts stdout "system('$xrdb -merge $var')"
               }
            }
         } else {
            switch -- $g_shellType {
               {sh} - {csh} - {fish} {
                  set var [charEscaped $var \"]
                  set val [charEscaped $val \"]
                  puts stdout "echo \"$var: $val\" | $xrdb -merge;"
               }
               {tcl} {
                  puts stdout "set XRDBPIPE \[open \"|$xrdb -merge\" r+\];"
                  set var [charEscaped $var \"]
                  set val [charEscaped $val \"]
                  puts stdout "puts \$XRDBPIPE \"$var: $val\";"
                  puts stdout "close \$XRDBPIPE;"
                  puts stdout "unset XRDBPIPE;"
               }
               {perl} {
                  puts stdout "open(XRDBPIPE, \"|$xrdb -merge\");"
                  set var [charEscaped $var \"]
                  set val [charEscaped $val \"]
                  puts stdout "print XRDBPIPE \"$var: $val\\n\";"
                  puts stdout "close XRDBPIPE;"
               }
               {python} {
                  set var [charEscaped $var \']
                  set val [charEscaped $val \']
                  puts stdout "subprocess.Popen(\['$xrdb', '-merge'\],\
                     stdin=subprocess.PIPE).communicate(input='$var:\
                     $val\\n')"
               }
               {ruby} {
                  set var [charEscaped $var \']
                  set val [charEscaped $val \']
                  puts stdout "Open3.popen2('$xrdb -merge') {|i,o,t| i.puts\
                     '$var: $val'}"
               }
               {lisp} {
                  puts stdout "(shell-command-to-string \"echo $var:\
                     $val | $xrdb -merge\")"
               }
               {cmake} {
                  set var [charEscaped $var \"]
                  set val [charEscaped $val \"]
                  puts stdout "execute_process(COMMAND echo \"$var: $val\"\
                     COMMAND $xrdb -merge)"
               }
               {r} {
                  set var [charEscaped $var \']
                  set val [charEscaped $val \']
                  puts stdout "system('$xrdb -merge', input='$var: $val')"
               }
            }
         }
      }
   }

   if {[array size g_delXResources] > 0} {
      set xrdb [getCommandPath "xrdb"]
      set xres_to_del {}
      foreach var [array names g_delXResources] {
         # empty val means that var is a file to parse
         if {$g_delXResources($var) eq ""} {
            # xresource file has to be parsed to find what resources
            # are declared there and need to be unset
            foreach fline [split [exec $xrdb -n load $var] "\n"] {
               lappend xres_to_del [lindex [split $fline ":"] 0]
            }
         } else {
            lappend xres_to_del $var
         }
      }

      # xresource strings are unset by emptying their value since there
      # is no command of xrdb that can properly remove one property
      switch -- $g_shellType {
         {sh} - {csh} - {fish} {
            foreach var $xres_to_del {
               puts stdout "echo \"$var:\" | $xrdb -merge;"
            }
         }
         {tcl} {
            foreach var $xres_to_del {
               puts stdout "set XRDBPIPE \[open \"|$xrdb -merge\" r+\];"
               set var [charEscaped $var \"]
               puts stdout "puts \$XRDBPIPE \"$var:\";"
               puts stdout "close \$XRDBPIPE;"
               puts stdout "unset XRDBPIPE;"
            }
         }
         {perl} {
            foreach var $xres_to_del {
               puts stdout "open(XRDBPIPE, \"|$xrdb -merge\");"
               set var [charEscaped $var \"]
               puts stdout "print XRDBPIPE \"$var:\\n\";"
               puts stdout "close XRDBPIPE;"
            }
         }
         {python} {
            foreach var $xres_to_del {
               set var [charEscaped $var \']
               puts stdout "subprocess.Popen(\['$xrdb', '-merge'\],\
                  stdin=subprocess.PIPE).communicate(input='$var:\\n')"
            }
         }
         {ruby} {
            foreach var $xres_to_del {
               set var [charEscaped $var \']
               puts stdout "Open3.popen2('$xrdb -merge') {|i,o,t| i.puts\
                  '$var:'}"
            }
         }
         {lisp} {
            foreach var $xres_to_del {
               puts stdout "(shell-command-to-string \"echo $var: |\
                  $xrdb -merge\")"
            }
         }
         {cmake} {
            foreach var $xres_to_del {
               set var [charEscaped $var \"]
               puts stdout "execute_process(COMMAND echo \"$var:\"\
                  COMMAND $xrdb -merge)"
            }
         }
         {r} {
            foreach var $xres_to_del {
               set var [charEscaped $var \']
               puts stdout "system('$xrdb -merge', input='$var:')"
            }
         }
      }
   }

   if {[info exists g_changeDir]} {
      switch -- $g_shellType {
         {sh} - {csh} - {fish} {
            puts stdout "cd '$g_changeDir';"
         }
         {tcl} {
            puts stdout "cd \"$g_changeDir\";"
         }
         {perl} {
            puts stdout "chdir '$g_changeDir';"
         }
         {python} {
            puts stdout "os.chdir('$g_changeDir')"
         }
         {ruby} {
            puts stdout "Dir.chdir('$g_changeDir')"
         }
         {lisp} {
            puts stdout "(shell-command-to-string \"cd '$g_changeDir'\")"
         }
         {r} {
            puts stdout "setwd('$g_changeDir')"
         }
      }
      # cannot change current directory of cmake "shell"
   }

   # send content deferred during modulefile interpretation
   if {[info exists g_stdoutPuts]} {
      foreach putsArgs $g_stdoutPuts {
         eval puts $putsArgs
         # check if a finishing newline will be needed after content sent
         if {[lindex $putsArgs 0] eq "-nonewline"} {
            set needPutsNl 1
         } else {
            set needPutsNl 0
         }
      }
      if {$needPutsNl} {
         puts stdout ""
      }
   }

   # return text value if defined even if error happened
   if {[info exists g_return_text]} {
      reportDebug "renderSettings: text value should be returned."
      renderText $g_return_text
   } elseif {$error_count > 0} {
      reportDebug "renderSettings: $error_count error(s) detected."
      renderFalse
   } elseif {$g_return_false} {
      reportDebug "renderSettings: false value should be returned."
      renderFalse
   } elseif {$has_rendered} {
      # finish with true statement if something has been put
      renderTrue
   }
}

proc renderAutoinit {} {
   global argv0 g_shellType g_shell

   reportDebug "renderAutoinit: called."

   # automatically detect which tclsh should be used for
   # future module commands
   set tclshbin [info nameofexecutable]

   # ensure script path is absolute
   set argv0 [getAbsolutePath $argv0]

   switch -- $g_shellType {
      {csh} {
         set pre_hi {set _histchars = $histchars; unset histchars;}
         set post_hi {set histchars = $_histchars; unset _histchars;}
         set pre_pr {set _prompt="$prompt"; set prompt="";}
         set post_pr {set prompt="$_prompt"; unset _prompt;}
         set eval_cmd "eval `$tclshbin $argv0 $g_shell \\!*`;"
         set pre_ex {set _exit="$status";}
         set post_ex {test 0 = $_exit}

         set fdef "if ( \$?histchars && \$?prompt )\
alias module '$pre_hi $pre_pr $eval_cmd $pre_ex $post_hi $post_pr $post_ex' ;
if ( \$?histchars && ! \$?prompt )\
alias module '$pre_hi $eval_cmd $pre_ex $post_hi $post_ex' ;
if ( ! \$?histchars && \$?prompt )\
alias module '$pre_pr $eval_cmd $pre_ex $post_pr $post_ex' ;
if ( ! \$?histchars && ! \$?prompt ) alias module '$eval_cmd' ;"
      }
      {sh} {
         # adapt shell function to define local variable, as 'typeset' is
         # not known by dash and 'local' is not known by ksh
         if {$g_shell eq "sh"} {
            set locf "local"
         } else {
            set locf "typeset"
         }
         # on zsh, word splitting should be enabled explicitly
         if {$g_shell eq "zsh"} {
            set wsplit "="
         } else {
            set wsplit ""
         }
         # only redirect module from stderr to stdout when session is
         # attached to a terminal to avoid breaking non-terminal session
         # (scp, sftp, etc)
         if {[isStderrTty]} {
            set fname "_moduleraw"
         } else {
            set fname "module"
         }
         # build quarantine mechanism in module function
         # an empty runtime variable is set even if no corresponding
         # MODULES_RUNENV_* variable found, as var cannot be unset on
         # modified environment command-line
         set fdef "${fname}() {
   if \[ \"\$MODULES_SILENT_SHELL_DEBUG\" = '1' \]; then
      case \"$-\" in
         *v*x*) set +vx; $locf _mlshdbg='vx' ;;
         *v*) set +v; $locf _mlshdbg='v' ;;
         *x*) set +x; $locf _mlshdbg='x' ;;
         *) $locf _mlshdbg='' ;;
      esac;
   fi;
   $locf _mlre=''; $locf _mlv; $locf _mlrv;
   if \[ -n \"\${IFS+x}\" \]; then
      $locf _mlIFS=\$IFS;
   fi;
   IFS=' ';
   for _mlv in \${${wsplit}MODULES_RUN_QUARANTINE}; do"
         append fdef {
      if [ "${_mlv}" = "${_mlv##*[!A-Za-z0-9_]}" -a "${_mlv}" = "${_mlv#[0-9]}" ]; then
         if [ -n "`eval 'echo ${'$_mlv'+x}'`" ]; then
            _mlre="${_mlre}${_mlv}_modquar='`eval 'echo ${'$_mlv'}'`' ";
         fi;
         _mlrv="MODULES_RUNENV_${_mlv}";
         _mlre="${_mlre}${_mlv}='`eval 'echo ${'$_mlrv'}'`' ";
      fi;
   done;
   if [ -n "$_mlre" ]; then
      _mlre="eval ${_mlre}";
   fi;}
         append fdef "\n   eval `\${${wsplit}_mlre}$tclshbin $argv0\
$g_shell \$*`;
   $locf _mlstatus=\$?;\n"
         append fdef {   if [ -n "${_mlIFS+x}" ]; then
      IFS=$_mlIFS;
   else
      unset IFS;
   fi;
   if [ -n "$_mlshdbg" ]; then
      set -$_mlshdbg;
      unset _mlshdbg;
   fi;
   return $_mlstatus;}
         append fdef "\n};"
         if {[isStderrTty]} {
            append fdef "\nmodule() { _moduleraw \$* 2>&1; };"
         }
      }
      {fish} {
         if {[isStderrTty]} {
            set fdef "function _moduleraw\n"
         } else {
            set fdef "function module\n"
         }
         append fdef {   set -l _mlre ''; set -l _mlv; set -l _mlrv;
   for _mlv in (string split ' ' $MODULES_RUN_QUARANTINE)
      if string match -r '^[A-Za-z_][A-Za-z0-9_]*$' $_mlv >/dev/null
         if set -q $_mlv
            set _mlre $_mlre$_mlv"_modquar='$$_mlv' "
         end
         set _mlrv "MODULES_RUNENV_$_mlv"
         set _mlre "$_mlre$_mlv='$$_mlrv' "
      end
   end
   if [ -n "$_mlre" ]
      set _mlre "env $_mlre"
   end}
         # use "| source -" rather than "eval" to be able
         # to redirect stderr after stdout being evaluated
         append fdef "\n   eval \$_mlre $tclshbin $argv0 $g_shell \$argv\
            | source -\n"
         if {[isStderrTty]} {
            append fdef {end
function module
   _moduleraw $argv ^&1
end}
         } else {
            append fdef {end}
         }
      }
      {tcl} {
         set fdef "proc module {args} {\n"
         append fdef {   global env; set _mlre {};
   if {[info exists env(MODULES_RUN_QUARANTINE)]} {
      foreach _mlv [split $env(MODULES_RUN_QUARANTINE) " "] {
         if {[regexp {^[A-Za-z_][A-Za-z0-9_]*$} $_mlv]} {
            if {[info exists env($_mlv)]} {
               lappend _mlre "${_mlv}_modquar=$env($_mlv)"
            }
            set _mlrv "MODULES_RUNENV_${_mlv}"
            if {[info exists env($_mlrv)]} {
               lappend _mlre "${_mlv}=$env($_mlrv)"
            } else {
               lappend _mlre "${_mlv}="
            }
         }
      }
      if {[llength $_mlre] > 0} {
         set _mlre [linsert $_mlre 0 "env"]
      }
   }
   set _mlstatus 1;}
         append fdef "\n   catch {eval exec \$_mlre \"$tclshbin\"\
            \"$argv0\" \"$g_shell\" \$args 2>@stderr} script\n"
         append fdef {   eval $script;
   return $_mlstatus}
         append fdef "\n}"
      }
      {cmd} {
         set fdef "start /b \%MODULESHOME\%/init/module.cmd %*"
      }
      {perl} {
         set fdef "sub module {\n"
         append fdef {   my $_mlre = '';
   if (defined $ENV{'MODULES_RUN_QUARANTINE'}) {
      foreach my $_mlv (split(' ', $ENV{'MODULES_RUN_QUARANTINE'})) {
         if ($_mlv =~ /^[A-Za-z_][A-Za-z0-9_]*$/) {
            if (defined $ENV{$_mlv}) {
               $_mlre .= "${_mlv}_modquar='$ENV{$_mlv}' ";
            }
            my $_mlrv = "MODULES_RUNENV_$_mlv";
            $_mlre .= "$_mlv='$ENV{$_mlrv}' ";
        }
      }
      if ($_mlre ne "") {
         $_mlre = "env $_mlre";
      }
   }
   my $_mlstatus = 1;}
         append fdef "\n   eval `\${_mlre}$tclshbin $argv0 perl @_`;\n"
         append fdef {   return $_mlstatus;}
         append fdef "\n}"
      }
      {python} {
         set fdef {import re, subprocess
def module(command, *arguments):
   _mlre = os.environ.copy()
   if 'MODULES_RUN_QUARANTINE' in os.environ:
      for _mlv in os.environ['MODULES_RUN_QUARANTINE'].split():
         if re.match('^[A-Za-z_][A-Za-z0-9_]*$', _mlv):
            if _mlv in os.environ:
               _mlre[_mlv + '_modquar'] = os.environ[_mlv]
            _mlrv = 'MODULES_RUNENV_' + _mlv
            if _mlrv in os.environ:
               _mlre[_mlv] = os.environ[_mlrv]
            else:
               _mlre[_mlv] = ''
   _mlstatus = True}
         append fdef "\n   exec(subprocess.Popen(\['$tclshbin',\
            '$argv0', 'python', command\] +\
            list(arguments),\
            stdout=subprocess.PIPE, env=_mlre).communicate()\[0\])\n"
         append fdef {   return _mlstatus}
      }
      {ruby} {
         set fdef {class ENVModule
   def ENVModule.module(*args)
      _mlre = ''
      if ENV.has_key?('MODULES_RUN_QUARANTINE') then
         ENV['MODULES_RUN_QUARANTINE'].split(' ').each do |_mlv|
            if _mlv =~ /^[A-Za-z_][A-Za-z0-9_]*$/ then
               if ENV.has_key?(_mlv) then
                  _mlre << _mlv + "_modquar='" + ENV[_mlv].to_s + "' "
               end
               _mlrv = 'MODULES_RUNENV_' + _mlv
               _mlre << _mlv + "='" + ENV[_mlrv].to_s + "' "
            end
         end
         unless _mlre.empty?
            _mlre = 'env ' + _mlre
         end
      end
      if args[0].kind_of?(Array) then
         args = args[0].join(' ')
      else
         args = args.join(' ')
      end
      _mlstatus = true}
         append fdef "\n      eval `#{_mlre}$tclshbin $argv0 ruby #{args}`\n"
         append fdef {      return _mlstatus
   end
end}
      }
      {lisp} {
         reportErrorAndExit "lisp mode autoinit not yet implemented"
      }
      {cmake} {
         set fdef {function(module)
   set(_mlre "")
   if(DEFINED ENV{MODULES_RUN_QUARANTINE})
      string(REPLACE " " ";" _mlv_list "$ENV{MODULES_RUN_QUARANTINE}")
      foreach(_mlv ${_mlv_list})
         if(${_mlv} MATCHES "^[A-Za-z_][A-Za-z0-9_]*$")
            if(DEFINED ENV{${_mlv}})
               set(_mlre "${_mlre}${_mlv}_modquar=$ENV{${_mlv}};")
            endif()
            set(_mlrv "MODULES_RUNENV_${_mlv}")
            set(_mlre "${_mlre}${_mlv}=$ENV{${_mlrv}};")
        endif()
      endforeach()
      if (NOT "${_mlre}" STREQUAL "")
         set(_mlre "env;${_mlre}")
      endif()
   endif()
   set(_mlstatus TRUE)
   execute_process(COMMAND mktemp -t moduleinit.cmake.XXXXXXXXXXXX
      OUTPUT_VARIABLE tempfile_name
      OUTPUT_STRIP_TRAILING_WHITESPACE)}
            append fdef "\n   execute_process(COMMAND \${_mlre} $tclshbin\
               $argv0 cmake \${ARGV}\n"
            append fdef {      OUTPUT_FILE ${tempfile_name})
   if(EXISTS ${tempfile_name})
      include(${tempfile_name})
      file(REMOVE ${tempfile_name})
   endif()
   set(module_result ${_mlstatus} PARENT_SCOPE)
endfunction(module)}
      }
      {r} {
         set fdef "module <- function(...){\n"
         append fdef {   mlre <- ''
   if (!is.na(Sys.getenv('MODULES_RUN_QUARANTINE', unset=NA))) {
      for (mlv in strsplit(Sys.getenv('MODULES_RUN_QUARANTINE'), ' ')[[1]]) {
         if (grepl('^[A-Za-z_][A-Za-z0-9_]*$', mlv)) {
            if (!is.na(Sys.getenv(mlv, unset=NA))) {
               mlre <- paste0(mlre, mlv, "_modquar='", Sys.getenv(mlv), "' ")
            }
            mlrv <- paste0('MODULES_RUNENV_', mlv)
            mlre <- paste0(mlre, mlv, "='", Sys.getenv(mlrv), "' ")
         }
      }
      if (mlre != '') {
         mlre <- paste0('env ', mlre)
      }
   }
   arglist <- as.list(match.call())
   arglist[1] <- 'r'
   args <- paste0(arglist, collapse=' ')}
         append fdef "\n   cmd <- paste(mlre, '$tclshbin', '$argv0', args,\
            sep=' ')\n"
         append fdef {   mlstatus <- TRUE
   hndl <- pipe(cmd)
   eval(expr = parse(file=hndl))
   close(hndl)
   invisible(mlstatus)}
         append fdef "\n}"

      }
   }

   # output function definition
   puts stdout $fdef
}

proc cacheCurrentModules {} {
   global g_loadedModules g_loadedModuleFiles

   reportDebug "cacheCurrentModules"

   # mark specific as well as generic modules as loaded
   set i 0
   set filelist [getLoadedModuleFileList]
   foreach mod [getLoadedModuleList] {
      set g_loadedModules($mod) [lindex $filelist $i]
      # a loaded modfile may correspond to multiple loaded virtual modules
      lappend g_loadedModuleFiles([lindex $filelist $i]) $mod
      incr i
   }
}

# This proc resolves module aliases or version aliases to the real module name
# and version.
proc resolveModuleVersionOrAlias {name} {
   global g_moduleResolved

   if {[info exists g_moduleResolved($name)]} {
      set ret $g_moduleResolved($name)
   } else {
      set ret $name
   }

   reportDebug "resolveModuleVersionOrAlias: '$name' resolved to '$ret'"

   return $ret
}

proc charEscaped {str {charlist { \\\t\{\}|<>!;#^$&*"'`()}}} {
   return [regsub -all "\(\[$charlist\]\)" $str {\\\1}]
}

proc charUnescaped {str {charlist { \\\t\{\}|<>!;#^$&*"'`()}}} {
   return [regsub -all "\\\\\(\[$charlist\]\)" $str {\1}]
}

# find command path and remember it
proc getCommandPath {cmd} {
   return [lindex [auto_execok $cmd] 0]
}

# find then run command or raise error if command not found
proc runCommand {cmd args} {
   set cmdpath [getCommandPath $cmd]
   if {$cmdpath eq ""} {
      error "WARNING: Command '$cmd' cannot be found"
   } else {
      return [eval exec $cmdpath $args]
   }
}

proc getAbsolutePath {path} {
   global cwd ModulesCurrentModulefile

   # currently executing a modulefile or rc, so get the directory of this file
   if {$ModulesCurrentModulefile ne ""} {
      set curdir [file dirname $ModulesCurrentModulefile]
   # elsewhere get module command current working directory
   } else {
      # register pwd at first call
      if {![info exists cwd]} {
         set cwd [pwd]
      }
      set curdir $cwd
   }

   set abslist {}
   # get a first version of the absolute path by joining the current working
   # directory to the given path. if given path is already absolute
   # 'file join' will not break it as $curdir will be ignored as soon a
   # beginning '/' character is found on $path. this first pass also clean
   # extra '/' character. then each element of the path is analyzed to clear
   # "." and ".." components.
   foreach elt [file split [file join $curdir $path]] {
      if {$elt eq ".."} {
         # skip ".." element if it comes after root element, remove last
         # element elsewhere
         if {[llength $abslist] > 1} {
            set abslist [lreplace $abslist end end]
         }
      # skip any "." element
      } elseif {$elt ne "."} {
         lappend abslist $elt
      }
   }

   # return cleaned absolute path
   return [eval file join $abslist]
}

# split string while ignore any separator character that is espaced
proc psplit {str sep} {
   set previdx -1
   set idx [string first $sep $str]
   while {$idx != -1} {
      # look ahead if found separator is escaped
      if {[string index $str [expr {$idx-1}]] ne "\\"} {
         # unescape any separator character when adding to list
         lappend res [charUnescaped [string range $str [expr {$previdx+1}]\
            [expr {$idx-1}]] $sep]
         set previdx $idx
      }
      set idx [string first $sep $str [expr {$idx+1}]]
   }

   lappend res [charUnescaped [string range $str [expr {$previdx+1}] end]\
      $sep]

   return $res
}

# join list while escape any character equal to separator
proc pjoin {lst sep} {
   set res ""

   foreach elt $lst {
      # preserve empty entries
      if {[info exists not_first]} {
         append res $sep
      } else {
         set not_first 1
      }
      # escape any separator character when adding to string
      append res [charEscaped $elt $sep]
   }

   return $res
}

# provide a lreverse proc for Tcl8.4 and earlier
if {[info commands lreverse] eq ""} {
   proc lreverse l {
      set r {}
      set i [llength $l]
      while {[incr i -1] > 0} {
         lappend r [lindex $l $i]
      }
      lappend r [lindex $l 0]
   }
}

# provide a lassign proc for Tcl8.4 and earlier
if {[info commands lassign] eq ""} {
   proc lassign {values args} {
      uplevel 1 [list foreach $args [linsert $values end {}] break]
      lrange $values [llength $args] end
   }
}

proc replaceFromList {list1 item {item2 {}}} {
    while {[set xi [lsearch -exact $list1 $item]] >= 0} {
       if {[string length $item2] == 0} {
          set list1 [lreplace $list1 $xi $xi]
       } else {
          set list1 [lreplace $list1 $xi $xi $item2]
       }
    }

    return $list1
}

proc parseAccessIssue {modfile} {
   global errorCode

   # retrieve and return access issue message
   if {[regexp {POSIX .* \{(.*)\}$} $errorCode match errMsg]} {
      return "[string totitle $errMsg] on '$modfile'"
   } else {
      return "Cannot access '$modfile'"
   }
}

proc checkValidModule {modfile} {
   global g_modfileValid

   reportDebug "checkValidModule: $modfile"

   # use cached result
   if {[info exists g_modfileValid($modfile)]} {
      return $g_modfileValid($modfile)
   } else {
      # Check for valid module
      if {[catch {
         set fid [open $modfile r]
         set fheader [read $fid 8]
         close $fid
      }]} {
         set check_valid "accesserr"
         set check_msg [parseAccessIssue $modfile]
      } else {
         if {$fheader eq "\#%Module"} {
            set check_valid "true"
            set check_msg ""
         } else {
            set check_valid "invalid"
            set check_msg "Magic cookie '#%Module' missing"
         }
      }

      # cache result at first query
      return [set g_modfileValid($modfile) [list $check_valid $check_msg]]
   }
}

# get file modification time, cache it at first query, use cache afterward
proc getFileMtime {fpath} {
   global g_fileMtime

   if {[info exists g_fileMtime($fpath)]} {
      return $g_fileMtime($fpath)
   } else {
      return [set g_fileMtime($fpath) [file mtime $fpath]]
   }
}

proc readModuleContent {modfile {report_read_issue 0} {must_have_cookie 1}} {
   reportDebug "readModuleContent: $modfile"

   # read file
   if {[catch {
      set fid [open $modfile r]
      set fdata [read $fid]
      close $fid
   } errMsg ]} {
      if {$report_read_issue} {
         reportError [parseAccessIssue $modfile]
      }
      return {}
   }

   # check module validity if magic cookie is mandatory
   if {[string first "\#%Module" $fdata] == 0 || !$must_have_cookie} {
      return $fdata
   } else {
      reportInternalBug "Magic cookie '#%Module' missing" $modfile
      return {}
   }
}

# If given module maps to default or other symbolic versions, a list of
# those versions is returned. This takes module/version as an argument.
proc getVersAliasList {mod} {
   global g_symbolHash

   if {[info exists g_symbolHash($mod)]} {
      set tag_list $g_symbolHash($mod)
   } else {
      set tag_list {}
   }

   reportDebug "getVersAliasList: '$mod' has tag list '$tag_list'"

   return $tag_list
}

# finds all module-related files matching mod in the module path dir
proc findModules {dir {mod {}} {fetch_mtime 0} {fetch_hidden 0}} {
   global ignoreDir

   reportDebug "findModules: finding '$mod' in $dir\
      (fetch_mtime=$fetch_mtime, fetch_hidden=$fetch_hidden)"

   # use catch protection to handle non-readable and non-existent dir
   if {[catch {
      # On Cygwin, glob may change the $dir path if there are symlinks
      # involved. So it is safest to reglob the $dir.
      # example:
      # [glob /home/stuff] -> "//homeserver/users0/stuff"
      set dir [glob $dir]
      set full_list [glob -nocomplain "$dir/$mod"]
   }]} {
      return {}
   }

   # remove trailing / needed on some platforms
   regsub {\/$} $full_list {} full_list

   array set mod_list {}
   for {set i 0} {$i < [llength $full_list]} {incr i 1} {
      set element [lindex $full_list $i]
      set tag_list {}

      # Cygwin TCL likes to append ".lnk" to the end of symbolic links.
      # This is not necessary and pollutes the module names, so let's
      # trim it off.
      if { [isWin] } {
         regsub {\.lnk$} $element {} element
      }

      set tail [file tail $element]
      set modulename [getModuleNameFromModulepath $element $dir]
      set add_ref_to_parent 0
      if {[file isdirectory $element]} {
         if {![info exists ignoreDir($tail)]} {
            # try then catch any issue rather than test before trying
            # workaround 'glob -nocomplain' which does not return permission
            # error on Tcl 8.4, so we need to avoid registering issue if
            # raised error is about a no match
            set treat_dir 1
            if {[catch {set elt_list [glob "$element/*"]} errMsg]} {
               if {$errMsg eq "no files matched glob pattern\
                  \"$element/*\""} {
                  set elt_list {}
               } else {
                  set mod_list($modulename) [list "accesserr"\
                     [parseAccessIssue $element] $element]
                  set treat_dir 0
               }
            }
            if {$treat_dir} {
               set mod_list($modulename) [list "directory"]
               # Add each element in the current directory to the list
               if {[file readable $element/.modulerc]} {
                  lappend full_list $element/.modulerc
               }
               if {[file readable $element/.version]} {
                  lappend full_list $element/.version
               }
               if {[llength $elt_list] > 0} {
                  set full_list [concat $full_list $elt_list]
               }
               # search for hidden files if asked
               if {$fetch_hidden} {
                  foreach elt [glob -nocomplain -types hidden -directory\
                     $element -tails "*"] {
                     switch -- $elt {
                        {.modulerc} - {.version} - {.} - {..} { }
                        default {
                           lappend full_list $element/$elt
                           set hidden_list($element/$elt) 1
                        }
                     }
                  }
               }
               set add_ref_to_parent 1
            }
         }
      } else {
         switch -glob -- $tail {
            {.modulerc} {
               set mod_list($modulename) [list "modulerc"]
            }
            {.version} {
               set mod_list($modulename) [list "modulerc"]
            }
            {*~} - {*,v} - {\#*\#} { }
            default {
               lassign [checkValidModule $element] check_valid check_msg
               switch -- $check_valid {
                  {true} {
                     if {$fetch_mtime} {
                        set mtime [getFileMtime $element]
                     } else {
                        set mtime {}
                     }
                     set mod_list($modulename) [list "modulefile" $mtime]
                     # if modfile hidden, do not reference it in parent list
                     if {$fetch_hidden && [info exists\
                        hidden_list($element)]} {
                        set add_ref_to_parent 0
                     } else {
                        set add_ref_to_parent 1
                     }
                  }
                  default {
                     # register check error and relative message to get it in
                     # case of direct access of this module element, but no
                     # registering in parent directory structure as element
                     # is not valid
                     set mod_list($modulename) [list $check_valid $check_msg\
                        $element]
                  }
               }
            }
         }
      }

      # add reference to parent structure
      if {$add_ref_to_parent} {
         set parentname [file dirname $modulename]
         if {[info exists mod_list($parentname)]} {
            lappend mod_list($parentname) $tail
         }
      }
   }

   reportDebug "findModules: found [array names mod_list]"

   return [array get mod_list]
}

proc getModules {dir {mod {}} {fetch_mtime 0} {search {}} {fetch_hidden 0}} {
   global ModulesCurrentModulefile
   global g_sourceAlias g_sourceVersion g_sourceVirtual g_resolvedPath
   global g_rcAlias g_moduleAlias g_rcVersion g_moduleVersion
   global g_rcVirtual g_moduleVirtual

   reportDebug "getModules: get '$mod' in $dir (fetch_mtime=$fetch_mtime,\
      search=$search, fetch_hidden=$fetch_hidden)"

   # if search for global or user rc alias only, no dir lookup is performed
   # and aliases from g_rcAlias are returned
   if {[lsearch -exact $search "rc_alias_only"] >= 0} {
      set add_rc_defs 1
      array set found_list {}
   } else {
      # find modules by searching with first path element if mod is a deep
      # modulefile (elt1/etl2/vers) in order to catch all .modulerc and
      # .version files of module-related parent directories in case we need
      # to translate an alias or a version
      set parentlist [split $mod "/"]
      set findmod [lindex $parentlist 0]
      # if searched mod is an empty or flat element append wildcard character
      # to match anything starting with mod
      if {[lsearch -exact $search "wild"] >= 0 &&\
         [llength $parentlist] <= 1} {
         append findmod "*"
      }
      # add alias/version definitions from global or user rc to result
      if {[lsearch -exact $search "rc_defs_included"] >= 0} {
         set add_rc_defs 1
      } else {
         set add_rc_defs 0
      }
      if {!$fetch_hidden} {
         set fetch_hidden [isModuleHidden $mod]
         reportDebug "getModules: is '$mod' requiring hidden search\
            ($fetch_hidden)"
      }
      array set found_list [findModules $dir $findmod $fetch_mtime\
         $fetch_hidden]
   }

   array set dir_list {}
   array set mod_list {}
   foreach elt [lsort [array names found_list]] {
      if {[lindex $found_list($elt) 0] eq "modulerc"} {
         # push name to be found by module-alias and version
         pushSpecifiedName $elt
         pushModuleName $elt
         execute-modulerc $dir/$elt
         popModuleName
         popSpecifiedName
      # add other entry kind to the result list
      } elseif {[string match $mod* $elt]} {
         set mod_list($elt) $found_list($elt)
         # list dirs to rework their definition at the end
         if {[lindex $found_list($elt) 0] eq "directory"} {
            set dir_list($elt) 1
         }
      }
   }

   # add versions found when parsing .version or .modulerc files in this
   # directory (skip versions not registered from this directory except if
   # global or user rc definitions should be included)) if they match passed
   # $mod (as for regular modulefiles)
   foreach vers [array names g_moduleVersion -glob $mod*] {
      set versmod $g_moduleVersion($vers)
      if {($dir ne "" && [string first "$dir/" $g_sourceVersion($vers)] == 0)\
         || ($add_rc_defs && [info exists g_rcVersion($vers)])} {
         set mod_list($vers) [list "version" $versmod]
      }
      # no reference add to parent directory structure as versions are virtual

      # add the target of symbolic versions found when parsing .version or
      # .modulerc files if these symbols match passed $mod (as for regular
      # modulefiles). modulefile target of these version symbol should have
      # been found previously to be added
      if {![info exists mod_list($versmod)]} {
         # exception made to hidden modulefile target which should not be
         # found previously as not searched (except if we already look for
         # hidden modules). in case symbolic version matches passed $mod
         # look for this hidden target
         if {$mod eq $vers && !$fetch_hidden && [isModuleHidden $versmod]} {
            array set found_list [findModules $dir $versmod $fetch_mtime 1]
         }

         # symbolic version targets a modulefile most of the time
         if {[info exists found_list($versmod)]} {
            set mod_list($versmod) $found_list($versmod)
         # but sometimes they may target an alias
         } elseif {[info exists g_moduleAlias($versmod)]} {
            lappend matching_versalias $versmod
         # or a virtual module
         } elseif {[info exists g_moduleVirtual($versmod)]} {
            lappend matching_versvirt $versmod
         }
      }
   }

   # add aliases found when parsing .version or .modulerc files in this
   # directory (skip aliases not registered from this directory except if
   # global or user rc definitions should be included) if they match passed
   # $mod (as for regular modulefiles) or if a symbolic versions targeting
   # alias match passed $mod
   set matching_alias [array names g_moduleAlias -glob $mod*]
   if {[info exists matching_versalias]} {
      foreach versalias $matching_versalias {
         if {[lsearch -exact $matching_alias $versalias] == -1} {
            lappend matching_alias $versalias
         }
      }
   }
   foreach alias $matching_alias {
      if {($dir ne "" && [string first "$dir/" $g_sourceAlias($alias)] == 0)\
         || ($add_rc_defs && [info exists g_rcAlias($alias)])} {
         set mod_list($alias) [list "alias" $g_moduleAlias($alias)]

         # in case alias overwrites a directory definition
         if {[info exists dir_list($alias)]} {
             unset dir_list($alias)
         }

         # add reference to this alias version in parent structure
         set parentname [file dirname $alias]
         if {[info exists mod_list($parentname)]} {
            lappend mod_list($parentname) [file tail $alias]
         } else {
            # add reference to orphan list if dir does not exist may be added
            # below if dir is virtually set by a virtual deep module
            lappend orphan_list($parentname) [file tail $alias]
         }
      }
   }

   # add virtual mods found when parsing .version or .modulerc files in this
   # directory (skip virtual mods not registered from this directory except if
   # global or user rc definitions should be included) if they match passed
   # $mod (as for regular modulefiles) or if a symbolic versions targeting
   # virtual mod match passed $mod
   set matching_virtual [array names g_moduleVirtual -glob $mod*]
   if {[info exists matching_versvirt]} {
      foreach versvirt $matching_versvirt {
         if {[lsearch -exact $matching_virtual $versvirt] == -1} {
            lappend matching_virtual $versvirt
         }
      }
   }
   foreach virt $matching_virtual {
      if {($dir ne "" && [string first "$dir/" $g_sourceVirtual($virt)] == 0)\
         || ($add_rc_defs && [info exists g_rcVirtual($virt)])} {
         lassign [checkValidModule $g_moduleVirtual($virt)] check_valid\
            check_msg
         switch -- $check_valid {
            {true} {
               if {$fetch_mtime} {
                  set mtime [getFileMtime $g_moduleVirtual($virt)]
               } else {
                  set mtime {}
               }
               # set mtime at index 1 like a modulefile entry
               set mod_list($virt) [list "virtual" $mtime\
                  $g_moduleVirtual($virt)]

               set add_ref_to_parent 1
            }
            default {
               # register check error and relative message to get it in
               # case of direct access of this module element, but no
               # registering in parent directory structure as element
               # is not valid
               set mod_list($virt) [list $check_valid $check_msg\
                  $g_moduleVirtual($virt)]

               # no reference to parent list
               set add_ref_to_parent 0
            }
         }

         # in case virtual mod overwrites a directory definition
         if {[info exists dir_list($virt)]} {
             unset dir_list($virt)
         }

         # add reference to this virtual mod in parent structure
         if {$add_ref_to_parent} {
            set parentname [file dirname $virt]
            set elt [file tail $virt]

            # initialize virtual parent structure if it does not exist
            if {![info exists mod_list($parentname)]} {
               # loop until reaching an existing or a top entry
               while {![info exists mod_list($parentname)]\
                  && $parentname ne "."} {
                  # create virtual directory entry
                  set mod_list($parentname) [list "directory" $elt]
                  set dir_list($parentname) 1

                  set elt [file tail $parentname]
                  set parentname [file dirname $parentname]
               }
               # add reference to reached existing entry
               if {[info exists mod_list($parentname)]} {
                  lappend mod_list($parentname) $elt
               }
            } else {
               lappend mod_list($parentname) $elt
            }
         }
      }
   }

   # integrate aliases defined in orphan directories if these dirs have been
   # virtually created by a virtual module reference
   foreach dir [array names orphan_list] {
      if {[info exists mod_list($dir)]} {
         set mod_list($dir) [concat $mod_list($dir) $orphan_list($dir)]
      }
   }

   # work on directories integrated in the result list by registering
   # default element in this dir and list of all child elements dictionary
   # sorted, so last element in dir is also last element in this list
   # this treatment happen at the end to find all directory entries in
   # result list (alias and virtual included)
   foreach dir [lsort [array names dir_list]] {
      set elt_list [lsort -dictionary [lrange $mod_list($dir) 1 end]]
      # remove dir from list if it is empty
      if {[llength $elt_list] == 0} {
         unset mod_list($dir)
         # rework upper directories content if registered
         while {[set par_dir [file dirname $dir]] ne "."\
            && [info exists mod_list($par_dir)]} {
            set dir_name [file tail $dir]
            set dir $par_dir
            # get upper dir content without empty dir (as dir_list is sorted
            # parent dir information have already been consolidated)
            set elt_list [lsearch -all -inline -not -exact [lrange\
               $mod_list($dir) 2 end] $dir_name]
            # remove also parent dir if it becomes empty
            if {[llength $elt_list] == 0} {
               unset mod_list($dir)
            } else {
               # change default by last element if empty dir was default
               set dfl_elt [lindex $mod_list($dir) 1]
               if {$dfl_elt eq $dir_name} {
                  set dfl_elt [lindex $elt_list end]
               }
               set mod_list($dir) [concat [list "directory" $dfl_elt]\
                  $elt_list]
               # no need to update upper directory as this one persists
               break
            }
         }
      } else {
         # get default element (defined or implicit)
         if {[info exists g_resolvedPath($dir)]} {
            set dfl_elt [file tail $g_resolvedPath($dir)]
         } else {
            set dfl_elt [lindex $elt_list end]
         }
         set mod_list($dir) [concat [list "directory" $dfl_elt] $elt_list]
      }
   }

   reportDebug "getModules: got [array names mod_list]"

   return [array get mod_list]
}

# Finds all module versions for mod in the module path dir
proc listModules {dir mod {show_flags {1}} {filter {}} {search "wild"}} {
   global ignoreDir flag_default_mf flag_default_dir show_modtimes

   reportDebug "listModules: get '$mod' in $dir\
      (show_flags=$show_flags, filter=$filter, search=$search)"

   # report flags for directories and modulefiles depending on show_flags
   # procedure argument and global variables
   if {$show_flags && $flag_default_mf} {
      set show_flags_mf 1
   } else {
      set show_flags_mf 0
   }
   if {$show_flags && $flag_default_dir} {
      set show_flags_dir 1
   } else {
      set show_flags_dir 0
   }
   if {$show_flags && $show_modtimes} {
      set show_mtime 1
   } else {
      set show_mtime 0
   }

   # get module list
   # as we treat a full directory content do not exit on an error
   # raised from one modulerc file
   array set mod_list [getModules $dir $mod $show_mtime $search]

   # prepare results for display
   set clean_list {}
   foreach elt [array names mod_list] {
      set elt_type [lindex $mod_list($elt) 0]

      set add_to_clean_list 1
      if {$filter ne ""} {
         # only analyze directories or modulefile at the root in case of
         # result filtering. depending on filter kind the selection of the
         # modulefile to display will be made using the definition
         # information of its upper directory
         if {$elt_type eq "directory"} {
            switch -- $filter {
               {onlydefaults} {
                  set elt_vers [lindex $mod_list($elt) 1]
               }
               {onlylatest} {
                  set elt_vers [lindex $mod_list($elt) end]
               }
            }
            # switch to selected modulefile to display
            append elt "/$elt_vers"
            # verify it exists elsewhere skip result for this directory
            if {![info exists mod_list($elt)]} {
               continue
            }
            set elt_type [lindex $mod_list($elt) 0]
            # skip if directory selected, will be looked at in a next round
            if {$elt_type eq "directory"} {
               set add_to_clean_list 0
            }
         } elseif {[file dirname $elt] ne "."} {
            set add_to_clean_list 0
         }

         if {$add_to_clean_list} {
            set tag_list [getVersAliasList $elt]
         }
      } else {
         set tag_list [getVersAliasList $elt]
         # do not add a dir if it does not hold tags
         if {$elt_type eq "directory" && [llength $tag_list] == 0} {
            set add_to_clean_list 0
         }
      }

      if {$add_to_clean_list} {
         switch -- $elt_type {
            {directory} {
               if {$show_flags_dir} {
                  if {$show_mtime} {
                     lappend clean_list [format "%-40s%-20s" $elt\
                        [join $tag_list ":"]]
                  } else {
                     lappend clean_list [join [list $elt "("\
                        [join $tag_list ":"] ")"] {}]
                  }
               } else {
                  lappend clean_list $elt
               }
            }
            {modulefile} - {virtual} {
               if {$show_mtime} {
                  # add to display file modification time in addition
                  # to potential tags
                  lappend clean_list [format "%-40s%-20s%19s" $elt\
                     [join $tag_list ":"]\
                     [clock format [lindex $mod_list($elt) 1]\
                     -format "%Y/%m/%d %H:%M:%S"]]
               } elseif {$show_flags_mf && [llength $tag_list] > 0} {
                  lappend clean_list [join [list $elt "("\
                     [join $tag_list ":"] ")"] {}]
               } else {
                  lappend clean_list $elt
               }
            }
            {alias} {
               if {$show_mtime} {
                  lappend clean_list [format "%-40s%-20s"\
                     "$elt -> [lindex $mod_list($elt) 1]"\
                     [join $tag_list ":"]]
               } elseif {$show_flags_mf} {
                  lappend tag_list "@"
                  lappend clean_list [join [list $elt "("\
                     [join $tag_list ":"] ")"] {}]
               } else {
                  lappend clean_list $elt
               }
            }
         }
         # ignore "version" entries as symbolic version are treated
         # along to their relative modulefile not independently
      }
   }

   # always dictionary-sort results
   set clean_list [lsort -dictionary $clean_list]
   reportDebug "listModules: Returning $clean_list"

   return $clean_list
}

proc showModulePath {} {
   reportDebug "showModulePath"

   set modpathlist [getModulePathList]
   if {[llength $modpathlist] > 0} {
      report "Search path for module files (in search order):"
      foreach path $modpathlist {
         report "  $path"
      }
   } else {
      reportWarning "No directories on module search path"
   }
}

proc displayTableHeader {args} {
   set first 1
   foreach title $args {
      if {$first} {
         set first 0
         if {[llength $args] > 2} {
            set col_len 39
         } else {
            set col_len 59
         }
      } else {
         set col_len 19
      }

      set col "- $title "
      append col [string repeat {-} [expr {$col_len - [string length $col]}]]
      lappend col_list $col
   }

   report [join $col_list "."]
}

proc displaySeparatorLine {{title {}}} {

   if {$title eq ""} {
      report "[string repeat {-} 67]"
   } else {
      set tty_cols [getTtyColumns]
      set len  [string length $title]
      # max expr function is not supported in Tcl8.4 and earlier
      if {[set lrep [expr {($tty_cols - $len - 2)/2}]] < 1} {
         set lrep 1
      }
      if {[set rrep [expr {$tty_cols - $len - 2 - $lrep}]] < 1} {
         set rrep 1
      }
      report "[string repeat {-} $lrep] $title [string repeat {-} $rrep]"
   }
}

# get a list of elements and print them in a column or in a
# one-per-line fashion
proc displayElementList {header hstyle one_per_line display_idx args} {
   global g_eltlist_disp

   set elt_cnt [llength $args]
   reportDebug "displayElementList: header=$header, hstyle=$hstyle,\
      elt_cnt=$elt_cnt, one_per_line=$one_per_line, display_idx=$display_idx"

   # display header if any provided
   if {$header ne "noheader"} {
      # if list already displayed, separate with a blank line before header
      if {![info exists g_eltlist_disp]} {
         set g_eltlist_disp 1
      } else {
         report ""
      }

      if {$hstyle eq "sepline"} {
         displaySeparatorLine $header
      } else {
         report "$header:"
      }
   }

   # end proc if no element are to print
   if {$elt_cnt == 0} {
      return
   }

   # display one element per line
   if {$one_per_line} {
      if {$display_idx} {
         set idx 1
         foreach elt $args {
            report [format "%2d) %s " $idx $elt]
            incr idx
         }
      } else {
         foreach elt $args {
            report $elt
         }
      }
   # elsewhere display elements in columns
   } else {
      if {$display_idx} {
         # save room for numbers and spacing: 2 digits + ) + space
         set elt_prefix_len 4
      } else {
         set elt_prefix_len 0
      }
      # save room for two spaces after element
      set elt_suffix_len 2

      # compute rows*cols grid size with optimized column number
      # the size of each column is computed to display as much column
      # as possible on each line
      set max_len 0
      foreach arg $args {
         lappend elt_len [set len [expr {[string length $arg] +\
            $elt_suffix_len}]]
         if {$len > $max_len} {
            set max_len $len
         }
      }

      set tty_cols [getTtyColumns]
      # find valid grid by starting with non-optimized solution where each
      # column length is equal to the length of the biggest element to display
      set cur_cols [expr {int($tty_cols / $max_len)}]
      # when display is found too short to display even one column
      if {$cur_cols == 0} {
         set cols 1
         set rows $elt_cnt
         array set col_width [list 0 $max_len]
      } else {
         set cols 0
      }
      set last_round 0
      set restart_loop 0
      while {$cur_cols > $cols} {
         if {!$restart_loop} {
            if {$last_round} {
               incr cur_rows
            } else {
               set cur_rows [expr {int(ceil(double($elt_cnt) / $cur_cols))}]
            }
            for {set i 0} {$i < $cur_cols} {incr i} {
               set cur_col_width($i) 0
            }
            for {set i 0} {$i < $cur_rows} {incr i} {
               set row_width($i) 0
            }
            set istart 0
         } else {
            set istart [expr {$col * $cur_rows}]
            # only remove width of elements from current col
            for {set row 0} {$row < ($i % $cur_rows)} {incr row} {
               incr row_width($row) -[expr {$pre_col_width + $elt_prefix_len}]
            }
         }
         set restart_loop 0
         for {set i $istart} {$i < $elt_cnt} {incr i} {
            set col [expr {int($i / $cur_rows)}]
            set row [expr {$i % $cur_rows}]
            # restart loop if a column width change
            if {[lindex $elt_len $i] > $cur_col_width($col)} {
               set pre_col_width $cur_col_width($col)
               set cur_col_width($col) [lindex $elt_len $i]
               set restart_loop 1
               break
            }
            # end search of maximum number of columns if computed row width
            # is larger than terminal width
            if {[incr row_width($row) +[expr {$cur_col_width($col) \
               + $elt_prefix_len}]] > $tty_cols} {
               # start last optimization pass by increasing row number until
               # reaching number used for previous column number, by doing so
               # this number of column may pass in terminal width, if not
               # fallback to previous number of column
               if {$last_round && $cur_rows == $rows} {
                  incr cur_cols -1
               } else {
                  set last_round 1
               }
               break
            }
         }
         # went through all elements without reaching terminal width limit so
         # this number of column solution is valid, try next with a greater
         # column number
         if {$i == $elt_cnt} {
            set cols $cur_cols
            set rows $cur_rows
            array set col_width [array get cur_col_width]
            # number of column is fixed if last optimization round has started
            # reach end also if there is only one row of results
            if {!$last_round && $rows > 1} {
               incr cur_cols
            }
         }

      }
      reportDebug "displayElementList: list=$args"
      reportDebug "displayElementList: rows/cols=$rows/$cols,\
         lastcol_item_cnt=[expr {int($elt_cnt % $rows)}]"

      for {set row 0} {$row < $rows} {incr row} {
         for {set col 0} {$col < $cols} {incr col} {
            set index [expr {$col * $rows + $row}]
            if {$index < $elt_cnt} {
               if {$display_idx} {
                  append displist [format "%2d) %-$col_width($col)s"\
                     [expr {$index +1}] [lindex $args $index]]
               } else {
                  append displist [format "%-$col_width($col)s"\
                     [lindex $args $index]]
               }
            }
         }
         append displist "\n"
      }
      report "$displist" -nonewline
   }
}

# build list of what to undo then do to move
# from an initial list to a target list
proc getMovementBetweenList {from to} {
   reportDebug "getMovementBetweenList: from($from) to($to)"

   set undo {}
   set do {}

   # determine what element to undo then do
   # to restore a target list from a current list
   # with preservation of the element order
   if {[llength $to] > [llength $from]} {
      set imax [llength $to]
   } else {
      set imax [llength $from]
   }
   set list_equal 1
   for {set i 0} {$i < $imax} {incr i} {
      set to_obj [lindex $to $i]
      set from_obj [lindex $from $i]

      if {$to_obj ne $from_obj} {
         set list_equal 0
      }
      if {$list_equal == 0} {
         if {$to_obj ne ""} {
            lappend do $to_obj
         }
         if {$from_obj ne ""} {
            lappend undo $from_obj
         }
      }
   }

   return [list $undo $do]
}

# build list of currently loaded modules where modulename
# is registered minus module version if loaded version is
# the default one. a helper list may be provided and looked
# at if no module path is set
proc getSimplifiedLoadedModuleList {{helper_raw_list {}}\
   {helper_list {}}} {
   reportDebug "getSimplifiedLoadedModuleList"

   set curr_mod_list {}
   set modpathlist [getModulePathList]
   foreach mod [getLoadedModuleList] {
      if {[string length $mod] > 0} {
         set modparent [file dirname $mod]
         if {$modparent eq "."} {
            lappend curr_mod_list $mod
         } elseif {[llength $modpathlist] > 0} {
            # fetch all module version available
            set modlist {}
            foreach dir $modpathlist {
               if {[file isdirectory $dir]} {
                  set modlist [listModules $dir $modparent 0 "onlydefaults"]
                  # quit loop if result found
                  if {[llength $modlist] > 0} {
                     break
                  }
               }
            }
            # check if loaded version is default
            if {[lsearch -exact $modlist $mod] >-1 } {
               lappend curr_mod_list $modparent
            } else {
               lappend curr_mod_list $mod
            }
         } else {
            # if no path set currently, cannot search for all
            # available version so use helper lists if provided
            set helper_idx [lsearch -exact $helper_raw_list $mod]
            if {$helper_idx == -1} {
               lappend curr_mod_list $mod
            } else {
               # if mod found in a previous LOADEDMODULES list use
               # simplified version of this module found in relative
               # helper list (previously computed simplified list)
               lappend curr_mod_list [lindex $helper_list $helper_idx]
            }
         }
      }
   }

   return $curr_mod_list
}

# get collection target currently set if any.
# a target is a domain on which a collection is only valid.
# when a target is set, only the collections made for that target
# will be available to list and restore, and saving will register
# the target footprint
proc getCollectionTarget {} {
   global env

   if {[info exists env(MODULES_COLLECTION_TARGET)]} {
      return $env(MODULES_COLLECTION_TARGET)
   } else {
      return ""
   }
}

# should modulefile version be pinned when saving collection?
proc pinVersionInCollection {} {
   global env

   if {[info exists env(MODULES_COLLECTION_PIN_VERSION)] &&\
      $env(MODULES_COLLECTION_PIN_VERSION) eq "1"} {
      return 1
   } else {
      return 0
   }
}

# return saved collections found in user directory which corresponds to
# enabled collection target if any set.
proc findCollections {} {
   global env

   set coll_search "$env(HOME)/.module/*"

   # find saved collections (matching target suffix)
   set colltarget [getCollectionTarget]
   if {$colltarget ne ""} {
      append coll_search ".$colltarget"
   }

   # workaround 'glob -nocomplain' which does not return permission
   # error on Tcl 8.4, so we need to avoid raising error if no match
   if {[catch {set coll_list [glob $coll_search]} errMsg ]} {
      if {$errMsg eq "no files matched glob pattern \"$coll_search\""} {
         set coll_list {}
      } else {
         reportErrorAndExit "Cannot access collection directory.\n$errMsg"
      }
   }

   return $coll_list
}

# get filename corresponding to collection name provided as argument.
# name provided may already be a file name. collection description name
# (with target info if any) is returned along with collection filename
proc getCollectionFilename {coll} {
   global env

   # initialize description with collection name
   set colldesc $coll

   # is collection a filepath
   if {[string first "/" $coll] > -1} {
      # collection target has no influence when
      # collection is specified as a filepath
      set collfile "$coll"
   # elsewhere collection is a name
   } elseif {[info exists env(HOME)]} {
      set collfile "$env(HOME)/.module/$coll"
      # if a target is set, append the suffix corresponding
      # to this target to the collection file name
      set colltarget [getCollectionTarget]
      if {$colltarget ne ""} {
         append collfile ".$colltarget"
         # add knowledge of collection target on description
         append colldesc " (for target \"$colltarget\")"
      }
   } else {
      reportErrorAndExit "HOME not defined"
   }

   return [list $collfile $colldesc]
}

# generate collection content based on provided path and module lists
proc formatCollectionContent {path_list mod_list} {
   set content ""

   # start collection content with modulepaths
   foreach path $path_list {
      # 'module use' prepends paths by default so we clarify
      # path order here with --append flag
      append content "module use --append $path" "\n"
   }

   # then add modules
   foreach mod $mod_list {
      append content "module load $mod" "\n"
   }

   return $content
}

# read given collection file and return the path and module lists it defines
proc readCollectionContent {collfile colldesc} {
   # init lists (maybe coll does not set mod to load)
   set path_list {}
   set mod_list {}

   # read file
   if {[catch {
      set fid [open $collfile r]
      set fdata [split [read $fid] "\n"]
      close $fid
   } errMsg ]} {
      reportErrorAndExit "Collection $colldesc cannot be read.\n$errMsg"
   }

   # analyze collection content
   foreach fline $fdata {
      if {[regexp {module use (.*)$} $fline match patharg] == 1} {
         # paths are appended by default
         set stuff_path "append"
         # manage with "split" multiple paths and path options
         # specified on single line, for instance:
         # module use --append path1 path2 path3
         foreach path [split $patharg] {
            # following path is asked to be appended
            if {($path eq "--append") || ($path eq "-a")\
               || ($path eq "-append")} {
               set stuff_path "append"
            # following path is asked to be prepended
            # collection generated with 'save' does not prepend
            } elseif {($path eq "--prepend") || ($path eq "-p")\
               || ($path eq "-prepend")} {
               set stuff_path "prepend"
            } else {
               # ensure given path is absolute to be able to correctly
               # compare with paths registered in MODULEPATH
               set path [getAbsolutePath $path]
               # add path to end of list
               if {$stuff_path eq "append"} {
                  lappend path_list $path
               # insert path to first position
               } else {
                  set path_list [linsert $path_list 0 $path]
               }
            }
         }
      } elseif {[regexp {module load (.*)$} $fline match modarg] == 1} {
         # manage multiple modules specified on a
         # single line with "split", for instance:
         # module load mod1 mod2 mod3
         set mod_list [concat $mod_list [split $modarg]]
      }
   }

   return [list $path_list $mod_list]
}


########################################################################
# command line commands
#
proc cmdModuleList {} {
   global show_oneperline show_modtimes
   global g_loadedModules

   set loadedmodlist [getLoadedModuleList]

   if {[llength $loadedmodlist] == 0} {
      report "No Modulefiles Currently Loaded."
   } else {
      set list {}
      if {$show_modtimes} {
         displayTableHeader "Package" "Versions" "Last mod."
      }
      report "Currently Loaded Modulefiles:"
      set display_list {}
      if {$show_modtimes || $show_oneperline} {
         set display_idx 0
         set one_per_line 1
      } else {
         set display_idx 1
         set one_per_line 0
      }

      foreach mod $loadedmodlist {
         if {[string length $mod] > 0} {
            if {$show_oneperline} {
               lappend display_list $mod
            } else {
               # skip rc find and execution if mod is registered as full path
               if {[isModuleFullPath $mod]} {
                  set mtime [getFileMtime $mod]
                  set tag_list {}
               # or if loaded module is a virtual module
               } elseif {[isModuleVirtual $mod $g_loadedModules($mod)]} {
                  set mtime [getFileMtime $g_loadedModules($mod)]
                  set tag_list {}
               } else {
                  # call getModules to find and execute rc files for this mod
                  set dir [getModulepathFromModuleName $g_loadedModules($mod)\
                     $mod]
                  array set mod_list [getModules $dir $mod $show_modtimes]
                  set mtime [lindex $mod_list($mod) 1]
                  set tag_list [getVersAliasList $mod]
               }

               if {$show_modtimes} {
                  # add to display file modification time in addition
                  # to potential tags
                  lappend display_list [format "%-40s%-20s%19s" $mod\
                     [join $tag_list ":"]\
                     [clock format $mtime -format "%Y/%m/%d %H:%M:%S"]]
               } else {
                  if {[llength $tag_list]} {
                     append mod "(" [join $tag_list ":"] ")"
                  }
                  lappend display_list $mod
               }
            }
         }
      }

      eval displayElementList "noheader" "{}" $one_per_line $display_idx\
         $display_list
   }
}

proc cmdModuleDisplay {args} {
   reportDebug "cmdModuleDisplay: displaying $args"

   pushMode "display"
   set first_report 1
   foreach mod $args {
      lassign [getPathToModule $mod] modfile modname
      if {$modfile ne ""} {
         pushSpecifiedName $mod
         pushModuleName $modname
         # only one separator lines between 2 modules
         if {$first_report} {
            displaySeparatorLine
            set first_report 0
         }
         report "$modfile:\n"
         execute-modulefile $modfile
         popModuleName
         popSpecifiedName
         displaySeparatorLine
      }
   }
   popMode
}

proc cmdModulePaths {mod} {
   global g_return_text

   reportDebug "cmdModulePaths: ($mod)"

   set dir_list [getModulePathList "exiterronundef"]
   foreach dir $dir_list {
      array unset mod_list
      array set mod_list [getModules $dir $mod 0 "rc_defs_included"]

      # prepare list of dirs for alias/symbol target search, will first search
      # in currently looked dir, then in other dirs following precedence order
      set target_dir_list [concat [list $dir] [replaceFromList $dir_list\
         $dir]]

      # build list of modulefile to print
      foreach elt [array names mod_list] {
         switch -- [lindex $mod_list($elt) 0] {
            {modulefile} {
               lappend g_return_text $dir/$elt
            }
            {virtual} {
               lappend g_return_text [lindex $mod_list($elt) 2]
            }
            {alias} - {version} {
               # resolve alias target
               set aliastarget [lindex $mod_list($elt) 1]
               lassign [getPathToModule $aliastarget $target_dir_list]\
                  modfile modname
               # add module target as result instead of alias
               if {$modfile ne "" && ![info exists mod_list($modname)]} {
                  lappend g_return_text $modfile
               }
            }
         }
      }
   }

   # sort results if any and remove duplicates
   if {[info exists g_return_text]} {
      set g_return_text [lsort -dictionary -unique $g_return_text]
   } else {
      # set empty value to return empty if no result
      set g_return_text ""
   }
}

proc cmdModulePath {mod} {
   global g_return_text

   reportDebug "cmdModulePath: ($mod)"
   lassign [getPathToModule $mod] modfile modname
   # if no result set empty value to return empty
   set g_return_text $modfile
}

proc cmdModuleWhatIs {{mod {}}} {
   cmdModuleSearch $mod {}
}

proc cmdModuleApropos {{search {}}} {
   cmdModuleSearch {} $search
}

proc cmdModuleSearch {{mod {}} {search {}}} {
   global g_whatis g_inhibit_errreport g_sourceVirtual

   reportDebug "cmdModuleSearch: ($mod, $search)"

   # disable error reporting to avoid modulefile errors
   # to mix with valid search results
   inhibitErrorReport

   lappend searchmod "rc_defs_included"
   if {$mod eq ""} {
      lappend searchmod "wild"
   }
   set foundmod 0
   pushMode "whatis"
   set dir_list [getModulePathList "exiterronundef"]
   foreach dir $dir_list {
      array unset mod_list
      array set mod_list [getModules $dir $mod 0 $searchmod]
      array unset interp_list
      array set interp_list {}

      # build list of modulefile to interpret
      foreach elt [array names mod_list] {
         switch -- [lindex $mod_list($elt) 0] {
            {modulefile} {
               set interp_list($elt) $dir/$elt
               # register module name in a global list (shared across
               # modulepaths) to get hints when solving aliases/version
               set full_list($elt) 1
            }
            {virtual} {
               set interp_list($elt) [lindex $mod_list($elt) 2]
               set full_list($elt) 1
            }
            {alias} - {version} {
               # resolve alias target
               set elt_target [lindex $mod_list($elt) 1]
               if {![info exists full_list($elt_target)]} {
                  lassign [getPathToModule $elt_target $dir]\
                     modfile modname issuetype issuemsg
                  # add module target as result instead of alias
                  if {$modfile ne "" && ![info exists mod_list($modname)]} {
                     set interp_list($modname) $modfile
                     set full_list($modname) 1
                  } elseif {$modfile eq ""} {
                     # if module target not found in current modulepath add to
                     # list for global search after initial modulepath lookup
                     if {[string first "Unable to locate" $issuemsg] == 0} {
                        set extra_search($modname) [list $dir [expr {$elt eq\
                           $mod}]]
                     # register resolution error if alias name matches search
                     } elseif {$elt eq $mod} {
                        set err_list($modname) [list $issuetype $issuemsg]
                     }
                  }
               }
            }
            {invalid} - {accesserr} {
               # register any error occuring on element matching search
               if {$elt eq $mod} {
                  set err_list($elt) $mod_list($elt)
               }
            }
         }
      }

      # in case during modulepath lookup we find an alias target we were
      # looking for in previous modulepath, remove this element from global
      # search list
      foreach elt [array names extra_search] {
         if {[info exists full_list($elt)]} {
            unset extra_search($elt)
         }
      }

      # save results from this modulepath for interpretation step as there
      # is an extra round of search to match missing alias target, we cannot
      # process modulefiles found immediately
      if {[array size interp_list] > 0} {
         set interp_save($dir) [array get interp_list]
      }
   }

   # find target of aliases in all modulepath except the one already tried
   foreach elt [array names extra_search] {
      lassign [getPathToModule $elt "" "no" [lindex $extra_search($elt) 0]]\
         modfile modname issuetype issuemsg issuefile
      # found target so append it to results in corresponding modulepath
      if {$modfile ne ""} {
         # get belonging modulepath dir depending of module kind
         if {[isModuleVirtual $modname $modfile]} {
            set dir [findModulepathFromModulefile $g_sourceVirtual($modname)]
         } else {
            set dir [getModulepathFromModuleName $modfile $modname]
         }
         array unset interp_list
         if {[info exists interp_save($dir)]} {
            array set interp_list $interp_save($dir)
         }
         set interp_list($modname) $modfile
         set interp_save($dir) [array get interp_list]
      # register resolution error if primal alias name matches search
      } elseif {$modfile eq "" && [lindex $extra_search($elt) 1]} {
         set err_list($modname) [list $issuetype $issuemsg $issuefile]
      }
   }

   # interpret all modulefile we got for each modulepath
   foreach dir $dir_list {
      if {[info exists interp_save($dir)]} {
         array unset interp_list
         array set interp_list $interp_save($dir)
         set foundmod 1
         set display_list {}
         # interpret every modulefiles obtained to get their whatis text
         foreach elt [lsort -dictionary [array names interp_list]] {
            set g_whatis {}
            pushSpecifiedName $elt
            pushModuleName $elt
            execute-modulefile $interp_list($elt)
            popModuleName
            popSpecifiedName

            # treat whatis as a multi-line text
            if {$search eq "" || [regexp -nocase $search $g_whatis]} {
               foreach line $g_whatis {
                  lappend display_list [format "%20s: %s" $elt $line]
               }
            }
         }

         if {[llength $display_list] > 0} {
            eval displayElementList $dir "sepline" 1 0 $display_list
         }
      }
   }
   popMode

   reenableErrorReport

   # report errors if a modulefile was searched but not found
   if {$mod ne "" && !$foundmod} {
      # no error registered means nothing was found to match search
      if {![array exists err_list]} {
         set err_list($mod) [list "none" "Unable to locate a modulefile for\
            '$mod'"]
      }
      foreach elt [array names err_list] {
         eval reportIssue $err_list($elt)
      }
   }
}

proc cmdModuleSwitch {old {new {}}} {
   # if a single name is provided it matches for the module to load and in
   # this case the module to unload is searched to find the closest match
   # (loaded module that shares at least the same root name)
   if {$new eq ""} {
      set new $old
      set unload_match "close"
   } else {
      set unload_match "match"
   }

   reportDebug "cmdModuleSwitch: old='$old' new='$new'"

   # attempt load only if unload succeed
   if {![cmdModuleUnload $unload_match $old]} {
      cmdModuleLoad $new
   }
}

proc cmdModuleSave {{coll {}}} {
   # default collection used if no name provided
   if {$coll eq ""} {
      set coll "default"
   }
   reportDebug "cmdModuleSave: $coll"

   # format collection content, version number of modulefile are saved if
   # version pinning is enabled
   if {[pinVersionInCollection]} {
      set curr_mod_list [getLoadedModuleList]
   } else {
      set curr_mod_list [getSimplifiedLoadedModuleList]
   }
   set save [formatCollectionContent [getModulePathList "returnempty" 0]\
      $curr_mod_list]

   if { [string length $save] == 0} {
      reportErrorAndExit "Nothing to save in a collection"
   }

   # get coresponding filename and its directory
   lassign [getCollectionFilename $coll] collfile colldesc
   set colldir [file dirname $collfile]

   if {![file exists $colldir]} {
      reportDebug "cmdModuleSave: Creating $colldir"
      file mkdir $colldir
   } elseif {![file isdirectory $colldir]} {
      reportErrorAndExit "$colldir exists but is not a directory"
   }

   reportDebug "cmdModuleSave: Saving $collfile"

   if {[catch {
      set fid [open $collfile w]
      puts $fid $save
      close $fid
   } errMsg ]} {
      reportErrorAndExit "Collection $colldesc cannot be saved.\n$errMsg"
   }
}

proc cmdModuleRestore {{coll {}}} {
   # default collection used if no name provided
   if {$coll eq ""} {
      set coll "default"
   }
   reportDebug "cmdModuleRestore: $coll"

   # get coresponding filename
   lassign [getCollectionFilename $coll] collfile colldesc

   if {![file exists $collfile]} {
      reportErrorAndExit "Collection $colldesc cannot be found"
   }

   # read collection
   lassign [readCollectionContent $collfile $colldesc] coll_path_list\
      coll_mod_list

   # collection should at least define a path
   if {[llength $coll_path_list] == 0} {
      reportErrorAndExit "$colldesc is not a valid collection"
   }

   # fetch what is currently loaded
   set curr_path_list [getModulePathList "returnempty" 0]
   # get current loaded module list in simplified and raw versions
   # these lists may be used later on, see below
   set curr_mod_list_raw [getLoadedModuleList]
   set curr_mod_list [getSimplifiedLoadedModuleList]

   # determine what module to unload to restore collection
   # from current situation with preservation of the load order
   lassign [getMovementBetweenList $curr_mod_list $coll_mod_list] \
      mod_to_unload mod_to_load
   # determine unload movement with raw loaded list in case versions are
   # pinning in saved collection
   lassign [getMovementBetweenList $curr_mod_list_raw $coll_mod_list] \
      mod_to_unload_raw mod_to_load_raw
   if {[llength $mod_to_unload] > [llength $mod_to_unload_raw]} {
      set mod_to_unload $mod_to_unload_raw
   }

   # proceed as well for modulepath
   lassign [getMovementBetweenList $curr_path_list $coll_path_list] \
      path_to_unuse path_to_use

   # unload modules
   if {[llength $mod_to_unload] > 0} {
      eval cmdModuleUnload "match" [lreverse $mod_to_unload]
   }
   # unuse paths
   if {[llength $path_to_unuse] > 0} {
      eval cmdModuleUnuse [lreverse $path_to_unuse]
   }

   # since unloading a module may unload other modules or
   # paths, what to load/use has to be determined after
   # the undo phase, so current situation is fetched again
   set curr_path_list [getModulePathList "returnempty" 0]

   # here we may be in a situation were no more path is left
   # in module path, so we cannot easily compute the simplified loaded
   # module list. so we provide two helper lists: simplified and raw
   # versions of the loaded module list computed before starting to
   # unload modules. these helper lists may help to learn the
   # simplified counterpart of a loaded module if it was already loaded
   # before starting to unload modules
   set curr_mod_list [getSimplifiedLoadedModuleList\
      $curr_mod_list_raw $curr_mod_list]
   set curr_mod_list_raw [getLoadedModuleList]

   # determine what module to load to restore collection
   # from current situation with preservation of the load order
   lassign [getMovementBetweenList $curr_mod_list $coll_mod_list] \
      mod_to_unload mod_to_load
   # determine load movement with raw loaded list in case versions are
   # pinning in saved collection
   lassign [getMovementBetweenList $curr_mod_list_raw $coll_mod_list] \
      mod_to_unload_raw mod_to_load_raw
   if {[llength $mod_to_load] > [llength $mod_to_load_raw]} {
      set mod_to_load $mod_to_load_raw
   }

   # proceed as well for modulepath
   lassign [getMovementBetweenList $curr_path_list $coll_path_list] \
      path_to_unuse path_to_use

   # use paths
   if {[llength $path_to_use] > 0} {
      # always append path here to guaranty the order
      # computed above in the movement lists
      eval cmdModuleUse --append $path_to_use
   }

   # load modules
   if {[llength $mod_to_load] > 0} {
      eval cmdModuleLoad $mod_to_load
   }
}

proc cmdModuleSaverm {{coll {}}} {
   # default collection used if no name provided
   if {$coll eq ""} {
      set coll "default"
   }
   reportDebug "cmdModuleSaverm: $coll"

   # avoid to remove any kind of file with this command
   if {[string first "/" $coll] > -1} {
      reportErrorAndExit "Command does not remove collection specified as\
         filepath"
   }

   # get coresponding filename
   lassign [getCollectionFilename $coll] collfile colldesc

   if {![file exists $collfile]} {
      reportErrorAndExit "Collection $colldesc cannot be found"
   }

   # attempt to delete specified colletion
   if {[catch {
      file delete $collfile
   } errMsg ]} {
      reportErrorAndExit "Collection $colldesc cannot be removed.\n$errMsg"
   }
}

proc cmdModuleSaveshow {{coll {}}} {
   # default collection used if no name provided
   if {$coll eq ""} {
      set coll "default"
   }
   reportDebug "cmdModuleSaveshow: $coll"

   # get coresponding filename
   lassign [getCollectionFilename $coll] collfile colldesc

   if {![file exists $collfile]} {
      reportErrorAndExit "Collection $colldesc cannot be found"
   }

   # read collection
   lassign [readCollectionContent $collfile $colldesc] coll_path_list\
      coll_mod_list

   # collection should at least define a path
   if {[llength $coll_path_list] == 0} {
      reportErrorAndExit "$colldesc is not a valid collection"
   }

   displaySeparatorLine
   report "$collfile:\n"
   report [formatCollectionContent $coll_path_list $coll_mod_list]
   displaySeparatorLine
}

proc cmdModuleSavelist {} {
   global show_oneperline show_modtimes

   # if a target is set, only list collection matching this
   # target (means having target as suffix in their name)
   set colltarget [getCollectionTarget]
   if {$colltarget ne ""} {
      set suffix ".$colltarget"
      set targetdesc " (for target \"$colltarget\")"
   } else {
      set suffix ""
      set targetdesc ""
   }

   reportDebug "cmdModuleSavelist: list collections for target\
      \"$colltarget\""

   set coll_list [findCollections]

   if { [llength $coll_list] == 0} {
      report "No named collection$targetdesc."
   } else {
      set list {}
      if {$show_modtimes} {
         displayTableHeader "Collection" "Last mod."
      }
      report "Named collection list$targetdesc:"
      set display_list {}
      if {$show_modtimes || $show_oneperline} {
         set display_idx 0
         set one_per_line 1
      } else {
         set display_idx 1
         set one_per_line 0
      }

      foreach coll [lsort -dictionary $coll_list] {
         # remove target suffix from names to display
         regsub "$suffix$" [file tail $coll] {} mod

         if {[string length $mod] > 0} {
            if {$show_modtimes} {
               set filetime [clock format [getFileMtime $coll]\
                  -format "%Y/%m/%d %H:%M:%S"]
               lappend display_list [format "%-60s%19s" $mod $filetime]
            } else {
               lappend display_list $mod
            }
         }
      }

      eval displayElementList "noheader" "{}" $one_per_line $display_idx\
         $display_list
   }
}


proc cmdModuleSource {args} {
   reportDebug "cmdModuleSource: $args"
   foreach fpath $args {
      set absfpath [getAbsolutePath $fpath]
      if {$fpath eq ""} {
         reportErrorAndExit "File name empty"
      } elseif {[file exists $absfpath]} {
         pushMode "load"
         pushSpecifiedName $absfpath
         pushModuleName $absfpath
         # relax constraint of having a magic cookie at the start of the
         # modulefile to execute as sourced files may need more flexibility
         # as they may be managed outside of the modulefile environment like
         # the initialization modulerc file
         execute-modulefile $absfpath 0
         popModuleName
         popSpecifiedName
         popMode
      } else {
         reportErrorAndExit "File $fpath does not exist"
      }
   }
}

proc cmdModuleUnsource {args} {
   reportDebug "cmdModuleUnsource: $args"
   foreach fpath $args {
      set absfpath [getAbsolutePath $fpath]
      if {$fpath eq ""} {
         reportErrorAndExit "File name empty"
      } elseif {[file exists $absfpath]} {
         pushMode "unload"
         pushSpecifiedName $absfpath
         pushModuleName $absfpath
         # relax constraint of having a magic cookie at the start of the
         # modulefile to execute as sourced files may need more flexibility
         # as they may be managed outside of the modulefile environment like
         # the initialization modulerc file
         execute-modulefile $absfpath 0
         popModuleName
         popSpecifiedName
         popMode
      } else {
         reportErrorAndExit "File $fpath does not exist"
      }
   }
}

proc cmdModuleLoad {args} {
   global g_loadedModules g_loadedModuleFiles g_force

   reportDebug "cmdModuleLoad: loading $args"

   set ret 0
   pushMode "load"
   foreach mod $args {
      lassign [getPathToModule $mod] modfile modname
      if {$modfile ne ""} {
         # check if passed modname correspond to an already loaded modfile
         # and get its loaded name (in case it has been loaded as full path)
         set loadedmodname [getLoadedMatchingName $modname]
         if {$loadedmodname ne ""} {
            set modname $loadedmodname
         }

         set currentModule $modname

         if {$g_force || ! [info exists g_loadedModules($currentModule)]} {
            pushSpecifiedName $mod
            pushModuleName $currentModule
            pushSettings

            if {[execute-modulefile $modfile]} {
               restoreSettings
               set ret 1
            } else {
               add-path "append" LOADEDMODULES $currentModule
               # allow duplicate modfile entries for virtual modules
               add-path "append" --duplicates _LMFILES_ $modfile
               # update cache arrays
               set g_loadedModules($currentModule) $modfile
               lappend g_loadedModuleFiles($modfile) $currentModule
            }

            popSettings
            popModuleName
            popSpecifiedName
         } else {
            reportDebug "cmdModuleLoad: $modname ($modfile) already loaded"
         }
      } else {
         set ret 1
      }
   }
   popMode

   return $ret
}

proc cmdModuleUnload {match args} {
   global g_loadedModules g_loadedModuleFiles

   reportDebug "cmdModuleUnload: unloading $args (match=$match)"

   set ret 0
   pushMode "unload"
   foreach mod $args {
      # resolve by also looking at matching loaded module
      lassign [getPathToModule $mod {} $match] modfile modname
      if {$modfile ne ""} {
         set currentModule $modname

         if {[info exists g_loadedModules($currentModule)]} {
            pushSpecifiedName $mod
            pushModuleName $currentModule
            pushSettings

            if {[execute-modulefile $modfile]} {
               restoreSettings
               set ret 1
            } else {
               # get module position in loaded list to remove corresponding
               # loaded modulefile (entry at same position in _LMFILES_)
               set lmidx [lsearch -exact [getLoadedModuleList] $currentModule]
               unload-path LOADEDMODULES $currentModule
               unload-path --index _LMFILES_ $lmidx
               # update cache arrays
               unset g_loadedModules($currentModule)
               if {[llength $g_loadedModuleFiles($modfile)] == 1} {
                  unset g_loadedModuleFiles($modfile)
               } else {
                  set g_loadedModuleFiles($modfile) [replaceFromList\
                     $g_loadedModuleFiles($modfile) $currentModule]
               }
            }

            popSettings
            popModuleName
            popSpecifiedName
         } else {
            reportDebug "cmdModuleUnload: $modname ($modfile) is not loaded"
         }
      } else {
         set ret 1
      }
   }
   popMode

   return $ret
}

proc cmdModulePurge {} {
   reportDebug "cmdModulePurge"

   eval cmdModuleUnload "match" [lreverse [getLoadedModuleList]]
}

proc cmdModuleReload {} {
   reportDebug "cmdModuleReload"

   set list [getLoadedModuleList]
   set rlist [lreverse $list]
   foreach mod $rlist {
      cmdModuleUnload "match" $mod
   }
   foreach mod $list {
      cmdModuleLoad $mod
   }
}

proc cmdModuleAliases {} {
   global g_moduleAlias g_moduleVersion

   # disable error reporting to avoid modulefile errors
   # to mix with avail results
   inhibitErrorReport

   # parse paths to fill g_moduleAlias and g_moduleVersion
   foreach dir [getModulePathList "exiterronundef"] {
      getModules $dir "" 0 ""
   }

   reenableErrorReport

   set display_list {}
   foreach name [lsort -dictionary [array names g_moduleAlias]] {
      lappend display_list "$name -> $g_moduleAlias($name)"
   }
   if {[llength $display_list] > 0} {
      eval displayElementList "Aliases" "sepline" 1 0 $display_list
   }

   set display_list {}
   foreach name [lsort -dictionary [array names g_moduleVersion]] {
      lappend display_list "$name -> $g_moduleVersion($name)"
   }
   if {[llength $display_list] > 0} {
      eval displayElementList "Versions" "sepline" 1 0 $display_list
   }
}

proc cmdModuleAvail {{mod {*}}} {
   global show_oneperline show_modtimes show_filter

   if {$show_modtimes || $show_oneperline} {
      set one_per_line 1
      set hstyle "terse"
      set theader_shown 0
      set theader_cols [list "Package/Alias" "Versions" "Last mod."]
   } else {
      set one_per_line 0
      set hstyle "sepline"
   }

   # disable error reporting to avoid modulefile errors
   # to mix with avail results
   inhibitErrorReport


   # look if aliases have been defined in the global or user-specific
   # modulerc and display them if any in a dedicated list
   set display_list [listModules "" "$mod" 1 $show_filter "rc_alias_only"]
   if {[llength $display_list] > 0} {
      if {$show_modtimes && !$theader_shown} {
         set theader_shown 1
         eval displayTableHeader $theader_cols
      }
      eval displayElementList "{global/user modulerc}" $hstyle $one_per_line\
         0 $display_list
   }

   foreach dir [getModulePathList "exiterronundef"] {
      set display_list [listModules "$dir" "$mod" 1 $show_filter]
      if {[llength $display_list] > 0} {
         if {$show_modtimes && !$theader_shown} {
            set theader_shown 1
            eval displayTableHeader $theader_cols
         }
         eval displayElementList $dir $hstyle $one_per_line 0 $display_list
      }
   }

   reenableErrorReport
}

proc cmdModuleUse {args} {
   reportDebug "cmdModuleUse: $args"

   if {$args eq ""} {
      showModulePath
   } else {
      set pos "prepend"
      foreach path $args {
         switch -- $path {
            {-a} - {--append} - {-append} {
               set pos "append"
            }
            {-p} - {--prepend} - {-prepend} {
               set pos "prepend"
            }
            {} {
               reportError "Directory name empty"
            }
            default {
               # tranform given path in an absolute path to avoid
               # dependency to the current work directory.
               set path [getAbsolutePath $path]
               if {[file isdirectory [resolvStringWithEnv $path]]} {
                  pushMode "load"
                  catch {
                     add-path $pos MODULEPATH $path
                  }
                  popMode
               } else {
                  reportError "Directory '$path' not found"
               }
            }
         }
      }
   }
}

proc cmdModuleUnuse {args} {
   reportDebug "cmdModuleUnuse: $args"

   if {$args eq ""} {
      showModulePath
   } else {
      foreach path $args {
         # get current module path list
         if {![info exists modpathlist]} {
            set modpathlist [getModulePathList "returnempty" 0]
         }

         # transform given path in an absolute path which should have been
         # registered in the MODULEPATH env var. however for compatibility
         # with previous behavior where relative paths were registered in
         # MODULEPATH given path is first checked against current path list
         set abspath [getAbsolutePath $path]
         if {[lsearch -exact $modpathlist $path] >= 0} {
            set unusepath $path
         } elseif {[lsearch -exact $modpathlist $abspath] >= 0} {
            set unusepath $abspath
         } else {
            set unusepath ""
         }

         if {$unusepath ne ""} {
            pushMode "unload"
            catch {
               unload-path MODULEPATH $unusepath
            }
            popMode

            # refresh path list after unload
            set modpathlist [getModulePathList "returnempty" 0]
            if {[lsearch -exact $modpathlist $unusepath] >= 0} {
               reportWarning "Did not unuse $unusepath"
            }
         }
      }
   }
}

proc cmdModuleAutoinit {} {
   global g_autoInit argv0 env

   reportDebug "cmdModuleAutoinit:"

   # flag to make renderSettings define the module command
   set g_autoInit 1

   # initialize env variables around module command
   pushMode "load"

   # default MODULESHOME
   setenv MODULESHOME "@prefix@"

   # register command location
   setenv MODULES_CMD [getAbsolutePath $argv0]

   # define current Modules version if versioning enabled
   @VERSIONING@if {![info exists env(MODULE_VERSION)]} {
   @VERSIONING@   setenv MODULE_VERSION "@MODULES_RELEASE@@MODULES_BUILD@"
   @VERSIONING@   setenv MODULE_VERSION_STACK "@MODULES_RELEASE@@MODULES_BUILD@"
   @VERSIONING@}

   # initialize default MODULEPATH and LOADEDMODULES
   if {![info exists env(MODULEPATH)] || $env(MODULEPATH) eq ""} {
      # set modpaths defined in .modulespath config file if it exists
      if {[file readable "@initdir@/.modulespath"]} {
         set fid [open "@initdir@/.modulespath" r]
         set fdata [split [read $fid] "\n"]
         close $fid
         foreach fline $fdata {
            if {[regexp {^\s*(.*?)\s*(#.*|)$} $fline match patharg] == 1\
               && $patharg ne ""} {
               eval cmdModuleUse --append [split $patharg ":"]
            }
         }
      }

      if {![info exists env(MODULEPATH)]} {
         setenv MODULEPATH ""
      }
   }
   if {![info exists env(LOADEDMODULES)]} {
      setenv LOADEDMODULES ""
   }

   # source initialization modulerc if any and if no env already initialized
   if {$env(MODULEPATH) eq "" && $env(LOADEDMODULES) eq ""\
      && [file exists "@initdir@/modulerc"]} {
      cmdModuleSource "@initdir@/modulerc"
   }

   popMode
}

proc cmdModuleInit {args} {
   global g_shell env

   set init_cmd [lindex $args 0]
   set init_list [lrange $args 1 end]
   set notdone 1
   set nomatch 1

   reportDebug "cmdModuleInit: $args"

   # Define startup files for each shell
   set files(csh) [list ".modules" ".cshrc" ".cshrc_variables" ".login"]
   set files(tcsh) [list ".modules" ".tcshrc" ".cshrc" ".cshrc_variables"\
      ".login"]
   set files(sh) [list ".modules" ".bash_profile" ".bash_login" ".profile"\
      ".bashrc"]
   set files(bash) $files(sh)
   set files(ksh) $files(sh)
   set files(fish) [list ".modules" ".config/fish/config.fish"]
   set files(zsh) [list ".modules" ".zshrc" ".zshenv" ".zlogin"]

   # Process startup files for this shell
   set current_files $files($g_shell)
   foreach filename $current_files {
      if {$notdone} {
         set filepath $env(HOME)
         append filepath "/" $filename

         reportDebug "cmdModuleInit: Looking at $filepath"
         if {[file readable $filepath] && [file isfile $filepath]} {
            set newinit {}
            set thismatch 0
            set fid [open $filepath r]

            while {[gets $fid curline] >= 0} {
               # Find module load/add command in startup file 
               set comments {}
               if {$notdone && [regexp {^([ \t]*module[ \t]+(load|add)[\
                  \t]*)(.*)} $curline match cmd subcmd modules]} {
                  set nomatch 0
                  set thismatch 1
                  regexp {([ \t]*\#.+)} $modules match comments
                  regsub {\#.+} $modules {} modules

                  # remove existing references to the named module from
                  # the list Change the module command line to reflect the 
                  # given command
                  switch -- $init_cmd {
                     {list} {
                        if {![info exists notheader]} {
                           report "$g_shell initialization file\
                              \$HOME/$filename loads modules:"
                           set notheader 0
                        }
                        report "\t$modules"
                     }
                     {add} {
                        foreach newmodule $init_list {
                           set modules [replaceFromList $modules $newmodule]
                        }
                        lappend newinit "$cmd$modules $init_list$comments"
                        # delete new modules in potential next lines
                        set init_cmd "rm"
                     }
                     {prepend} {
                        foreach newmodule $init_list {
                           set modules [replaceFromList $modules $newmodule]
                        }
                        lappend newinit "$cmd$init_list $modules$comments"
                        # delete new modules in potential next lines
                        set init_cmd "rm"
                     }
                     {rm} {
                        set oldmodcount [llength $modules]
                        foreach oldmodule $init_list {
                           set modules [replaceFromList $modules $oldmodule]
                        }
                        set modcount [llength $modules]
                        if {$modcount > 0} {
                           lappend newinit "$cmd$modules$comments"
                        } else {
                           lappend newinit [string trim $cmd]
                        }
                        if {$oldmodcount > $modcount} {
                           set notdone 0
                        }
                     }
                     {switch} {
                        set oldmodule [lindex $init_list 0]
                        set newmodule [lindex $init_list 1]
                        set newmodules [replaceFromList $modules\
                           $oldmodule $newmodule]
                        lappend newinit "$cmd$newmodules$comments"
                        if {"$modules" ne "$newmodules"} {
                           set notdone 0
                        }
                     }
                     {clear} {
                        lappend newinit [string trim $cmd]
                     }
                  }
               } else {
                  # copy the line from the old file to the new
                  lappend newinit $curline
               }
            }

            close $fid

            if {$init_cmd ne "list" && $thismatch} {
               reportDebug "cmdModuleInit: Writing $filepath"
               if {[catch {
                  set fid [open $filepath w]
                  puts $fid [join $newinit "\n"]
                  close $fid
               } errMsg ]} {
                  reportErrorAndExit "Init file $filepath cannot be\
                     written.\n$errMsg"
               }
            }
         }
      }
   }

   # quit in error if command was not performed due to no match
   if {$nomatch && $init_cmd ne "list"} {
      reportErrorAndExit "Cannot find a 'module load' command in any of the\
         '$g_shell' startup files"
   }
}

# provide access to modulefile specific commands from the command-line, making
# them standing as a module sub-command (see module procedure)
proc cmdModuleResurface {cmd args} {
   global g_return_false g_return_text

   reportDebug "cmdModuleResurface: cmd='$cmd', args='$args'"

   pushMode "load"
   pushCommandName $cmd

   # run modulefile command and get its result
   if {[catch {eval $cmd $args} res]} {
      # report error if any and return false
      reportError $res
   } else {
      # register result depending of return kind (false or text)
      switch -- $cmd {
         {module-info} {
            set g_return_text $res
         }
         default {
            if {$res == 0} {
               # render false if command returned false
               set g_return_false 1
            }
         }
      }
   }

   popCommandName
   popMode
}

proc cmdModuleTest {args} {
   reportDebug "cmdModuleTest: testing $args"

   pushMode "test"
   set first_report 1
   foreach mod $args {
      lassign [getPathToModule $mod] modfile modname
      if {$modfile ne ""} {
         pushSpecifiedName $mod
         pushModuleName $modname
         # only one separator lines between 2 modules
         if {$first_report} {
            displaySeparatorLine
            set first_report 0
         }
         report "Module Specific Test for $modfile:\n"
         execute-modulefile $modfile
         popModuleName
         popSpecifiedName
         displaySeparatorLine
      }
   }
   popMode
}

proc cmdModuleHelp {args} {
   pushMode "help"
   set first_report 1
   foreach arg $args {
      if {$arg ne ""} {
         lassign [getPathToModule $arg] modfile modname

         if {$modfile ne ""} {
            pushSpecifiedName $arg
            pushModuleName $modname
            # only one separator lines between 2 modules
            if {$first_report} {
               displaySeparatorLine
               set first_report 0
            }
            report "Module Specific Help for $modfile:\n"
            execute-modulefile $modfile
            popModuleName
            popSpecifiedName
            displaySeparatorLine
         }
      }
   }
   popMode
   if {[llength $args] == 0} {
      reportVersion
      report {Usage: module [options] [command] [args ...]

Loading / Unloading commands:
  add | load      modulefile [...]  Load modulefile(s)
  rm | unload     modulefile [...]  Remove modulefile(s)
  purge                             Unload all loaded modulefiles
  reload | refresh                  Unload then load all loaded modulefiles
  switch | swap   [mod1] mod2       Unload mod1 and load mod2

Listing / Searching commands:
  list            [-t|-l]           List loaded modules
  avail   [-d|-L] [-t|-l] [mod ...] List all or matching available modules
  aliases                           List all module aliases
  whatis          [modulefile ...]  Print whatis information of modulefile(s)
  apropos | keyword | search  str   Search all name and whatis containing str
  is-loaded       [modulefile ...]  Test if any of the modulefile(s) are loaded
  is-avail        modulefile [...]  Is any of the modulefile(s) available
  info-loaded     modulefile        Get full name of matching loaded module(s)

Collection of modules handling commands:
  save            [collection|file] Save current module list to collection
  restore         [collection|file] Restore module list from collection or file
  saverm          [collection]      Remove saved collection
  saveshow        [collection|file] Display information about collection
  savelist        [-t|-l]           List all saved collections
  is-saved        [collection ...]  Test if any of the collection(s) exists

Shell's initialization files handling commands:
  initlist                          List all modules loaded from init file
  initadd         modulefile [...]  Add modulefile to shell init file
  initrm          modulefile [...]  Remove modulefile from shell init file
  initprepend     modulefile [...]  Add to beginning of list in init file
  initswitch      mod1 mod2         Switch mod1 with mod2 from init file
  initclear                         Clear all modulefiles from init file

Environment direct handling commands:
  prepend-path [-d c] var val [...] Prepend value to environment variable
  append-path [-d c] var val [...]  Append value to environment variable
  remove-path [-d c] var val [...]  Remove value from environment variable

Other commands:
  help            [modulefile ...]  Print this or modulefile(s) help info
  display | show  modulefile [...]  Display information about modulefile(s)
  test            [modulefile ...]  Test modulefile(s)
  use     [-a|-p] dir [...]         Add dir(s) to MODULEPATH variable
  unuse           dir [...]         Remove dir(s) from MODULEPATH variable
  is-used         [dir ...]         Is any of the dir(s) enabled in MODULEPATH
  path            modulefile        Print modulefile path
  paths           modulefile        Print path of matching available modules
  source          scriptfile [...]  Execute scriptfile(s)

Switches:
  -t | --terse    Display output in terse format
  -l | --long     Display output in long format
  -d | --default  Only show default versions available
  -L | --latest   Only show latest versions available
  -a | --append   Append directory to MODULEPATH
  -p | --prepend  Prepend directory to MODULEPATH

Options:
  -h | --help     This usage info
  -V | --version  Module version
  -D | --debug    Enable debug messages
  --paginate      Pipe mesg output into a pager if stream attached to terminal
  --no-pager      Do not pipe message output into a pager}
   }
}

########################################################################
# main program

# needed on a gentoo system. Shouldn't hurt since it is
# supposed to be the default behavior
fconfigure stderr -translation auto

if {[catch {
   # parse all command-line arguments before doing any action, no output is
   # made during argument parse to wait for potential paging to be setup
   set show_help 0
   set show_version 0
   reportDebug "CALLING $argv0 $argv"

   # source site configuration script if any
   if {[file readable $g_siteconfig]} {
      reportDebug "Source site configuration ($g_siteconfig)"
      if {[catch {source $g_siteconfig} errMsg]} {
         reportErrorAndExit "Site configuration source failed\n$errMsg"
      }
   }

   # Parse shell
   set g_shell [lindex $argv 0]
   switch -- $g_shell {
      {sh} - {bash} - {ksh} - {zsh} {
         set g_shellType sh
      }
      {csh} - {tcsh} {
         set g_shellType csh
      }
      {fish} - {cmd} - {tcl} - {perl} - {python} - {ruby} - {lisp} - {cmake}\
         - {r} {
         set g_shellType $g_shell
      }
      default {
         reportErrorAndExit "Unknown shell type \'($g_shell)\'"
      }
   }

   # extract options and command switches from other args
   set otherargv {}
   set ddelimarg 0
   foreach arg [lrange $argv 1 end] {
      if {[info exists ignore_next_arg]} {
         unset ignore_next_arg
      } else {
         switch -glob -- $arg {
            {-D} - {--debug} {
               set g_debug 1
            }
            {--help} - {-h} {
               set show_help 1
            }
            {-V} - {--version} {
               set show_version 1
            }
            {--paginate} {
               set asked_pager 1
            }
            {--no-pager} {
               set asked_pager 0
            }
            {-t} - {--terse} {
               set show_oneperline 1
               set show_modtimes 0
            }
            {-l} - {--long} {
               set show_modtimes 1
               set show_oneperline 0
            }
            {-d} - {--default} {
               # in case of *-path command, -d means --delim
               if {$arg eq "-d" && $ddelimarg} {
                  lappend otherargv $arg
               } else {
                  set show_filter "onlydefaults"
               }
            }
            {-L} - {--latest} {
               set show_filter "onlylatest"
            }
            {-a} - {--append} - {-append} - {-p} - {--prepend} - {-prepend} \
            - {--delim} - {-delim} - {--delim=*} - {-delim=*} \
            - {--duplicates} - {--index} {
               # command-specific switches interpreted later on
               lappend otherargv $arg
            }
            {append-path} - {prepend-path} - {remove-path} {
               # detect *-path commands to say -d means --delim, not --default
               set ddelimarg 1
               lappend otherargv $arg
            }
            {-f} - {--force} - {--human} - {-v} - {--verbose} - {-s} -\
               {--silent} - {-c} - {--create} - {-i} - {--icase} -\
               {--userlvl=*} {
               # ignore C-version specific option, no error only warning
               reportWarning "Unsupported option '$arg'"
            }
            {-u} - {--userlvl} {
               reportWarning "Unsupported option '$arg'"
               # also ignore argument value
               set ignore_next_arg 1
            }
            {-*} {
                reportErrorAndExit "Invalid option '$arg'\nTry\
                  'module --help' for more information."
            }
            default {
               lappend otherargv $arg
            }
         }
      }
   }

   # now options are known initialize error report (start pager if enabled)
   initErrorReport

   # put back quarantine variables in env, if quarantine mechanism supported
   if {[info exists env(MODULES_RUN_QUARANTINE)] && $g_shellType ne "csh"} {
      foreach var [split $env(MODULES_RUN_QUARANTINE) " "] {
         # check variable name is valid
         if {[regexp {^[A-Za-z_][A-Za-z0-9_]*$} $var]} {
            set quarvar "${var}_modquar"
            # put back value
            if {[info exists env($quarvar)]} {
               reportDebug "Release '$var' environment variable from\
                  quarantine ($env($quarvar))"
               set env($var) $env($quarvar)
               unset env($quarvar)
            # or unset env var if no value found in quarantine
            } elseif {[info exists env($var)]} {
               reportDebug "Unset '$var' environment variable after\
                  quarantine"
               unset env($var)
            }
         } elseif {[string length $var] > 0} {
            reportWarning "Bad variable name set in MODULES_RUN_QUARANTINE\
               ($var)"
         }
      }
   }

   if {$show_help} {
      cmdModuleHelp
      cleanupAndExit 0
   }
   if {$show_version} {
      reportVersion
      cleanupAndExit 0
   }

   set command [lindex $otherargv 0]
   # default command is help if none supplied
   if {$command eq ""} {
      set command "help"
   }
   set otherargv [lreplace $otherargv 0 0]

   cacheCurrentModules

   # no modulefile is currently being interpreted
   pushModuleFile {}

   # Find and execute any .modulerc file found in the module directories
   # defined in env(MODULESPATH)
   runModulerc

   # eval needed to pass otherargv as list to module proc
   eval module $command $otherargv
} errMsg ]} {
   # no use of reportError here to get independent from any
   # previous error report inhibition
   report "ERROR: $errMsg"
   cleanupAndExit 1
}

cleanupAndExit 0

# ;;; Local Variables: ***
# ;;; mode:tcl ***
# ;;; End: ***
# vim:set tabstop=3 shiftwidth=3 expandtab autoindent:
