MOON
Server: Apache
System: Linux s5.yayogua.com.py 6.12.0-124.55.3.el10_1.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 7 16:54:02 EDT 2026 x86_64
User: w3com (1005)
PHP: 8.2.32
Disabled: NONE
Upload Files
File: /home/w3com/public_html/home/000~ROOT~000/bin/modulecmd
#!/usr/bin/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-2025 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/>.

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

# Runtime state properties (default value, proc to call to initialize state
# value?)
##nagelfar ignore +38 Found constant
array set g_state_defs [list\
   autoinit {0}\
   cache_mcookie_version {5.3}\
   clock_seconds {<undef> initStateClockSeconds}\
   domainname {<undef> {runCommand domainname}}\
   error_count {0}\
   extra_siteconfig_loaded {0}\
   false_rendered {0}\
   force {0}\
   hiding_threshold {0 initStateHidingThreshold}\
   inhibit_errreport {0}\
   inhibit_interp {0}\
   init_error_report {0}\
   is_stderr_tty {<undef> initStateIsStderrTty}\
   is_win {<undef> initStateIsWin}\
   kernelversion {<undef> {runCommand uname -v}}\
   lm_info_cached {0}\
   logfd {{} initStateLogfd}\
   logging {<undef> initStateLogging}\
   lsb_codename {<undef> {runCommand lsb_release -s -c}}\
   lsb_id {<undef> {runCommand lsb_release -s -i}}\
   lsb_release {<undef> {runCommand lsb_release -s -r}}\
   machine [list $::tcl_platform(machine)]\
   modules_release {5.6.1}\
   nodename {<undef> {runCommand uname -n}}\
   os [list $::tcl_platform(os)]\
   osversion [list $::tcl_platform(osVersion)]\
   paginate {<undef> initStatePaginate}\
   path_separator {<undef> initStatePathSeparator}\
   report_format {regular}\
   reportfd {stderr initStateReportfd}\
   return_false {0}\
   siteconfig_loaded {0}\
   shelltype {<undef> initStateShellType}\
   sub1_separator {&}\
   sub2_separator {|}\
   tcl_ext_lib_loaded {0}\
   tcl_version [list [info patchlevel]]\
   term_columns {<undef> initStateTermColumns}\
   timer {0}\
   usergroups {<undef> initStateUsergroups}\
   username {<undef> initStateUsername}\
]

# Configuration option properties (superseding environment variable, default
# value, is configuration lockable to default value, value kind, valid value
# list?, internal value representation?, proc to call to initialize option
# value, valid value list kind
##nagelfar ignore #81 Too long line
array set g_config_defs [list\
   contact {MODULECONTACT root@localhost 0 s}\
   abort_on_error {MODULES_ABORT_ON_ERROR {ml:reload:switch_unload} 0 l {load ml\
      mod-to-sh purge reload switch switch_unload try-load unload} {} {}\
      eltlist}\
   auto_handling {MODULES_AUTO_HANDLING 1 0 b {0 1}}\
   avail_indepth {MODULES_AVAIL_INDEPTH 1 0 b {0 1}}\
   avail_output {MODULES_AVAIL_OUTPUT {modulepath:alias:dirwsym:sym:tag:variantifspec:key} 0 l {modulepath alias\
      provided-alias dirwsym indesym sym tag key hidden variant variantifspec\
      via} {} {} eltlist}\
   avail_terse_output {MODULES_AVAIL_TERSE_OUTPUT {modulepath:alias:dirwsym:sym:tag:variantifspec} 0 l\
      {modulepath alias provided-alias dirwsym indesym sym tag key hidden\
      variant variantifspec} {} {} eltlist}\
   cache_buffer_bytes {MODULES_CACHE_BUFFER_BYTES 32768 0 i {4096 1000000} {}\
      {} intbe}\
   cache_expiry_secs {MODULES_CACHE_EXPIRY_SECS 0 0 i {0 31536000} {} {}\
      intbe}\
   collection_pin_version {MODULES_COLLECTION_PIN_VERSION 0 0 b {0 1}}\
   collection_pin_tag {MODULES_COLLECTION_PIN_TAG 0 0 b {0 1}}\
   collection_target {MODULES_COLLECTION_TARGET <undef> 0 s}\
   color {MODULES_COLOR auto 0 s {never auto always} {0 1 2}\
      initConfColor}\
   colors {MODULES_COLORS {} 0 l {} {} initConfColors}\
   conflict_unload {MODULES_CONFLICT_UNLOAD 0 0 b {0 1}}\
   csh_limit {{} 4000 0 i}\
   extra_siteconfig {MODULES_SITECONFIG <undef> 1 s {}}\
   editor {MODULES_EDITOR {vi} 0 s {} {} initConfEditor}\
   hide_auto_loaded {MODULES_HIDE_AUTO_LOADED 0 0 b {0 1}}\
   home {MODULESHOME {/usr/share/Modules} 0 s}\
   icase {MODULES_ICASE search 0 s {never search always}}\
   ignore_cache {MODULES_IGNORE_CACHE 0 0 b {0 1}}\
   ignore_user_rc {MODULES_IGNORE_USER_RC 0 0 b {0 1}}\
   ignored_dirs {{} {CVS RCS SCCS .svn .git .SYNC .sos} 0 o}\
   implicit_requirement {MODULES_IMPLICIT_REQUIREMENT 1 0\
      b {0 1}}\
   list_output {MODULES_LIST_OUTPUT {header:idx:variant:sym:tag:key} 0 l {header idx variant\
      alias indesym sym tag hidden key} {} {} eltlist}\
   list_terse_output {MODULES_LIST_TERSE_OUTPUT {header} 0 l\
      {header idx variant alias indesym sym tag hidden key} {} {} eltlist}\
   locked_configs {{} {} 0 o}\
   logged_events {MODULES_LOGGED_EVENTS {} 1 l {auto_eval\
      requested_eval requested_cmd} {} {} eltlist}\
   logger {MODULES_LOGGER {/usr/bin/logger -t modules} 1 s}\
   mcookie_check {MODULES_MCOOKIE_CHECK always 0 s {eval always}}\
   mcookie_version_check {MODULES_MCOOKIE_VERSION_CHECK\
      1 0 b {0 1}}\
   ml {MODULES_ML 1 0 b {0 1}}\
   nearly_forbidden_days {MODULES_NEARLY_FORBIDDEN_DAYS 14\
      0 i {0 365} {} {} intbe}\
   pager {MODULES_PAGER {/usr/bin/less -eFKRX} 0 s}\
   protected_envvars {MODULES_PROTECTED_ENVVARS <undef> 0 l}\
   rcfile {MODULERCFILE <undef> 0 l}\
   redirect_output {MODULES_REDIRECT_OUTPUT 1 0 b {0 1}}\
   require_via {MODULES_REQUIRE_VIA 0 0 b {0 1}}\
   reset_target_state {MODULES_RESET_TARGET_STATE __init__ 0 s}\
   quarantine_support {MODULES_QUARANTINE_SUPPORT 0 0 b {0\
      1}}\
   run_quarantine {MODULES_RUN_QUARANTINE <undef> 0 o}\
   shells_with_ksh_fpath {MODULES_SHELLS_WITH_KSH_FPATH {} 0 l {sh bash csh\
      tcsh fish} {} {} eltlist}\
   silent_shell_debug {MODULES_SILENT_SHELL_DEBUG 0 0 b {0\
      1}}\
   siteconfig {{} {/etc/environment-modules/siteconfig.tcl} 0 s}\
   spider_indepth {MODULES_SPIDER_INDEPTH 1 0 b {0 1}}\
   spider_output {MODULES_SPIDER_OUTPUT {modulepath:alias:dirwsym:sym:tag:variantifspec:via:key} 0 l {modulepath\
      alias provided-alias dirwsym indesym sym tag key hidden variant\
      variantifspec via} {} {} eltlist}\
   spider_terse_output {MODULES_SPIDER_TERSE_OUTPUT {modulepath:alias:dirwsym:sym:tag:variantifspec} 0 l\
      {modulepath alias provided-alias dirwsym indesym sym tag key hidden\
      variant variantifspec} {} {} eltlist}\
   source_cache {MODULES_SOURCE_CACHE 0 0 b {0 1}}\
   sticky_purge {MODULES_STICKY_PURGE {error} 0 s {error warning\
      silent}}\
   tag_abbrev {MODULES_TAG_ABBREV {auto-loaded=aL:loaded=L:hidden=H:hidden-loaded=H:forbidden=F:nearly-forbidden=nF:sticky=S:super-sticky=sS:keep-loaded=kL:warning=W} 0 l {} {} initConfTagAbbrev}\
   tag_color_name {MODULES_TAG_COLOR_NAME {} 0 l {} {}\
      initConfTagColorName}\
   tcl_ext_lib {{} {} 0 s {} {} initConfTclExtLib}\
   tcl_linter {MODULES_TCL_LINTER {nagelfar.tcl} 0 s}\
   term_background {MODULES_TERM_BACKGROUND dark 0 s {dark light}}\
   term_width {MODULES_TERM_WIDTH 0 0 i {0 1000} {} {} intbe}\
   unique_name_loaded {MODULES_UNIQUE_NAME_LOADED 0 0 b {0\
      1}}\
   unload_match_order {MODULES_UNLOAD_MATCH_ORDER returnlast 0 s\
      {returnlast returnfirst}}\
   implicit_default {MODULES_IMPLICIT_DEFAULT 1 1 b {0 1}}\
   extended_default {MODULES_EXTENDED_DEFAULT 1 0 b {0 1}}\
   advanced_version_spec {MODULES_ADVANCED_VERSION_SPEC 1 0 b {0\
      1}}\
   search_match {MODULES_SEARCH_MATCH starts_with 0 s {starts_with\
      contains}}\
   set_shell_startup {MODULES_SET_SHELL_STARTUP 0 0 b {0 1}}\
   variant_shortcut {MODULES_VARIANT_SHORTCUT {} 0 l {} {}\
      initConfVariantShortcut}\
   verbosity {MODULES_VERBOSITY normal 0 s {silent concise normal\
      verbose verbose2 trace debug debug2}}\
   wa_277 {MODULES_WA_277 0 0 b {0 1}}\
]

# Get state value
proc getState {state {valifundef {}} {catchinitproc 0}} {
   if {![info exists ::g_states($state)]} {
      # fetch state properties (including its default value) if defined
      if {[info exists ::g_state_defs($state)]} {
         lassign $::g_state_defs($state) value initproclist
      } else {
         set value <undef>
         set initproclist {}
      }

      # call specific proc to initialize state if any
      if {$initproclist ne {}} {
         # catch init procedure error and report it as warning, so default
         # value will get set for state
         if {$catchinitproc} {
            if {[catch {set value [{*}$initproclist]} errMsg]} {
               reportWarning $errMsg
            }
         } else {
            set value [{*}$initproclist]
         }
      # overridden value coming the command-line
      ##nagelfar ignore Suspicious variable
      } elseif {[info exists ::asked_$state]} {
         set value [set ::asked_$state]
      }

      # return passed value if undefined and no value record
      if {$value eq {<undef>}} {
         set value $valifundef
      } else {
         setState $state $value
      }
      return $value
   } else {
      return $::g_states($state)
   }
}

# Clear state
proc unsetState {state} {
   if {[isStateDefined $state]} {
      unset ::g_states($state)
      reportDebug "$state unset"
   }
}

# Set state value
proc setState {state value} {
   set ::g_states($state) $value
   reportDebug "$state set to '$value'"
}

# Append each passed value to the existing state value list
proc lappendState {state args} {
   if {$state eq {-nodup}} {
      set state [lindex $args 0]
      # retrieve current val through getState to initialize it if still undef
      set value [getState $state]
      ##nagelfar ignore Found constant
      appendNoDupToList value {*}[lrange $args 1 end]
      setState $state $value
   } else {
      lappend ::g_states($state) {*}$args
      reportDebug "$state appended with '$args'"
   }
}

# Remove last element from state value list
proc lpopState {state} {
   setState $state [lrange [getState $state] 0 end-1]
}

# Return first element from state value list
proc topState {state} {
   return [lindex [getState $state] 0]
}

# Return last element from state value list
proc currentState {state} {
   return [lindex [getState $state] end]
}

# Get number of element from state value list
proc depthState {state} {
   return [llength [getState $state]]
}

# Check if state has been defined
proc isStateDefined {state} {
   return [info exists ::g_states($state)]
}

# Check if state equals passed value
proc isStateEqual {state value} {
   return [expr {[getState $state] eq $value}]
}

proc isConfigLocked {option} {
   return [expr {[lsearch -exact [getConf locked_configs] $option] != -1}]
}

# Get configuration option value
proc getConf {option {valifundef {}}} {
   if {![info exists ::g_configs($option)]} {
      # fetch option properties (including its default value)
      lassign $::g_config_defs($option) envvar value islockable valuekind\
         validvallist intvallist initproc validvallistkind

      # ensure option is not locked before superseding its default value
      if {!$islockable || ![isConfigLocked $option]} {
         # call specific proc to initialize config option if any
         if {$initproc ne {}} {
            set value [$initproc $envvar $value $validvallist $intvallist]
         } else {
            # overridden value coming from environment
            if {$envvar ne {} && [isEnvVarDefined $envvar]} {
               switch -- $validvallistkind {
                  eltlist {
                     # ignore non-valid values
                     if {![isDiffBetweenList [split $::env($envvar) :]\
                        $validvallist]} {
                        set value $::env($envvar)
                     }
                  }
                  intbe {
                     # ignore non-valid values
                     if {[string is integer -strict $::env($envvar)] &&\
                        $::env($envvar) >= [lindex $validvallist 0] &&\
                        $::env($envvar) <= [lindex $validvallist 1]} {
                        set value $::env($envvar)
                     }
                  }
                  {} {
                     # ignore non-valid values
                     ##nagelfar ignore +3 Non static subcommand
                     if {[switch -- [llength $validvallist] {
                        0 {expr {1 == 1}}
                        1 {string is $validvallist -strict $::env($envvar)}
                        default {expr {$::env($envvar) in $validvallist}}
                     }]} {
                        set value $::env($envvar)
                     }
                  }
               }
            }

            # overridden value coming the command-line (already validated)
            ##nagelfar ignore Suspicious variable
            if {[info exists ::asked_$option]} {
               set askedval [set ::asked_$option]
               # append or subtract value to existing configuration value if
               # new value starts with '+' or '-' (for colon-separated list
               # option only)
               if {$valuekind eq {l} && [string index $askedval 0] in {+ -}} {
                  set curvaluelist [split $value :]
                  switch -- [string index $askedval 0] {
                     + {
                        ##nagelfar ignore Found constant
                        appendNoDupToList curvaluelist {*}[split [string\
                           range $askedval 1 end] :]
                     }
                     - {
                        lassign [getDiffBetweenList $curvaluelist [split\
                           [string range $askedval 1 end] :]] curvaluelist
                     }
                  }
                  set value [join $curvaluelist :]
               } else {
                  set value $askedval
               }
            }

            # convert value to its internal representation
            if {[llength $intvallist]} {
               set value [lindex $intvallist [lsearch -exact $validvallist\
                  $value]]
            }
         }
      }

      # return passed value if undefined and no value record
      if {$value eq {<undef>}} {
         set value $valifundef
      } else {
         setConf $option $value
      }
      return $value
   } else {
      return $::g_configs($option)
   }
}

# Set configuration option value
proc setConf {option value} {
   set ::g_configs($option) $value
   reportDebug "$option set to '$value'"
}

# Unset configuration option value if it is set
proc unsetConf {option} {
   if {[info exists ::g_configs($option)]} {
      unset ::g_configs($option)
      reportDebug "$option unset"
   }
}

# Append each passed value to the existing config option value list
proc lappendConf {option args} {
   # retrieve current value through getConf to initialize it if still undef
   set value [getConf $option]
   ##nagelfar ignore Found constant
   appendNoDupToList value {*}$args
   setConf $option $value
}

# Get configuration option value split as a list
proc getConfList {option {valifundef {}}} {
   return [split [getConf $option $valifundef] :]
}

# Source site config which can be used to define global procedures or
# settings. We first look for the global siteconfig, then if an extra
# siteconfig is defined and allowed, source that file if it exists
proc sourceSiteConfig {} {
   lappend siteconfiglist [getConf siteconfig]
   for {set i 0} {$i < [llength $siteconfiglist]} {incr i} {
      set siteconfig [lindex $siteconfiglist $i]
      if {[file readable $siteconfig]} {
         reportDebug "Source site configuration ($siteconfig)"
         if {[catch {uplevel 1 source "{$siteconfig}"} errMsg]} {
            set errMsg "Site configuration source failed\n"
            # issue line number is lost due to uplevel use
            append errMsg [formatErrStackTrace $::errorInfo $siteconfig {}]
            reportErrorAndExit $errMsg
         }
         ##nagelfar ignore Found constant
         if {$siteconfig eq [getConf siteconfig]} {
            setState siteconfig_loaded 1
         } else {
            setState extra_siteconfig_loaded 1
         }
      }
      # check on extra_siteconfig after initial siteconfig loaded in case
      # it inhibits this extra load
      ##nagelfar ignore Found constant
      if {$siteconfig eq [getConf siteconfig] && [getConf\
         extra_siteconfig] ne {}} {
         lappend siteconfiglist [getConf extra_siteconfig]
      }
   }
}

# Used to tell if a machine is running Windows or not
proc initStateIsWin {} {
   return [expr {$::tcl_platform(platform) eq {windows}}]
}

# Get default path separator
proc initStatePathSeparator {} {
   return [expr {[getState is_win] ? {;} : {:}}]
}

# Detect if terminal is attached to stderr message channel
proc initStateIsStderrTty {} {
   return [expr {![catch {fconfigure stderr -mode}]}]
}

# Determine if pagination need to be started
proc initStatePaginate {} {
   set pager [getConf pager]
   # empty or 'cat' pager command means no-pager
   set no_cmds [list {} cat]
   set only_shell_types [list sh csh fish cmd pwsh]
   # default pager enablement depends on pager command value and current shell
   set paginate [expr {[file tail [lindex $pager 0]] ni $no_cmds && [getState\
      shelltype] in $only_shell_types}]

   # asked enablement could only nullify a previous asked disablement as it
   # requires a valid pager command configuration, which by default enables
   # pagination; some module command may also turn off pager; also if error
   # stream is not attached to a terminal
   set no_subcmds [list clear edit]
   if {$paginate && (([info exists ::asked_paginate] && !$::asked_paginate)\
      || [getState subcmd] in $no_subcmds || ([getState subcmd] eq {ml} &&\
      [lindex [getState subcmd_args] 0] in $no_subcmds) || ![getState\
      is_stderr_tty])} {
      set paginate 0
   }

   return $paginate
}

# start pager pipe process with defined configuration
proc initStateReportfd {} {
   # get default value
   lassign $::g_state_defs(reportfd) reportfd

   # start pager at first call and only if enabled
   if {[getState paginate]} {
      if {[catch {
         set reportfd [open "|[getConf pager] >@stderr 2>@stderr" w]
         fconfigure $reportfd -buffering line -blocking 1 -buffersize 65536
      } errMsg]} {
         # silently set reportfd to its fallback value to process warn msg
         set ::g_states(reportfd) $reportfd
         reportWarning $errMsg
      }
   }

   # startup content in case of structured output format (puts here rather
   # calling report proc to avoid infinite reportfd init loop
   if {[isStateEqual report_format json]} {
      puts -nonewline $reportfd \{
   }

   return $reportfd
}

# Determine if logging need to be started
proc initStateLogging {} {
   set logger_not_empty [string length [lindex [getConf logger] 0]]
   set something_to_log [info exists ::g_log_msg_list]
   return [expr {$logger_not_empty && $something_to_log}]
}

# start logger pipe process with defined configuration
proc initStateLogfd {} {
   # sets default fallback value
   lassign $::g_state_defs(logfd) logfd

   # start logger at first call and only if enabled
   if {[getState logging]} {
      if {[catch {
         # drop output of logger command to avoid it pollutes main channels
         set logfd [open "|[getConf logger] >/dev/null 2>/dev/null" w]
         fconfigure $logfd -buffering none -blocking 1
      } errMsg]} {
         reportWarning $errMsg
      }
   }

   return $logfd
}

# Provide columns number for output formatting
proc initStateTermColumns {} {
   set cols [getConf term_width]
   if {$cols == 0} {
      # determine col number from tty capabilities
      # tty info query depends on running OS
      switch -- $::tcl_platform(os) {
         SunOS {
            catch {regexp {columns = (\d+);} [exec stty] match cols} errMsg
         }
         {Windows NT} {
            catch {regexp {Columns:\s+(\d+)} [exec mode] match cols} errMsg
         }
         default {
            catch {set cols [lindex [exec stty size] 1]} errMsg
         }
      }
      # default size if tty cols cannot be found
      set cols [expr {![info exists cols] || $cols eq {0} ? 80 : $cols}]
   }
   return $cols
}

# Deduce shelltype value from shell state value
proc initStateShellType {} {
   switch -- [getState shell] {
      sh - bash - ksh - zsh {
         return sh
      }
      csh - tcsh {
         return csh
      }
      default {
         return [getState shell]
      }
   }
}

proc initStateHidingThreshold {} {
   # sets default fallback value
   lassign $::g_state_defs(hiding_threshold) hiding_threshold

   if {[isEltInReport hidden 0]} {
      set hiding_threshold 2
   } elseif {[info exists ::asked_hiding_threshold]} {
      set hiding_threshold $::asked_hiding_threshold
   }

   return $hiding_threshold
}

# Get all groups of user running modulecmd.tcl process
proc __initStateUsergroups {} {
   # ensure groups including space in their name (found on Cygwin/MSYS
   # platforms) are correctly set as list element
   if {[catch {
      return [split [string range [runCommand id -G -n -z] 0 end-1] \0]
   } errMsg]} {
      # fallback if '-z' option is not supported
      return [runCommand id -G -n]
   }
}

# Get name of user running modulecmd.tcl process
proc __initStateUsername {} {
   return [runCommand id -u -n]
}

# Get Epoch time (number of seconds elapsed since Unix epoch)
proc __initStateClockSeconds {} {
   return [clock seconds]
}

# Initialize Select Graphic Rendition table
proc initConfColors {envvar value validvallist intvallist} {
   # overridden value coming from environment
   if {[isEnvVarDefined $envvar]} {
      set colors_list $::env($envvar)
      if {[catch {
         # test overridden value could be set to a dummy array variable
         array set test_colors [split $colors_list {:=}]
      } errMsg ]} {
         # report issue as a debug message rather warning to avoid
         # disturbing user with a warning message in the middle of a
         # useful output as this table will be initialized at first use
         reportDebug "Ignore invalid value set in $envvar ($colors_list)"
         unset colors_list
      }
   }

   # if no valid override set use default color theme for terminal
   # background color kind (light or dark)
   if {![info exists colors_list]} {
      if {[getConf term_background] eq {light}} {
         ##nagelfar ignore Too long line
         set colors_list {hi=1:db=2:tr=2:se=2:er=31:wa=33:me=35:in=34:mp=1;34:di=34:al=36:va=33:sy=35:de=4:cm=32:aL=107:L=47:H=2:F=101:nF=91;103:S=106:sS=104:kL=48;5;109:W=103}
      } else {
         ##nagelfar ignore Too long line
         set colors_list {hi=1:db=2:tr=2:se=2:er=91:wa=93:me=95:in=94:mp=1;94:di=94:al=96:va=93:sy=95:de=4:cm=92:aL=100:L=90;47:H=2:F=41:nF=31;43:S=46:sS=44:kL=30;48;5;109:W=30;43}
      }
      if {[catch {
         array set test_colors [split $colors_list {:=}]
      } errMsg ]} {
         reportDebug "Ignore invalid default [getConf term_background]\
            background colors ($colors_list)"
         # define an empty list if no valid value set
         set colors_list {}
      }
   }

   # check each color defined and unset invalid codes
   set value {}
   foreach {elt col} [split $colors_list {:=}] {
      if {![regexp {^[\d;]+$} $col]} {
         reportDebug "Ignore invalid color code for '$elt' ($col)"
      } else {
         lappend value $elt=$col
      }
   }
   set value [join $value :]

   # set SGR table as an array to easily access rendition for each key
   array unset ::g_colors
   array set ::g_colors [split $value {:=}]

   return $value
}

# Initialize color configuration value
proc initConfColor {envvar value validvallist intvallist} {
   # overridden value coming from environment via standard variable
   # https://no-color.org/ and https://bixense.com/clicolors/
   if {[isEnvVarDefined NO_COLOR]} {
      set value never
   } elseif {[isEnvVarDefined CLICOLOR]} {
      if {[envVarEquals CLICOLOR 0]} {
         set value never
      } else {
         set value auto
      }
   } elseif {[isEnvVarDefined CLICOLOR_FORCE] && $::env(CLICOLOR_FORCE) ne\
      {0}} {
      set value always
   }

   # overridden value coming from environment via Modules-specific variable
   if {$envvar ne {} && [isEnvVarDefined $envvar]} {
      # ignore non-valid values
      if {![llength $validvallist] || $::env($envvar) in $validvallist} {
         set value $::env($envvar)
      }
   }

   # overridden value coming the command-line
   if {[info exists ::asked_color]} {
      set value [set ::asked_color]
   }

   # convert value to its internal representation
   if {[llength $intvallist]} {
      set value [lindex $intvallist [lsearch -exact $validvallist $value]]
   }

   # disable color mode if no terminal attached except if 'always' asked
   if {$value != 0 && (![getState is_stderr_tty] || $value == 2)} {
      incr value -1
   }

   # initialize color theme if color mode enabled
   getConf colors

   return $value
}

# Initialize tcl_ext_lib configuration value
proc initConfTclExtLib {envvar value validvallist intvallist} {
   set libfile libtclenvmodules.so

   # determine lib directory
   ##nagelfar ignore #19 Strange command
   ##nagelfar ignore +13 Too long line
   #set libdir {@libdir@}
   switch -- [getState machine] {
      x86_64 - aarch64 - ppc64le - s390x {
         set libdirmain {/usr/lib64/environment-modules}
         set libdiralt {/usr/lib/environment-modules}
      }
      default {
         set libdirmain {/usr/lib/environment-modules}
         set libdiralt {/usr/lib64/environment-modules}
      }
   }
   # use alternative arch lib if available and not main one
   if {![file exists [file join $libdirmain $libfile]] && [file exists [file\
      join $libdiralt $libfile]]} {
      set libdir $libdiralt
   } else {
      set libdir $libdirmain
   }
   ##nagelfar variable libdir

   return [file join $libdir $libfile]
}

# Initialize module tag abbreviation table
proc initConfTagAbbrev {envvar value validvallist intvallist} {
   # overridden value coming from environment
   if {[isEnvVarDefined $envvar]} {
      if {[catch {
         # try to set the tag-abbreviation mapping table
         array set ::g_tagAbbrev [split $::env($envvar) {:=}]
         set value $::env($envvar)
      } errMsg ]} {
         reportWarning "Ignore invalid value set in $envvar ($::env($envvar))"
         array unset ::g_tagAbbrev
      }
   }

   # test default value
   if {![array exists ::g_tagAbbrev]} {
      if {[catch {
         array set ::g_tagAbbrev [split $value {:=}]
      } errMsg ]} {
         reportWarning "Ignore invalid default value for 'tag_abbrev' config\
            ($value)"
         array unset ::g_tagAbbrev
         # define an empty list if no valid value set
         set value {}
      }
   }

   # build abbrev:tagname array
   foreach {tag abbrev} [array get ::g_tagAbbrev] {
      # skip tags not relevant for current command, that share their
      # abbreviation with another tag
      switch -- $tag {
         hidden-loaded {
            set setabbrevtag [expr {[currentState commandname] eq {list}}]
         }
         hidden {
            set setabbrevtag [expr {[currentState commandname] in {avail\
               spider}}]
         }
         default {
            set setabbrevtag 1
         }
      }
      if {$setabbrevtag} {
         set ::g_abbrevTag($abbrev) $tag
      }
   }

   return $value
}

# Initialize module tag color name table
proc initConfTagColorName {envvar value validvallist intvallist} {
   # overridden value coming from environment
   if {[isEnvVarDefined $envvar]} {
      set value $::env($envvar)
   }

   # set table for efficient search
   foreach tag [split $value :] {
      set ::g_tagColorName($tag) 1
   }

   return $value
}

# Initialize interactive editor command
proc initConfEditor {envvar value validvallist intvallist} {
   # overridden value coming from environment via Modules-specific variable
   if {$envvar ne {} && [isEnvVarDefined $envvar]} {
      set value $::env($envvar)
   # overridden value coming from environment via standard variable
   } elseif {[isEnvVarDefined VISUAL]} {
      set value $::env(VISUAL)
   } elseif {[isEnvVarDefined EDITOR]} {
      set value $::env(EDITOR)
   }
   return $value
}

# Initialize variant shortcut table
proc initConfVariantShortcut {envvar value validvallist intvallist} {
   # overridden value coming from environment
   if {[isEnvVarDefined $envvar]} {
      if {[catch {
         # try to set the variant-shortcut mapping table
         array set testarr [split $::env($envvar) {:=}]
         set value $::env($envvar)
         set setfromenv 1
      } errMsg ]} {
         reportWarning "Ignore invalid value set in $envvar ($::env($envvar))"
      }
   }

   # test default value
   if {![info exists setfromenv]} {
      if {[catch {
         array set testarr [split $value {:=}]
      } errMsg ]} {
         reportWarning "Ignore invalid default value for 'variant_shortcut'\
            config ($value)"
         # define an empty list if no valid value set
         set value {}
      }
   }

   # ignore shortcut if not equal to one character or if set on alphanum char
   # or on char with special meaning
   foreach {vr sc} [split $value {:=}] {
      if {[string length $sc] == 1 && ![string match {[a-zA-Z0-9+~/@=-,:]}\
         $sc]} {
         # remove duplicate shortcut or variant definition
         if {[info exists ::g_variantShortcut($vr)]} {
            unset ::g_shortcutVariant($::g_variantShortcut($vr))
         }
         if {[info exists ::g_shortcutVariant($sc)]} {
            unset ::g_variantShortcut($::g_shortcutVariant($sc))
         }
         set ::g_variantShortcut($vr) $sc
         set ::g_shortcutVariant($sc) $vr
      }
   }

   # update value after above filtering step
   set value {}
   foreach vr [array names ::g_variantShortcut] {
      if {[string length $value]} {
         append value :
      }
      append value $vr=$::g_variantShortcut($vr)
   }

   return $value
}

# Is currently set verbosity level is equal or higher than level passed as arg
proc isVerbosityLevel {name} {
   return [expr {[lsearch -exact [lindex $::g_config_defs(verbosity) 4]\
      [getConf verbosity]] >= [lsearch -exact [lindex\
      $::g_config_defs(verbosity) 4] $name]}]
}

# Is match performed in a case sensitive or insensitive manner
proc isIcase {} {
   # depending on current sub-command, list values that equal to a case
   # insensitive match enablement
   lappend enabledValList always
   if {[currentState commandname] in [list avail list whatis search paths\
      savelist spider]} {
      lappend enabledValList search
   }
   return [expr {[getConf icase] in $enabledValList}]
}

proc commandAbortOnError {{command {}}} {
   if {![string length $command]} {
      set command [currentState commandname]
   }
   set abort_command_list [getConfList abort_on_error]
   return [expr {[isTopEvaluation] && ![getState force] && $command in\
      $abort_command_list}]
}

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

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

proc escapeGlobChars {str} {
   return [charEscaped $str {*?[]\\}]
}

proc strTo {lang str {esc 1}} {
   switch -- $lang {
      tcl { set enco \{; set encc \}}
      shell { set enco '; set encc '}
   }
   # escape all special characters
   if {$esc} {
      set str [charEscaped $str]
   }
   # enclose if empty or if contain a space character unless already escaped
   if {$str eq {} || (!$esc && [regexp {\s} $str])} {
      set str "$enco$str$encc"
   }
   return $str
}

proc listTo {lang lst {esc 1}} {
   set lout [list]
   # transform each list element
   foreach str $lst {
      lappend lout [strTo $lang $str $esc]
   }
   return [join $lout { }]
}

# 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 {}} {
      knerror "Command '$cmd' cannot be found" MODULES_ERR_GLOBAL
   } else {
      return [exec $cmdpath {*}$args]
   }
}

proc getAbsolutePath {path} {
   # currently executing a modulefile or rc, so get the directory of this file
   if {[currentState modulefile] ne {}} {
      set curdir [file dirname [currentState modulefile]]
   # elsewhere get module command current working directory
   } else {
      # register pwd at first call
      if {![isStateDefined cwd]} {
         # raise a global known error if cwd cannot be retrieved (especially
         # when this directory has been removed)
         if {[catch {setState cwd [pwd]} errorMsg]} {
            knerror $errorMsg
         }
      }
      set curdir [getState cwd]
   }

   # empty result if empty path
   if {$path eq {}} {
      set abspath {}
   # consider path absolute if it starts with a variable ref
   } elseif {[string index $path 0] eq {$}} {
      set abspath $path
   } else {
      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
         }
      }
      set abspath [file join {*}$abslist]
   }

   # return cleaned absolute path
   return $abspath
}

# if no exact match found but icase mode is enabled then search if an icase
# match exists among all array key elements, select dictionary highest version
# if multiple icase matches are returned
proc getArrayKey {arrname name icase} {
   if {$icase} {
      upvar $arrname arr
      if {![info exists arr($name)]} {
         foreach elt [lsort -dictionary -decreasing [array names arr]] {
            if {[string equal -nocase $name $elt]} {
               reportDebug "key '$elt' in array '$arrname' matches '$name'"
               set name $elt
               break
            }
         }
      }
   }
   return $name
}

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

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

   return $res
}

# join list while escape any character equal to separator
proc pjoin {lst sep} {
   # use standard join if no sep character found
   if {[string first $sep $lst] == -1} {
      set res [join $lst $sep]
   } else {
      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
}

# Is provided string a version number: consider first element of string if
# '.' character used in it. [0-9af] on this first part is considered valid
# anything else could be used in latter elements
proc isVersion {str} {
   return [string is xdigit -strict [lindex [split $str .] 0]]
}

# Return number of occurrences of passed character in passed string
proc countChar {str char} {
   return [expr {[string length $str] - [string length [string map [list\
      $char {}] $str]]}]
}

proc appendNoDupToList {lstname args} {
   set ret 0
   upvar $lstname lst
   foreach elt $args {
      if {![info exists lst] || $elt ni $lst} {
         lappend lst $elt
         set ret 1
      }
   }
   return $ret
}

proc replaceFromList {list1 item {item2 {}}} {
   while {[set xi [lsearch -exact $list1 $item]] >= 0} {
      ##nagelfar ignore #2 Badly formed if statement
      set list1 [if {![string length $item2]} {lreplace $list1 $xi $xi}\
         {lreplace $list1 $xi $xi $item2}]
   }

   return $list1
}

proc lprepend {lst_name args} {
   upvar $lst_name lst
   if {[info exists lst]} {
      set lst [list {*}$args {*}$lst]
   } else {
      set lst $args
   }
}

# test if 2 lists have at least one element in common
proc isIntBetweenList {list1 list2} {
   foreach elt $list1 {
      if {$elt in $list2} {
         return 1
      }
   }
   return 0
}

# test if 2 lists have at least one element in diff
proc isDiffBetweenList {list1 list2} {
   foreach elt $list1 {
      if {$elt ni $list2} {
         return 1
      }
   }
   return 0
}

# returns elements from list1 not part of list2 and elements from list2 not
# part of list1
proc getDiffBetweenList {list1 list2} {
   set res1 [list]
   set res2 [list]

   foreach elt $list1 {
      if {$elt ni $list2} {
         lappend res1 $elt
      }
   }
   foreach elt $list2 {
      if {$elt ni $list1} {
         lappend res2 $elt
      }
   }

   return [list $res1 $res2]
}

# return intersection of all lists: elements present in every list
proc getIntersectBetweenList {args} {
   foreach lst $args {
      if {![info exists res]} {
         set cur_res $lst
      } else {
         set cur_res [list]
         foreach elt $res {
            if {$elt in $lst} {
               lappend cur_res $elt
            }
         }
      }
      set res $cur_res
      # stop when intersection result becomes empty
      if {![llength $res]} {
         break
      }
   }
   return $res
}

# return elements from arr1 not in arr2, elements from arr1 in arr2 but with a
# different value and elements from arr2 not in arr1.
# if notset_equals_empty is enabled, not-set element in array is equivalent to
# element set to an empty value.
# if unordered_lists_compared is enabled, value of array element is considered
# a list and difference between list entries is made (order insensitive)
proc getDiffBetweenArray {arrname1 arrname2 {notset_equals_empty 0}\
   {unordered_lists_compared 0}} {
   upvar $arrname1 arr1
   upvar $arrname2 arr2
   set notin2 [list]
   set diff [list]
   set notin1 [list]

   foreach name [array names arr1] {
      # element in arr1 not in arr2
      if {![info exists arr2($name)]} {
         if {!$notset_equals_empty} {
            lappend notin2 $name
         # if we consider a not-set entry equal to an empty value, there is a
         # difference only if entry in the other array is not empty
         } elseif {$arr1($name) ne {}} {
            lappend diff $name
         }
      # element present in both arrays but with a different value
      } elseif {!$unordered_lists_compared} {
         # but with a different value
         if {$arr1($name) ne $arr2($name)} {
            lappend diff $name
         }
      } else {
         # with a different value, not considering order
         lassign [getDiffBetweenList $arr1($name) $arr2($name)] notin2 notin1
         if {[llength $notin2] || [llength $notin1]} {
            lappend diff $name
         }
      }
   }

   foreach name [array names arr2] {
      # element in arr2 not in arr1
      if {![info exists arr1($name)]} {
         if {!$notset_equals_empty} {
            lappend notin1 $name
         } elseif {$arr2($name) ne {}} {
            lappend diff $name
         }
      }
   }

   return [list $notin2 $diff $notin1]
}

proc getCallingProcName {} {
   if {[info level] > 2} {
      return [lindex [info level -2] 0]
   }
}

proc incrErrorCount {} {
   updateErrorCount [expr {[getState error_count] + 1}]
}

proc decrErrorCount {} {
   updateErrorCount [expr {[getState error_count] - 1}]
}

proc updateErrorCount {count} {
   # hold update of error count
   if {[depthState reportholdid] > 0} {
      lappend ::g_holdReport([currentState reportholdid]) [list\
         updateErrorCount $count]
   } else {
      setState error_count $count
   }
}

proc renderFalse {} {
   if {[isStateDefined false_rendered]} {
      reportDebug {false already rendered}
   # no shell code to render false if shell not set
   } elseif {[isStateDefined shell]} {
      # setup flag to render only once
      setState false_rendered 1

      # render a false value most of the time through a variable assignment
      # 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 -- [getState shelltype] {
         sh - csh - fish {
            # no need to set a variable on real shells as last statement
            # result can easily be checked
            lappend ::g_shcode_out {test 0 = 1;}
         }
         tcl {
            lappend ::g_shcode_out {set _mlstatus 0;}
         }
         cmd {
            lappend ::g_shcode_out {set errorlevel=1}
         }
         perl {
            lappend ::g_shcode_out {{ no strict 'vars'; $_mlstatus = 0; }}
         }
         python {
            lappend ::g_shcode_out {_mlstatus = False}
         }
         ruby {
            lappend ::g_shcode_out {_mlstatus = false}
         }
         lisp {
            lappend ::g_shcode_out {nil}
         }
         cmake {
            lappend ::g_shcode_out {set(_mlstatus FALSE)}
         }
         r {
            lappend ::g_shcode_out {mlstatus <- FALSE}
         }
         pwsh {
            lappend ::g_shcode_out {Set-Variable -Name "_mlstatus" -Value\
               $false -Scope Global}
         }
      }
   }
}

proc renderTrue {} {
   # render a true value most of the time through a variable assignment that
   # will be looked at in the shell module function calling modulecmd.tcl to
   # return in turns a boolean status. Except for python, cmake, and pwsh,
   # the value assigned to variable is also returned as the full rendering
   # status
   switch -- [getState shelltype] {
      sh - csh - fish {
         # no need to set a variable on real shells as last statement
         # result can easily be checked
         lappend ::g_shcode_out {test 0;}
      }
      tcl {
         lappend ::g_shcode_out {set _mlstatus 1;}
      }
      cmd {
         lappend ::g_shcode_out {set errorlevel=0}
      }
      perl {
         lappend ::g_shcode_out {{ no strict 'vars'; $_mlstatus = 1; }}
      }
      python {
         lappend ::g_shcode_out {_mlstatus = True}
      }
      ruby {
         lappend ::g_shcode_out {_mlstatus = true}
      }
      lisp {
         lappend ::g_shcode_out {t}
      }
      cmake {
         lappend ::g_shcode_out {set(_mlstatus TRUE)}
      }
      r {
         lappend ::g_shcode_out {mlstatus <- TRUE}
      }
      pwsh {
         lappend ::g_shcode_out {Set-Variable -Name "_mlstatus" -Value\
            $true -Scope Global}
      }
   }
}

proc renderText {text} {
   # render a text value most of the time through a variable assignment that
   # will be looked at in the shell module function calling modulecmd.tcl to
   # return in turns a string value.
   switch -- [getState shelltype] {
      sh - csh - fish {
         foreach word $text {
            # no need to set a variable on real shells, echoing text will make
            # it available as result
            lappend ::g_shcode_out "echo '$word';"
         }
      }
      tcl {
         lappend ::g_shcode_out "set _mlstatus \"$text\";"
      }
      cmd {
         foreach word $text {
            lappend ::g_shcode_out "echo $word"
         }
      }
      perl {
         lappend ::g_shcode_out "{ no strict 'vars'; \$_mlstatus = '$text'; }"
      }
      python {
         lappend ::g_shcode_out "_mlstatus = '$text'"
      }
      ruby {
         lappend ::g_shcode_out "_mlstatus = '$text'"
      }
      lisp {
         lappend ::g_shcode_out "(message \"$text\")"
      }
      cmake {
         lappend ::g_shcode_out "set(_mlstatus \"$text\")"
      }
      r {
         lappend ::g_shcode_out "mlstatus <- '$text'"
      }
      pwsh {
         lappend ::g_shcode_out "Set-Variable -Name \"_mlstatus\" -Value\
            '$text' -Scope Global"
      }
   }
}

proc renderSettings {} {
   global g_stateEnvVars g_stateAliases g_stateFunctions g_stateCompletes\
      g_newXResources g_delXResources g_shcode_out

   # preliminaries if there is stuff to render
   if {[getState autoinit] || [array size g_stateEnvVars] || [array size\
      g_stateAliases] || [array size g_newXResources] || [array size\
      g_stateFunctions] || [array size g_stateCompletes] || [array size\
      g_delXResources] || [info exists ::g_changeDir] || [info exists\
      ::g_stdoutPuts] || [info exists ::g_prestdoutPuts] || [info exists\
      ::g_return_text]} {
      switch -- [getState shelltype] {
         python {
            lappend g_shcode_out {import os}
         }
      }
      set has_rendered 1
   } else {
      set has_rendered 0
   }

   # send pre content deferred during modulefile interpretation
   if {[info exists ::g_prestdoutPuts]} {
      foreach {newline msg} $::g_prestdoutPuts {
         append outmsg $msg
         if {$newline} {
            lappend g_shcode_out $outmsg
            unset outmsg
         }
      }
      # add last remaining message
      if {[info exists outmsg]} {
         lappend g_shcode_out $outmsg
      }
   }

   if {[getState autoinit]} {
      renderAutoinit
   }

   # filter Modules-specific environment variables for mod-to-sh subcmd
   if {[getState modtosh_real_shell] ne {}} {
      foreach globvar [getModulesEnvVarGlobList 1] {
         foreach var [array names g_stateEnvVars -glob $globvar] {
            unset g_stateEnvVars($var)
         }
      }
   }

   # new environment variables
   foreach var [array names g_stateEnvVars] {
      switch -- $g_stateEnvVars($var) {
         new {
            switch -- [getState shelltype] {
               csh {
                  # cannot handle newline character in env var value with csh
                  # shells: chop newline character in value (as done on
                  # Modules v3 for all shells)
                  set val [string map {\n {}} $::env($var)]
                  set val [charEscaped $val]
                  # csh barfs on long env vars
                  if {[getState shell] eq {csh} && [string length $val] >\
                     [getConf csh_limit]} {
                     if {$var eq {PATH}} {
                        reportWarning "PATH exceeds [getConf csh_limit]\
                           characters, truncating and appending\
                           /usr/bin:/bin ..."
                        set val [string range $val 0 [getConf\
                           csh_limit]-1]:/usr/bin:/bin
                     } else {
                        reportWarning "$var exceeds [getConf csh_limit]\
                           characters, truncating..."
                         set val [string range $val 0 [getConf csh_limit]-1]
                     }
                  }
                  lappend g_shcode_out "setenv $var $val;"
               }
               sh {
                  set val [string map {' '\\''} $::env($var)]
                  lappend g_shcode_out "$var='$val'; 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}} {
                     set val [join [split $val :] {' '}]
                  }
                  lappend g_shcode_out "set -xg $var '$val';"
               }
               tcl {
                  set val $::env($var)
                  lappend g_shcode_out "set ::env($var) {$val};"
               }
               cmd {
                  set val $::env($var)
                  lappend g_shcode_out "set $var=$val"
               }
               perl {
                  set val [charEscaped $::env($var) ']
                  lappend g_shcode_out "\$ENV{'$var'} = '$val';"
               }
               python {
                  set val [charEscaped $::env($var) ']
                  lappend g_shcode_out "os.environ\['$var'\] = '$val'"
               }
               ruby {
                  set val [charEscaped $::env($var) ']
                  lappend g_shcode_out "ENV\['$var'\] = '$val'"
               }
               lisp {
                  set val [charEscaped $::env($var) \"]
                  lappend g_shcode_out "(setenv \"$var\" \"$val\")"
               }
               cmake {
                  set val [charEscaped $::env($var) \"]
                  lappend g_shcode_out "set(ENV{$var} \"$val\")"
               }
               r {
                  set val [charEscaped $::env($var) {\\'}]
                  lappend g_shcode_out "Sys.setenv('$var'='$val')"
               }
               pwsh {
                  set val [charEscaped $::env($var) ']
                  lappend g_shcode_out "\$env:$var = '$val'"
               }
            }
         }
         del {
            switch -- [getState shelltype] {
               csh {
                  lappend g_shcode_out "unsetenv $var;"
               }
               sh {
                  lappend g_shcode_out "unset $var;"
               }
               fish {
                  lappend g_shcode_out "set -e $var;"
               }
               tcl {
                  lappend g_shcode_out "catch {unset ::env($var)};"
               }
               cmd {
                  lappend g_shcode_out "set $var="
               }
               perl {
                  lappend g_shcode_out "delete \$ENV{'$var'};"
               }
               python {
                  lappend g_shcode_out "os.environ\['$var'\] = ''"
                  lappend g_shcode_out "del os.environ\['$var'\]"
               }
               ruby {
                  lappend g_shcode_out "ENV\['$var'\] = nil"
               }
               lisp {
                  lappend g_shcode_out "(setenv \"$var\" nil)"
               }
               cmake {
                  lappend g_shcode_out "unset(ENV{$var})"
               }
               r {
                  lappend g_shcode_out "Sys.unsetenv('$var')"
               }
               pwsh {
                  lappend g_shcode_out "Remove-Item -Path env:$var\
                     -ErrorAction SilentlyContinue"
               }
            }
         }
      }
   }

   foreach var [array names g_stateAliases] {
      switch -- $g_stateAliases($var) {
         new {
            set val $::g_Aliases($var)
            # convert $n in !!:n and $* in !* on csh (like on compat version)
            if {[getState shelltype] eq {csh}} {
               regsub -all {([^\\]|^)\$([0-9]+)} $val {\1!!:\2} val
               regsub -all {([^\\]|^)\$\*} $val {\1!*} val
            }
            # unescape \$ after now csh-specific conversion is over
            regsub -all {\\\$} $val {$} val
            switch -- [getState shelltype] {
               csh {
                  set val [charEscaped $val]
                  lappend g_shcode_out "alias $var $val;"
               }
               sh {
                  set val [charEscaped $val]
                  lappend g_shcode_out "alias $var=$val;"
               }
               fish {
                  set val [charEscaped $val]
                  lappend g_shcode_out "alias $var $val;"
               }
               cmd {
                  lappend g_shcode_out "doskey $var=$val"
               }
               pwsh {
                  lappend g_shcode_out "function global:$var { $val }"
               }
            }
         }
         del {
            switch -- [getState shelltype] {
               csh {
                  lappend g_shcode_out "unalias $var;"
               }
               sh {
                  lappend g_shcode_out "unalias $var 2>/dev/null || true;"
               }
               fish {
                  lappend g_shcode_out "functions -e $var;"
               }
               cmd {
                  lappend g_shcode_out "doskey $var="
               }
               pwsh {
                  lappend g_shcode_out "Remove-Item -Path function:$var\
                     -ErrorAction SilentlyContinue"
               }
            }
         }
      }
   }
   foreach funcname [array names g_stateFunctions] {
      switch -- $g_stateFunctions($funcname) {
         new {
            # trim function body to smoothly add a finishing ;
            set val [string trim $::g_Functions($funcname) "; \t\n\r"]
            switch -- [getState shell] {
               sh - ksh - zsh {
                  lappend g_shcode_out "$funcname () { $val; };"
               }
               bash {
                  lappend g_shcode_out "$funcname () { $val; }; export -f\
                     $funcname;"
               }
               fish {
                  lappend g_shcode_out "function $funcname; $val; end;"
               }
               pwsh {
                  lappend g_shcode_out "function global:$funcname { $val }"
               }
            }
         }
         del {
            switch -- [getState shelltype] {
               sh {
                  lappend g_shcode_out "unset -f $funcname 2>/dev/null ||\
                     true;"
               }
               fish {
                  lappend g_shcode_out "functions -e $funcname;"
               }
               pwsh {
                  lappend g_shcode_out "Remove-Item -Path function:$funcname\
                     -ErrorAction SilentlyContinue"
               }
            }
         }
      }
   }
   foreach compname [array names g_stateCompletes] {
      switch -- $g_stateCompletes($compname) {
         new {
            foreach {compshell body} $::g_Completes($compname) {
               # skip definition not made for current shell
               if {$compshell eq [getState shell]} {
                  switch -- [getState shell] {
                     bash {
                        lappend g_shcode_out "complete $body $compname;"
                     }
                     tcsh {
                        lappend g_shcode_out "complete $compname $body;"
                     }
                     fish {
                        # ensure pre-existing fish completion is cleared
                        if {![info exists fishcompclear($compname)]} {
                           lappend g_shcode_out "complete -e -c $compname;"
                           set fishcompclear($compname) 1
                        }
                        lappend g_shcode_out "complete -c $compname $body;"
                     }
                     pwsh {
                        lappend g_shcode_out "Register-ArgumentCompleter\
                           -CommandName $compname -ScriptBlock { $body }"
                     }
                  }
               }
            }
         }
         del {
            switch -- [getState shell] {
               bash {
                  lappend g_shcode_out "complete -r $compname;"
               }
               tcsh {
                  lappend g_shcode_out "uncomplete $compname;"
               }
               fish {
                  lappend g_shcode_out "complete -e -c $compname;"
               }
               pwsh {
                  lappend g_shcode_out "Unregister-ArgumentCompleter\
                     -CommandName $compname"
               }
            }
         }
      }
   }

   # preliminaries for x-resources stuff
   if {[array size g_newXResources] || [array size g_delXResources]} {
      switch -- [getState shelltype] {
         python {
            lappend g_shcode_out {import subprocess}
         }
         ruby {
            lappend g_shcode_out {require 'open3'}
         }
      }
   }

   # new x resources
   if {[array size g_newXResources]} {
      # 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 -- [getState shelltype] {
               sh - csh - fish {
                  lappend g_shcode_out "$xrdb -merge $var;"
               }
               tcl {
                  lappend g_shcode_out "exec $xrdb -merge $var;"
               }
               perl {
                  lappend g_shcode_out "system(\"$xrdb -merge $var\");"
               }
               python {
                  set var [charEscaped $var ']
                  lappend g_shcode_out "subprocess.Popen(\['$xrdb',\
                     '-merge', '$var'\])"
               }
               ruby {
                  set var [charEscaped $var ']
                  lappend g_shcode_out "Open3.popen2('$xrdb -merge $var')"
               }
               lisp {
                  lappend g_shcode_out "(shell-command-to-string \"$xrdb\
                     -merge $var\")"
               }
               cmake {
                  lappend g_shcode_out "execute_process(COMMAND $xrdb -merge\
                     $var)"
               }
               r {
                  set var [charEscaped $var {\\'}]
                  lappend g_shcode_out "system('$xrdb -merge $var')"
               }
            }
         } else {
            switch -- [getState shelltype] {
               sh - csh - fish {
                  set var [charEscaped $var \"]
                  set val [charEscaped $val \"]
                  lappend g_shcode_out "echo \"$var: $val\" | $xrdb -merge;"
               }
               tcl {
                  lappend g_shcode_out "set XRDBPIPE \[open \"|$xrdb -merge\"\
                     r+\];"
                  set var [charEscaped $var \"]
                  set val [charEscaped $val \"]
                  lappend g_shcode_out "puts \$XRDBPIPE \"$var: $val\";"
                  lappend g_shcode_out {close $XRDBPIPE;}
                  lappend g_shcode_out {unset XRDBPIPE;}
               }
               perl {
                  lappend g_shcode_out "open(XRDBPIPE, \"|$xrdb -merge\");"
                  set var [charEscaped $var \"]
                  set val [charEscaped $val \"]
                  lappend g_shcode_out "print XRDBPIPE \"$var: $val\\n\";"
                  lappend g_shcode_out {close XRDBPIPE;}
               }
               python {
                  set var [charEscaped $var ']
                  set val [charEscaped $val ']
                  lappend g_shcode_out "subprocess.Popen(\['$xrdb',\
                     '-merge'\],\
                     stdin=subprocess.PIPE).communicate(input='$var:\
                     $val\\n')"
               }
               ruby {
                  set var [charEscaped $var ']
                  set val [charEscaped $val ']
                  lappend g_shcode_out "Open3.popen2('$xrdb -merge') {|i,o,t|\
                     i.puts '$var: $val'}"
               }
               lisp {
                  lappend g_shcode_out "(shell-command-to-string \"echo $var:\
                     $val | $xrdb -merge\")"
               }
               cmake {
                  set var [charEscaped $var \"]
                  set val [charEscaped $val \"]
                  lappend g_shcode_out "execute_process(COMMAND echo \"$var:\
                     $val\" COMMAND $xrdb -merge)"
               }
               r {
                  set var [charEscaped $var {\\'}]
                  set val [charEscaped $val {\\'}]
                  lappend g_shcode_out "system('$xrdb -merge', input='$var:\
                     $val')"
               }
            }
         }
      }
   }

   if {[array size g_delXResources]} {
      ##nagelfar ignore Found constant
      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 -- [getState shelltype] {
         sh - csh - fish {
            foreach var $xres_to_del {
               lappend g_shcode_out "echo \"$var:\" | $xrdb -merge;"
            }
         }
         tcl {
            foreach var $xres_to_del {
               lappend g_shcode_out "set XRDBPIPE \[open \"|$xrdb -merge\"\
                  r+\];"
               set var [charEscaped $var \"]
               lappend g_shcode_out "puts \$XRDBPIPE \"$var:\";"
               lappend g_shcode_out {close $XRDBPIPE;}
               lappend g_shcode_out {unset XRDBPIPE;}
            }
         }
         perl {
            foreach var $xres_to_del {
               lappend g_shcode_out "open(XRDBPIPE, \"|$xrdb -merge\");"
               set var [charEscaped $var \"]
               lappend g_shcode_out "print XRDBPIPE \"$var:\\n\";"
               lappend g_shcode_out {close XRDBPIPE;}
            }
         }
         python {
            foreach var $xres_to_del {
               set var [charEscaped $var ']
               lappend g_shcode_out "subprocess.Popen(\['$xrdb', '-merge'\],\
                  stdin=subprocess.PIPE).communicate(input='$var:\\n')"
            }
         }
         ruby {
            foreach var $xres_to_del {
               set var [charEscaped $var ']
               lappend g_shcode_out "Open3.popen2('$xrdb -merge') {|i,o,t|\
                  i.puts '$var:'}"
            }
         }
         lisp {
            foreach var $xres_to_del {
               lappend g_shcode_out "(shell-command-to-string \"echo $var: |\
                  $xrdb -merge\")"
            }
         }
         cmake {
            foreach var $xres_to_del {
               set var [charEscaped $var \"]
               lappend g_shcode_out "execute_process(COMMAND echo \"$var:\"\
                  COMMAND $xrdb -merge)"
            }
         }
         r {
            foreach var $xres_to_del {
               set var [charEscaped $var {\\'}]
               lappend g_shcode_out "system('$xrdb -merge', input='$var:')"
            }
         }
      }
   }

   if {[info exists ::g_changeDir]} {
      switch -- [getState shelltype] {
         sh - csh - fish {
            lappend g_shcode_out "cd '$::g_changeDir';"
         }
         tcl {
            lappend g_shcode_out "cd \"$::g_changeDir\";"
         }
         cmd {
            lappend g_shcode_out "cd $::g_changeDir"
         }
         perl {
            lappend g_shcode_out "chdir '$::g_changeDir';"
         }
         python {
            lappend g_shcode_out "os.chdir('$::g_changeDir')"
         }
         ruby {
            lappend g_shcode_out "Dir.chdir('$::g_changeDir')"
         }
         lisp {
            lappend g_shcode_out "(shell-command-to-string \"cd\
               '$::g_changeDir'\")"
         }
         r {
            lappend g_shcode_out "setwd('$::g_changeDir')"
         }
         pwsh {
            lappend g_shcode_out "Set-Location '$::g_changeDir'"
         }
      }
      # cannot change current directory of cmake "shell"
   }

   # send content deferred during modulefile interpretation
   if {[info exists ::g_stdoutPuts]} {
      foreach {newline msg} $::g_stdoutPuts {
         append outmsg $msg
         if {$newline} {
            lappend g_shcode_out $outmsg
            unset outmsg
         }
      }
      # add last remaining message
      if {[info exists outmsg]} {
         lappend g_shcode_out $outmsg
      }
   }

   # if currently processing mod-to-sh subcmd, send shell code to the message
   # channel then reset code and shell state to get evaluation result shell
   # code on stdout channel
   if {[getState modtosh_real_shell] ne {}} {
      if {[info exists g_shcode_out]} {
         report [join $::g_shcode_out \n]
         unset g_shcode_out
      }

      setState shell [getState modtosh_real_shell]
      unsetState shelltype
   }

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

# Output all gathered shell code to stdout. This is a separate proc rather the
# final code of renderSettings as such output is also done when exiting after
# an error. Stdout is flushed from flushAndExit proc
proc renderFlush {} {
   if {[info exists ::g_shcode_out]} {
      # required to work on cygwin, shouldn't hurt real linux
      fconfigure stdout -translation lf

      puts stdout [join $::g_shcode_out \n]
   }
}

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

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

   set is_mogui_avail [string length [getCommandPath mogui-cmd]]

   ##nagelfar ignore #482 Close brace not aligned with line
   switch -- [getState shelltype] {
      csh {
         set pre_hi {set _histchars = $histchars; unset histchars;}
         set post_hi {set histchars = $_histchars; unset _histchars;}
         set pre_pr {set _prompt=$prompt:q; set prompt="";}
         set post_pr {set prompt=$_prompt:q; unset _prompt;}
         # apply workaround for Tcsh history if set
         set eval_cmd [expr {[getConf wa_277] ? "eval `$tclshbin\
            '\"'\"'$::argv0'\"'\"' [getState shell] \\!*`;" : "eval\
            \"`$tclshbin '\"'\"'$::argv0'\"'\"' [getState shell] \\!*:q`\";"}]
         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' ;"
         if {[getConf ml]} {
            append fdef {
alias ml 'module ml \!*' ;}
         }
         if {$is_mogui_avail} {
            append fdef "\nalias mogui 'eval \"`mogui-cmd [getState shell]\
               \\!*:q`\"' ;"
         }
      }
      sh {
         # Considering the diversity of ways local variables are handled
         # through the sh-variants ('local' known everywhere except on ksh,
         # 'typeset' known everywhere except on pure-sh, and on some systems
         # the pure-sh is in fact a 'ksh'), no local variables are defined and
         # these variables that should have been local are unset at the end

         # on zsh, word splitting should be enabled explicitly
         set wsplit [expr {[getState shell] eq {zsh} ? {^^=} : {}}]
         # 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 "_module_raw() {"
         if {[getConf silent_shell_debug]} {
            append fdef {
   unset _mlshdbg;
   if [ "${MODULES_SILENT_SHELL_DEBUG:-0}" = '1' ]; then
      case "$-" in
         *v*x*) set +vx; _mlshdbg='vx' ;;
         *v*) set +v; _mlshdbg='v' ;;
         *x*) set +x; _mlshdbg='x' ;;
         *) _mlshdbg='' ;;
      esac;
   fi;}
         }
         if {[getConf quarantine_support]} {
            append fdef "
   unset _mlre _mlIFS;
   if \[ -n \"\${IFS+x}\" \]; then
      _mlIFS=\$IFS;
   fi;
   IFS=' ';
   for _mlv in \${${wsplit}MODULES_RUN_QUARANTINE:-}; do"
            ##nagelfar ignore #4 Too long line
            append fdef {
      if [ "${_mlv}" = "${_mlv##*[!A-Za-z0-9_]}" ] && [ "${_mlv}" = "${_mlv#[0-9]}" ]; then
         if [ -n "$(eval 'echo ${'"$_mlv"'+x}')" ]; then
            _mlre="${_mlre:-}__MODULES_QUAR_${_mlv}='$(eval 'echo ${'"$_mlv"'}')' ";
         fi;
         _mlrv="MODULES_RUNENV_${_mlv}";
         _mlre="${_mlre:-}${_mlv}='$(eval 'echo ${'"$_mlrv"':-}')' ";
      fi;
   done;
   if [ -n "${_mlre:-}" ]; then
      _mlre="${_mlre:-}__MODULES_QUARANTINE_SET=1 ";}
            append fdef "\n      eval \"\$(eval \${${wsplit}_mlre} $tclshbin\
               '$::argv0' [getState shell] '\"\$@\"')\";
   else
      eval \"\$($tclshbin '$::argv0' [getState shell] \"\$@\")\";
   fi;"
         } else {
            append fdef "
   eval \"\$($tclshbin '$::argv0' [getState shell] \"\$@\")\";"
         }
         append fdef {
   _mlstatus=$?;}
         if {[getConf quarantine_support]} {
            append fdef {
   if [ -n "${_mlIFS+x}" ]; then
      IFS=$_mlIFS;
   else
      unset IFS;
   fi;
   unset _mlre _mlv _mlrv _mlIFS;}
         }
         if {[getConf silent_shell_debug]} {
            append fdef {
   if [ -n "${_mlshdbg:-}" ]; then
      set -"$_mlshdbg";
   fi;
   unset _mlshdbg;}
         }
         append fdef {
   return $_mlstatus;}
         # define both _module_raw and module functions in any cases to allow
         # content redirection when command-line switch is used (--redirect or
         # --no-redirect)
         # content is redirected by default when shell session is attached to
         # a terminal (non-terminal session are not redirected to avoid
         # breaking things like scp or sftp transfer)
         # use local/typeset variable in this context as we cannot unset it
         # afterward (since _module_raw should be the cmd to return status)
         set localcmd [expr {[getState shell] eq {ksh} ? {typeset} : {local}}]
         ##nagelfar ignore Unescaped close brace
         append fdef "\n};\nmodule() {
   $localcmd _mlredir=[getState is_stderr_tty];\n"
         append fdef {   if [ -n "${MODULES_REDIRECT_OUTPUT+x}" ]; then
      if [ "$MODULES_REDIRECT_OUTPUT" = '0' ]; then
         _mlredir=0;
      elif [ "$MODULES_REDIRECT_OUTPUT" = '1' ]; then
         _mlredir=1;
      fi;
   fi;
   case " $@ " in
      *' --no-redirect '*) _mlredir=0 ;;
      *' --redirect '*) _mlredir=1 ;;
   esac;
   if [ $_mlredir -eq 0 ]; then
      _module_raw "$@";
   else
      _module_raw "$@" 2>&1;
   fi;}
         append fdef "\n};"
         if {[getState shell] eq {bash}} {
            append fdef {
export -f _module_raw;
export -f module;}
         }
         if {[getConf ml]} {
            append fdef {
ml() { module ml "$@"; };}
            if {[getState shell] eq {bash}} {
               append fdef {
export -f ml;}
            }
         }
         if {$is_mogui_avail} {
            append fdef "\nmogui() { eval \"\$(mogui-cmd [getState shell]\
               \"\$@\")\"; };"
            if {[getState shell] eq {bash}} {
               append fdef {
export -f mogui;}
            }
         }
      }
      fish {
         set fdef "function _module_raw\n"
         if {[getConf quarantine_support]} {
            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"__MODULES_QUAR_"$_mlv"='$$_mlv' "
         end
         set _mlrv "MODULES_RUNENV_$_mlv"
         set _mlre "$_mlre$_mlv='$$_mlrv' "
      end
   end
   if [ -n "$_mlre" ]
      set _mlre "env $_mlre __MODULES_QUARANTINE_SET=1"
   end}
            # use "| source -" rather than "eval" to be able
            # to redirect stderr after stdout being evaluated
            append fdef "\n   eval \$_mlre $tclshbin \\'$::argv0\\' [getState\
               shell] (string escape -- \$argv) | source -\n"
         } else {
            append fdef "   eval $tclshbin \\'$::argv0\\' [getState shell]\
               (string escape -- \$argv) | source -\n"
         }
         append fdef "end
function module
   set _mlredir [getState is_stderr_tty]
   if set -q MODULES_REDIRECT_OUTPUT
      if \[ \"\$MODULES_REDIRECT_OUTPUT\" = '0' \]
         set _mlredir 0
      else if \[ \"\$MODULES_REDIRECT_OUTPUT\" = '1' \]
         set _mlredir 1
      end
   end
   if contains -- --no-redirect \$argv; or begin ; \[ \$_mlredir -eq 0\
      \]; and not contains -- --redirect \$argv ; end
      _module_raw \$argv
   else
      _module_raw \$argv 2>&1
   end
end"
         if {[getConf ml]} {
            append fdef {
function ml
   module ml $argv
end}
         }
         if {$is_mogui_avail} {
            append fdef {
function mogui
   eval mogui-cmd fish (string escape -- $argv) | source -
end}
         }
      }
      tcl {
         set fdef "proc module {args} {"
         if {[getConf quarantine_support]} {
            append fdef {
   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 "__MODULES_QUAR_${_mlv}=$::env($_mlv)"
            }
            set _mlrv "MODULES_RUNENV_${_mlv}"
            lappend _mlre [expr {[info exists ::env($_mlrv)] ?\
               "${_mlv}=$::env($_mlrv)" : "${_mlv}="}]
         }
      }
      if {[llength $_mlre]} {
         lappend _mlre "__MODULES_QUARANTINE_SET=1"
         set _mlre [linsert $_mlre 0 "env"]
      }
   }}
         }
         append fdef {
   set _mlstatus 1;}
         if {[getConf quarantine_support]} {
            append fdef "\n   catch {exec {*}\$_mlre \"$tclshbin\"\
               \"$::argv0\" \"[getState shell]\" {*}\$args 2>@stderr}\
               script\n"
         } else {
            append fdef "\n   catch {exec \"$tclshbin\" \"$::argv0\"\
               \"[getState shell]\" {*}\$args 2>@stderr} script\n"
         }
         append fdef {   eval $script;
   return $_mlstatus}
         append fdef "\n}"
         if {[getConf ml]} {
            append fdef {
proc ml {args} {
   return [module ml {*}$args]
}}
         }
      }
      cmd {
         reportErrorAndExit {No autoinit mode available for 'cmd' shell}
      }
      perl {
         set fdef "sub module {"
         if {[getConf quarantine_support]} {
            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 .= "__MODULES_QUAR_${_mlv}='$ENV{$_mlv}' ";
            }
            my $_mlrv = "MODULES_RUNENV_$_mlv";
            $_mlre .= "$_mlv='$ENV{$_mlrv}' ";
        }
      }
      if ($_mlre ne "") {
         $_mlre = "env ${_mlre}__MODULES_QUARANTINE_SET=1 ";
      }
   }}
         }
         append fdef {
   my $args = '';
   if (@_ > 0) {
      $args = '"' . join('" "', @_) . '"';
   }
   my $_mlstatus = 1;}
         if {[getConf quarantine_support]} {
            append fdef "\n   eval `\${_mlre}$tclshbin '$::argv0' perl\
               \$args`;\n"
         } else {
            append fdef "\n   eval `$tclshbin '$::argv0' perl \$args`;\n"
         }
         append fdef {   return $_mlstatus;}
         append fdef "\n}"
         if {[getConf ml]} {
            append fdef {
sub ml {
   return module('ml', @_);
}}
         }
      }
      python {
         set fdef {import sys, re, subprocess
def module(*arguments):}
         if {[getConf quarantine_support]} {
            append fdef {
   _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['__MODULES_QUAR_' + _mlv] = os.environ[_mlv]
            _mlrv = 'MODULES_RUNENV_' + _mlv
            if _mlrv in os.environ:
               _mlre[_mlv] = os.environ[_mlrv]
            else:
               _mlre[_mlv] = ''
      _mlre['__MODULES_QUARANTINE_SET'] = '1'}
         }
         append fdef {
   ns = {}}
         if {[getConf quarantine_support]} {
            append fdef "\n   out, err = subprocess.Popen(\['$tclshbin',\
               '$::argv0', 'python'\] + list(arguments),\
               stdout=subprocess.PIPE, stderr=subprocess.PIPE,\
               env=_mlre).communicate()\n"
         } else {
            append fdef "\n   out, err = subprocess.Popen(\['$tclshbin',\
               '$::argv0', 'python'\] + list(arguments),\
               stdout=subprocess.PIPE,\
               stderr=subprocess.PIPE).communicate()\n"
         }
         append fdef {   exec(out, ns)
   if '_mlstatus' in ns:
      _mlstatus = ns['_mlstatus']
   else:
      _mlstatus = True
   if err.decode():
      print(err.decode(), end='', file=sys.stderr)
   return _mlstatus}
         if {[getConf ml]} {
            append fdef {
def ml(*arguments):
   return module('ml', *arguments)
}
         }
      }
      ruby {
         set fdef {class ENVModule
   def ENVModule.module(*args)}
         if {[getConf quarantine_support]} {
            ##nagelfar ignore +7 Too long line
            append fdef {
      _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 << "__MODULES_QUAR_" + _mlv + "='" + ENV[_mlv].to_s + "' "
               end
               _mlrv = 'MODULES_RUNENV_' + _mlv
               _mlre << _mlv + "='" + ENV[_mlrv].to_s + "' "
            end
         end
         unless _mlre.empty?
            _mlre = 'env ' + _mlre + '__MODULES_QUARANTINE_SET=1 '
         end
      end}
         }
         append fdef {
      if args[0].kind_of?(Array) then
         args = args[0]
      end
      if args.length == 0 then
         args = ''
      else
         args = "\"#{args.join('" "')}\""
      end
      _mlstatus = true}
         if {[getConf quarantine_support]} {
            ##nagelfar ignore +2 Suspicious # char
            append fdef "\n      eval `#{_mlre}$tclshbin '$::argv0' ruby\
               #{args}`\n"
         } else {
            append fdef "\n      eval `$tclshbin '$::argv0' ruby #{args}`\n"
         }
         append fdef {      return _mlstatus
   end}
         if {[getConf ml]} {
            append fdef {
   def ENVModule.ml(*args)
      return ENVModule.module('ml', *args)
   end}
         }
         append fdef {
end}
      }
      lisp {
         reportErrorAndExit {lisp mode autoinit not yet implemented}
      }
      cmake {
         if {[getConf quarantine_support]} {
            set pre_exec "\n      execute_process(COMMAND \${_mlre} $tclshbin\
            \"$::argv0\" cmake "
         } else {
            set pre_exec "\n      execute_process(COMMAND $tclshbin\
            \"$::argv0\" cmake "
         }
         set post_exec "\n         OUTPUT_FILE \${tempfile_name})\n"
         set fdef {function(module)
   cmake_policy(SET CMP0007 NEW)}
         if {[getConf quarantine_support]} {
            append fdef {
   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}__MODULES_QUAR_${_mlv}=$ENV{${_mlv}};")
            endif()
            set(_mlrv "MODULES_RUNENV_${_mlv}")
            set(_mlre "${_mlre}${_mlv}=$ENV{${_mlrv}};")
        endif()
      endforeach()
      if (NOT "${_mlre}" STREQUAL "")
         set(_mlre "env;${_mlre}__MODULES_QUARANTINE_SET=1;")
      endif()
   endif()}
         }
         append fdef {
   set(_mlstatus TRUE)
   execute_process(COMMAND mktemp -t moduleinit.cmake.XXXXXXXXXXXX
      OUTPUT_VARIABLE tempfile_name
      OUTPUT_STRIP_TRAILING_WHITESPACE)
   if(${ARGC} EQUAL 1)}
            # adapt command definition depending on the number of args to be
            # able to pass to some extend (<5 args) empty string element to
            # modulecmd (no other way as empty element in ${ARGV} are skipped
            append fdef "$pre_exec\"\${ARGV0}\"$post_exec"
            append fdef {   elseif(${ARGC} EQUAL 2)}
            append fdef "$pre_exec\"\${ARGV0}\" \"\${ARGV1}\"$post_exec"
            append fdef {   elseif(${ARGC} EQUAL 3)}
            append fdef "$pre_exec\"\${ARGV0}\" \"\${ARGV1}\"\
               \"\${ARGV2}\"$post_exec"
            append fdef {   elseif(${ARGC} EQUAL 4)}
            append fdef "$pre_exec\"\${ARGV0}\" \"\${ARGV1}\"\
               \"\${ARGV2}\" \"\${ARGV3}\"$post_exec"
            append fdef {   else()}
            append fdef "$pre_exec\${ARGV}$post_exec"
            append fdef {   endif()
   if(EXISTS ${tempfile_name})
      include(${tempfile_name})
      file(REMOVE ${tempfile_name})
   endif()
   set(module_result ${_mlstatus} PARENT_SCOPE)
endfunction(module)}
         if {[getConf ml]} {
            append fdef {
function(ml)
   module(ml ${ARGV})
   set(module_result ${module_result} PARENT_SCOPE)
endfunction(ml)}
         }
      }
      r {
         set fdef "module <- function(...){"
         if {[getConf quarantine_support]} {
            ##nagelfar ignore +7 Too long line
            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, "__MODULES_QUAR_", mlv, "='", Sys.getenv(mlv), "' ")
            }
            mlrv <- paste0('MODULES_RUNENV_', mlv)
            mlre <- paste0(mlre, mlv, "='", Sys.getenv(mlrv), "' ")
         }
      }
      if (mlre != '') {
         mlre <- paste0('env ', mlre, '__MODULES_QUARANTINE_SET=1 ')
      }
   }}
         }
         append fdef {
   arglist <- as.list(match.call())
   arglist[1] <- 'r'
   args <- paste0('"', paste0(arglist, collapse='" "'), '"')}
         if {[getConf quarantine_support]} {
            append fdef "\n   cmd <- paste(mlre, '$tclshbin', '\"$::argv0\"',\
               args, sep=' ')\n"
         } else {
            append fdef "\n   cmd <- paste('$tclshbin', '\"$::argv0\"', args,\
               sep=' ')\n"
         }
         append fdef {   mlstatus <- TRUE
   hndl <- pipe(cmd)
   eval(expr = parse(file=hndl))
   close(hndl)
   invisible(mlstatus)}
         append fdef "\n}"
         if {[getConf ml]} {
            append fdef {
ml <- function(...){
   module('ml', ...)
}}
         }
      }
      pwsh {
         if {[getState is_win]} {
            reportErrorAndExit {No autoinit mode available for 'pwsh' shell\
               on Windows}
         }

         set fdef "function global:_envmodule_common {"
         append fdef {
   param([Parameter(ValueFromRemainingArguments)] [string[]] $allargs)
   $global:_mlstatus = $true
   $cmd_is_query = "$allargs" -match '(is-loaded|is-avail|is-used|is-saved)'}
         if {[getConf quarantine_support]} {
            append fdef {
   $_mlre_set = ''
   $_mlre_reset = ''
   if ($env:MODULES_RUN_QUARANTINE) {
      foreach ($_mlv in $env:MODULES_RUN_QUARANTINE -split ' ') {
         if ($_mlv -match '^[A-Za-z_][A-Za-z0-9_]*$') {
            $_mlv_value = [System.Environment]::GetEnvironmentVariable($_mlv)
            if ($_mlv_value) {
               $_mlre_set += "`$env:__MODULES_QUAR_${_mlv}='$_mlv_value';"
               $_mlre_reset = "Remove-Item -Path env:__MODULES_QUAR_${_mlv};"\
                  + $_mlre_reset
            }
            $_mlrv = "MODULES_RUNENV_$_mlv"
            $_mlrv_value =\
               [System.Environment]::GetEnvironmentVariable($_mlrv)
            $_mlre_set += "`$env:$_mlv='$_mlrv_value';"
            $_mlre_reset = "`$env:$_mlv=`"`$env:__MODULES_QUAR_${_mlv}`";"\
               + $_mlre_reset
         }
      }
      if ($_mlre_set -ne '') {
         $_mlre_set += "`$env:__MODULES_QUARANTINE_SET=1"
         $_mlre_reset += "Remove-Item -Path env:__MODULES_QUARANTINE_SET"
      }
   }
   if ($_mlre_set -ne '') {
      Invoke-Expression "$_mlre_set"
      Remove-Variable "_mlre_set"
   }}
         }
         append fdef {
   $output = }
         append fdef "& \"$tclshbin\" \"$::argv0\" pwsh \$allargs 2>&1"
         if {[getConf quarantine_support]} {
            append fdef {
   if ($_mlre_reset -ne '') {
      Invoke-Expression "$_mlre_reset"
      Remove-Variable "_mlre_reset"
   }}
         }
         append fdef {
   $outmsg = ($output | ? {$_.gettype().Name -ne "ErrorRecord"}) -join "`n"
   $errmsg = ($output | ? {$_.gettype().Name -eq "ErrorRecord"}) -join "`n"
   $errmsg = $errmsg.replace(
      "System.Management.Automation.RemoteException", ""
   )
   if ($outmsg) {
      Invoke-Expression $outmsg
   }
   if (($_mlstatus -eq $false) -and (!$cmd_is_query)) {
      $global:LastExitCode = 1
   } else {
      $global:LastExitCode = 0
   }}
         append fdef "\n   \$mlredir = \[bool\][getState is_stderr_tty]"
         append fdef {
   if ($env:MODULES_REDIRECT_OUTPUT -ne $null) {
      if ($env:MODULES_REDIRECT_OUTPUT -eq '0') {
         $mlredir = $false
      } elseif ($env:MODULES_REDIRECT_OUTPUT -eq '1') {
         $mlredir = $true
      }
   }
   if ("$allargs" -match '--no-redirect') {
      $mlredir = $false
   } elseif ("$allargs" -match '--redirect') {
      $mlredir = $true
   }
   if ($errmsg) {
      if ($_mlstatus -eq $false) {
         $global:LastExitCode = 1
      } else {
         if ($mlredir) {
            [Console]::WriteLine("$errmsg")
         } else {
            [Console]::Error.WriteLine("$errmsg")
         }
         $errmsg = $null
      }
   }
   if ((($_mlstatus -ne $true) -and ($_mlstatus -ne $false)) -or\
       ($cmd_is_query)) {
      return $errmsg,$_mlstatus
   }
   return $errmsg,$null}
         append fdef "\n}\nfunction global:envmodule {"
         append fdef {
   param([Parameter(ValueFromRemainingArguments)] [string[]] $allargs)
   $errmsg,$retmsg = & _envmodule_common $allargs
   if ("$errmsg" -ne "") {
      $PSCmdlet.ThrowTerminatingError(
         [System.Management.Automation.ErrorRecord]::new(
            ([System.Exception]"$errmsg"),
            'error while running modulecmd.tcl via envmodule',
            [System.Management.Automation.ErrorCategory]::FromStdErr,
            $null
      ))
   }
   if ("$retmsg" -ne "") {
      return $retmsg
   }}
         append fdef "\n}"
         if {[getConf ml]} {
            append fdef "\nfunction global:ml {"
            append fdef {
   param([Parameter(ValueFromRemainingArguments)] [string[]] $allargs)
   $mlargs = @('ml')
   if ($allargs.Length) {
      $mlargs += $allargs
   }
   $errmsg,$retmsg = _envmodule_common $mlargs
   if ("$errmsg" -ne "") {
      $PSCmdlet.ThrowTerminatingError(
         [System.Management.Automation.ErrorRecord]::new(
            ([System.Exception]"$errmsg"),
            'error while running modulecmd.tcl via ml',
            [System.Management.Automation.ErrorCategory]::FromStdErr,
            $null
      ))
   }
   if ("$retmsg" -ne "") {
      return $retmsg
   }}
            append fdef "\n}"
         }
      }
   }

   # output function definition
   lappend ::g_shcode_out $fdef
}

proc get-env {var {valifunset {}}} {
   if {[isEnvVarDefined $var] && ![isEnvVarCleared $var]} {
      return $::env($var)
   } else {
      return $valifunset
   }
}

proc envVarEquals {var_name value} {
   return [expr {[isEnvVarDefined $var_name] && [get-env $var_name]\
      eq $value}]
}

proc isEnvVarDefined {var_name} {
   return [info exists ::env($var_name)]
}

proc setEnvVarIfUndefined {var_name value} {
   if {![isEnvVarDefined $var_name]} {
      set ::env($var_name) $value
   }
}

proc set-env {var val} {
   set mode [currentState mode]
   reportDebug "$var=$val"

   if {![isEnvVarProtected $var]} {
      # an empty string value means unsetting variable on Windows platform, so
      # call unset-env to ensure variable will not be seen defined yet raising
      # an error when trying to access it
      if {[getState is_win] && $val eq {}} {
         unset-env $var
      } else {
         interp-sync-env set $var $val

         unsetEnvVarAsCleared $var

         # propagate variable setup to shell environment on load and unload
         # mode
         if {$mode eq {load} || $mode eq {unload}} {
            set ::g_stateEnvVars($var) new
         }
      }
   } else {
      reportWarning "Modification of protected environment variable $var\
         ignored"
   }
}

proc isEnvVarProtected {var_name} {
   return [expr {$var_name in [getConfList protected_envvars]}]
}

proc reset-to-unset-env {var {val {}}} {
   interp-sync-env set $var $val
   # set var as cleared if val is empty
   if {$val eq {}} {
      setEnvVarAsCleared $var
   }
}

proc setEnvVarAsCleared {var_name} {
   set ::g_clearedEnvVars($var_name) 1
}

proc unsetEnvVarAsCleared {var_name} {
   if {[isEnvVarCleared $var_name]} {
      unset ::g_clearedEnvVars($var_name)
   }
}

proc isEnvVarCleared {var_name} {
   return [info exists ::g_clearedEnvVars($var_name)]
}

proc unset-env {var {internal 0} {val {}}} {
   set mode [currentState mode]
   reportDebug "$var (internal=$internal, val=$val)"

   if {![isEnvVarProtected $var]} {
      # clear value instead of unset it not to break variable later reference
      # in modulefile. clear whether variable set or not to get a later usage
      # consistent behavior whatever env is setup
      if {!$internal} {
         reset-to-unset-env $var $val
      # internal variables (like ref counter var) are purely unset if they
      # exists
      } elseif {[isEnvVarDefined $var]} {
         interp-sync-env unset $var
         set intwasset 1
      }

      # propagate deletion in any case if variable is public and for internal
      # one only if variable was set
      if {($mode eq {load} || $mode eq {unload}) && (!$internal ||\
         [info exists intwasset])} {
         set ::g_stateEnvVars($var) del
      }
   } else {
      reportWarning "Modification of protected environment variable $var\
         ignored"
   }
}

proc getPushenvVarName {var} {
   return __MODULES_PUSHENV_${var}
}

proc getModshareVarName {var} {
   # no modshare variable for Modules-specific path variables as each entry
   # should be unique (no reference counting mechanism for these variables
   # expect for MODULEPATH)
   if {$var in {_LMFILES_ LOADEDMODULES} || [string equal -length 10\
      __MODULES_ $var] || [string equal -length 8 MODULES_ $var]} {
      return {}
   # specific modshare variable use a Modules-specific prefix (rather suffix
   # that may lead to warning message for instance for DYLD-related variables)
   } else {
      return __MODULES_SHARE_${var}
   }
}

# path fiddling
proc getReferenceCountArray {var separator} {
   # get reference counter set in environment
   set sharevar [getModshareVarName $var]
   array set refcount {}
   if {[isEnvVarDefined $sharevar]} {
      set modsharelist [psplit $::env($sharevar) [getState path_separator]]
      # ignore environment ref count variable if malformed
      if {([llength $modsharelist] % 2) == 0} {
         array set refcount $modsharelist
      } else {
         reportDebug "Reference counter value in '$sharevar' is malformed\
            ($modsharelist)"
      }
   }

   array set countarr {}
   if {[isEnvVarDefined $var]} {
      # 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 refcount()]} {
         lappend eltlist {}
      } else {
         set eltlist [split $::env($var) $separator]
      }

      # just go thought the elements of the variable, which means additional
      # elements part of the reference counter variable will be ignored
      foreach elt $eltlist {
         # no reference counter, means value has been set once
         if {![info exists refcount($elt)]} {
            set count 1
         # bad reference counter value is ignored
         } elseif {![string is digit -strict $refcount($elt)] ||\
            $refcount($elt) < 1} {
            reportDebug "Reference counter value for '$elt' in '$sharevar' is\
               erroneous ($refcount($elt))"
            set count 1
         } else {
            set count $refcount($elt)
         }
         set countarr($elt) $count
      }
   }

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

   return $count_list
}

proc getPathReferenceCount {var path separator {resolv 0}} {
   array set ref_count_arr [getReferenceCountArray $var $separator]
   if {[info exists ref_count_arr($path)]} {
      set ref_count $ref_count_arr($path)
   } else {
      set ref_count 0
      if {$resolv} {
         set matching_path [getStringFromListMatchingResString [array names\
            ref_count_arr] $path]
         if {[string length $matching_path]} {
            set ref_count $ref_count_arr($matching_path)
         }
      }
   }
   return $ref_count
}

# generate reference count value to export in user environment
proc setReferenceCountRecordValue {sharevar countlist} {
   foreach {elt refcount} $countlist {
      # do not export elements with a reference count of 1 as no reference
      # count entry means a reference count of 1
      # exception made for empty string which is recorded even for a single
      # reference count to be able to distinguish between an empty path
      # variable and a path variable with an empty string as unique element
      if {$refcount > 1 || $elt eq {}} {
         lappend reclist $elt $refcount
      }
   }

   if {[info exists reclist]} {
      set-env $sharevar [pjoin $reclist [getState path_separator]]
   # unset ref count var if found set whereas no counter should be recorded
   } elseif {[isEnvVarDefined $sharevar]} {
      unset-env $sharevar 1
   }
}

proc unload-path {cmd mode dflbhv args} {
   reportDebug "($args) cmd=$cmd, mode=$mode, dflbhv=$dflbhv"

   lassign [parsePathCommandArgs $cmd $mode $dflbhv {*}$args] separator\
      allow_dup idx_val ign_refcount val_set_is_delim glob_match bhv var\
      path_list

   switch -- $bhv {
      noop {
         return [list $bhv $var]
      }
      append - prepend {
         # set paths instead of removing them
         add-path unload-path load $bhv $cmd $separator $var {*}$path_list
         return [list $bhv $var]
      }
   }

   # clean any previously defined pushenv stack
   unset-env [getPushenvVarName $var] 1

   # no reference count management when no share variable is returned
   set isrefcount [expr {[set sharevar [getModshareVarName $var]] ne {}}]
   if {$isrefcount} {
      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 [list $bhv $var]
   }

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

   # match glob pattern against existing values
   if {$glob_match} {
      set new_path_list [list]
      foreach glob_pattern $path_list {
         set pattern_match_list [lsearch -glob -all -inline $dir_list\
            $glob_pattern]
         if {[llength $pattern_match_list]} {
            lappend new_path_list {*}$pattern_match_list
         }
      }
      set path_list $new_path_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)]} {
         # unload value in any case if counter ignored (--ignore-refcount set)
         if {$ign_refcount} {
            set countarr($dir) 0
         } else {
            incr countarr($dir) -1
         }
         if {$countarr($dir) <= 0} {
            unset countarr($dir)
            set newcount 0
         } else {
            set newcount $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 {$newcount <= 0} {
         # only remove passed position in --index mode
         if {$idx_val} {
            lappend del_idx_list $idx
         } else {
            lappend 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)
            lappend del_idx_list {*}[lrange $found_idx_list $newcount end]
         }
      }
   }

   # update variable if some element need to be removed
   if {[llength $del_idx_list]} {
      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 {$i ni $del_idx_list} {
            lappend newpath [lindex $dir_list $i]
         }
      }
   } else {
      set newpath $dir_list
   }

   # set env variable and corresponding reference counter in any case
   if {![llength $newpath]} {
      unset-env $var
   } else {
      set-env $var [join $newpath $separator]
   }

   if {$isrefcount} {
      setReferenceCountRecordValue $sharevar [array get countarr]
   }
   return [list $bhv $var]
}

proc add-path {cmd mode dflbhv args} {
   reportDebug "($args) cmd=$cmd, mode=$mode, dflbhv=$dflbhv"

   # may be called from unload-path where argument parsing was already done
   if {$cmd eq {unload-path}} {
      set path_list [lassign $args cmd separator var]
      set allow_dup 0
      set ign_refcount 0
      set val_set_is_delim 0
      set bhv $dflbhv
   } else {
      lassign [parsePathCommandArgs $cmd $mode $dflbhv {*}$args] separator\
         allow_dup idx_val ign_refcount val_set_is_delim glob_match bhv var\
         path_list
   }

   # clean any previously defined pushenv stack
   unset-env [getPushenvVarName $var] 1

   # no reference count management when no share variable is returned
   set isrefcount [expr {[set sharevar [getModshareVarName $var]] ne {}}]
   if {$isrefcount} {
      array set countarr [getReferenceCountArray $var $separator]
   }

   if {$bhv eq {prepend}} {
      set path_list [lreverse $path_list]
   }

   set val [get-env $var]

   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 {$val ne {} || [info exists countarr()]} {
            set sep [expr {$val eq $separator ? {} : $separator}]
            set val [expr {$bhv eq {prepend} ? "$dir$sep$val" :\
               "$val$sep$dir"}]
         } else {
            set val $dir
         }
      }
      if {[info exists countarr($dir)]} {
         # do not increase counter if bare separator string is added or if
         # reference count is ignored (--ignore-refcount set) unless if
         # duplicate mode is enabled (--duplicates set)
         if {!$val_set_is_delim && (!$ign_refcount || $allow_dup)} {
            incr countarr($dir)
         }
      } else {
         set countarr($dir) 1
      }
   }

   set-env $var $val
   if {$isrefcount} {
      setReferenceCountRecordValue $sharevar [array get countarr]
   }

   if {$var eq {MODULEPATH}} {
      registerCurrentModuleUse {*}$path_list
   }

   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 mode dflbhv args} {
   # parse argument list
   set next_is_delim 0
   set next_is_ignored 0
   set next_is_reset 0
   set allow_dup 0
   set idx_val 0
   set ign_refcount 0
   set val_set_is_delim 0
   set glob_match 0
   set bhv $dflbhv
   foreach arg $args {
      # everything passed after variable name is considered a value
      if {[info exists var]} {
         switch -- $arg {
            --append-on-unload - --prepend-on-unload {
               if {$cmd ne {remove-path}} {
                  knerror "invalid option '$arg' for $cmd"
               } elseif {$mode ne {unload}} {
                  # ignore value set after this argument if not unloading
                  # unless if no list is already set for load mode
                  if {[info exists val_raw_list]} {
                     set next_is_ignored 1
                  }
               } else {
                  set bhv [expr {$arg eq {--append-on-unload} ? {append} :\
                     {prepend}}]
                  # if another argument is set, current value list will be
                  # withdrawn to start from new list set after this argument
                  set next_is_reset 1
               }
               set bhvopt $arg
            }
            --remove-on-unload - --noop-on-unload {
               if {$cmd ne {remove-path}} {
                  knerror "invalid option '$arg' for $cmd"
               } elseif {$mode eq {unload}} {
                  set bhv [expr {$arg eq {--remove-on-unload} ? {remove} :\
                     {noop}}]
               }
               set bhvopt $arg
            }
            default {
               if {$next_is_reset} {
                  set next_is_reset 0
                  set val_raw_list [list $arg]
               } elseif {!$next_is_ignored} {
                  # set multiple passed values in a list
                  lappend val_raw_list $arg
               }
            }
         }
      } else {
         switch -glob -- $arg {
            --index {
               if {$cmd ne {remove-path}} {
                  reportWarning "--index option has no effect on $cmd"
               } else {
                  set idx_val 1
               }
            }
            --duplicates {
               # raise warning when option is used by remove-path
               if {$cmd eq {remove-path}} {
                  reportWarning "--duplicates option has no effect on $cmd"
               } else {
                  set allow_dup 1
               }
            }
            --glob {
               # raise warning when option is not used by remove-path
               if {$cmd eq {remove-path}} {
                  set glob_match 1
               } else {
                  reportWarning "--glob option has no effect on $cmd"
               }
            }
            -d - -delim - --delim {
               set next_is_delim 1
            }
            --delim=* {
               set delim [string range $arg 8 end]
            }
            --ignore-refcount {
               set ign_refcount 1
            }
            --append-on-unload - --prepend-on-unload - --remove-on-unload -\
            --noop-on-unload {
               if {$cmd ne {remove-path}} {
                  knerror "invalid option '$arg' for $cmd"
               } elseif {$mode eq {unload}} {
                  switch -- [string range $arg 2 5] {
                     noop { set bhv noop }
                     remo { set bhv remove }
                     appe { set bhv append }
                     prep { set bhv prepend }
                  }
               }
               set bhvopt $arg
            }
            -* {
               knerror "invalid option '$arg' for $cmd"
            }
            default {
               if {$next_is_delim} {
                  set delim $arg
                  set next_is_delim 0
               } else {
                  set var $arg
               }
            }
         }
      }
   }

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

   # some options cannot be mixed
   if {$idx_val != 0 && ([info exists bhvopt] && $bhvopt ne\
      {--noop-on-unload})} {
      knerror "--index and $bhvopt options cannot be simultaneously set"
   }
   if {$idx_val && $glob_match} {
      knerror "--index and --glob options cannot be simultaneously set"
   }
   if {$glob_match && ([info exists bhvopt] && $bhvopt in\
      {--append-on-unload --prepend-on-unload})} {
      knerror "--glob and $bhvopt options cannot be simultaneously set"
   }

   set nb_path_eq_delim [llength [lsearch -all -exact $val_raw_list $delim]]
   if {$nb_path_eq_delim && $cmd eq {remove-path}} {
      knerror "$cmd cannot handle path equals to separator string"
   } elseif {$nb_path_eq_delim > 1} {
      knerror "$cmd cannot handle multiple paths equal to separator string"
   }

   # 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]} {
         knerror "$cmd should get valid number as index value"
      }

      if {![string length $val]} {
         lappend val_list {}
      } elseif {$val eq $delim} {
         if {[llength $val_raw_list] > 1 || [get-env $var] ne {}} {
            lappend val_list {}
         # make separator string appear once in resulting value
         } elseif {[getPathReferenceCount $var {} $delim]} {
            lappend val_list {}
            set allow_dup 1
         } else {
            lappend val_list {} {}
            set val_set_is_delim 1
            set allow_dup 1
            set ign_refcount 1
         }
      } else {
         # split passed value with delimiter
         lappend val_list {*}[split $val $delim]
      }
   }

   reportDebug "(delim=$delim, allow_dup=$allow_dup, idx_val=$idx_val,\
      ign_refcount=$ign_refcount, val_set_is_delim=$val_set_is_delim,\
      glob_match=$glob_match, bhv=$bhv, var=$var, val=$val_list,\
      nbval=[llength $val_list])"

   return [list $delim $allow_dup $idx_val $ign_refcount $val_set_is_delim\
      $glob_match $bhv $var $val_list]
}

# get list of Modules-specific environment variable in glob form
proc getModulesEnvVarGlobList {{loaded_ctx 0}} {
   set envvar_glob_list [list LOADEDMODULES _LMFILES_]
   # only return list of variables related to module loaded context
   if {$loaded_ctx} {
      lappend envvar_glob_list __MODULES_*
   } else {
      lappend envvar_glob_list MODULE* __MODULES_* *_module*
   }
   return $envvar_glob_list
}

#
# Debug, Info, Warnings and Error message handling.
#

# save message when report is not currently initialized as we do not
# know yet if debug mode is enabled or not
proc reportDebug {message {caller _undef_}} {
   set caller [getCallingProcName]
   lappend ::errreport_buffer [list reportDebug $message $caller]
}

# regular procedure to use once error report is initialized
proc __reportDebug {message {caller _undef_}} {
   # display active interp details if not the main one
   set prefix [currentState debug_msg_prefix]
   if {$caller eq {_undef_}} {
      set caller [getCallingProcName]
   }
   # display caller name as prefix
   if {$caller ne {}} {
      append prefix "$caller: "
   }
   report [sgr db "DEBUG $prefix$message"] 0 1
}

# alternative procedure used when debug is disabled
proc __reportDebugNop {message {caller _undef_}} {}

proc reportWarning {message} {
   reportError $message WARNING wa 0
}

proc reportError {message {severity ERROR} {sgrkey er} {incr_count 1}} {
   lappend ::errreport_buffer [list reportError $message $severity $sgrkey\
      $incr_count]
}

proc __reportError {message {severity ERROR} {sgrkey er} {incr_count 1}} {
   # if report disabled, also disable error raise to get a coherent behavior
   # (if no message printed, no error code change). also no report if no msg
   if {![getState inhibit_errreport] && [string length $message]} {
      if {$incr_count} {
         incrErrorCount
      }
      set msgsgr "[sgr $sgrkey $severity]: $message"
      # record message to report it later on if a record id is found
      if {[currentState msgrecordid] ne {}} {
         recordMessage $msgsgr
      # skip message report if silent
      } elseif {[isVerbosityLevel concise]} {
         # save error messages to render them all together in JSON format
         if {[isStateEqual report_format json]} {
            lappend ::g_report_erralist $severity $message
         } else {
            report $msgsgr 0 0 1
         }
      }
   }
}

# throw known error (call error with 'known error' code)
proc knerror {message {code MODULES_ERR_KNOWN}} {
   error $message {} $code
}

proc knerrorOrWarningIfForced {message {code MODULES_ERR_KNOWN}} {
   if {[getState force]} {
      reportWarning $message
   } else {
      knerror $message $code
   }
}

# save message if report is not yet initialized
proc reportErrorAndExit {message} {
   lappend ::errreport_buffer [list reportErrorAndExit $message]
}

# regular procedure to use once error report is initialized
proc __reportErrorAndExit {message} {
   incrErrorCount
   renderFalse
   error $message {} MODULES_ERR_RENDERED
}

proc reportErrorOrWarningIfForced {message} {
   if {[getState force]} {
      reportWarning $message
   } else {
      reportError $message
   }
}

proc reportInternalBug {message {modfile {}} {title {Module ERROR}}} {
   reportError [formatInternalBugMsg $message $modfile] $title me
}

proc reportInternalBugWarning {message {modfile {}} {title {Module\
   WARNING}}} {
   reportError [formatInternalBugMsg $message $modfile] $title wa 0
}

proc reportInternalBugOrWarningIfForced {message {modfile {}}} {
   if {[getState force]} {
      reportInternalBugWarning $message $modfile
   } else {
      reportInternalBug $message $modfile
   }
}

proc formatInternalBugMsg {message modfile} {
   if {$modfile ne {}} {
      set message [formatMessageInModule $message $modfile]
   }
   return "$message\nPlease contact <[getConf contact]>"
}

proc formatMessageInModule {message modfile} {
   return "$message\nIn '$modfile'"
}

proc reportInfo {message {title INFO}} {
   if {[isVerbosityLevel normal]} {
      # use reportError for convenience but there is no error here
      reportError $message $title in 0
   }
}

proc reportTrace {message {title TRACE}} {
   if {[isVerbosityLevel trace]} {
      # use reportError for convenience but there is no error here
      reportError [sgr tr $message] $title tr 0
   }
}

proc reportTimer {message timestr start_us stop_us} {
   set elapsed_ms [expr {($stop_us - $start_us) / 1000.0}]
   report [sgr db "TIMER $message [format $timestr $elapsed_ms]"] 0 1
}

# trace procedure execution start
proc reportTraceExecEnter {cmdstring op} {
   reportDebug $cmdstring [getCallingProcName]
}

# time procedure execution duration
proc reportTimerExecEnter {cmdstring op} {
   uplevel 1 set proc_timer_start [clock microseconds]
}

proc reportTimerExecLeave {cmdstring code result op} {
   reportTimer $cmdstring {(%.3f ms)} [uplevel 1 set proc_timer_start]\
      [clock microseconds]
}

# record messages on the eventual additional module evaluations that have
# occurred during the current evaluation
proc reportModuleEval {} {
   set evalid [currentState evalid]
   array set contexttitle {conun {Unloading conflict} reqlo {Loading\
      requirement} depre {Reloading dependent} depun {Unloading dependent}\
      urequn {Unloading useless requirement}}

   if {[info exists ::g_moduleEval($evalid)]} {
      foreach contextevallist $::g_moduleEval($evalid) {
         set msgrecidlist [lassign $contextevallist context]
         # skip context with no description title
         if {[info exists contexttitle($context)]} {
            # exclude hidden modules from report unless an high level of
            # verbosity is set
            if {[info exists ::g_moduleHiddenEval($evalid:$context)] &&\
               ![isVerbosityLevel verbose2]} {
               lassign [getDiffBetweenList $msgrecidlist\
                  $::g_moduleHiddenEval($evalid:$context)] msgrecidlist
            }

            if {[llength $msgrecidlist]} {
               set moddesiglist {}
               foreach msgrecid $msgrecidlist {
                  lappend moddesiglist [getModuleDesignation $msgrecid]
               }
               reportInfo [join $moddesiglist] $contexttitle($context)
            }
         }
      }
      # purge list in case same evaluation is re-done afterward
      unset ::g_moduleEval($evalid)
   }
}

# render messages related to current record id under an header block
proc reportMsgRecord {header {hidden 0}} {
   set recid [currentState msgrecordid]
   if {[info exists ::g_msgRecord($recid)]} {
      # skip message report if silent (report even if hidden as soon as msgs
      # are associated to hidden module evaluation)
      if {[isVerbosityLevel concise]} {
         set tty_cols [getState term_columns]
         set padding {  }

         set dispmsg $header
         foreach msg $::g_msgRecord($recid) {
            # split lines if too large for terminal
            set first 1
            set max_idx [tcl::mathfunc::max [expr {$tty_cols - [string length\
               $padding]}] 1]
            set linelist [list]
            foreach line [split $msg \n] {
               set lineadd {}
               while {$lineadd ne $line} {
                  set line_max_idx $max_idx
                  # sgr tags consume no length
                  set eidx 0
                  while {[set sidx [string first "\033\[" $line $eidx]] !=\
                     -1} {
                     set eidx [string first m $line $sidx]
                     incr line_max_idx [expr {1 + $eidx - $sidx}]
                  }

                  # no split if no whitespace found to slice
                  if {[string length $line] > $line_max_idx && [set cut_idx\
                     [string last { } $line $line_max_idx]] != -1} {
                     set lineadd [string range $line 0 $cut_idx-1]
                     set line [string range $line $cut_idx+1 end]
                  } else {
                     set lineadd $line
                  }
                  # skip empty line
                  if {[string trim $lineadd] ne {}} {
                     lappend linelist $lineadd
                  }
                  if {$first} {
                     set first 0
                     incr max_idx -[string length $padding]
                     if {$max_idx < 1} {set max_idx 1}
                  }
               }
            }

            # display each line
            set first 1
            foreach line $linelist {
               append dispmsg \n
               if {$first} {
                  set first 0
               } else {
                  append dispmsg $padding
               }
               append dispmsg $padding$line
            }
         }
         reportSeparateNextContent
         report $dispmsg
         reportSeparateNextContent
      }

      # purge message list in case same evaluation is re-done afterward
      unset ::g_msgRecord($recid)
   # report header if no other specific msg to output in verbose mode or in
   # normal verbosity mode if currently processing a cmd which triggers
   # multiple module evaluations that cannot be guessed by the user (excluding
   # dependency evaluations which are reported by triggering top evaluation)
   # if hidden flag is enabled report only if verbosity >= verbose2
   } elseif {(!$hidden && ([isVerbosityLevel verbose] || ([isVerbosityLevel\
      normal] && ([ongoingCommandName restore] || [ongoingCommandName source]\
      || [ongoingCommandName reset] || [ongoingCommandName stash] ||\
      [ongoingCommandName stashpop] || [ongoingCommandName cacheclear] ||\
      [ongoingCommandName cachebuild]) && $recid eq [topState msgrecordid])))\
      || ($hidden && [isVerbosityLevel verbose2])} {
      report $header
   }
}

# separate next content produced if any
proc reportSeparateNextContent {} {
   lappend ::errreport_buffer [list reportSeparateNextContent]
}

# regular procedure to use once error report is initialized
proc __reportSeparateNextContent {} {
   # hold or apply
   if {[depthState reportholdid] > 0} {
      lappend ::g_holdReport([currentState reportholdid]) [list\
         reportSeparateNextContent]
   } else {
      setState report_sep_next 1
   }
}

# save message for block rendering
proc recordMessage {message} {
   set recid [currentState msgrecordid]

   if {$recid eq [currentState reportholdrecid]} {
      lappend ::g_holdReport([currentState reportholdid]) [list\
         recordMessage $message]
   } else {
      lappend ::g_msgRecord($recid) $message
   }
}

# check if some msg have been recorded for current evaluation
proc isMsgRecorded {} {
   return [info exists ::g_msgRecord([currentState msgrecordid])]
}

# filter and format error stack trace to only report useful content
proc formatErrStackTrace {errmsg loc {cmdlist {}}} {
   set headstr "\n    while executing\n"
   set splitstr "\n    invoked from within\n"
   set splitstrlen [string length $splitstr]
   set aftheadidx [string first $headstr $errmsg]
   if {$aftheadidx != -1} {
      incr aftheadidx [string length $headstr]
   }

   # get name of invalid command name to maintain it in error stack trace
   if {[string equal -length 22 {invalid command name "} $errmsg]} {
      set unkcmd [lindex [split [string range $errmsg 0 $aftheadidx] {"}] 1]
   } else {
      set unkcmd {}
   }

   # get list of modulecmd.tcl internal procedure to filter out from stack
   # also add the way subcmds are launched through module proc ($cmdprocname)
   # skip this when no interp command list is provided
   if {[llength $cmdlist]} {
      lassign [getDiffBetweenList [list {*}[info commands] {*}[info procs]\
         {$cmdprocname}] $cmdlist] filtercmdlist keepcmdlist
   } else {
      set filtercmdlist {}
   }

   # define commands to filter out from bottom of stack
   set filtercmdendlist [list {eval [getModuleContent\
      $::ModulesCurrentModulefile]} "source $loc" {uplevel 1 source\
      $siteconfig} {eval $cachecontent}]
   # define commands to filter out from middle of stack
   set filtercmdmidlist [list {interp eval $itrp $::source_cache($filename)}]

   # filter out modulecmd internal references at beginning of stack
   set internals 1
   while {$internals && $aftheadidx != -1} {
      # fetch erroneous command and its caller
      set stackelt [string range $errmsg $aftheadidx [string first\
         $splitstr $errmsg $aftheadidx]]
      lassign [split [lindex [split $stackelt {"}] 1]] cmd1 cmd2
      set cmdcaller [lindex [split [string range $stackelt [string last\
         {(procedure } $stackelt] end] {"}] 1]
      if {$cmd1 eq {eval}} {
         set cmd1 $cmd2
      }

      # filter out stack element referring to or called by an unknown
      # procedure (ie. a modulecmd.tcl internal procedure)
      if {$cmd1 ne $unkcmd && ($cmdcaller in $filtercmdlist || $cmd1 in\
         $filtercmdlist)} {
         set errmsg [string replace $errmsg $aftheadidx [expr {[string first\
            $splitstr $errmsg] + $splitstrlen - 1}]]
      } else {
         set internals 0
      }
   }

   # filter out modulecmd internal references at end of stack
   set internals 1
   while {$internals} {
      set beffootidx [string last $splitstr $errmsg]
      set stackelt [string range $errmsg $beffootidx end]
      set cmd [lindex [split $stackelt {"}] 1]

      if {$cmd in $filtercmdendlist} {
         set errmsg [string replace $errmsg $beffootidx end]
      } else {
         set internals 0
      }
   }

   # filter out modulecmd internal references in middle of stack
   foreach filtercmdmid $filtercmdmidlist {
      set filterstartidx [string first \"$filtercmdmid\" $errmsg]
      if {$filterstartidx != -1} {
         set filterendidx [expr {[string first $splitstr $errmsg\
            $filterstartidx] + [string length $splitstr] - 1}]
         set errmsg [string replace $errmsg $filterstartidx $filterendidx]
      }
   }

   # replace error location at end of stack
   set lastnl [string last \n $errmsg]
   set lastline [string range $errmsg $lastnl+1 end]
   if {[string match {    ("eval" body line*} $lastline]} {
      set errmsg [string replace $errmsg $lastnl $lastnl+[string length\
         "    (\"eval\" body line"] "\n    (file \"$loc\" line"]
   } elseif {![string match {    (file *} $lastline]} {
      # add error location at end of stack
      append errmsg "\n    (file \"$loc\")"
   }

   return $errmsg
}

# Test if color is enabled and passed sgrkey is defined and not null
proc isSgrkeyColored {sgrkey} {
   return [expr {[getConf color] && [info exists ::g_colors($sgrkey)] &&\
      $::g_colors($sgrkey) ne {}}]
}

# Select Graphic Rendition of a string with passed sgr keys (if color enabled)
proc sgr {keylist str {himatchmap {}} {othkeylist {}}} {
   if {[getConf color]} {
      set sgrreset 22
      foreach sgrkey $keylist {
         if {[info exists ::g_colors($sgrkey)]} {
            # track color key that have been used
            if {![info exists ::g_used_colors($sgrkey)]} {
               set ::g_used_colors($sgrkey) 1
            }
            if {[info exists sgrset]} {
               append sgrset {;}
            }
            append sgrset $::g_colors($sgrkey)
            # if render bold or faint just reset that attribute, not all
            if {$sgrreset != 0 && $sgrset != 1 && $sgrset != 2} {
               set sgrreset 0
            }
         }
      }
      if {![llength $othkeylist]} {
         # highlight matching substring
         if {[llength $himatchmap]} {
            set str [string map $himatchmap $str]
         }
         if {[info exists sgrset]} {
            set str "\033\[${sgrset}m$str\033\[${sgrreset}m"
         }
      } else {
         if {![info exists sgrset]} {
            set sgrset {}
         } else {
            append sgrset {;}
         }
         # determine each chunk where the other sgr keys apply
         set tagsgrlen [expr {int(ceil([string length $str]/[llength\
            $othkeylist]))}]
         for {set i 0} {$i < [llength $othkeylist]} {incr i} {
            set idx [expr {$i*$tagsgrlen}]
            set sgrkey [lindex $othkeylist $i]
            lappend sgridxlist $idx $::g_colors($sgrkey)
            # track color key that have been used
            if {![info exists ::g_used_colors($sgrkey)]} {
               set ::g_used_colors($sgrkey) 1
            }
         }
         # determine each chunk where the highlight applies
         set hiidxlist {}
         foreach {mstr sgrmstr} $himatchmap {
            set idx 0
            while {$idx != -1} {
               if {[set idx [string first $mstr $str $idx]] != -1} {
                  lappend hiidxlist $idx
                  incr idx [string length $mstr]
                  # add highlight end index unless if end of string
                  if {$idx < [string length $str]} {
                     lappend hiidxlist $idx
                  }
               }
            }
            # no need to look at next match string if this one was found
            if {[llength $hiidxlist]} {
               break
            }
         }

         # mix other sgr chunks with highlighted chunks to define sgr codes
         set i 0
         set j 0
         set sgridx [lindex $sgridxlist 0]
         set hiidx [lindex $hiidxlist 0]
         set hicur 0
         set sgrcur {}
         set sgrrst {0;}
         while {$i < [llength $sgridxlist] || $j < [llength $hiidxlist]} {
            set sgrcode {}
            set cursgridx $sgridx
            # sgr chunk change
            if {$sgridx ne {} && ($hiidx eq {} || $sgridx <= $hiidx)} {
               incr i
               set sgrcur $sgrset[lindex $sgridxlist $i]
               set idx $sgridx
               if {$idx != 0} {
                  append sgrcode $sgrrst
               }
               append sgrcode $sgrcur
               incr i
               set sgridx [lindex $sgridxlist $i]
            }
            # highlight change
            if {$hiidx ne {} && ($cursgridx eq {} || $hiidx <= $cursgridx)} {
               set idx $hiidx
               set hicur [expr {!$hicur}]
               if {$hicur} {
                  if {$sgrcode ne {}} {
                     append sgrcode {;}
                  }
                  append sgrcode $::g_colors(hi)
               # restore current sgr set to only clear highlight
               } elseif {$sgrcode eq {}} {
                  append sgrcode $sgrrst $sgrcur
               }
               incr j
               set hiidx [lindex $hiidxlist $j]
            } elseif {$hicur} {
               append sgrcode {;} $::g_colors(hi)
            }
            lappend fullsgridxlist $idx $sgrcode
         }
         # reset sgr at end of string
         lappend fullsgridxlist [string length $str] 0

         # apply defined sgr codes to the string
         set stridx 0
         foreach {sgridx sgrcode} $fullsgridxlist {
            if {$sgridx != 0} {
               append sgrstr [string range $str $stridx $sgridx-1]
            }
            append sgrstr "\033\[${sgrcode}m"
            set stridx $sgridx
         }
         set str $sgrstr
      }
   }
   return $str
}

# Sort tags to return those matching defined sgr keys in a list up to a given
# maxnb number and if tag not set to be displayed by its name. Other elements
# are returned in a separate list
proc getTagSgrForModname {keylist maxnb} {
   set sgrkeylist {}
   if {[getConf color]} {
      set otherlist {}
      foreach key $keylist {
         if {[info exists ::g_colors($key)] && ![info exists\
            ::g_tagColorName($key)] && [llength $sgrkeylist] < $maxnb} {
            lappend sgrkeylist $key
         } else {
            lappend otherlist $key
         }
      }
   } else {
      set otherlist $keylist
   }
   return [list $sgrkeylist $otherlist]
}

# save message if report is not yet initialized
proc report {message {nonewline 0} {immed 0} {padnl 0}} {
   lappend ::errreport_buffer [list report $message $nonewline $immed $padnl]
}

# regular procedure to use once error report is initialized
proc __report {message {nonewline 0} {immed 0} {padnl 0}} {
   # hold or print output
   if {!$immed && [depthState reportholdid] > 0} {
      lappend ::g_holdReport([currentState reportholdid]) [list report\
         $message $nonewline $immed $padnl]
   } else {
      # produce blank line prior message if asked to
      if {[isStateDefined reportfd] && [isStateDefined report_sep_next]} {
         unsetState report_sep_next
         report [expr {[isStateEqual report_format json] ? {,} : {}}]
      }
      # prefix msg lines after first one with 2 spaces
      if {$padnl} {
         set first 1
         foreach line [split $message \n] {
            if {$first} {
               set first 0
            } else {
               append padmsg "\n  "
            }
            append padmsg $line
         }
         set message $padmsg
      }

      # protect from issue with fd, just ignore it
      catch {
         if {$nonewline} {
            puts -nonewline [getState reportfd] $message
         } else {
            puts [getState reportfd] $message
         }
      }
   }
}

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

# report defined command (used in display evaluation mode)
proc reportCmd {cmd args} {
   # use Tcl native string representation of list
   if {$cmd eq {-nativeargrep}} {
      set cmd [lindex $args 0]
      set cmdargs [lrange $args 1 end]
   } else {
      set cmdargs [listTo tcl $args 0]
   }
   set extratab [expr {[string length $cmd] < 8 ? "\t" : {}}]
   report [sgr cm $cmd]$extratab\t$cmdargs

   # empty string returns if command result is another command input
   return {}
}

# report defined command (called as an execution trace)
proc reportCmdTrace {cmdstring args} {
   reportCmd {*}$cmdstring
}

proc reportVersion {} {
   report {Modules Release 5.6.1\
      (2025-11-25)}
}

proc reportName {} {
   report Modules
}

# disable error reporting (non-critical report only) unless debug enabled
proc inhibitErrorReport {} {
   if {![isVerbosityLevel trace]} {
      setState inhibit_errreport 1
   }
}

proc initProcReportTrace {type prc} {
   ##nagelfar ignore #7 Non static subcommand
   if {[isVerbosityLevel debug] && [getState timer]} {
      # time execution of procedure instead of regular debug report
      trace $type execution $prc enter reportTimerExecEnter
      trace $type execution $prc leave reportTimerExecLeave
   } elseif {[isVerbosityLevel debug2]} {
      # trace each procedure call
      trace $type execution $prc enter reportTraceExecEnter
   }
}

# init error report and output buffered messages
proc initErrorReport {} {
   # ensure init is done only once
   if {![isStateDefined init_error_report]} {
      setState init_error_report 1

      # ask for color init now as debug mode has already fire lines to render
      # and we want them to be reported first (not the color init lines)
      if {[isVerbosityLevel debug]} {
         getConf color
      }

      # trigger pager start if something needs to be printed, to guaranty
      # reportDebug calls during pager start are processed in buffer mode
      if {[isVerbosityLevel debug]} {
         getState reportfd
      }

      # only report timing information in debug mode if timer mode is enabled
      if {[isVerbosityLevel debug] && ![getState timer]} {
         # replace report procedures used to buffer messages until error
         # report being initialized by regular report procedures
         # delete initial reportDebug proc after getState which needs it
         rename ::reportDebug {}
         rename ::__reportDebug ::reportDebug
      } else {
         rename ::reportDebug {}
         # set a disabled version if debug is disabled
         rename ::__reportDebugNop ::reportDebug
      }
      rename ::reportError {}
      rename ::__reportError ::reportError
      rename ::reportErrorAndExit {}
      rename ::__reportErrorAndExit ::reportErrorAndExit
      rename ::reportSeparateNextContent {}
      rename ::__reportSeparateNextContent ::reportSeparateNextContent
      rename ::report {}
      rename ::__report ::report

      # setup traces for either debug or timer reports
      if {[isVerbosityLevel debug] && [getState timer] || [isVerbosityLevel\
         debug2]} {
         # list of core procedures to exclude from tracing
         set excl_prc_list [list report reportDebug reportFlush reportTimer\
            reportTraceExecEnter reportTimerExecEnter reportTimerExecLeave\
            initProcReportTrace isVerbosityLevel reportSeparateNextContent\
            getState setState unsetState lappendState lpopState currentState\
            depthState isStateDefined isStateEqual sgr getConf setConf\
            unsetConf lappendConf getCallingProcName isEnvVarDefined\
            envVarEquals log getConfList]

         foreach prc [info procs] {
            if {$prc ni $excl_prc_list} {
               initProcReportTrace add $prc
            }
         }
      }

      # now error report is init output every message saved in buffer; first
      # message will trigger message paging configuration and startup unless
      # already done if debug mode enabled
      foreach errreport $::errreport_buffer {
         {*}$errreport
      }
   }
}

# drop or report held messages
proc releaseHeldReport {args} {
   foreach {holdid action} $args {
      if {[info exists ::g_holdReport($holdid)]} {
         if {$action eq {report}} {
            foreach repcall $::g_holdReport($holdid) {
               {*}$repcall
            }
         }
         unset ::g_holdReport($holdid)
      }
      # also drop reported tag of conflict error
      if {[info exists ::g_holdReportConflict($holdid)]} {
         if {$action eq {drop}} {
            foreach {evalid drop_mod_list} $::g_holdReportConflict($holdid) {
               lassign [getDiffBetweenList $::report_conflict($evalid)\
                  $drop_mod_list] ::report_conflict($evalid)
            }
         }
         unset ::g_holdReportConflict($holdid)
      }
      # also drop failed module evaluation to get their failure information
      # if later retried
      if {[info exists ::g_holdModuleFailedEval($holdid)]} {
         if {$action eq {drop}} {
            foreach {evalid failed_context failed_mod}\
               $::g_holdModuleFailedEval($holdid) {
               set failed_eval_list $::g_moduleFailedEval($evalid)
               for {set i 0} {$i < [llength $failed_eval_list]} {incr i} {
                  if {[lindex $failed_eval_list $i] eq $failed_context &&\
                     [lindex $failed_eval_list $i+1] eq $failed_mod} {
                     set failed_eval_list [lreplace $failed_eval_list $i $i+1]
                     break
                  }
               }
               set ::g_moduleFailedEval($evalid) $failed_eval_list
            }
         }
         unset ::g_holdModuleFailedEval($holdid)
      }
   }
}

# final message output and reportfd flush and close
proc reportFlush {} {
   # report execution time if asked
   if {[getState timer]} {
      reportSeparateNextContent
      reportTimer {Total execution took} {%.3f ms} $::timer_start [clock\
         microseconds]
   }

   # finish output document if json format enabled
   if {[isStateEqual report_format json]} {
      # render error messages all together
      if {[info exists ::g_report_erralist]} {
         # ignite report first to get eventual error message from report
         # initialization in order 'foreach' got all messages prior firing
         report "\"errors\": \[" 1
         foreach {sev msg} $::g_report_erralist {
            # split message in lines
            lappend dispmsglist "\n{ \"severity\": \"$sev\", \"message\": \[\
               \"[join [split [charEscaped $msg \"] \n] {", "}]\" \] }"
         }
         report "[join $dispmsglist ,] \]"
      }
      # inhibit next content separator if output is ending
      unsetState report_sep_next
      report \}
   }

   # close pager if enabled
   if {[isStateDefined reportfd] && ![isStateEqual reportfd stderr]} {
      catch {flush [getState reportfd]}
      catch {close [getState reportfd]}
   }
}

proc logEvent {event args} {
   if {$event in [getConfList logged_events]} {
      set log_info_list [list user [getState username] {*}$args]
      set log_formatted_list {}
      foreach {key val} $log_info_list {
         lappend log_formatted_list "$key=\"$val\""
      }
      set log_message [join $log_formatted_list]
      log $log_message
   }
}

proc log {log_message} {
   lappend ::g_log_msg_list $log_message
}

# send messages to log
proc logFlush {} {
   # logger pipe is started only if enabled and some msgs need to be logged
   set logfd [getState logfd]

   # logging only occurs from this procedure with is run during termination
   if {[string length $logfd]} {
      if {[catch {
         foreach log_msg $::g_log_msg_list {
            puts $logfd $log_msg
         }
         flush $logfd
         close $logfd
      } errMsg]} {
         reportWarning {Issue occurred when logging information}
      }
   }
}

# check if element passed as argument (corresponding to a kind of information)
# should be part of output content
proc isEltInReport {elt {retifnotdef 1}} {
   # get config name relative to current sub-command and output format
   set conf [currentState commandname]
   if {[getState report_format] ne {regular}} {
      append conf _[getState report_format]
   }
   append conf _output
   set arrname ::g_$conf
   ##nagelfar vartype arrname varName

   if {[info exists ::g_config_defs($conf)]} {
      # build value cache if it does not exist yet
      if {![array exists $arrname]} {
         array set $arrname {}
         foreach confelt [getConfList $conf] {
            ##nagelfar ignore Suspicious variable name
            set ${arrname}($confelt) 1
         }
      }

      # check if elt is marked to be included in output
      ##nagelfar ignore Suspicious variable name
      return [info exists ${arrname}($elt)]
   } else {
      # return $retifnotdef (ok by default) in case no config option
      # corresponds to the current module sub-command and output format
      return $retifnotdef
   }
}

proc registerModuleDesignation {evalid mod vrlist taglist} {
   set ::g_moduleDesgination($evalid) [list $mod $vrlist $taglist]
}

proc getModuleFromEvalId {evalid} {
   if {[info exists ::g_moduleDesgination($evalid)]} {
      return [lindex $::g_moduleDesgination($evalid) 0]
   }
}

proc getModuleDesignation {from {mod {}} {sgr 1}} {
   # fetch module name version and variants from specified context
   switch -- $from {
      spec {
         set moddesig [getModuleNameAndVersFromVersSpec $mod]
         set vrlist [getVariantList $mod 7 0 1]
         set taglist {}
      }
      loaded {
         set moddesig $mod
         set vrlist [getVariantList $mod 7]
         set taglist {}
      }
      default {
         # fetch information from passed evaluation id
         if {[info exists ::g_moduleDesgination($from)]} {
            lassign $::g_moduleDesgination($from) moddesig vrlist taglist
         # if not found, use passed spec to compute designation
         } else {
            set moddesig [getModuleNameAndVersFromVersSpec $mod]
            set vrlist [getVariantList $mod 7 0 1]
            set taglist [getExportTagList $mod]
         }
      }
   }

   # build module designation
   switch -- $sgr {
      2 {
         set vrsgr va
         set himatchmap [prepareMapToHightlightSubstr $moddesig]

         set showtags 1
         # prepare list of tag abbreviations that can be substituted and list
         # of tags whose name should be colored
         getConf tag_abbrev
         getConf tag_color_name
         # abbreviate tags
         set taglist [abbrevTagList $taglist]
      }
      1 {
         set vrsgr {se va}
         set himatchmap {}
         set showtags 0
      }
      0 {
         set vrsgr {}
         set himatchmap {}
         set showtags 0
      }
   }
   lassign [formatListEltToDisplay $moddesig {} {} {} {} 0 0 $taglist\
      $showtags $vrlist $vrsgr 1 $himatchmap] disp dispsgr displen

   return $dispsgr
}

#
# Helper procedures to format various messages
#

proc getHintUnFirstMsg {modlist} {
   return "HINT: Might try \"module unload [join $modlist]\" first."
}

proc getHintLoFirstMsg {modlist} {
   if {[llength $modlist] > 1} {
      set oneof {at least one of }
      set mod modules
   } else {
      set oneof {}
      set mod module
   }
   return "HINT: ${oneof}the following $mod must be loaded first: [join\
      $modlist]"
}

proc getErrConflictMsg {conlist} {
   return "Module cannot be loaded due to a conflict.\n[getHintUnFirstMsg\
      $conlist]"
}

proc getErrPrereqMsg {prelist {load 1} {is_path_specific 0}} {
   if {$load} {
      foreach pre $prelist {
         lappend predesiglist [getModuleDesignation spec $pre]
      }
      lassign [list {} missing [getHintLoFirstMsg $predesiglist]] un miss\
         hintmsg
   } else {
      ##nagelfar ignore Found constant
      lassign [list un a [getHintUnFirstMsg $prelist]] un miss hintmsg
   }
   set path_specific_msg [expr {$is_path_specific ? [getSpecificPathMsg] :\
      {}}]
   return "Module cannot be ${un}loaded due to $miss\
      prereq$path_specific_msg.\n$hintmsg"
}

proc getErrReqLoMsg {prelist {is_path_specific 0}} {
   foreach pre $prelist {
      lappend predesiglist [getModuleDesignation spec $pre]
   }
   set path_specific_msg [expr {$is_path_specific ? [getSpecificPathMsg] :\
      {}}]
   return "Load of requirement [join $predesiglist { or }]$path_specific_msg\
      failed"
}

proc getReqNotLoadedMsg {prelist {is_path_specific 0}} {
   foreach pre $prelist {
      lappend predesiglist [getModuleDesignation spec $pre]
   }
   set path_specific_msg [expr {$is_path_specific ? [getSpecificPathMsg] :\
      {}}]
   return "Requirement [join $predesiglist { or }]$path_specific_msg is not\
      loaded"
}

proc getSpecificPathMsg {} {
   return { (specific path)}
}

proc getKindModuleBeStateMsg {kind mod_list state} {
   set is [expr {[llength $mod_list] > 1 ? {are} : {is}}]
   foreach mod $mod_list {
      lappend mod_desig_list [getModuleDesignation loaded $mod]
   }
   return "$kind [join $mod_desig_list { and }] $is $state"
}

proc getDepLoadedMsg {pre_mod_list} {
   return [getKindModuleBeStateMsg Dependent $pre_mod_list loaded]
}

proc getDepLoadingMsg {pre_mod_list} {
   return [getKindModuleBeStateMsg Dependent $pre_mod_list loading]
}

proc getErrConUnMsg {conlist} {
   set condesiglist {}
   foreach con $conlist {
      lappend condesiglist [getModuleDesignation spec $con]
   }
   return "Unload of conflicting [join $condesiglist { and }] failed"
}

proc getConLoadedMsg {con_mod_list} {
   return [getKindModuleBeStateMsg Conflicting $con_mod_list loaded]
}

proc getConLoadingMsg {con_mod_list} {
   return [getKindModuleBeStateMsg Conflicting $con_mod_list loading]
}

proc getPresentConflictErrorMsg {curmodnamevr con_mod_list is_loading} {
   if {[isModuleEvaluated any $curmodnamevr {} {*}$con_mod_list] || [getState\
      force]} {
      return [expr {$is_loading ? [getConLoadingMsg $con_mod_list] :\
         [getConLoadedMsg $con_mod_list]}]
   } else {
      return [getErrConflictMsg $con_mod_list]
   }
}

proc getForbiddenMsg {mod fpmod} {
   set msg "Access to module [getModuleDesignation spec $mod 2] is denied"
   set extramsg [getModuleTagProp $mod $fpmod forbidden message]
   if {$extramsg ne {}} {
      append msg \n$extramsg
   }
   return $msg
}

proc getNearlyForbiddenMsg {mod fpmod} {
   set after [getModuleTagProp $mod $fpmod nearly-forbidden after]
   set msg "Access to module will be denied starting '$after'"
   set extramsg [getModuleTagProp $mod $fpmod nearly-forbidden message]
   if {$extramsg ne {}} {
      append msg \n$extramsg
   }
   return $msg
}

proc getWarningMsg {mod fpmod} {
   return [getModuleTagProp $mod $fpmod warning message]
}

proc getStickyUnloadMsg {{tag sticky}} {
   return "Unload of $tag module skipped"
}

proc getStickyForcedUnloadMsg {} {
   return {Unload of sticky module forced}
}

proc getModWithAltVrIsLoadedMsg {mod is_loading} {
   set vrdesiglist {}
   foreach vr [getVariantList $mod 1] {
      lappend vrdesiglist [sgr va $vr]
   }
   set state [expr {$is_loading ? {loading} : {loaded}}]
   return "Variant [sgr se "\{"][join $vrdesiglist [sgr se :]][sgr se "\}"]\
      is already $state"
}

proc getModFromDiffPathIsLoadedMsg {} {
   return {Module already loaded from a different modulepath}
}

proc getEmptyNameMsg {type} {
   return "Invalid empty $type name"
}

#
# Stack of message recording/eval unique identifiers
#

proc pushMsgRecordId {recid {setmsgid 1}} {
   lappendState evalid $recid
   if {$setmsgid} {
      lappendState msgrecordid $recid
   }
}

proc popMsgRecordId {{setmsgid 1}} {
   lpopState evalid
   if {$setmsgid} {
      lpopState msgrecordid
   }
}

proc clearAllMsgRecordId {} {
   unsetState evalid
   unsetState msgrecordid
}

#
# Format output text
#

# format an element with its syms for display in a list
proc formatListEltToDisplay {elt eltsgr eltsuffix sym_list symsgr show_syms\
   sgrdef tag_list show_tags vr_list vrsgr show_vrs {himatchmap {}}\
   {himatcharrvrmap {}} {himatcharrvrvalmap {}}} {
   # fetch sgr codes from tags to apply directly on main element
   if {$show_tags && [llength $tag_list]} {
      # if more codes than character in elt, additional codes apply to the
      # side tag list
      lassign [getTagSgrForModname $tag_list [string length $elt]] tagsgrlist\
         tag_list
   } else {
      set tagsgrlist {}
   }
   # display default sym graphically over element name
   if {$show_syms} {
      if {[set defidx [lsearch -exact $sym_list default]] != -1 && $sgrdef} {
         set sym_list [lreplace $sym_list $defidx $defidx]
         lappend eltsgrlist de
      }
   }

   set displen 0
   set disp $elt
   lappend eltsgrlist $eltsgr
   set dispsgr [sgr $eltsgrlist $elt $himatchmap $tagsgrlist]
   # enclose name between quotes if a space is found
   if {[string first { } $elt] != -1} {
      incr displen 2
      set dispsgr '$dispsgr'
   }
   # append suffix
   append disp $eltsuffix
   append dispsgr $eltsuffix

   # format variant list if any
   if {$show_vrs && [llength $vr_list]} {
      array set himatchvrvalarr $himatcharrvrvalmap
      array set himatchvrarr $himatcharrvrmap
      set commasgr [sgr se ,]
      set vrssgr "[sgr se \{]"
      set vrs \{
      foreach vrspec $vr_list {
         lassign $vrspec vrname vrnameset vrvalues vrdflidx vrloadedidx\
            loadedsgrkey
         array unset vrvalsgridx
         # apply sgr to default and loaded variant value
         if {$vrdflidx != -1} {
            lappend vrvalsgridx($vrdflidx) de
         }
         if {$vrloadedidx != -1} {
            lappend vrvalsgridx($vrloadedidx) $loadedsgrkey
         }
         set vrsgrvalues $vrvalues
         foreach vrvalidx [array names vrvalsgridx] {
            lset vrsgrvalues $vrvalidx [sgr $vrvalsgridx($vrvalidx) [lindex\
               $vrvalues $vrvalidx]]
         }
         if {[info exists notfirstvr]} {
            set colonsgr [sgr se :]
            append vrssgr $colonsgr
            append vrs :
         } else {
            set notfirstvr 1
         }
         # highlight variant if corresponds to one set in query
         if {[info exists himatchvrarr($vrname)]} {
            set hivrmap $himatchvrarr($vrname)
            set hivrvalmap $himatchvrvalarr($vrname)
         } else {
            set hivrmap {}
            set hivrvalmap {}
         }
         if {[string length $vrnameset]} {
            append vrssgr [sgr $vrsgr $vrnameset $hivrmap]
         }
         append vrssgr [sgr $vrsgr [lindex $vrsgrvalues 0] $hivrvalmap]
         foreach vrvalue [lrange $vrsgrvalues 1 end] {
            append vrssgr $commasgr[sgr $vrsgr $vrvalue $hivrvalmap]
         }
         append vrs $vrnameset[join $vrvalues :]
      }
      append vrssgr [sgr se \}]
      append vrs \}
      append dispsgr $vrssgr
      append disp $vrs
   }

   # format remaining sym list
   if {$show_syms && [llength $sym_list]} {
      # track if a symbol has been reported excluding sym for alias '@'
      if {![info exists ::g_used_sym_nocolor] && ([llength $sym_list] > 1 ||
         [lindex $sym_list 0] ne {@})} {
         set ::g_used_sym_nocolor 1
      }
      append disp "([join $sym_list :])"
      set symssgr [sgr se (]
      foreach sym $sym_list {
         if {[info exists notfirstsym]} {
            if {![info exists colonsgr]} {
               set colonsgr [sgr se :]
            }
            append symssgr $colonsgr
         } else {
            set notfirstsym 1
         }
         append symssgr [sgr $symsgr $sym]
      }
      append symssgr [sgr se )]
      append dispsgr $symssgr
   }

   # format tag list if any remaining
   if {$show_tags && [llength $tag_list]} {
      append disp " <[join $tag_list :]>"
      set tagssgr " [sgr se <]"
      foreach tag $tag_list {
         # track tag name or abbreviation that have been used
         if {![info exists ::g_used_tags($tag)]} {
            set ::g_used_tags($tag) 1
         }
         if {[info exists notfirsttag]} {
            if {![info exists colonsgr]} {
               set colonsgr [sgr se :]
            }
            append tagssgr $colonsgr
         } else {
            set notfirsttag 1
         }
         # try to sgr in case a code apply to the tag
         append tagssgr [sgr $tag $tag]
      }
      append tagssgr [sgr se >]
      append dispsgr $tagssgr
   }

   # compute length
   incr displen [string length $disp]

   return [list $disp $dispsgr $displen]
}

# format an element with its syms for a long/detailed display in a list
proc formatListEltToLongDisplay {elt eltsgr eltsuffix sym_list symsgr mtime\
   sgrdef {himatchmap {}}} {
   # display default sym graphically over element name
   if {[set defidx [lsearch -exact $sym_list default]] != -1 && $sgrdef} {
      set sym_list [lreplace $sym_list $defidx $defidx]
      lappend eltsgrlist de
   }
   lappend eltsgrlist $eltsgr

   set displen 0
   set disp $elt
   set dispsgr [sgr $eltsgrlist $elt $himatchmap]
   # enclose name between quotes if a space is found
   if {[string first { } $elt] != -1} {
      incr displen 2
      set dispsgr '$dispsgr'
   }
   # append suffix
   append disp $eltsuffix
   append dispsgr $eltsuffix

   # compute length
   incr displen [string length $disp]

   # format remaining sym list
   if {[llength $sym_list]} {
      set symslen [string length [join $sym_list :]]
      foreach sym $sym_list {
         if {![info exists colonsgr]} {
            set colonsgr [sgr se :]
         } else {
            append symssgr $colonsgr
         }
         append symssgr [sgr $symsgr $sym]
      }
   } else {
      set symssgr {}
      set symslen 0
   }
   set nbws1 [expr {40 - $displen}]
   set nbws2 [expr {$nbws1 < 0 ? 20 - $symslen + $nbws1 : 20 - $symslen}]
   return [list $disp $dispsgr[string repeat { } $nbws1]$symssgr[string\
      repeat { } $nbws2]$mtime $displen]
}

proc formatArrayValToJson {vallist} {
   return [expr {[llength $vallist] ? "\[ \"[join $vallist {", "}]\" \]" :\
      {[]}}]
}

proc formatObjectValToJson {objlist} {
   foreach {key val isbool} $objlist {
      if {[info exists disp]} {
         append disp {, }
      }
      append disp "\"$key\": "
      if {$isbool} {
         append disp [expr {$val ? {true} : {false}}]
      } else {
         append disp "\"$val\""
      }
   }
   ##nagelfar ignore Bad expression
   return [expr {[info exists disp] ? "{ $disp }" : "{}"}]
}

# format an element with its syms for a json display in a list
proc formatListEltToJsonDisplay {elt args} {
   set disp "\"$elt\": \{ \"name\": \"$elt\""
   foreach {key vtype val show} $args {
      if {!$show} {
         continue
      }
      append disp ", \"$key\": "
      switch -- $vtype {
         a {append disp [formatArrayValToJson $val]}
         o {append disp [formatObjectValToJson $val]}
         s {append disp "\"$val\""}
      }
   }
   append disp "\}"

   return $disp
}

# Prepare a map list to translate later on a substring in its highlighted
# counterpart. Translate substring into all module it specifies in case of an
# advanced version specification. Each string obtained is right trimmed from
# wildcard. No highlight is set for strings still containing wildcard chars
# after right trim operation. No highlist map is returned at all if highlight
# rendering is disabled.
proc prepareMapToHightlightSubstr {args} {
   set maplist {}
   if {[sgr hi {}] ne {}} {
      foreach substr $args {
         foreach m [getAllModulesFromVersSpec $substr] {
            set m [string trimright $m {*?}]
            if {$m ne {} && [string first * $m] == -1 && [string first ? $m]\
               == -1} {
               lappend maplist $m [sgr hi $m]
            }
         }
      }
   }
   return $maplist
}

# Specific highlight translation map for variant name and value
proc prepareMapToHightlightVariant {args} {
   if {[sgr hi {}] ne {}} {
      foreach modspec $args {
         foreach vrspec [getVariantListFromVersSpec $modspec] {
            set vrvalues [lassign $vrspec vrname vrnot vrisbool]
            if {![info exists vrname_map($vrname)]} {
               set maplist [list $vrname [sgr hi $vrname]]
               # also highlight shortcut if any
               if {[info exists ::g_variantShortcut($vrname)]} {
                  lappend maplist $::g_variantShortcut($vrname) [sgr hi\
                     $::g_variantShortcut($vrname)]
               }
               set vrname_map($vrname) $maplist
            }
            # adapt variant value to highlight if boolean
            set maplist {}
            foreach vrvalue $vrvalues {
               if {$vrisbool} {
                  set vrshort [expr {$vrvalue ? {+} : {-}}]$vrname
                  lappend maplist $vrshort [sgr hi $vrshort]
                  set vrvalue [expr {$vrvalue ? {on} : {off}}]
               }
               lappend maplist $vrvalue [sgr hi $vrvalue]
            }
            lappend vrval_map($vrname) {*}$maplist
         }
      }
   }
   return [list [array get vrname_map] [array get vrval_map]]
}

# Format list of modules obtained from a getModules call in upper context
proc reportModules {search_queries header hsgrkey hstyle show_mtime show_idx\
   one_per_line theader_cols excluded_tag {mod_list_order {}}} {
   # link to the result module list obtained in caller context
   upvar mod_list mod_list

   # output is JSON format
   set json [isStateEqual report_format json]

   # is some module variant specified in search query
   set variant_spec_in_query 0
   foreach modspec $search_queries {
      if {[llength [getVariantListFromVersSpec $modspec]]} {
         set variant_spec_in_query 1
         break
      }
   }

   # elements to include in output
   if {[set report_indesym [isEltInReport indesym 0]]} {
      set report_sym 0
   } else {
      set report_sym [isEltInReport sym]
   }
   set report_tag [isEltInReport tag]
   set report_alias [expr {[isEltInReport alias] || [isEltInReport\
      provided-alias]}]
   # enable variant report if variantifspec configured and some variant is
   # specified in query or variant configured for report or list sub-command
   # json output
   set report_variant [expr {($variant_spec_in_query && [isEltInReport\
      variantifspec 0]) || [isEltInReport variant [expr {[currentState\
      commandname] eq {list} && $json}]]}]
   set collect_variant_from [expr {[currentState commandname] in {avail\
      spider} ? {2} : {0}}]

   # prepare list of tag abbreviations that can be substituted and list of
   # tags whose name should be colored
   getConf tag_abbrev
   getConf tag_color_name

   # prepare results for display
   set alias_colored [isSgrkeyColored al]
   set default_colored [isSgrkeyColored de]
   set himatchmap [prepareMapToHightlightSubstr {*}$search_queries]
   lassign [prepareMapToHightlightVariant {*}$search_queries]\
      himatcharrvrmap himatcharrvrvalmap
   set clean_list {}
   set vr_list {}
   set via [expr {[isEltInReport via] ? [getViaModuleForModulepath $header] :\
      {}}]

   # treat elements in specified order if any
   ##nagelfar ignore #2 Badly formed if statement
   foreach elt [if {![llength $mod_list_order]} {array names mod_list}\
      {set mod_list_order}] {
      if {$report_variant} {
         set vr_list [getVariantList $elt [expr {$json ? 4 : 7}] 0\
            $collect_variant_from]
      }
      set sym_list [getVersAliasList $elt]
      # fetch tags but clear excluded tag
      set tag_list [replaceFromList [getTagList $elt [lindex $mod_list($elt)\
         2]] $excluded_tag]
      # abbreviate tags unless for json output
      if {!$json} {
         set tag_list [abbrevTagList $tag_list]
      }
      set dispsgr {}
      # ignore "version" entries as symbolic version are treated
      # along to their relative modulefile not independently
      switch -- [lindex $mod_list($elt) 0] {
         directory {
            if {$json} {
               ##nagelfar ignore +2 Found constant
               set dispsgr [formatListEltToJsonDisplay $elt type s directory\
                  1 symbols a $sym_list 1 via s $via 1]
            } elseif {$show_mtime} {
               # append / char after name to clearly indicate this is a dir
               lassign [formatListEltToLongDisplay $elt di / $sym_list sy {}\
                  $default_colored $himatchmap] disp dispsgr displen
            } else {
               lassign [formatListEltToDisplay $elt di / $sym_list sy\
                  $report_sym $default_colored {} 0 {} {} 0 $himatchmap] disp\
                  dispsgr displen
            }
         }
         modulefile - virtual {
            if {$json} {
               ##nagelfar ignore +4 Found constant
               set dispsgr [formatListEltToJsonDisplay $elt type s modulefile\
                  1 variants o $vr_list $report_variant symbols a $sym_list 1\
                  tags a $tag_list 1 pathname s [lindex $mod_list($elt) 2] 1\
                  via s $via 1]
            } elseif {$show_mtime} {
               set clock_mtime [expr {[lindex $mod_list($elt) 1] ne {} ?\
                  [clock format [lindex $mod_list($elt) 1] -format {%Y/%m/%d\
                  %H:%M:%S}] : {}}]
               # add to display file modification time in addition
               # to potential syms
               lassign [formatListEltToLongDisplay $elt {} {} $sym_list sy\
                  $clock_mtime $default_colored $himatchmap] disp dispsgr\
                  displen
            } else {
               lassign [formatListEltToDisplay $elt {} {} $sym_list sy\
                  $report_sym $default_colored $tag_list $report_tag $vr_list\
                  va $report_variant $himatchmap $himatcharrvrmap\
                  $himatcharrvrvalmap] disp dispsgr displen
            }
         }
         alias {
            if {$json} {
               ##nagelfar ignore +3 Found constant
               set dispsgr [formatListEltToJsonDisplay $elt type s alias 1\
                  symbols a $sym_list 1 tags a $tag_list 1 target s [lindex\
                  $mod_list($elt) 1] 1 via s $via 1]
            } elseif {$show_mtime} {
               lassign [formatListEltToLongDisplay $elt al " -> [lindex\
                  $mod_list($elt) 1]" $sym_list sy {} $default_colored\
                  $himatchmap] disp dispsgr displen
            } elseif {$report_alias} {
               # add a '@' sym to indicate elt is an alias if not colored
               if {!$alias_colored} {
                  lappend sym_list @
                  # track use of '@' sym to add it to the output key
                  if {$report_sym && ![info exists ::g_used_alias_nocolor]} {
                     set ::g_used_alias_nocolor 1
                  }
               }
               lassign [formatListEltToDisplay $elt al {} $sym_list sy\
                  $report_sym $default_colored $tag_list $report_tag {} {} 0\
                  $himatchmap] disp dispsgr displen
            }
         }
         version {
            # report symbolic version independently from the module it is
            # attached to. only done on regular or terse output when 'indesym'
            # element is in relative output configuration option
            if {$report_indesym} {
               lassign [formatListEltToDisplay $elt sy {} {} {} 0 0 {} 0 {}\
                  {} 0 $himatchmap] disp dispsgr displen
            }
         }
      }
      if {$dispsgr ne {}} {
         if {$json} {
            lappend clean_list $dispsgr
         } else {
            lappend clean_list $disp
            set sgrmap($disp) $dispsgr
            set lenmap($disp) $displen
         }
      }
   }

   set len_list {}
   set max_len 0
   # dictionary-sort results unless if output order is specified
   if {![llength $mod_list_order]} {
      set clean_list [lsort -dictionary $clean_list]
   }
   if {$json} {
      ##nagelfar ignore Found constant
      upvar 0 clean_list display_list
      if {![info exists display_list]} {
         set display_list {}
      }
   } else {
      set display_list {}
      foreach disp $clean_list {
         # compute display element length list on sorted result
         lappend display_list $sgrmap($disp)
         lappend len_list $lenmap($disp)
         if {$lenmap($disp) > $max_len} {
            set max_len $lenmap($disp)
         }
      }
   }

   # output table header if needed and not yet done
   if {[llength $display_list] && $show_mtime && ![isStateDefined\
      theader_shown]} {
      setState theader_shown 1
      displayTableHeader {*}$theader_cols
   }

   # output formatted elements
   displayElementList $header $hsgrkey $hstyle $one_per_line $show_idx 1\
      $display_list $len_list $max_len $via
}

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

proc displayTableHeader {sgrkey args} {
   foreach {title col_len} $args {
      set col "- [sgr $sgrkey $title] "
      append col [string repeat - [expr {$col_len - [string length $title] -\
         3}]]
      lappend col_list $col
   }

   report [join $col_list .]
}

proc displaySeparatorLine {{title {}} {sgrkey {}} {extra {}}} {
   set tty_cols [getState term_columns]
   if {$title eq {}} {
      # adapt length if screen width is very small
      set max_rep 67
      set rep [expr {$tty_cols > $max_rep ? $max_rep : $tty_cols}]
      report [string repeat - $rep]
   } else {
      set len [string length $title$extra]
      set lrep [tcl::mathfunc::max [expr {($tty_cols - $len - 2)/2}] 1]
      set rrep [tcl::mathfunc::max [expr {$tty_cols - $len - 2 - $lrep}] 1]
      report "[string repeat - $lrep] [sgr $sgrkey $title]$extra [string\
         repeat - $rrep]"
   }
}

# get a list of elements and print them in a column or in a
# one-per-line fashion
proc displayElementList {header sgrkey hstyle one_per_line display_idx\
   start_idx display_list {len_list {}} {max_len 0} {via {}}} {
   set elt_cnt [llength $display_list]
   reportDebug "header=$header, sgrkey=$sgrkey, hstyle=$hstyle,\
      elt_cnt=$elt_cnt, max_len=$max_len, one_per_line=$one_per_line,\
      display_idx=$display_idx, start_idx=$start_idx, via=$via"

   # end proc if no element are to print
   if {$elt_cnt == 0} {
      return
   }
   # output is JSON format
   set json [isStateEqual report_format json]

   # display header if any provided
   if {$header ne {noheader}} {
      set header [getModulepathLabel $header]
      if {$json} {
         report "\"$header\": \{"
      } elseif {$hstyle eq {sepline}} {
         set extra [expr {[string length $via] ? " (via $via)" : {}}]
         displaySeparatorLine $header $sgrkey $extra
      } else {
         report [sgr $sgrkey $header]:
      }
   }

   # increase index length when 100+ modules to report
   if {$display_idx} {
      set idx_len [expr {$elt_cnt > 99 ? {3} : {2}}]
   }

   if {$json} {
      set displist [join $display_list ,\n]
   # display one element per line
   } elseif {$one_per_line} {
      if {$display_idx} {
         set idx $start_idx
         foreach elt $display_list {
            append displist [format "%${idx_len}d) %s " $idx $elt] \n
            incr idx
         }
      } else {
         append displist [join $display_list \n] \n
      }
   # elsewhere display elements in columns
   } else {
      # save room for numbers and spacing: 2 or 3 digits + ) + space
      set elt_prefix_len [expr {$display_idx ? $idx_len + 2 : {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
      incr max_len $elt_suffix_len
      foreach len $len_list {
         lappend elt_len [incr len $elt_suffix_len]
      }

      set tty_cols [getState term_columns]
      # 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 [tcl::mathfunc::max [expr {int(($tty_cols - \
         $elt_prefix_len) / $max_len)}] 0]
      # 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 rows 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 {
            ##nagelfar ignore Unknown variable
            set istart [expr {$col * $cur_rows}]
            # only remove width of elements from current col
            for {set row 0} {$row < ($i % $cur_rows)} {incr row} {
               ##nagelfar ignore Unknown variable
               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 list=$display_list
      reportDebug "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 "%${idx_len}d) " [expr {$index +\
                     $start_idx}]]
               }
               # cannot use 'format' as strings may contain SGR codes
               append displist [lindex $display_list $index][string repeat\
                  { } [expr {$col_width($col) - [lindex $len_list $index]}]]
            }
         }
         append displist \n
      }
   }
   if {$json && $header ne {noheader}} {
      append displist "\n\}"
   }
   report $displist 1
   reportSeparateNextContent
}

# Report an output key to help understand what the SGR used on this output
# correspond to
proc displayKey {} {
   # specific key entry for symbolic version if reported independently
   set typesym [list {symbolic-version}]
   if {![isEltInReport indesym 0]} {
      lappend typesym [sgr se (]<SGR>[sgr se )] 18
   }

   array set skipsgr [list hi 1 db 1 tr 1 se 1 er 1 wa 1 me 1 in 1 cm 1 va 1]
   array set typesgr [list mp modulepath di [list directory <SGR>/ 10] al\
      module-alias sy $typesym de [list {default-version}]]

   set display_list {}
   set len_list {}
   foreach key [array names ::g_used_colors] {
      # sgr key matches a basic modulefile type
      if {[info exists typesgr($key)]} {
         # the way to describe key is already defined
         if {[llength $typesgr($key)] > 1} {
            lassign $typesgr($key) desc desctmp len
            set desc [string map [list <SGR> [sgr $key $desc]] $desctmp]
         } else {
            set desc [lindex $typesgr($key) 0]
         }
         if {$key eq {sy} && [info exists ::g_used_sym_nocolor]} {
            unset ::g_used_sym_nocolor
         }
      # key is a tag abbreviation
      } elseif {[info exists ::g_abbrevTag($key)]} {
         set desc $::g_abbrevTag($key)
      # if not part of the ignored list, this key corresponds to a tag name
      } elseif {![info exists skipsgr($key)]} {
         set desc $key
      }
      if {[info exists desc]} {
         # define key description
         if {![info exists len]} {
            set len [string length $desc]
            set desc [sgr $key $desc]
         }
         lappend display_list $desc
         lappend len_list $len
         unset desc
         unset len
      }
   }

   # include var=val key if any other variant form is present in report
   if {![info exists ::g_used_va(val)] && [array exists ::g_used_va]} {
      set ::g_used_va(val) 1
   }
   # add key for variant reports
   if {[info exists ::g_used_va(on)]} {
      lappend display_list "[sgr se \{][sgr va +variant][sgr se\
         \}]=[sgr se \{][sgr va variant=on][sgr se \}]"
      lappend len_list 23
   }
   if {[info exists ::g_used_va(off)]} {
      lappend display_list "[sgr se \{][sgr va -variant][sgr se\
         \}]=[sgr se \{][sgr va variant=off][sgr se \}]"
      lappend len_list 24
   }
   foreach sc [array names ::g_used_va] {
      if {$sc ni {on off val}} {
         lappend display_list "[sgr se \{][sgr va ${sc}value][sgr se\
            \}]=[sgr se \{][sgr va $::g_used_va($sc)=value][sgr se \}]"
         lappend len_list [expr {17 + [string length $::g_used_va($sc)]}]
      }
   }
   # finish with variant=value entry as it is referred by other variant keys
   if {[info exists ::g_used_va(val)]} {
      lappend display_list "[sgr se \{][sgr va variant=value][sgr se \}]"
      lappend len_list 15
   }

   # add key for alias if '@' put in parentheses
   if {[info exists ::g_used_alias_nocolor]} {
      lappend display_list "[sgr se (]@[sgr se )]=module-alias"
      lappend len_list 9
   }
   # add key for symbolic version if any put in parentheses but no color
   if {[info exists ::g_used_sym_nocolor]} {
      lappend display_list "[sgr se (]symbolic-version[sgr se )]"
      lappend len_list 18
   }

   # add key for module tag if any put in angle brackets
   if {[array exists ::g_used_tags]} {
      lappend display_list "[sgr se <]module-tag[sgr se >]"
      lappend len_list 12
   }
   # report translation of each uncolored tag abbreviation that have been used
   foreach tag [array names ::g_used_tags] {
      if {![info exists ::g_used_colors($tag)] && [info exists\
         ::g_abbrevTag($tag)]} {
         lappend display_list [sgr se <]$tag[sgr se >]=$::g_abbrevTag($tag)
         lappend len_list [expr {[string length $tag] + [string length\
            $::g_abbrevTag($tag)] + 3}]
      }
   }

   # find largest element
   set max_len 0
   foreach len $len_list {
      if {$len > $max_len} {
         set max_len $len
      }
   }

   if {[llength $display_list]} {
      # display header
      report Key:
      # display key content
      displayElementList noheader {} {} 0 0 0 $display_list $len_list $max_len
   }
}

# Return conf value and from where an eventual def value has been overridden
proc displayConfig {val env_var {asked 0} {trans {}} {locked 0}} {
   array set transarr $trans

   # get overridden value and know what has overridden it
   if {$asked} {
      set defby " (cmd-line)"
   } elseif {$env_var ne {} && !$locked && [isEnvVarDefined $env_var]} {
      set defby " (env-var)"
   } elseif {$locked} {
      set defby " (locked)"
   } else {
      set defby {}
   }

   # translate fetched value if translation table exists
   if {[info exists transarr($val)]} {
      set val $transarr($val)
   }

   return $val$defby
}

# report linter output as error/warning messages
proc displayLinterOutput {linter output} {
   switch -- $linter {
      nagelfar {
         # parsing linter output
         set report_list {}
         foreach line [split $output \n] {
            set firstword [string range $line 0 [string first { } $line]-1]
            switch -- $firstword {
               Checking - Parsing {}
               Line {
                  # add message of previous line if any
                  if {[info exists msg]} {
                     lappend report_list $msg
                  }

                  # extract information from message line
                  set colidx [string first : $line]
                  set linenum [string trimleft [string range $line 5\
                     $colidx-1]]
                  set severity [string index $line $colidx+2]
                  switch -- $severity {
                     W {
                        set severity WARNING
                        set sgrkey wa
                        set raisecnt 0
                     }
                     E {
                        set severity ERROR
                        set sgrkey er
                        set raisecnt 1
                     }
                     default {
                        set severity NOTICE
                        set sgrkey in
                        set raisecnt 0
                     }
                  }
                  set msg [string range $line $colidx+4 end]

                  # start recorded message properties
                  lappend report_list $linenum $severity $sgrkey $raisecnt
               }
               default {
                  # this line is continuing message started previously
                  append msg \n[string trimleft $line]
               }
            }
         }
         # add message of last line if any
         if {[info exists msg]} {
            lappend report_list $msg
            unset msg
         }

         # report messages
         foreach {linenum severity sgrkey raisecnt mesg} $report_list {
            reportError $mesg "[format %-7s $severity] line $linenum"\
               $sgrkey $raisecnt
         }
      }
      default {
         reportError $output
      }
   }
}

proc reportMlUsage {} {
   reportVersion
   report {Usage: ml [options] [command] [args ...]
       ml [options] [[-]modulefile ...]

Examples:
  ml                 equivalent to: module list
  ml foo bar         equivalent to: module load foo bar
  ml -foo -bar baz   equivalent to: module unload foo bar; module load baz
  ml avail -t        equivalent to: module avail -t

See 'module --help' to get available commands and options.}
}

proc reportUsage {} {
   reportVersion
   ##nagelfar ignore #111 Too long line
   report {Usage: module [options] [command] [args ...]

Loading / Unloading commands:
  add | load      modulefile [...]  Load modulefile(s)
  try-add | try-load modfile [...]  Load modfile(s), no complain if not found
  add-any | load-any modfile [...]  Load first available modulefile in list
  rm | unload     modulefile [...]  Remove modulefile(s)
  purge                             Unload all loaded modulefiles
  reload | update                   Unload then load all loaded modulefiles
  switch | swap   [mod1] mod2       Unload mod1 and load mod2
  refresh                           Refresh loaded module volatile components
  reset                             Restore initial environment

Listing / Searching commands:
  list   [-a] [-t|-l|-j] [-S|-C] [mod ...]
                                    List all or matching loaded modules
  avail  [-a] [-t|-l|-j] [-S|-C] [-d|-L] [--indepth|--no-indepth] [mod ...]
                                    List all or matching available modules
  aliases         [-a]              List all module aliases
  whatis [-a] [-j] [modulefile ...] Print whatis information of modulefile(s)
  apropos | keyword | search [-a] [-j] str
                                    Search all name and whatis containing str
  spider [-a] [-t|-l|-j] [-S|-C] [-d|-L] [--indepth|--no-indepth] [mod ...]
                                    Scan all modulepaths and list all or
                                    matching available modules
  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 | disable     [collection] Remove saved collection
  saveshow | describe   [coll|file] Display information about collection
  savelist  [-a] [-t|-l|-j] [-S|-C] [collection ...]
                                    List all or matching saved collections
  is-saved        [collection ...]  Test if any of the collection(s) exists
  stash                             Save current environment and reset
  stashpop        [stash]           Restore then remove stash collection
  stashrm         [stash]           Remove stash collection
  stashshow       [stash]           Display information about stash collection
  stashclear                        Remove all stash collections
  stashlist                         List all stash collections

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

Module cache handling commands:
  cachebuild      [modulepath ...]  Create cache file for modulepath(s)
  cacheclear                        Delete cache file in enabled modulepath(s)

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
  clear           [-f]              Reset Modules-specific runtime information
  source          scriptfile [...]  Execute scriptfile(s)
  config [--dump-state|name [val]]  Display or set Modules configuration
  state           [name]            Display Modules state
  sh-to-mod       shell shellscript [arg ...]
                                    Make modulefile from script env changes
  mod-to-sh       shell modulefile [...]
                                    Make shell code from modulefile env changes
  edit            modulefile        Open modulefile in editor
  lint       [-a] [modulefile ...]  Check syntax of modulefile

Switches:
  -t | --terse    Display output in terse format
  -l | --long     Display output in long format
  -j | --json     Display output in JSON format
  -o LIST | --output=LIST
                  Define elements to output on 'avail', 'spider' or 'list'
                  sub-cmds in addition to module names (LIST is made of items
                  like 'sym', 'tag', 'variant' or 'key' separated by ':')
  -a | --all      Include hidden modules in search
  -d | --default  Only show default versions available
  -L | --latest   Only show latest versions available
  -S | --starts-with
                  Search modules whose name begins with query string
  -C | --contains Search modules whose name contains query string
  -i | --icase    Case insensitive match
  -a | --append   Append directory to MODULEPATH (on 'use' sub-command)
  -p | --prepend  Prepend directory to MODULEPATH
  --auto          Enable automated module handling mode
  --no-auto       Disable automated module handling mode
  -f | --force    By-pass dependency consistency, abort on error or
                  confirmation dialog
  --tag=LIST      Apply tag to loading module on 'load', 'try-load', 'load-any'
                  or 'switch' sub-commands (LIST is made of tag names
                  separated by ':')
  --ignore-cache  Ignore module cache
  --ignore-user-rc
                  Skip evaluation of user-specific module rc file

Options:
  -h | --help     This usage info
  -V | --version  Module version
  --dumpname      Module implementation name
  -D | --debug    Enable debug messages
  -T | --trace    Enable trace messages
  -v | --verbose  Enable verbose messages
  -s | --silent   Turn off error, warning and informational messages
  --timer         Report execution times
  --paginate      Pipe mesg output into a pager if stream attached to terminal
  --no-pager      Do not pipe message output into a pager
  --redirect      Send output to stdout (only for sh, bash, ksh, zsh and fish)
  --no-redirect   Send output to stderr
  --color[=WHEN]  Colorize the output; WHEN can be 'always' (default if
                  omitted), 'auto' or 'never'
  -w COLS | --width=COLS
                  Set output width to COLS columns.}
}

# create appropriate message and kind of report when a requirement is not
# satisfied
proc reportMissingPrereqError {curmodnamevr modulepath_list args} {
   set is_path_specific [llength $modulepath_list]
   if {[isModuleEvaluated reqlo $curmodnamevr $modulepath_list {*}$args]} {
      set msg [getErrReqLoMsg $args $is_path_specific]
   } else {
      set retiseval [isModuleEvaluated any $curmodnamevr $modulepath_list\
         {*}$args]
      # more appropriate msg if an evaluation was attempted or is by-passed
      set msg [expr {$retiseval || [getState force] ? [getReqNotLoadedMsg\
         $args $is_path_specific] : [getErrPrereqMsg $args 1\
         $is_path_specific]}]
   }

   knerrorOrWarningIfForced $msg MODULES_ERR_GLOBAL
}

proc setConflictErrorAsReported {args} {
   appendNoDupToList ::report_conflict([currentState evalid]) {*}$args
   # save content added to remove it later if evaluation is withdrawn
   if {[depthState reportholdid]} {
      lappend ::g_holdReportConflict([currentState reportholdid]) \
         [currentState evalid] $args
   }
}

proc isConflictErrorAlreadyReported {msgrecid mod_con_list} {
   if {[info exists ::report_conflict($msgrecid)]} {
      return [isIntBetweenList $mod_con_list $::report_conflict($msgrecid)]
   } else {
      return 0
   }
}

# Report final error caught on main catch block
proc reportFinalError {error_msg} {
   # render error if not done yet
   if {$::errorCode ne {MODULES_ERR_RENDERED}} {
      incrErrorCount
      renderFalse
   }

   if {$::errorCode ni [list MODULES_ERR_RENDERED MODULES_ERR_KNOWN]} {
      # add web link to report issue unless if an external error is detected
      set external_error_list [list\
         {Can't find a usable init.tcl in the following directories}\
         {interpreter uses an incompatible stubs mechanism}\
         {dlopen(}\
         {couldn't fork child process: resource temporarily unavailable}\
         {invalid command name "tcl::mathfunc::max"}]
      set add_report_link 1
      foreach external_error $external_error_list {
         if {[string equal -length [string length $external_error]\
            $external_error $error_msg]} {
            set add_report_link 0
            break
         }
      }

      # report stack trace in addition to the error msg if error is unknown
      set error_msg $::errorInfo

      if {$add_report_link} {
         append error_msg \n[sgr hi {Please report this issue at\
            https://github.com/envmodules/modules/issues}]
      }
   }

   reportError $error_msg
}

# dummy proc to disable modulefile commands on some evaluation modes
proc nop {args} {}

# dummy proc for commands available on other Modules flavor but not here
proc nimp {cmd args} {
   reportWarning "'$cmd' command not implemented"
}


# Get identifier name of current Tcl modulefile interpreter. An interp is
# dedicated to each mode/auto_handling option value/depth level of modulefile
# interpretation
proc getCurrentModfileInterpName {} {
   return __modfile_[currentState mode]_[getConf auto_handling]_[depthState\
      modulename]
}

# synchronize environment variable change over all started sub interpreters
proc interp-sync-env {op var {val {}}} {
   set envvar ::env($var)
   ##nagelfar vartype envvar varName

   # apply operation to main interpreter
   switch -- $op {
      set { set $envvar $val }
      unset { unset $envvar }
   }

   # apply operation to each sub-interpreters if not found autosynced
   if {[llength [interp slaves]]} {
      reportDebug "$op var='$envvar', val='$val' on interp(s) [interp slaves]"

      foreach itrp [interp slaves] {
         switch -- $op {
            set {
               # no value pre-check on Windows platform as an empty value set
               # means unsetting variable which lead querying value to error
               if {[getState is_win] || ![interp eval $itrp [list info exists\
                  $envvar]] || [interp eval $itrp [list set $envvar]] ne\
                  $val} {
                  interp eval $itrp [list set $envvar $val]
               }
            }
            unset {
               if {[interp eval $itrp [list info exists $envvar]]} {
                  interp eval $itrp [list unset $envvar]
               }
            }
         }
      }
   }
}

# Initialize list of interp alias commands to define for given evaluation mode
# and auto_handling enablement
proc initModfileModeAliases {mode auto aliasesVN aliasesPassArgVN\
   tracesVN} {
   global g_modfilePerModeAliases
   upvar #0 $aliasesVN aliases
   upvar #0 $aliasesPassArgVN aliasesPassArg
   upvar #0 $tracesVN traces

   if {![info exists g_modfilePerModeAliases]} {
      set ::g_modfileBaseAliases [list versioncmp versioncmp getenv getenv\
         getvariant getvariant is-loaded is-loaded is-saved is-saved is-used\
         is-used is-avail is-avail uname uname module-info module-info\
         modulepath-label modulepath-label exit exitModfileCmd reportCmdTrace\
         reportCmdTrace reportWarning reportWarning reportError reportError\
         incrErrorCount incrErrorCount report report isWin initStateIsWin\
         puts putsModfileCmd getModuleContent getModuleContent lsb-release\
         lsb-release getModuleHelpLines getModuleHelpLines]

      if {[getConf source_cache]} {
         lappend ::g_modfileBaseAliases source sourceModfileCmd
      }

      # list of alias commands whose target procedure is adapted according to
      # the evaluation mode
      set ::g_modfileEvalModes {load unload display help test whatis refresh\
         scan}
      ##nagelfar ignore #49 Too long line
      array set g_modfilePerModeAliases {
add-property   {add-property   nop            reportCmd      nop            nop            nop            nop          nop}
always-load    {always-load    nop            reportCmd      nop            nop            nop            nop          always-load-sc}
append-path    {append-path    append-path-un append-path    append-path    append-path    edit-path-wh   nop          edit-path-sc}
chdir          {chdir          nop            reportCmd      nop            nop            nop            nop          chdir-sc    }
complete       {complete       complete-un    reportCmd      nop            nop            nop            complete     complete-sc }
conflict       {conflict       nop            reportCmd      nop            nop            nop            nop          conflict-sc }
depends-on     {prereqAllModfileCmd  nop      reportCmd      nop            nop            nop            nop          prereq-all-sc}
depends-on-any {prereqAnyModfileCmd  nop      reportCmd      nop            nop            nop            nop          prereq-sc   }
extensions     {provide        nop            reportCmd      nop            nop            nop            nop          provide-sc  }
family         {family         family-un      reportCmd      nop            nop            nop            nop          family-sc   }
haveDynamicMPATH {nop          nop            nop            nop            nop            nop            nop          nop         }
hide-modulefile {hide-modulefile hide-modulefile hide-modulefile hide-modulefile hide-modulefile hide-modulefile nop   nop         }
hide-version   {hide-modulefile hide-modulefile hide-modulefile hide-modulefile hide-modulefile hide-modulefile nop    nop         }
module         {module         module         reportCmd      nop            nop            nop            nop          module-sc   }
module-alias   {module-alias   module-alias   module-alias   module-alias   module-alias   module-alias   nop          nop         }
module-log     {nimp           nimp           reportCmd      nop            nop            nop            nop          nop         }
module-trace   {nimp           nimp           reportCmd      nop            nop            nop            nop          nop         }
module-user    {nimp           nimp           reportCmd      nop            nop            nop            nop          nop         }
module-verbosity {nimp         nimp           reportCmd      nop            nop            nop            nop          nop         }
module-version {module-version module-version module-version module-version module-version module-version nop          nop         }
module-virtual {module-virtual module-virtual module-virtual module-virtual module-virtual module-virtual nop          nop         }
module-forbid  {module-forbid  module-forbid  module-forbid  module-forbid  module-forbid  module-forbid  nop          nop         }
module-help    {nop            nop            reportCmd      module-help    nop            nop            nop          nop         }
module-hide    {module-hide    module-hide    module-hide    module-hide    module-hide    module-hide    nop          nop         }
module-tag     {module-tag     module-tag     module-tag     module-tag     module-tag     module-tag     nop          nop         }
module-warn    {module-warn    module-warn    module-warn    module-warn    module-warn    module-warn    nop          nop         }
module-whatis  {nop            nop            reportCmd      nop            nop            module-whatis  nop          nop         }
prepend-path   {prepend-path   prepend-path-un prepend-path  prepend-path   prepend-path   edit-path-wh   nop          edit-path-sc}
prereq-all     {prereqAllModfileCmd  nop      reportCmd      nop            nop            nop            nop          prereq-all-sc}
prereq-any     {prereqAnyModfileCmd  nop      reportCmd      nop            nop            nop            nop          prereq-sc   }
prereq         {prereqAnyModfileCmd  nop      reportCmd      nop            nop            nop            nop          prereq-sc   }
provide        {provide        nop            reportCmd      nop            nop            nop            nop          provide-sc  }
pushenv        {pushenv        pushenv-un     pushenv        pushenv        pushenv        pushenv-wh     nop          pushenv-sc  }
remove-path    {remove-path    remove-path-un remove-path    remove-path    remove-path    edit-path-wh   nop          edit-path-sc}
remove-property {nop           nop            nop            nop            nop            nop            nop          nop         }
require-fullname {require-fullname nop        reportCmd      nop            nop            nop            nop          nop         }
set-alias      {set-alias      set-alias-un   reportCmd      nop            nop            nop            set-alias    set-alias-sc}
set-function   {set-function   set-function-un reportCmd     nop            nop            nop            set-function set-function-sc}
setenv         {setenv         setenv-un      setenv         setenv         setenv         setenv-wh      nop          setenv-sc   }
source-sh      {source-sh      source-sh-un   source-sh-di   nop            nop            nop            source-sh    source-sh   }
system         {system         system         reportCmd      nop            nop            nop            nop          nop         }
unique-name-conflict {unique-name-conflict nop nop           nop            nop            nop            nop          nop         }
uncomplete     {uncomplete     nop            reportCmd      nop            nop            nop            nop          uncomplete-sc}
unset-alias    {unset-alias    nop            reportCmd      nop            nop            nop            nop          unset-alias-sc}
unset-function {unset-function nop            reportCmd      nop            nop            nop            nop          unset-function-sc}
unsetenv       {unsetenv       unsetenv-un    unsetenv       unsetenv       unsetenv       unsetenv-wh    nop          unsetenv-sc }
variant        {variant        variant        variant        variant        variant        variant-wh     variant      variant-sc  }
x-resource     {x-resource     x-resource     reportCmd      nop            nop            nop            nop          nop         }
      }
   }

   # alias commands where interpreter ref should be passed as argument
   array set aliasesPassArg [list getvariant [list __itrp__] puts [list\
      __itrp__] variant [list __itrp__] source [list __itrp__]]

   # initialize list with all commands not dependent of the evaluation mode
   array set aliases $::g_modfileBaseAliases

   # add site-specific command aliases for modulefile interp
   if {[info exists ::modulefile_extra_cmds]} {
      if {[catch {array set aliases $::modulefile_extra_cmds} errorMsg]} {
         knerror "Invalid value '$::modulefile_extra_cmds' ($errorMsg)\nfor\
            siteconfig variable 'modulefile_extra_cmds'"
      }
   }

   # add alias commands whose target command vary depending on the eval mode
   set modeidx [lsearch -exact $::g_modfileEvalModes $mode]
   foreach alias [array names g_modfilePerModeAliases] {
      set aliastarget [set aliases($alias) [lindex\
         $g_modfilePerModeAliases($alias) $modeidx]]
      # some target procedures need command name as first arg
      if {$aliastarget in {reportCmd nimp edit-path-wh edit-path-sc}} {
         set aliasesPassArg($alias) [list $alias]
      # prereq commands need auto_handling state as first arg
      } elseif {$mode eq {load} && $alias in {prereq prereq-any prereq-all\
         depends-on depends-on-any}} {
         set aliasesPassArg($alias) [list 0 $auto]
      # associate a trace command if per-mode alias command is not reportCmd
      # in display mode (except for source-sh and unique-name-conflict)
      } elseif {$mode eq {display} && $alias ni {source-sh\
         unique-name-conflict}} {
         set traces($alias) reportCmdTrace
      }
   }
}

proc execute-modulefile {modfile modname modnamevrvar modspec requested\
   {up_namevr 1} {fetch_tags 1} {modpath {}}} {
   # link to modnamevr variable name from calling ctx if content update asked
   if {$up_namevr} {
      upvar $modnamevrvar modnamevr
   } else {
      set modnamevr $modnamevrvar
   }

   lappendState modulefile $modfile
   lappendState modulename $modname
   lappendState modulenamevr $modnamevr
   lappendState specifiedname $modspec
   lappendState modulepath $modpath
   set mode [currentState mode]
   lappendState debug_msg_prefix\
      "\[#[depthState modulename]:$mode:$modname\] "

   # skip modulefile if interpretation has been inhibited
   if {[getState inhibit_interp]} {
      reportDebug "skipping $modfile"
      return 1
   }

   reportTrace "'$modfile' as '$modname'" {Evaluate modulefile}

   # reset modulefile-specific information
   set ::g_help_lines {}

   # gather all tags of evaluated modulefile
   if {$fetch_tags} {
      cacheCurrentModules 0
      collectModuleTags $modnamevr
   }

   if {![info exists ::g_modfileUntrackVars]} {
      # list variable that should not be tracked for saving
      array set ::g_modfileUntrackVars [list ModulesCurrentModulefile 1 env 1]

      # commands that should be renamed before aliases setup
      array set ::g_modfileRenameCmds [list puts _puts]
   }
   # dedicate an interpreter per mode and per level of interpretation to have
   # a dedicated interpreter in case of cascaded multi-mode interpretations
   set itrp [getCurrentModfileInterpName]

   # evaluation mode-specific configuration
   set autosuf [expr {[getConf auto_handling] ? {AH} : {}}]
   set dumpCommandsVN g_modfile${mode}${autosuf}Commands
   set aliasesVN g_modfile${mode}${autosuf}Aliases
   set aliasesPassArgVN g_modfile${mode}${autosuf}AliasesPassArg
   set tracesVN g_modfile${mode}${autosuf}Traces
   ##nagelfar ignore Suspicious variable name
   if {![info exists ::$aliasesVN]} {
      ##nagelfar vartype aliasesVN varName
      ##nagelfar vartype aliasesPassArgVN varName
      ##nagelfar vartype tracesVN varName
      initModfileModeAliases $mode [getConf auto_handling] $aliasesVN\
         $aliasesPassArgVN $tracesVN
   }

   # variable to define in modulefile interp
   if {![info exists ::g_modfileBaseVars]} {
      # record module tool properties
      set ::g_modfileBaseVars [list ModuleTool Modules ModuleToolVersion\
         [getState modules_release]]

      if {[info exists ::modulefile_extra_vars]} {
         if {([llength $::modulefile_extra_vars] % 2) != 0} {
            knerror "Invalid value '$::modulefile_extra_vars' (list must have\
               an even number of elements)\nfor siteconfig variable\
               'modulefile_extra_vars'"
         }
         foreach {var val} $::modulefile_extra_vars {
            if {[string first { } $var] != -1} {
               knerror "Invalid variable name '$var'\ndefined in siteconfig\
                  variable 'modulefile_extra_vars'"
            }
         }
         lappend ::g_modfileBaseVars {*}$::modulefile_extra_vars
      }
   }

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

      # initialize global static variables for modulefile interp
      foreach {var val} $::g_modfileBaseVars {
         interp eval $itrp set ::$var "{$val}"
      }

      # dump initial interpreter state to restore it before each modulefile
      # interpretation. use same dump state for all modes/levels
      if {![info exists ::g_modfileVars]} {
         dumpInterpState $itrp g_modfileVars g_modfileArrayVars\
            g_modfileUntrackVars g_modfileProcs
      }

      # interp has just been created
      set fresh 1
   } else {
      set fresh 0
   }

   # reset interp state command before each interpretation
   resetInterpState $itrp $fresh g_modfileVars g_modfileArrayVars\
      g_modfileUntrackVars g_modfileProcs $aliasesVN $aliasesPassArgVN\
      $tracesVN g_modfileRenameCmds $dumpCommandsVN

   set vr_spec_list [getVariantListFromVersSpec $modnamevr]
   set failed_eval [catch {evaluateModulefile $itrp $modfile $vr_spec_list}\
      errorMsg]

   set eval_return_code [renderModulefileEvalError $itrp $mode $modfile\
      $failed_eval $errorMsg]

   if {$mode eq {load} && ![isStateDefined rc_running]} {
      if {[catch {checkModuleConflict $modname $modnamevr} errorMsg]} {
         reportError $errorMsg
         set eval_return_code 1
      }
   }

   # check if mod name version and variant has changed (default variant set)
   # update modnamevr if so and collect tags applying to new name
   if {$up_namevr} {
      set newmodnamevr "{$modname}"
      if {[set vr [getVariantList $modname 1]] ne {}} {
         append newmodnamevr " $vr"
      }
      if {$modnamevr ne $newmodnamevr} {
         set modnamevr_tag_list [getTagList $modnamevr $modfile]
         set modnamevr_extratag_list [getExtraTagList $modnamevr]
         lassign [parseModuleSpecification 0 0 0 0 {*}$newmodnamevr] modnamevr
         # $up_namevr is only enabled when $fetch_tags is also enabled
         collectModuleTags $modnamevr
         # set tags applying to previous name (without default variant set)
         # not to forget extra defined tags
         setModuleTag $modnamevr {*}$modnamevr_tag_list
         setModuleExtraTag $modnamevr {*}$modnamevr_extratag_list
      }
   }

   # check if special tags now applies and require to raise an error
   if {$mode ne {unload}} {
      if {[isModuleTagged $modnamevr forbidden 1 $modfile]} {
         set eval_return_code 1
         reportError [getForbiddenMsg $modnamevr $modfile]
      }
   }
   if {$mode ni {unload refresh scan whatis}} {
      if {[isModuleTagged $modnamevr nearly-forbidden 1 $modfile]} {
         reportWarning [getNearlyForbiddenMsg $modnamevr $modfile]
      }
      if {[isModuleTagged $modnamevr warning 1 $modfile]} {
         reportWarning [getWarningMsg $modnamevr $modfile]
      }
   }

   # record all module evaluated in scan structure for negation pattern search
   if {$mode eq {scan}} {
      recordScanModuleElt modulename all
   }

   # skip evaluation log for internal evaluation mode
   if {$mode ni {refresh scan whatis}} {
      set log_info_list [list {mode} $mode module $modnamevr specified\
         $modspec modulefile $modfile {requested} $requested]
      set log_event [expr {$requested ? {requested_eval} : {auto_eval}}]
      logEvent $log_event {*}$log_info_list
   }

   reportDebug "exiting $modfile"
   lpopState debug_msg_prefix
   lpopState modulepath
   lpopState specifiedname
   lpopState modulename
   lpopState modulenamevr
   lpopState modulefile

   return $eval_return_code
}

proc evaluateModulefile {itrp mod_file vr_spec_list} {
   # reset modulefile-specific variable before each interpretation
   interp eval $itrp set ::ModulesCurrentModulefile "{$mod_file}"
   interp eval $itrp set vrspeclist "{$vr_spec_list}"

   # eval then call for specific proc depending mode under same catch
   ##nagelfar ignore +3 Suspicious # char
   interp eval $itrp {
      info script $::ModulesCurrentModulefile
      # raise conflict error if one name of currently loading module is shared
      # by an already loaded module
      unique-name-conflict
      eval [getModuleContent $::ModulesCurrentModulefile]
      # raise error if a variant specified is not defined in modulefile
      set vrerrlist {}
      foreach vrspec $vrspeclist {
         set vrname [lindex $vrspec 0]
         if {![info exists ::ModuleVariant($vrname)]} {
            lappend vrerrlist "Unknown variant '$vrname' specified"
         }
      }
      # report all unknown variants specified, raise error on last report
      # take caution with vrerrlist variable as we are in mod_file eval ctx
      if {[info exists vrerrlist] && [llength $vrerrlist]} {
         for {set i 0} {$i < ([llength $vrerrlist] - 1)} {incr i} {
            reportError [lindex $vrerrlist $i]
         }
         error [lindex $vrerrlist $i] {} MODULES_ERR_GLOBAL
      }

      switch -- [module-info mode] {
         help {
            foreach help_line [getModuleHelpLines] {
               report $help_line
            }
            if {[info procs ModulesHelp] eq {ModulesHelp}} {
               ModulesHelp
            } elseif {![llength [getModuleHelpLines]]} {
               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}
                  incrErrorCount
               }
            } else {
               reportWarning "Unable to find ModulesTest in\
                  $::ModulesCurrentModulefile."
            }
         }
      }
   }
}

proc renderModulefileEvalError {itrp mode mod_file failed_eval error_msg} {
   if {!$failed_eval} {
      return 0
   }

   set eval_return_code 1
   # no error in case of "continue" command
   # catch continue even if called outside of a loop
   if {$error_msg eq {invoked "continue" outside of a loop} || $failed_eval\
      == 4} {
      set eval_return_code 0
      unset error_msg
   # catch break even if called outside of a loop
   # on Darwin, error is different: no errorCode & return code set to 3
   } elseif {$error_msg eq {invoked "break" outside of a loop} || ($error_msg\
      eq {} && [getInterpVar $itrp ::errorInfo] eq {}) ||\
      (![isInterpVarDefined $itrp ::errorCode] && $failed_eval == 3)} {
      # report load/unload/refresh evaluation break if verbosity level
      # >= normal, no error count raise during scan evaluation
      if {$mode in {load unload refresh} && [isVerbosityLevel normal]} {
         set error_msg {Module evaluation aborted}
      } else {
         unset error_msg
         if {$mode ne {scan}} {
            set raise_error_count 1
         }
      }
   } else {
      switch -- [getInterpVar $itrp errorCode] {
         MODULES_ERR_READ {}
         MODULES_ERR_VALIDITY {
            set error_msg [formatMessageInModule $error_msg $mod_file]
            set internal_bug 1
         }
         MODULES_ERR_SUBFAILED {
            # error counter and message already handled, just return err code
            unset error_msg
         }
         MODULES_ERR_GLOBAL {}
         default {
            set error_msg [formatInterpErrStackTrace $itrp $mod_file]
            set internal_bug 1
         }
      }
   }

   # split force mode management code as non-unload modes still have a
   # specific behavior (message returned as error and error exit code set)
   if {$mode eq {unload}} {
      if {[getState force]} {
         set eval_return_code 0
      }
      if {[info exists error_msg]} {
         if {[info exists internal_bug]} {
            reportInternalBugOrWarningIfForced $error_msg
         } else {
            reportErrorOrWarningIfForced $error_msg
         }
      }
   } else {
      if {[info exists error_msg]} {
         if {[info exists internal_bug]} {
            reportInternalBug $error_msg
         } else {
            reportError $error_msg
         }
      } elseif {[info exists raise_error_count]} {
         incrErrorCount
      }
   }

   return $eval_return_code
}

# Raise an error if a conflict violation is detected. This is done after
# modulefile eval to give it a chance to solve its conflicts during eval
proc checkModuleConflict {mod_name mod_name_vr} {
   set mod_con_list [getModuleLoadedConflict $mod_name]
   if {[llength $mod_con_list]} {
      set is_con_mod_conun [isModuleEvaluated conun $mod_name_vr {}\
         {*}$mod_con_list]
      set is_con_mod_loading [is-loading {*}$mod_con_list]
      if {!$is_con_mod_conun || $is_con_mod_loading} {
         set con_msg [getPresentConflictErrorMsg $mod_name_vr $mod_con_list\
            $is_con_mod_loading]
      } else {
         set con_msg {}
      }

      set msgrecid [currentState msgrecordid]
      if {![isConflictErrorAlreadyReported $msgrecid $mod_con_list]} {
         knerrorOrWarningIfForced $con_msg
      }
   }
}

# Smaller subset than main module load... This function runs modulerc and
# .version files
proc execute-modulerc {modfile modname modspec} {
   lappendState modulefile $modfile
   # push name to be found by module-alias and version
   lappendState modulename $modname
   lappendState specifiedname $modspec
   set ::ModulesVersion {}
   lappendState debug_msg_prefix "\[#[depthState modulename]:$modname\] "

   if {![info exists ::g_modrcUntrackVars]} {
      # list variable that should not be tracked for saving
      array set ::g_modrcUntrackVars [list ModulesCurrentModulefile 1\
         ModulesVersion 1 env 1]

      # commands that should be renamed before aliases setup
      array set ::g_modrcRenameCmds [list]

      # list interpreter alias commands to define
      array set ::g_modrcAliases [list uname uname system system versioncmp\
         versioncmp hide-modulefile hide-modulefile hide-version\
         hide-modulefile is-loaded is-loaded is-used is-used module-version\
         module-version module-alias module-alias module-virtual\
         module-virtual module-forbid module-forbid module-hide module-hide\
         module-tag module-tag module-info module-info modulepath-label\
         modulepath-label setModulesVersion setModulesVersion\
         getModuleContent getModuleContent lsb-release lsb-release\
         module-warn module-warn]

      if {[getConf source_cache]} {
         set ::g_modrcAliases(source) sourceModfileCmd
      }

      # add site-specific command aliases for modulerc interp
      if {[info exists ::modulerc_extra_cmds]} {
         if {[catch {array set ::g_modrcAliases $::modulerc_extra_cmds}\
            errorMsg]} {
            knerror "Invalid value '$::modulerc_extra_cmds' ($errorMsg)\nfor\
               siteconfig variable 'modulerc_extra_cmds'"
         }
      }

      # alias commands where an argument should be passed
      array set ::g_modrcAliasesPassArg [list source [list __itrp__]]

      # trace commands that should be associated to aliases
      array set ::g_modrcAliasesTraces [list]

      # variable to define in modulerc interp
      set ::g_modrcBaseVars [list ModuleTool Modules ModuleToolVersion\
         [getState modules_release]]

      if {[info exists ::modulerc_extra_vars]} {
         if {([llength $::modulerc_extra_vars] % 2) != 0} {
            knerror "Invalid value '$::modulerc_extra_vars' (list must have\
               an even number of elements)\nfor siteconfig variable\
               'modulerc_extra_vars'"
         }
         foreach {var val} $::modulerc_extra_vars {
            if {[string first { } $var] != -1} {
               knerror "Invalid variable name '$var'\ndefined in siteconfig\
                  variable 'modulerc_extra_vars'"
            }
         }
         lappend ::g_modrcBaseVars {*}$::modulerc_extra_vars
      }
   }

   # dedicate an interpreter per level of interpretation to have in case of
   # cascaded interpretations a specific interpreter per level
   set itrp __modrc_[depthState modulename]

   reportTrace '$modfile' {Evaluate modulerc}
   # create modulerc interpreter at first interpretation
   if {![interp exists $itrp]} {
      reportDebug "creating interp $itrp"
      interp create $itrp

      # initialize global static variables for modulerc interp
      foreach {var val} $::g_modrcBaseVars {
         interp eval $itrp set ::$var "{$val}"
      }

      # dump initial interpreter state to restore it before each modulerc
      # interpretation. use same dump state for all levels
      if {![info exists ::g_modrcVars]} {
         dumpInterpState $itrp g_modrcVars g_modrcArrayVars\
            g_modrcUntrackVars g_modrcProcs
      }

      # interp has just been created
      set fresh 1
   } else {
      set fresh 0
   }

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

   set failed_eval [catch {evaluateModulerc $itrp $modfile} errorMsg]

   renderModulercEvalError $itrp $modfile $failed_eval $errorMsg

   # default version set via ModulesVersion variable in .version file
   # override previously defined default version for modname
   lassign [getModuleNameVersion] mod modname modversion
   if {$modversion eq {.version} && $::ModulesVersion ne {}} {
      # ModulesVersion should target an element in current directory
      if {[string first / $::ModulesVersion] == -1} {
         setModuleResolution $modname/default $modname/$::ModulesVersion\
            default
      } else {
         reportError "Invalid ModulesVersion '$::ModulesVersion' defined"
      }
   }

   lpopState debug_msg_prefix
   lpopState specifiedname
   lpopState modulename
   lpopState modulefile

   return $::ModulesVersion
}

proc evaluateModulerc {itrp mod_file} {
   interp eval $itrp set ::ModulesCurrentModulefile "{$mod_file}"
   interp eval $itrp {set ::ModulesVersion {}}
   # create an alias ModuleVersion on ModulesVersion
   interp eval $itrp {upvar 0 ::ModulesVersion ::ModuleVersion}

   ##nagelfar ignore +4 Suspicious # char
   interp eval $itrp {
      info script $::ModulesCurrentModulefile
      eval [getModuleContent $::ModulesCurrentModulefile]
      # pass ModulesVersion value to main interp
      if {[info exists ::ModulesVersion]} {
         setModulesVersion $::ModulesVersion
      }
   }
}

proc renderModulercEvalError {itrp mod_file failed_eval error_msg} {
   if {!$failed_eval} {
      return 0
   }
   # no error if rc file cannot be read
   switch -- [getInterpVar $itrp errorCode] {
      MODULES_ERR_READ {}
      MODULES_ERR_VALIDITY {reportInternalBug $error_msg $mod_file}
      default {reportInternalBug [formatInterpErrStackTrace $itrp $mod_file]}
   }
   return 1
}

proc isInterpVarDefined {itrp var_name} {
   return [interp eval $itrp info exists $var_name]
}

proc getInterpVar {itrp var_name {val_if_unset {}}} {
   if {[isInterpVarDefined $itrp $var_name]} {
      return [interp eval $itrp set $var_name]
   } else {
      return $val_if_unset
   }
}

# format error stack trace to report modulefile information only
proc formatInterpErrStackTrace {itrp modfile} {
   return [formatErrStackTrace [getInterpVar $itrp ::errorInfo] $modfile\
      [concat [interp eval $itrp info procs] [interp eval $itrp info\
      commands]]]
}

# Save list of the defined procedure and the global variables with their
# associated values set in sub 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

   regexp {^__[a-z]+} $itrp itrpkind
   # save name and value for any other global variables
   foreach var [$itrp eval {info globals}] {
      if {![info exists untrackVars($var)]} {
         reportDebug "saving for $itrpkind 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 "saving for $itrpkind proc list [array names dumpProcs]"
}

# Define commands to be known by sub interpreter.
proc initInterpCommands {itrp fresh aliasesVN aliasesPassArgVN tracesVN\
   renameCmdsVN} {
   upvar #0 $aliasesVN aliases
   upvar #0 $aliasesPassArgVN aliasesPassArg
   upvar #0 $tracesVN traces
   upvar #0 $renameCmdsVN renameCmds

   # rename some commands on freshly created interp before aliases defined
   # below overwrite them
   if {$fresh} {
      foreach cmd [array names renameCmds] {
         $itrp eval rename $cmd $renameCmds($cmd)
      }
   }

   # set interpreter alias commands each time to guaranty them being
   # defined and not overridden by modulefile or modulerc content
   foreach alias [array names aliases] {
      if {[info exists aliasesPassArg($alias)]} {
         set aliasargs $aliasesPassArg($alias)
         # pass current itrp reference on special keyword
         if {[lindex $aliasargs 0] eq {__itrp__}} {
            lset aliasargs 0 $itrp
         }
         interp alias $itrp $alias {} $aliases($alias) {*}$aliasargs
      } else {
         interp alias $itrp $alias {} $aliases($alias)
      }
   }

   if {$fresh} {
      # trace each modulefile command call if verbosity is set to debug (when
      # higher verbosity level is set all cmds are already traced) and timer
      # mode is disabled
      if {[getConf verbosity] eq {debug} && ![getState timer]} {
         interp alias $itrp reportTraceExecEnter {} reportTraceExecEnter
         foreach alias [array names aliases] {
            # exclude internal commands expoxed to modulerc/file interpreter
            # exclude cachefile commands
            if {$alias ni {report reportDebug reportError reportWarning\
               reportCmdTrace incrErrorCount reportInternalBug\
               formatErrStackTrace isVerbosityLevel modulefile-content\
               modulerc-content modulefile-invalid limited-access-file\
               limited-access-directory}} {
               interp eval $itrp [list trace add execution $alias enter\
                  reportTraceExecEnter]
            }
         }
      }
   }

   foreach alias [array names traces] {
      interp eval $itrp [list trace add execution $alias leave\
         $traces($alias)]
   }
}

# Restore initial setup of sub interpreter passed as argument based on
# global structure previously filled with initial list of defined procedure
# and values of global variable.
proc resetInterpState {itrp fresh dumpVarsVN dumpArrayVarsVN untrackVarsVN\
   dumpProcsVN aliasesVN aliasesPassArgVN tracesVN renameCmdsVN\
   dumpCommandsVN} {
   upvar #0 $dumpVarsVN dumpVars
   upvar #0 $dumpArrayVarsVN dumpArrayVars
   upvar #0 $untrackVarsVN untrackVars
   upvar #0 $dumpProcsVN dumpProcs
   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 "removing on $itrp proc $var"
         $itrp eval [list rename $var {}]
      }
   }

   ##nagelfar vartype aliasesVN varName
   ##nagelfar vartype aliasesPassArgVN varName
   ##nagelfar vartype tracesVN varName
   ##nagelfar vartype renameCmdsVN varName
   # rename some commands and set aliases on interpreter
   initInterpCommands $itrp $fresh $aliasesVN $aliasesPassArgVN $tracesVN\
      $renameCmdsVN

   # 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 "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 "missing command(s), recreating interp $itrp"
      interp delete $itrp
      interp create $itrp
      initInterpCommands $itrp 1 $aliasesVN $aliasesPassArgVN $tracesVN\
         $renameCmdsVN
   }

   # 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 "removing on $itrp var $var"
            $itrp eval unset -nocomplain ::$var
         } elseif {![info exists dumpArrayVars($var)]} {
            if {$dumpVars($var) ne [$itrp eval set ::$var]} {
               reportDebug "restoring on $itrp var $var"
               if {[string is list $dumpVars($var)] && [llength\
                  $dumpVars($var)] > 1} {
                  # restore value as list
                  $itrp eval set ::$var [list $dumpVars($var)]
               } else {
                  # brace value to be able to restore empty string
                  $itrp eval set ::$var "{$dumpVars($var)}"
               }
            }
         } else {
            if {$dumpVars($var) ne [$itrp eval array get ::$var]} {
               reportDebug "restoring on $itrp var $var"
               $itrp eval array set ::$var [list $dumpVars($var)]
            }
         }
      }
   }
}

# Dictionary-style string comparison
# Use dictionary sort of lsort proc to compare two strings in the "string
# compare" fashion (returning -1, 0 or 1). Tcl dictionary-style comparison
# enables to compare software versions (ex: "1.10" is greater than "1.8")
proc versioncmp {str1 str2} {
   if {$str1 eq $str2} {
      return 0
   # put both strings in a list, then lsort it and get first element
   } elseif {[lindex [lsort -dictionary [list $str1 $str2]] 0] eq $str1} {
      return -1
   } else {
      return 1
   }
}

proc module-info {what {more {}}} {
   set mode [currentState mode]

   switch -- $what {
      mode {
         if {$more ne {}} {
            set command [currentState commandname]
            return [expr {$mode eq $more || ($more eq {remove} && $mode eq \
               {unload}) || ($more eq {switch} && $command eq {switch}) ||\
               ($more eq {nonpersist} && $mode eq {refresh})}]
         } else {
            return $mode
         }
      }
      command {
         set command [currentState commandname]
         if {$more eq {}} {
            return $command
         } else {
            return [expr {$command eq $more}]
         }
      }
      name {
         return [currentState modulename]
      }
      specified {
         return [currentState specifiedname]
      }
      shell {
         if {$more ne {}} {
            return [expr {[getState shell] eq $more}]
         } else {
            return [getState 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 {}} {
            return [expr {[getState shelltype] eq $more}]
         } else {
            return [getState 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 [isIcase]]
         if {$ret ne $more} {
            return $ret
         } else {
            return {}
         }
      }
      trace {
         return {}
      }
      tracepat {
         return {}
      }
      type {
         return Tcl
      }
      symbols {
         lassign [getModuleNameVersion $more 1] mod modname modversion
         set sym_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 $sym_list] && $modversion eq {default}} {
            set sym_list [getVersAliasList $modname]
         }
         return [join $sym_list :]
      }
      tags {
         # refresh mod name version and variant to correctly get all matching
         # tags (in case tags apply to specific module variant)
         set modname [currentState modulename]
         set modnamevr [getAndParseLoadedModuleWithVariant $modname]
         collectModuleTags $modnamevr

         if {$more ne {}} {
            return [expr {$more in [getTagList $modnamevr [currentState\
               modulefile]]}]
         } else {
            return [getTagList $modnamevr [currentState modulefile]]
         }
      }
      version {
         lassign [getModuleNameVersion $more 1] mod
         return [resolveModuleVersionOrAlias $mod [isIcase]]
      }
      loaded {
         lassign [getModuleNameVersion $more 1] mod
         return [getLoadedMatchingName $mod returnall]
      }
      usergroups {
         if {[getState is_win]} {
            knerror "module-info usergroups not supported on Windows platform"
         } else {
            if {$more ne {}} {
               return [expr {$more in [getState usergroups]}]
            } else {
               return [getState usergroups]
            }
         }
      }
      username {
         if {[getState is_win]} {
            knerror "module-info username not supported on Windows platform"
         } else {
            if {$more ne {}} {
               return [expr {[getState username] eq $more}]
            } else {
               return [getState username]
            }
         }
      }
      default {
         knerror "module-info $what not supported"
         return {}
      }
   }
}

proc module-whatis {args} {
   lappend ::g_whatis [join $args]

   return {}
}

# 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} {
   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"
         }
      }
   }

   return {}
}

proc module-alias {args} {
   lassign [getModuleNameVersion [lindex $args 0]] alias
   lassign [getModuleNameVersion [lindex $args 1] 1] mod

   reportDebug "$alias = $mod"

   if {[setModuleResolution $alias $mod]} {
      set ::g_moduleAlias($alias) $mod
      set ::g_sourceAlias($alias) [currentState modulefile]
   }

   return {}
}

proc module-virtual {args} {
   lassign [getModuleNameVersion [lindex $args 0]] mod
   set modfile [getAbsolutePath [lindex $args 1]]

   reportDebug "$mod = $modfile"

   set ::g_moduleVirtual($mod) $modfile
   set ::g_sourceVirtual($mod) [currentState modulefile]

   return {}
}

# Parse date time argument value and translate it into epoch time
proc __parseDateTimeArg {opt datetime} {
   if {[regexp {^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2})?$} $datetime match\
      timespec]} {
      # time specification is optional
      if {$timespec eq {}} {
         append datetime T00:00
      }
      # return corresponding epoch time
      return [clock scan $datetime -format %Y-%m-%dT%H:%M]
   } else {
      knerror "Incorrect $opt value '$datetime' (valid date time format is\
         'YYYY-MM-DD\[THH:MM\]')"
   }
}

# parse application criteria arguments and determine if command applies
proc parseApplicationCriteriaArgs {aftbef nearsec args} {
   set otherargs {}

   # parse argument list
   foreach arg $args {
      if {[info exists nextargisval]} {
         ##nagelfar vartype nextargisval varName
         set $nextargisval $arg
         unset nextargisval
      } elseif {[info exists nextargisdatetime]} {
         ##nagelfar ignore Suspicious variable name
         set ${nextargisdatetime}raw $arg
         # get epoch time from date time argument value
         ##nagelfar vartype nextargisdatetime varName
         ##nagelfar ignore Unknown variable
         set $nextargisdatetime [parseDateTimeArg $prevarg $arg]
         unset nextargisdatetime
      } else {
         switch -- $arg {
            --after - --before {
               # treat --after/--before as regular content if disabled
               if {!$aftbef} {
                  lappend otherargs $arg
               } else {
                  set nextargisdatetime [string trimleft $arg -]
               }
            }
            --not-group - --not-user - --group - --user {
               if {[getState is_win]} {
                  knerror "Option '$arg' not supported on Windows platform"
               } else {
                  set nextargisval [string map {- {}} $arg]list
               }
            }
            default {
               lappend otherargs $arg
            }
         }
         set prevarg $arg
      }
   }

   if {[info exists nextargisval] || [info exists nextargisdatetime]} {
      knerror "Missing value for '$prevarg' option"
   }

   set user [expr {[info exists userlist] && [getState username] in\
      $userlist}]
   set group [expr {[info exists grouplist] && [isIntBetweenList\
      $grouplist [getState usergroups]]}]

   # does it apply to current user?
   set notuser [expr {[info exists notuserlist] && [getState username] in\
      $notuserlist}]
   set notgroup [expr {[info exists notgrouplist] && [isIntBetweenList\
      $notgrouplist [getState usergroups]]}]

   # does it apply currently?
   set isbefore [expr {[info exists before] && [getState clock_seconds] <\
      $before}]
   set isafter [expr {[info exists after] && [getState clock_seconds] >=\
      $after}]


   set user_or_group_target_defined [expr {[info exists userlist] || [info\
      exists grouplist]}]
   set user_or_group_targeted [expr {$user || $group}]
   set user_or_group_excluded [expr {$notuser || $notgroup}]
   set time_frame_defined [expr {[info exists before] || [info exists after]}]
   set in_time_frame [expr {!$time_frame_defined || $isbefore || $isafter}]
   set in_near_time_frame [expr {[info exists after] && !$isafter &&\
      [getState clock_seconds] >= ($after - $nearsec)}]

   set apply [expr {$in_time_frame && ($user_or_group_targeted ||\
      (!$user_or_group_target_defined && !$user_or_group_excluded))}]

   # is end limit near ?
   set isnearly [expr {!$apply && ($user_or_group_targeted ||\
      (!$user_or_group_target_defined && !$user_or_group_excluded)) &&\
      $in_near_time_frame}]
   if {![info exists afterraw]} {
      set afterraw {}
   }

   return [list $apply $isnearly $afterraw $otherargs]
}

proc setModspecTag {modspec tag {props {}}} {
   reportDebug "Set tag '$tag' with properties '$props' on module\
      specification '$modspec'"

   if {[isModuleFullPath $modspec]} {
      # use dedicated structure for full path module specifications
      if {![info exists ::g_moduleTagFullPath($modspec)] || $tag ni\
         $::g_moduleTagFullPath($modspec)} {
            lappend ::g_moduleTagFullPath($modspec) $tag
      } else {
         set idx [lsearch -exact $::g_moduleTagFullPath($modspec) $tag]
      }
      # record props associated to tag
      if {[info exists idx]} {
         lset ::g_moduleTagFullPathSpec($modspec) $idx $props
      } else {
         lappend ::g_moduleTagFullPathSpec($modspec) $props
      }
   } else {
      # record tag list for mod root to optimize search
      set modroot [getModuleRootFromVersSpec $modspec]
      if {![info exists ::g_moduleTagRoot($modroot)]} {
         lappend ::g_moduleTagRoot($modroot) $tag
         set idx 0
         set new 1
      } else {
         set idx [lsearch -exact $::g_moduleTagRoot($modroot) $tag]
         if {$idx == -1} {
            set idx [llength $::g_moduleTagRoot($modroot)]
            lappend ::g_moduleTagRoot($modroot) $tag
            set new 1
         }
      }

      # then record mod spec and props at idx defined for tag. new spec are
      # appended and firstly matching spec is returned with its props on
      # search
      if {[info exists new]} {
         lappend ::g_moduleTagRootSpec($modroot) [list $modspec $props]
      } else {
         set tagrootlist [lindex $::g_moduleTagRootSpec($modroot) $idx]
         lappend tagrootlist $modspec $props
         lset ::g_moduleTagRootSpec($modroot) $idx $tagrootlist
      }
   }
}

proc module-forbid {args} {
   # parse application criteria arguments to determine if command apply
   lassign [parseApplicationCriteriaArgs 1 [expr {[getConf\
      nearly_forbidden_days] * 86400}] {*}$args] apply isnearly after\
      otherargs

   # parse remaining argument list, do it even if command does not apply to
   # raise any command specification error
   foreach arg $otherargs {
      if {[info exists nextargisval]} {
         ##nagelfar vartype nextargisval varName
         set $nextargisval $arg
         unset nextargisval
      } else {
         switch -glob -- $arg {
            --nearly-message {
               set nextargisval nearlymessage
            }
            --message {
               set nextargisval message
            }
            -* {
               knerror "Invalid option '$arg'"
            }
            default {
               lappend modarglist $arg
            }
         }
         set prevarg $arg
      }
   }

   if {[info exists nextargisval]} {
      knerror "Missing value for '$prevarg' option"
   }

   if {![info exists modarglist]} {
      knerror {No module specified in argument}
   }

   # skip record if application criteria are not met
   if {$apply} {
      set proplist {}
      if {[info exists message]} {
         ##nagelfar ignore Found constant
         lappend proplist message $message
      }

      # record each forbid spec after parsing them
      foreach modarg [parseModuleSpecification 0 0 0 0 {*}$modarglist] {
         setModspecTag $modarg forbidden $proplist
      }
   } elseif {$isnearly} {
      ##nagelfar ignore Found constant
      lappend proplist after $after
      if {[info exists nearlymessage]} {
         ##nagelfar ignore Found constant
         lappend proplist message $nearlymessage
      }
      # record each nearly forbid spec after parsing them
      foreach modarg [parseModuleSpecification 0 0 0 0 {*}$modarglist] {
         setModspecTag $modarg nearly-forbidden $proplist
      }
   }
}

proc module-hide {args} {
   set hidinglvl 1
   set hiddenloaded 0

   # parse application criteria arguments to determine if command apply
   lassign [parseApplicationCriteriaArgs 1 0 {*}$args] apply isnearly after\
      otherargs

   # parse remaining argument list, do it even if command does not apply to
   # raise any command specification error
   foreach arg $otherargs {
      switch -glob -- $arg {
         --hard {
            # hardened stealth
            set hidinglvl 2
         }
         --soft {
            # soften level of camouflage
            set hidinglvl 0
         }
         --hidden-loaded {
            # module should stay hidden once being loaded
            set hiddenloaded 1
         }
         -* {
            knerror "Invalid option '$arg'"
         }
         default {
            lappend modarglist $arg
         }
      }
   }

   if {![info exists modarglist]} {
      knerror {No module specified in argument}
   }

   # skip hide spec record if application criteria are not met
   if {$apply} {
      # record each hide spec after parsing them
      foreach modarg [parseModuleSpecification 0 0 0 0 {*}$modarglist] {
         setModspecHidingLevel $modarg $hidinglvl
         if {$hiddenloaded} {
            setModspecTag $modarg hidden-loaded
         }
      }
   }
}

proc hide-modulefile {modfile} {
   module-hide $modfile
}

proc module-tag {args} {
   # parse application criteria arguments to determine if command apply
   lassign [parseApplicationCriteriaArgs 0 0 {*}$args] apply isnearly after\
      otherargs

   # parse remaining argument list, do it even if command does not apply to
   # raise any command specification error
   foreach arg $otherargs {
      switch -glob -- $arg {
         -* {
            knerror "Invalid option '$arg'"
         }
         default {
            if {![info exists tag]} {
               set tag $arg
            } else {
               lappend modarglist $arg
            }
         }
      }
   }

   if {![info exists tag]} {
      knerror {No tag specified in argument}
   }
   if {![info exists modarglist]} {
      knerror {No module specified in argument}
   }
   if {$tag in [list loaded auto-loaded forbidden nearly-forbidden hidden\
      hidden-loaded warning]} {
      knerror "'$tag' is a reserved tag name and cannot be set"
   }

   # skip tag record if application criteria are not met
   if {$apply} {
      # record each hide spec after parsing them
      foreach modarg [parseModuleSpecification 0 0 0 0 {*}$modarglist] {
         setModspecTag $modarg $tag
      }
   }
}

# parse arguments sent to the unsetenv modulefile command
proc parseSetenvCommandArgs {mode dflbhv args} {
   set bhv $dflbhv
   foreach arg $args {
      switch -- $arg {
         --set-if-undef {
            if {$mode eq {load}} {
               set setifundef 1
            }
         }
         default {
            if {![info exists var]} {
               set var $arg
            } elseif {![info exists val]} {
               set val $arg
            } else {
               # too much argument
               set wrongargnum 1
            }
         }
      }
   }

   if {[info exists wrongargnum] || ![info exists var] || ![info exists\
      val]} {
      knerror {wrong # args: should be "setenv ?--set-if-undef? var val"}
   }

   if {[info exists setifundef] && [isEnvVarDefined $var]} {
      set bhv noop
   }

   reportDebug "bhv=$bhv, var=$var, val=$val"
   return [list $bhv $var $val]
}

proc setenv {args} {
   lassign [parseSetenvCommandArgs load set {*}$args] bhv var val

   if {$bhv eq {set}} {
      # clean any previously defined reference counter array
      unset-env [getModshareVarName $var] 1

      # clean any previously defined pushenv stack
      unset-env [getPushenvVarName $var] 1

      # Set the variable for later use during the modulefile evaluation
      set-env $var $val
   }

   return {}
}

# undo setenv in unload mode
proc setenv-un {args} {
   lassign [parseSetenvCommandArgs unload unset {*}$args] bhv var val

   # clean any existing reference counter array
   unset-env [getModshareVarName $var] 1

   # clean any previously defined pushenv stack
   unset-env [getPushenvVarName $var] 1

   # Add variable to the list of variable to unset in shell output code but
   # set it in interp context as done on load mode for later use during the
   # modulefile evaluation
   unset-env $var 0 $val

   return {}
}

# optimized setenv for whatis mode: init env variable with an empty
# value if undefined. do not care about value, just avoid variable to be
# undefined for later use during the modulefile evaluation
proc setenv-wh {args} {
   lassign [parseSetenvCommandArgs load set {*}$args] bhv var val

   setEnvVarIfUndefined $var {}
   return {}
}

# parse arguments sent to the getenv modulefile command
proc parseGetenvCommandArgs {cmd args} {
   set returnval 0
   set valifundef {}
   switch -- [llength $args] {
      1 {
         set var [lindex $args 0]
      }
      2 {
         switch -- [lindex $args 0] {
            --return-value {
               set returnval 1
               set var [lindex $args 1]
            }
            default {
               set var [lindex $args 0]
               set valifundef [lindex $args 1]
            }
         }
      }
      3 {
         if {[lindex $args 0] ne {--return-value}} {
            set wrongargs 1
         } else {
            set returnval 1
            set var [lindex $args 1]
            set valifundef [lindex $args 2]
         }
      }
      default {
         set wrongargs 1
      }
   }

   set argname [expr {$cmd eq {getenv} ? {variable} : {name}}]
   if {[info exists wrongargs]} {
      knerror "wrong # args: should be \"$cmd ?--return-value? $argname\
         ?valifundef?\""
   }

   reportDebug "$argname='$var', valifundef='$valifundef',\
      returnval='$returnval'"
   return [list $var $valifundef $returnval]
}

proc getenv {args} {
   # parse args
   lassign [parseGetenvCommandArgs getenv {*}$args] var valifundef returnval

   if {[currentState mode] ne {display} || $returnval} {
      return [get-env $var $valifundef]
   } else {
      return "\$$var"
   }
}

# parse arguments sent to the unsetenv modulefile command
proc parseUnsetenvCommandArgs {mode dflbhv args} {
   foreach arg $args {
      switch -- $arg {
         --unset-on-unload {
            if {$mode eq {unload}} {
               set bhv unset
            }
         }
         --noop-on-unload {
            if {$mode eq {unload}} {
               set bhv noop
            }
         }
         default {
            if {![info exists var]} {
               set var $arg
            } elseif {![info exists val]} {
               set val $arg
               if {$mode eq {unload} && ![info exists bhv]} {
                  set bhv set
               }
            } else {
               # too much argument
               set wrongargnum 1
            }
         }
      }
   }

   if {[info exists wrongargnum] || ![info exists var]} {
      knerror {wrong # args: should be "unsetenv ?--noop-on-unload?\
         ?--unset-on-unload? var ?val?"}
   }

   if {![info exists bhv]} {
      set bhv $dflbhv
   }

   # initialize val to always return same structure, val is only used if bhv
   # equals 'set'
   if {![info exists val]} {
      set val {}
   }

   reportDebug "bhv=$bhv, var=$var, val=$val"
   return [list $bhv $var $val]
}

proc unsetenv {args} {
   lassign [parseUnsetenvCommandArgs load unset {*}$args] bhv var val

   # clean any existing reference counter array
   unset-env [getModshareVarName $var] 1

   # clean any previously defined pushenv stack
   unset-env [getPushenvVarName $var] 1

   # Set the variable for later use during the modulefile evaluation
   unset-env $var

   return {}
}

# undo unsetenv in unload mode
proc unsetenv-un {args} {
   lassign [parseUnsetenvCommandArgs unload noop {*}$args] bhv var val

   switch -- $bhv {
      set {
         # apply value specified for set on unload
         return [setenv $var $val]
      }
      unset {
         return [unsetenv $var]
      }
      noop {
         # otherwise just clear variable if it does not exist on unload mode
         # for later use during the modulefile evaluation
         if {![isEnvVarDefined $var]} {
            reset-to-unset-env $var
         }
      }
   }
   return {}
}

# optimized unsetenv for whatis mode: init env variable with an empty
# value if undefined. do not care about value, just avoid variable to be
# undefined for later use during the modulefile evaluation
proc unsetenv-wh {args} {
   lassign [parseUnsetenvCommandArgs load noop {*}$args] bhv var val

   setEnvVarIfUndefined $var {}
   return {}
}

proc chdir {dir} {
   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 '[currentState modulename]'"
   }

   return {}
}

# supersede exit command to handle it if called within a modulefile
# rather than exiting the whole process
proc exitModfileCmd {{code 0}} {
   if {[currentState mode] in {load refresh}} {
      setState inhibit_interp 1
   }

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

# enables sub interp to return ModulesVersion value to the main interp
proc setModulesVersion {val} {
   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 {itrp args} {
   # determine if puts call targets the stdout or stderr channel
   switch -- [llength $args] {
      1 {
         # create struct with newline status and message to output
         set deferPuts [list 1 [lindex $args 0]]
      }
      2 {
         switch -- [lindex $args 0] {
            -nonewline {
               set deferPuts [list 0 [lindex $args 1]]
            }
            stdout {
               set deferPuts [list 1 [lindex $args 1]]
            }
            prestdout {
               set deferPrePuts [list 1 [lindex $args 1]]
            }
            stderr {
               set reportArgs [list [lindex $args 1]]
            }
            log {
               set logPuts [lindex $args 1]
            }
         }
      }
      3 {
         if {[lindex $args 0] eq {-nonewline}} {
            switch -- [lindex $args 1] {
               stdout {
                  set deferPuts [list 0 [lindex $args 2]]
               }
               prestdout {
                  set deferPrePuts [list 0 [lindex $args 2]]
               }
               stderr {
                  set reportArgs [list [lindex $args 2] 1]
               }
               log {
                  set logPuts [lindex $args 2]
               }
            }
         } else {
            set wrongNumArgs 1
         }
      }
      default {
         set wrongNumArgs 1
      }
   }

   # raise error if bad argument number detected, do this here rather in _puts
   # not to confuse people with an error reported by an internal name (_puts)
   if {[info exists wrongNumArgs]} {
      knerror {wrong # args: should be "puts ?-nonewline? ?channelId? string"}
   # send content to log system
   } elseif {[info exists logPuts]} {
      log $logPuts
   # defer puts if it targets stdout (see renderSettings)
   } elseif {[info exists deferPuts]} {
      # current module is qualified for refresh evaluation
      lappendState -nodup refresh_qualified [currentState modulename]
      lappend ::g_stdoutPuts {*}$deferPuts
   } elseif {[info exists deferPrePuts]} {
      lappendState -nodup refresh_qualified [currentState modulename]
      lappend ::g_prestdoutPuts {*}$deferPrePuts
   # if it targets stderr call report, which knows what channel to use
   } elseif {[info exists reportArgs]} {
      # report message only if not silent
      if {[isVerbosityLevel concise]} {
         report {*}$reportArgs
      }
   # pass to real puts command if not related to stdout and do that in modfile
   # interpreter context to get access to eventual specific channel
   } else {
      # re-throw error as a known error for accurate stack trace print
      if {[catch {$itrp eval _puts $args} errMsg]} {
         knerror $errMsg MODULES_ERR_CUSTOM
      }
   }
}

proc prepend-path {args} {
   # Set the variable for later use during the modulefile evaluation
   add-path prepend-path load prepend {*}$args

   return {}
}

proc prepend-path-un {args} {
   # Set the variable for later use during the modulefile evaluation
   unload-path prepend-path unload remove {*}$args

   return {}
}

proc append-path {args} {
   # Set the variable for later use during the modulefile evaluation
   add-path append-path load append {*}$args

   return {}
}

proc append-path-un {args} {
   # Set the variable for later use during the modulefile evaluation
   unload-path append-path unload remove {*}$args

   return {}
}

proc remove-path {args} {
   # Set the variable for later use during the modulefile evaluation
   unload-path remove-path load remove {*}$args

   return {}
}

# undo remove-path in unload mode
proc remove-path-un {args} {
   lassign [unload-path remove-path unload noop {*}$args] bhv var

   # clean any previously defined pushenv stack
   unset-env [getPushenvVarName $var] 1

   # clear variable if it does not exist on unload mode for later use
   # during the modulefile evaluation
   if {![isEnvVarDefined $var]} {
      reset-to-unset-env $var
   }
}

# optimized *-path for whatis mode: init env variable with an empty value if
# undefined. do not care about value, just avoid variable to be undefined for
# later use during the modulefile evaluation
proc edit-path-wh {cmd args} {
   # get variable name
   lassign [parsePathCommandArgs $cmd load noop {*}$args] separator allow_dup\
      idx_val ign_refcount val_set_is_delim glob_match bhv var path_list

   setEnvVarIfUndefined $var {}

   return {}
}

proc set-alias {alias what} {
   set ::g_Aliases($alias) $what
   set ::g_stateAliases($alias) new

   # current module is qualified for refresh evaluation
   lappendState -nodup refresh_qualified [currentState modulename]

   return {}
}

# undo set-alias in unload mode
proc set-alias-un {alias what} {
   return [unset-alias $alias]
}

proc unset-alias {alias} {
   set ::g_Aliases($alias) {}
   set ::g_stateAliases($alias) del

   return {}
}

proc set-function {function what} {
   set ::g_Functions($function) $what
   set ::g_stateFunctions($function) new

   # current module is qualified for refresh evaluation
   lappendState -nodup refresh_qualified [currentState modulename]

   return {}
}

# undo set-function in unload mode
proc set-function-un {function what} {
   return [unset-function $function]
}

proc unset-function {function} {
   set ::g_Functions($function) {}
   set ::g_stateFunctions($function) del

   return {}
}


proc is-loaded {args} {
   # parse module version specification
   set args [parseModuleSpecification 0 0 0 0 {*}$args]

   foreach mod $args {
      if {[getLoadedMatchingName $mod returnfirst] ne {}} {
         return 1
      }
   }
   # is something loaded whatever it is?
   return [expr {![llength $args] && [llength\
      [getEnvLoadedModulePropertyParsedList name]]}]
}

proc is-loading {args} {
   foreach mod $args {
      if {[getLoadedMatchingName $mod returnfirst 1] ne {}} {
         return 1
      }
   }
   # is something else loading whatever it is?
   return [expr {![llength $args] && [llength [getLoadingModuleList]] > 1}]
}

proc conflict {args} {
   set currentModule [currentState modulename]
   set curmodnamevr [currentState modulenamevr]
   # get module short name if loaded by its full pathname
   if {[set isfullpath [isModuleFullPath $currentModule]]} {
      set currentSModule [findModuleNameFromModulefile $currentModule]
   }
   defineModEqProc [isIcase] [getConf extended_default]

   set conflict_unload [expr {[getConf conflict_unload] && [getConf\
      auto_handling]}]

   # parse module version specification
   set args [parseModuleSpecification 0 0 0 0 {*}$args]

   registerCurrentModuleConflict {*}$args

   foreach mod $args {
      set is_conflict_loading 0
      set unload_attempt 0
      set loaded_conflict_mod_list [getLoadedMatchingName $mod returnall]

      if {![llength $loaded_conflict_mod_list]} {
         set eq_current_mod [expr {[modEq $mod $currentModule eqstart 1 2 1]\
            || ($isfullpath && [modEq $mod $currentSModule eqstart 1 2 1])}]
         # currently evaluating module should not be mistaken for loading
         # conflicting module
         if {!$eq_current_mod} {
            set loaded_conflict_mod_list [getLoadedMatchingName $mod\
               returnall 1]
            if {[llength $loaded_conflict_mod_list]} {
               # no conflict unload attempt on loading module
               set is_conflict_loading 1
            }
         }
      } elseif {$conflict_unload} {
         set still_loaded_conflict_mod_list {}
         # unload attempt in reverse load order
         foreach loaded_conflict_mod [lreverse $loaded_conflict_mod_list] {
            if {[cmdModuleUnload conun match 1 s 0 $loaded_conflict_mod]} {
               lappend still_loaded_conflict_mod_list $loaded_conflict_mod
            }
         }
         set loaded_conflict_mod_list $still_loaded_conflict_mod_list
         set unload_attempt 1
      }

      if {[llength $loaded_conflict_mod_list]} {
         setConflictErrorAsReported {*}$loaded_conflict_mod_list

         # error msg has already been sent if a conflict unload was attempted
         set msg [expr {$unload_attempt ? {} : [getPresentConflictErrorMsg\
            $curmodnamevr $loaded_conflict_mod_list $is_conflict_loading]}]
         knerrorOrWarningIfForced $msg MODULES_ERR_GLOBAL
      }
   }

   return {}
}

proc registerCurrentModuleConflict {args} {
   # register conflict list unless record inhibited for current iterp context
   if {[currentState inhibit_req_record] != [currentState evalid]} {
      setLoadedConflict [currentState modulename] {*}$args
   }
}

proc parsePrereqCommandArgs {cmd args} {
   set tag_list {}
   set modulepath_list {}
   set optional 0
   set opt_list {}
   set prereq_list {}

   # parse options defined
   set i 0
   foreach arg $args {
      if {[info exists nextargistaglist]} {
         set tag_list [split $arg :]
         lappend opt_list $arg
         unset nextargistaglist
      } elseif {[info exists nextargismodulepathlist]} {
         set modulepath_list {}
         # record list of absolute paths
         foreach modulepath [split $arg :] {
            lappend modulepath_list [getAbsolutePath $modulepath]
         }
         lappend opt_list $arg
         unset nextargismodulepathlist
      } else {
         switch -glob -- $arg {
            --optional {
               set optional 1
               lappend opt_list $arg
            }
            --tag=* {
               set tag_list [split [string range $arg 6 end] :]
               lappend opt_list $arg
               if {![llength $tag_list]} {
                  knerror "Missing value for '--tag' option"
               }
            }
            --tag {
               set nextargistaglist 1
               lappend opt_list $arg
            }
            --modulepath {
               set nextargismodulepathlist 1
               lappend opt_list $arg
            }
            -* {
               knerror "Invalid option '$arg'"
            }
            default {
               set prereq_list [lrange $args $i end]
               # end option parsing: remaining elts are list of prereqs
               break
            }
         }
      }
      incr i
   }

   foreach tag $tag_list {
      if {$tag in [list loaded auto-loaded forbidden nearly-forbidden\
         hidden warning]} {
         knerror "Tag '$tag' cannot be manually set"
      }
   }
   if {![llength $prereq_list]} {
      knerror "wrong # args: should be \"$cmd ?--optional? ?--tag? ?taglist?\
         ?--modulepath? ?modulepathlist? modulefile ?...?\""
   } elseif {[set mispopt [lsearch -inline -glob $prereq_list --*]] ne {}} {
      knerror "Misplaced option '$mispopt'"
   }
   return [list $tag_list $modulepath_list $optional $opt_list $prereq_list]
}

proc prereqAnyModfileCmd {tryload auto args} {
   lassign [parsePrereqCommandArgs prereq {*}$args] tag_list modulepath_list\
      optional opt_list args

   set currentModule [currentState modulename]
   set curmodnamevr [currentState modulenamevr]

   # parse module version specification
   set args [parseModuleSpecification 0 0 0 0 {*}$args]

   # register prereq list (sets of optional prereq are registered as list)
   # unless record inhibited for current iterp context
   if {[currentState inhibit_req_record] != [currentState evalid]} {
      # if requirement is optional, add current module to the recorded prereq
      # list to make the requirement rule satisfied even if none loaded, as
      # current module will be loaded
      if {$optional} {
         lappend record_list $currentModule
      }
      lappend record_list {*}$args
      setLoadedPrereq $currentModule $record_list

      if {[llength $modulepath_list]} {
         setLoadedPrereqPath $currentModule $record_list $modulepath_list
      }
   }

   if {$auto} {
      # convert modulepath list to keep entries not matching any enabled
      # modulepaths and transform entries into all matching enabled modulepath
      set converted_list {}
      foreach modulepath $modulepath_list {
         set matching_list [getMatchingModulepathList $modulepath]
         if {[llength $matching_list]} {
            appendNoDupToList converted_list {*}$matching_list
         } else {
            appendNoDupToList converted_list $modulepath
         }
      }
      set modulepath_list $converted_list

      # try to load prereq as dependency resolving is enabled
      lassign [loadRequirementModuleList $tryload $optional $tag_list\
         $modulepath_list {*}$args] retlo prereqloaded
   } else {
      set loadedmod_list {}
      foreach mod $args {
         # get all loaded or loading mod in args list
         if {[set loadedmod [getLoadedMatchingName $mod returnfirst 0 {}\
            $modulepath_list]] ne {} || [set loadedmod [getLoadedMatchingName\
            $mod returnfirst 1 {} $modulepath_list]] ne {}} {
            lappend loadedmod_list $loadedmod
         }
      }
      set prereqloaded [llength $loadedmod_list]
   }

   if {!$prereqloaded} {
      if {!$optional} {
         # error if requirement is not satisfied unless if optional
         reportMissingPrereqError $curmodnamevr $modulepath_list {*}$args
      }
   } elseif {!$auto} {
      # apply missing tag to all loaded module found (already done when
      # dependency resolving is enabled)
      cmdModuleTag 0 0 $tag_list {*}$loadedmod_list
   }

   return {}
}

proc 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] && ![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 $sepapos+1 end]
         set resource [string range $resource 0 $sepapos-1]
         reportDebug "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 {[catch {runCommand xrdb -query} errMsg]} {
      knerror "X11 resources cannot be edited, issue spotted\n[sgr er\
         ERROR]: $errMsg" MODULES_ERR_GLOBAL
   }

   # 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 {[currentState mode] eq {load}} {
      set ::g_newXResources($resource) $value
   } else {
      set ::g_delXResources($resource) $value
   }

   return {}
}

proc uname {what} {
   return [switch -- $what {
      sysname {getState os}
      machine {getState machine}
      nodename - node {getState nodename}
      release {getState osversion}
      domain {getState domainname}
      version {getState kernelversion}
      default {knerror "uname $what not supported"}
   }]
}

# run shell command
proc system {args} {
   # run through the appropriate shell
   if {[getState is_win]} {
      set shell cmd.exe
      set shellarg /c
   } else {
      set shell /bin/sh
      set shellarg -c
   }

   if {[catch {exec >&@stderr $shell $shellarg [join $args]}]} {
      # non-zero exit status, get it:
      return [lindex $::errorCode 2]
   } else {
      # exit status was 0
      return 0
   }
}

# test at least one of the collections passed as argument exists
proc is-saved {args} {
   foreach coll $args {
      lassign [findCollections $coll exact] collfile colldesc
      if {[string length $collfile]} {
         return 1
      }
   }
   # is something saved whatever it is?
   return [expr {![llength $args] && [llength [findCollections]]}]
}

# test at least one of the directories passed as argument is set in MODULEPATH
proc is-used {args} {
   set modpathlist [getModulePathList]
   foreach path $args {
      # transform given path in an absolute path to compare with dirs
      # registered in the MODULEPATH env var which are returned absolute.
      set abspath [getAbsolutePath $path]
      if {$abspath in $modpathlist} {
         return 1
      }
   }
   # is something used whatever it is?
   return [expr {![llength $args] && [llength $modpathlist]}]
}

# test at least one of the modulefiles passed as argument exists
proc is-avail {args} {
   # parse module version specification
   # a module name is mandatory
   set args [parseModuleSpecification 0 0 0 0 {*}$args]
   set ret 0

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

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

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

proc execShAndGetEnv {elt_ignored_list shell script args} {
   set sep {%ModulesShToMod%}
   set subsep {%ModulesSubShToMod%}
   set shdesc [list $script {*}$args]
   set sherr 0
   set shellopts [list]

   upvar ignvarlist ignvarlist
   set ignvarlist [list OLDPWD PWD _ _AST_FEATURES PS1 _LMFILES_\
      LOADEDMODULES]

   # define shell command to run to source script and analyze the environment
   # changes it performs
   switch -- [file tail $shell] {
      dash - sh {
         # declare is not supported by dash but functions cannot be retrieved
         # anyway, so keep using declare and throw errors out to avoid overall
         # execution error. dash does not pass arguments to sourced script but
         # it does not raise error if arguments are set
         ##nagelfar ignore +3 Found constant
         set command "export -p; echo $sep; declare -f 2>/dev/null; echo\
            $sep; alias; echo $sep; echo $sep; pwd; echo $sep; . [listTo\
            shell $shdesc] 2>&1; echo $sep; export -p; echo $sep; declare -f\
            2>/dev/null; echo $sep; alias; echo $sep; echo $sep; pwd"
         set varre {export (\S+?)=["']?(.*?)["']?$}
         set funcre {(\S+?) \(\)\s?\n?{\s?\n(.+?)\n}$}
         set aliasre {(\S+?)='(.*?)'$}
         set varvalmap [list {\"} \" \\\\ \\]
         set alvalmap [list {'\''} ' {'"'"'} ']
      }
      bash {
         ##nagelfar ignore +2 Found constant
         set command "export -p; echo $sep; declare -f; echo $sep; alias;\
            echo $sep; complete; echo $sep; pwd; echo $sep; . [listTo shell\
            $shdesc] 2>&1; echo $sep; export -p; echo $sep; declare -f; echo\
            $sep; alias; echo $sep; complete; echo $sep; pwd"
         set varre {declare -x (\S+?)="(.*?)"$}
         set funcre {(\S+?) \(\)\s?\n{\s?\n(.+?)\n}$}
         set aliasre {alias (\S+?)='(.*?)'$}
         set compre {complete (.+?) (\S+?)$}
         set comprevar [list match value name]
         set varvalmap [list {\"} \" \\\\ \\]
         set alvalmap [list {'\''} ']
         lappend shellopts --noprofile --norc
      }
      bash-eval {
         ##nagelfar ignore +3 Found constant
         set command "export -p; echo $sep; declare -f; echo $sep; alias;\
            echo $sep; complete; echo $sep; pwd; echo $sep; eval \"\$([listTo\
            shell $shdesc] 2>/dev/null)\"; echo $sep; export -p; echo $sep;\
            declare -f; echo $sep; alias; echo $sep; complete; echo $sep; pwd"
         set varre {declare -x (\S+?)="(.*?)"$}
         set funcre {(\S+?) \(\)\s?\n{\s?\n(.+?)\n}$}
         set aliasre {alias (\S+?)='(.*?)'$}
         set compre {complete (.+?) (\S+?)$}
         set comprevar [list match value name]
         set varvalmap [list {\"} \" \\\\ \\]
         set alvalmap [list {'\''} ']
         lappend shellopts --noprofile --norc
      }
      ksh - ksh93 {
         ##nagelfar ignore +3 Found constant
         set command "typeset -x; echo $sep; typeset +f | while read f; do\
            typeset -f \${f%\\(\\)}; echo; done; echo $sep; alias; echo $sep;\
            echo $sep; pwd; echo $sep; . [listTo shell $shdesc] 2>&1; echo\
            $sep; typeset -x; echo $sep; typeset +f | while read f; do\
            typeset -f \${f%\\(\\)}; echo; done; echo $sep; alias; echo $sep;\
            echo $sep; pwd"
         set varre {(\S+?)=\$?'?(.*?)'?$}
         set funcre {(\S+?)\(\) {\n?(.+?)}[;\n]?$}
         set aliasre {(\S+?)=\$?'?(.*?)'?$}
         set varvalmap [list {\'} ']
         set alvalmap [list {\"} \" {\\'} ' {\'} ' {\\\\} {\\}]
      }
      zsh {
         ##nagelfar ignore +2 Found constant
         set command "typeset -x; echo $sep; declare -f; echo $sep; alias;\
            echo $sep; echo $sep; pwd; echo $sep; . [listTo shell $shdesc]\
            2>&1; echo $sep; typeset -x; echo $sep; declare -f; echo $sep;\
            alias; echo $sep; echo $sep; pwd"
         set varre {(\S+?)=\$?'?(.*?)'?$}
         set funcre {(\S+?) \(\) {\n(.+?)\n}$}
         set aliasre {(\S+?)=\$?'?(.*?)'?$}
         set varvalmap [list {'\''} ']
         set alvalmap [list {'\''} ']
      }
      csh {
         ##nagelfar ignore +2 Found constant
         set command "setenv; echo $sep; echo $sep; alias; echo $sep; echo\
            $sep; pwd; echo $sep; source [listTo shell $shdesc] >&\
            /dev/stdout; echo $sep; setenv; echo $sep; echo $sep; alias; echo\
            $sep; echo $sep; pwd"
         set varre {(\S+?)=(.*?)$}
         set aliasre {(\S+?)\t(.*?)$}
         set varvalmap [list]
         set alvalmap [list]
         lappend shellopts -f
      }
      tcsh {
         ##nagelfar ignore +2 Found constant
         set command "setenv; echo $sep; echo $sep; alias; echo $sep;\
            complete; echo $sep; pwd; echo $sep; source [listTo shell\
            $shdesc] >& /dev/stdout; echo $sep; setenv; echo $sep; echo $sep;\
            alias; echo $sep; complete; echo $sep; pwd"
         set varre {(\S+?)=(.*?)$}
         set aliasre {(\S+?)\t\(?(.*?)\)?$}
         set compre {(\S+?)\t(.*?)$}
         set comprevar [list match name value]
         set varvalmap [list]
         set alvalmap [list]
         lappend shellopts -f
      }
      fish {
         # exclude from search builtins, fish-specific functions and private
         # functions defined prior script evaluation: reduce this way the
         # the number of functions to parse.
         set getfunc "set funcout (string match -r -v \$funcfilter (functions\
            -a -n) | while read f; functions \$f; echo '$subsep'; end)"
         ##nagelfar ignore +9 Found constant
         set command "set -xgL; echo '$sep'; status test-feature\
            regex-easyesc 2>/dev/null; and set escrepl '\\\\\\\\\$1'; or set\
            escrepl '\\\\\\\\\\\\\\\$1'; set funcfilter \\^\\((string\
            join '|' (string replace -r '(\\\[|\\.)' \$escrepl\
            (builtin -n; functions -a -n | string split ', ' | string match\
            -e -r '^_')))\\|fish\\.\\*\\)\\\$; $getfunc; $getfunc; string\
            split \$funcout; echo '$sep'; string split \$funcout; echo\
            '$sep'; complete; echo '$sep'; pwd; echo '$sep'; source [listTo\
            shell $shdesc] 2>&1; or exit \$status; echo '$sep'; set -xgL;\
            echo '$sep'; $getfunc; string split \$funcout; echo '$sep';\
            string split \$funcout; echo '$sep'; complete; echo '$sep'; pwd"
         set varre {^(\S+?\M) ?['"]?(.*?)['"]?$}
         # exclude alias from function list
         set funcre "^function (\\S+?)(?: \[^\\n\]*?--description\
            (?!'?alias)\[^\\n\]+)?\\n(.+?)?\\s*\\nend\\n$subsep\$"
         # fetch aliases from available functions
         set aliasre "^function (\\S+?) \[^\\n\]*?--description\
            '?alias\[^\\n\]+\\n\\s*(.+?)(?: \\\$argv)?\\s*\\nend\\n$subsep\$"
         set compre {complete ((?:-\S+? )*?)(?:(?:-c|--command)\
            )?([^-]\S+)(.*?)$}
         set comprevar [list match valpart1 name valpart2]
         # translate back fish-specific code
         set varvalmap [list {'  '} : {\'} ' {\"} \" \\\\ \\]
         set alvalmap [list { $argv;} {}]

         # fish builtins change LS_COLORS variable
         lappend ignvarlist LS_COLORS
      }
      default {
         knerror "Shell '$shell' not supported"
      }
   }

   if {![file exists $script]} {
      knerror "Script '$script' cannot be found"
   }

   set real_shell [expr {$shell eq {bash-eval} ? {bash} : $shell}]
   set shellpath [getCommandPath $real_shell]
   if {$shellpath eq {}} {
      knerror "Shell '$shell' cannot be found"
   }
   set shellexec [list $shellpath {*}$shellopts -c $command]

   reportDebug "running '$shellexec'"
   if {[catch {set output [exec {*}$shellexec]} output]} {
      set sherr 1
   }

   # link result variables to calling context
   upvar cwdbefout cwdbefout cwdaftout cwdaftout

   # extract each output sections
   set idx 0
   foreach varout {varbefout funcbefout aliasbefout compbefout cwdbefout\
      scriptout varaftout funcaftout aliasaftout compaftout cwdaftout} {
      ##nagelfar vartype varout varName
      if {[set sepidx [string first $sep $output $idx]] == -1} {
         set $varout [string trimright [string range $output $idx end] \n]
         if {$varout ne {cwdaftout} && !$sherr} {
            knerror "Unexpected output when sourcing '$shdesc' in shell\
               '$shell'"
         }
      } else {
         set $varout [string trimright [string range $output $idx $sepidx-1]\
            \n]
         set idx [expr {$sepidx + [string length $sep] + 1}]
      }
      # remove expected Tcl error message
      if {$sherr && $varout eq {scriptout} && [set erridx [string\
         last {child process exited abnormally} [set $varout]]] != -1} {
         set $varout [string range [set $varout] 0 $erridx-2]
      }
   }
   if {$sepidx != -1 && !$sherr} {
      knerror "Unexpected output when sourcing '$shdesc' in shell '$shell'"
   }

   reportDebug "script output is '$scriptout'"
   if {$sherr} {
      # throw error if script had an issue, send script output along if any
      set errmsg "Script '$script' exited abnormally"
      if {$scriptout ne {}} {
         append errmsg "\n  with following output\n$scriptout"
      }
      knerror $errmsg
   }

   # link result variables to calling context
   upvar varbef varbef varaft varaft
   upvar funcbef funcbef funcaft funcaft
   upvar aliasbef aliasbef aliasaft aliasaft
   upvar compbef compbef compaft compaft

   # clear current directory change if ignored
   if {{chdir} in $elt_ignored_list} {
      set cwdaftout $cwdbefout
   }

   # extract environment variable information
   if {{envvar} ni $elt_ignored_list} {
      ##nagelfar ignore Found constant
      foreach {out arr} [list varbefout varbef varaftout varaft] {
         ##nagelfar vartype out varName
         foreach {match name value} [regexp -all -inline -lineanchor $varre\
            [set $out]] {
            # convert shell-specific escaping
            ##nagelfar ignore Suspicious variable name
            set ${arr}($name) [string map $varvalmap $value]
         }
      }
   }
   # extract function information if function supported by shell
   if {{function} ni $elt_ignored_list && [info exists funcre]} {
      ##nagelfar ignore Found constant
      foreach {out arr} [list funcbefout funcbef funcaftout funcaft] {
         foreach {match name value} [regexp -all -inline -lineanchor $funcre\
            [set $out]] {
            # no specific escaping to convert for functions
            ##nagelfar ignore Suspicious variable name
            set ${arr}($name) $value
         }
      }
   }
   # extract alias information
   if {{alias} ni $elt_ignored_list} {
      ##nagelfar ignore Found constant
      foreach {out arr} [list aliasbefout aliasbef aliasaftout aliasaft] {
         foreach {match name value} [regexp -all -inline -lineanchor $aliasre\
            [set $out]] {
            ##nagelfar ignore Suspicious variable name
            set ${arr}($name) [string map $alvalmap $value]
         }
      }
   }
   # extract complete information if supported by shell
   if {{complete} ni $elt_ignored_list && [info exists compre]} {
      ##nagelfar ignore Found constant
      foreach {out arr} [list compbefout compbef compaftout compaft] {
         ##nagelfar ignore Non constant variable list to foreach statement
         foreach $comprevar [regexp -all -inline -lineanchor $compre [set\
            $out]] {
            if {[info exists valpart1]} {
               ##nagelfar ignore Unknown variable
               set value [concat $valpart1 $valpart2]
            }
            # no specific escaping to convert for completes
            ##nagelfar ignore Suspicious variable name
            lappend ${arr}($name) $value
         }
      }
   }
}

# execute script with args through shell and convert environment changes into
# corresponding modulefile commands
proc sh-to-mod {elt_ignored_list args} {
   set modcontent [list]
   set pathsep [getState path_separator]
   set shell [lindex $args 0]

   # evaluate script and retrieve environment before and after evaluation
   # procedure will set result variables in current context
   ##nagelfar implicitvarcmd {execShAndGetEnv *} ignvarlist cwdbefout\
      cwdaftout varbef varaft funcbef funcaft aliasbef aliasaft compbef\
      compaft
   execShAndGetEnv $elt_ignored_list {*}$args

   # check environment variable change
   lassign [getDiffBetweenArray varbef varaft] notaft diff notbef
   foreach name $notaft {
      # also ignore Modules variables intended for internal use
      if {$name ni $ignvarlist && ![string equal -length 10 $name\
         __MODULES_]} {
         lappend modcontent [list unsetenv $name]
      }
   }
   foreach name $diff {
      if {$name ni $ignvarlist && ![string equal -length 10 $name\
         __MODULES_]} {
         # new value is totally different (also consider a bare ':' as a
         # totally different value to avoid erroneous matches)
         if {$varbef($name) eq $pathsep || [set idx [string first\
            $varbef($name) $varaft($name)]] == -1} {
            lappend modcontent [list setenv $name $varaft($name)]
         } else {
            # content should be prepended
            if {$idx > 0} {
               set modcmd [list prepend-path]
               # check from the end to get the largest chunk to prepend
               set idx [string last $varbef($name) $varaft($name)]
               # get delimiter from char found between new and existing value
               set delim [string index $varaft($name) $idx-1]
               if {$delim ne $pathsep} {
                  lappend modcmd -d $delim
               }
               lappend modcmd $name
               # split value and remove duplicate entries
               set vallist [list]
               appendNoDupToList vallist {*}[split [string range\
                  $varaft($name) 0 $idx-2] $delim]
               # an empty element is added
               if {![llength $vallist]} {
                  lappend vallist {}
               }
               lappend modcontent [list {*}$modcmd {*}$vallist]
            }
            # content should be appended
            if {($idx + [string length $varbef($name)]) < [string length\
               $varaft($name)]} {
               set modcmd [list append-path]
               set delim [string index $varaft($name) $idx+[string length\
                  $varbef($name)]]
               if {$delim ne $pathsep} {
                  lappend modcmd -d $delim
               }
               lappend modcmd $name
               set vallist [list]
               appendNoDupToList vallist {*}[split [string range\
                  $varaft($name) [expr {$idx + [string length $varbef($name)]\
                  + 1}] end] $delim]
               if {![llength $vallist]} {
                  lappend vallist {}
               }
               lappend modcontent [list {*}$modcmd {*}$vallist]
            }
         }
      }
   }
   foreach name $notbef {
      if {$name ni $ignvarlist && ![string equal -length 10 $name\
         __MODULES_]} {
         if {[string first $pathsep $varaft($name)] == -1} {
            lappend modcontent [list setenv $name $varaft($name)]
         } else {
            # define a path-like variable if path separator found in it
            # split value and remove duplicate entries
            set vallist [list]
            appendNoDupToList vallist {*}[split $varaft($name) $pathsep]
            lappend modcontent [list prepend-path $name {*}$vallist]
         }
      }
   }
   # check function change
   lassign [getDiffBetweenArray funcbef funcaft] notaft diff notbef
   foreach name $notaft {
      lappend modcontent [list unset-function $name]
   }
   foreach name [list {*}$diff {*}$notbef] {
      lappend modcontent [list set-function $name \n$funcaft($name)]
   }
   # check alias change
   lassign [getDiffBetweenArray aliasbef aliasaft] notaft diff notbef
   foreach name $notaft {
      lappend modcontent [list unset-alias $name]
   }
   foreach name [list {*}$diff {*}$notbef] {
      lappend modcontent [list set-alias $name $aliasaft($name)]
   }
   # check complete change
   set real_shell [expr {$shell eq {bash-eval} ? {bash} : $shell}]
   lassign [getDiffBetweenArray compbef compaft] notaft diff notbef
   foreach name $notaft {
      lappend modcontent [list uncomplete $name]
   }
   foreach name [list {*}$diff {*}$notbef] {
      foreach body $compaft($name) {
         lappend modcontent [list complete $real_shell $name $body]
      }
   }
   # check current working directory change
   if {$cwdbefout ne $cwdaftout} {
      lappend modcontent [list chdir $cwdaftout]
   }

   # sort result to ensure consistent output whatever the evaluation shell
   set modcontent [lsort -dictionary $modcontent]

   reportDebug "resulting env changes '$modcontent'"
   return $modcontent
}

proc parseSourceShArgs {args} {
   set elt_ignored_list {}

   set i 0
   foreach arg $args {
      incr i
      if {[info exists nextargisval]} {
         ##nagelfar vartype nextargisval varName
         set $nextargisval $arg
         unset nextargisval
      } else {
         switch -glob -- $arg {
            --ignore {
               set nextargisval ignore_opt_raw
            }
            -* {
               knerror "Invalid option '$arg'"
            }
            default {
               set shell $arg
               # end option parsing: remaining elts are allowed values
               break
            }
         }
         set prevarg $arg
      }
   }

   if {[info exists nextargisval]} {
      knerror "Missing value for '$prevarg' option"
   }

   if {![info exists shell] || $i == [llength $args]} {
      knerror "wrong # args: should be \"source-sh ?--ignore? ?eltlist? shell\
         script ?arg ...?\""
   }

   set script_args [lassign [lrange $args $i end] script]

   if {[info exists ignore_opt_raw]} {
      set elt_ignored_list [split $ignore_opt_raw :]
      set allowed_elt_ignored_list {envvar function alias chdir complete}
      foreach elt_ignored $elt_ignored_list {
         if {$elt_ignored ni $allowed_elt_ignored_list} {
            knerror "Invalid ignored element '$elt_ignored'"
         }
      }
   }

   return [list $elt_ignored_list $shell $script $script_args]
}

proc source-sh {args} {
   lassign [parseSourceShArgs {*}$args] elt_ignored_list shell script\
      script_args
   # evaluate script and get the environment changes it performs translated
   # into modulefile commands
   set shtomodargs [list $shell $script {*}$script_args]
   set modcontent [sh-to-mod $elt_ignored_list {*}$shtomodargs]

   # register resulting modulefile commands
   setLoadedSourceSh [currentState modulename] [list $shtomodargs\
      {*}$modcontent]

   # get name of current module Tcl interp
   set itrp [getCurrentModfileInterpName]

   # evaluate resulting modulefile commands through current Tcl interp
   foreach modcmd $modcontent {
      interp eval $itrp $modcmd
   }
}

# undo source-sh in unload mode
proc source-sh-un {args} {
   lassign [parseSourceShArgs {*}$args] elt_ignored_list shell script\
      script_args
   set shtomodargs [list $shell $script {*}$script_args]
   # find commands resulting from source-sh evaluation recorded in env
   set modcontent [getLoadedSourceShScriptContent [currentState modulename]\
      $shtomodargs]

   # get name of current module unload Tcl interp
   set itrp [getCurrentModfileInterpName]

   # evaluate each recorded command in unload Tcl interp to get them reversed
   foreach modcmd $modcontent {
      interp eval $itrp $modcmd
   }
}

# report underlying modulefile cmds in display mode
proc source-sh-di {args} {
   lassign [parseSourceShArgs {*}$args] elt_ignored_list shell script\
      script_args
   set shtomodargs [list $shell $script {*}$script_args]

   # if module loaded, get as much content from environment as possible
   if {[is-loaded [currentState modulename]]} {
      # find commands resulting from source-sh evaluation recorded in env
      set reccontent [getLoadedSourceShScriptContent [currentState\
         modulename] $shtomodargs]

      # need to evaluate script to get alias/function/complete definition
      execShAndGetEnv $elt_ignored_list {*}$shtomodargs

      set modcontent {}
      foreach cmd $reccontent {
         # build modulefile content to show with recorded elements in env and
         # alias/function/complete definition obtained by reevaluating script
         switch -- [lindex $cmd 0] {
            complete {
               set cpname [lindex $cmd 2]
               if {[info exists compaft($cpname)]} {
                  set cpbodylist $compaft($cpname)
               } else {
                  set cpbodylist [list {}]
               }
               foreach cpbody $cpbodylist {
                  lappend modcontent [list complete $shell $cpname $cpbody]
               }
            }
            set-alias {
               set alname [lindex $cmd 1]
               if {[info exists aliasaft($alname)]} {
                  set albody $aliasaft($alname)
               } else {
                  set albody {}
               }
               lappend modcontent [list set-alias $alname $albody]
            }
            set-function {
               set fnname [lindex $cmd 1]
               if {[info exists funcaft($fnname)]} {
                  set fnbody \n$funcaft($fnname)
               } else {
                  set fnbody {}
               }
               lappend modcontent [list set-function $fnname $fnbody]
            }
            default {
               lappend modcontent $cmd
            }
         }
      }
   # not loaded, so get full content from script evaluation
   } else {
      set modcontent [sh-to-mod $elt_ignored_list {*}$shtomodargs]
   }

   # get name of current module unload Tcl interp
   set itrp [getCurrentModfileInterpName]

   # evaluate each recorded command in display Tcl interp to get them printed
   foreach modcmd $modcontent {
      interp eval $itrp $modcmd
   }
}

proc isVariantNameValid {name} {
   return [expr {![string is digit -strict $name] && [regexp\
      {^[A-Za-z0-9_][A-Za-z0-9_-]*$} $name]}]
}

# parse arguments set on a variant modulefile command
proc parseVariantCommandArgs {args} {
   set dflvalue {}
   set defdflvalue 0
   set isboolean 0
   set i 0
   foreach arg $args {
      incr i
      if {[info exists nextargisval]} {
         ##nagelfar vartype nextargisval varName
         set $nextargisval $arg
         unset nextargisval
      } else {
         switch -glob -- $arg {
            --default {
               ##nagelfar ignore Found constant
               set nextargisval dflvalue
               set defdflvalue 1
            }
            --boolean {
               set isboolean 1
            }
            -* {
               knerror "Invalid option '$arg'"
            }
            default {
               set name $arg
               # end option parsing: remaining elts are allowed values
               break
            }
         }
         set prevarg $arg
      }
   }

   if {[info exists nextargisval]} {
      knerror "Missing value for '$prevarg' option"
   }

   # check variant name and allowed values
   if {![info exists name]} {
      knerror {No variant name specified}
   }
   if {![isVariantNameValid $name]} {
      knerror "Invalid variant name '$name'"
   }
   set values [lrange $args $i end]
   if {$isboolean} {
      if {[llength $values]} {
         knerror "No value should be defined for boolean variant '$name'"
      } else {
         set values {1 0 yes no true false on off}
      }
   } else {
      foreach val $values {
         if {[string is boolean -strict $val] && ![string is integer\
            -strict $val]} {
            knerror "Boolean value defined on non-boolean variant '$name'"
         }
      }
   }
   if {$defdflvalue && $isboolean} {
      # default value should be bool if variant is boolean
      if {![string is boolean -strict $dflvalue]} {
         knerror "Boolean value is expected as default value for variant\
            '$name'"
      # translate default value in boolean canonical form (0 or 1)
      } else {
         set dflvalue [string is true -strict $dflvalue]
      }
   }

   return [list $name $values $defdflvalue $dflvalue $isboolean]
}

proc variant {itrp args} {
   # parse args
   lassign [parseVariantCommandArgs {*}$args] name values defdflvalue\
      dflvalue isboolean

   # version variant is forbidden until specific implementation
   if {$name eq {version}} {
      knerror "'version' is a restricted variant name" MODULES_ERR_GLOBAL
   }

   # get variant list defined on command line
   set vrlist [getVariantListFromVersSpec [currentState modulenamevr]]

   # search for variant specification (most right-positionned value wins)
   for {set i [expr {[llength $vrlist]-1}]} {$i >= 0} {incr i -1} {
      lassign [lindex $vrlist $i] vrname vrnot vrisbool vrvalue
      if {$vrname eq $name} {
         # translate value in boolean canonical form (0/1) if variant is bool
         if {$isboolean && [string is boolean -strict $vrvalue]} {
            set value [string is true -strict $vrvalue]
         } else {
            set value $vrvalue
         }
         set isdflval [expr {$defdflvalue && $dflvalue eq $value}]
         break
      }
   }

   # error if variant has not been specified unless a default is defined
   if {![info exists isdflval]} {
      if {$defdflvalue} {
         set value $dflvalue
         # 2 means default value automatically set
         set isdflval 2
      # no error if variant is undefined on display mode, return here not to
      # set any variant-specific variable
      } elseif {[currentState mode] eq {display}} {
         return
      } else {
         set allowedmsg [expr {![llength $values] ? {} : "\nAllowed values\
            are: $values"}]
         knerror "No value specified for variant '$name'$allowedmsg"\
            MODULES_ERR_GLOBAL
      }
   }

   # check defined value
   if {($isboolean && ![string is boolean -strict $value]) || (!$isboolean &&\
      [llength $values] && $value ni $values)} {
      # invalid value error is not a modulefile error
      knerror "Invalid value '$value' for variant '$name'\nAllowed values\
         are: $values" MODULES_ERR_GLOBAL
   } else {
      # instantiate variant in modulefile context
      reportDebug "Set variant on $itrp: ModuleVariant($name) = '$value'"
      $itrp eval set "{::ModuleVariant($name)}" "{$value}"
      # after modfile interp ModuleVariant is unset by resetInterpState

      # record variant for persistency (name value is-boolean is-default)
      # unless module is currently unloading
      if {[currentState mode] ne {unload}} {
         setLoadedVariant [currentState modulename] [list $name $value\
            $isboolean $isdflval]
      }
   }
}

# optimized variant command for whatis mode: init entry in ModuleVariant array
# to avoid variable being undefined when accessed during modulefile evaluation
proc variant-wh {itrp args} {
   # parse args
   lassign [parseVariantCommandArgs {*}$args] name values defdflvalue\
      dflvalue isboolean

   # instantiate variant in modulefile context to an empty value
   reportDebug "Set variant on $itrp: ModuleVariant($name) = ''"
   $itrp eval set "{::ModuleVariant($name)}" "{}"
}

proc getvariant {itrp args} {
   # parse args
   lassign [parseGetenvCommandArgs getvariant {*}$args] name valifundef\
      returnval

   if {[currentState mode] ne {display} || $returnval} {
      if {[$itrp eval info exists "{::ModuleVariant($name)}"]} {
         return [$itrp eval set "{::ModuleVariant($name)}"]
      } else {
         return $valifundef
      }
   } else {
      return [sgr va "{$name}"]
   }
}

proc require-fullname {} {
   # test specified name is any alternative name of currently evaluating mod
   # expect the default and parent dir name (which are considered unqualified)
   if {![modEq [currentState specifiedname] [currentState modulename] eqspec\
      1 4]} {
      knerror {Module version must be specified to load module}\
         MODULES_ERR_GLOBAL
   }
}

proc prereqAllModfileCmd {tryload auto args} {
   lassign [parsePrereqCommandArgs prereq-all {*}$args] tag_list\
      modulepath_list optional opt_list args
   # call prereq over each arg independently to emulate a prereq-all
   foreach arg $args {
      prereqAnyModfileCmd $tryload $auto {*}$opt_list $arg
   }
}

proc always-load {args} {
   lassign [parsePrereqCommandArgs always-load {*}$args] tag_list\
      modulepath_list optional opt_list args
   # append keep-loaded tag to the list, second tag list in opt_list will take
   # over the initial list defined
   lappend tag_list keep-loaded
   lappend opt_list --tag [join $tag_list :]

   # auto load is inhibited if currently in DepRe context
   set auto [expr {[currentModuleEvalContext] eq {depre} ? {0} : {1}}]

   # load all module specified
   prereqAllModfileCmd 0 $auto {*}$opt_list {*}$args
}

proc family {name} {
   # ensure name is valid to be part of the name of an environment variable
   if {![string length $name] || ![regexp {^[A-Za-z0-9_]*$} $name]} {
      knerror "Invalid family name '$name'"
   }

   # only one loaded module could provide a given family
   conflict $name
   # set name as an alias for currently loading module
   setLoadedAltname [currentState modulename] [list al $name]

   # set variable in environment to know what module name provides family
   set upname [string toupper $name]
   lassign [getModuleNameVersion] mod modname modversion
   if {$modname eq {.}} {
      set modname [currentState modulename]
   }
   setenv MODULES_FAMILY_$upname $modname
   # also set Lmod-specific variable for compatibility
   setenv LMOD_FAMILY_$upname $modname
}

proc family-un {name} {
   # ensure name is valid to be part of the name of an environment variable
   if {![string length $name] || ![regexp {^[A-Za-z0-9_]*$} $name]} {
      knerror "Invalid family name '$name'"
   }

   # unset family-related environment variable
   set upname [string toupper $name]
   unsetenv MODULES_FAMILY_$upname
   unsetenv LMOD_FAMILY_$upname
}

proc complete {shell name body} {
   if {![string length $name]} {
      knerror "Invalid command name '$name'"
   }
   # append definition retaining for which shell they are made
   # also some shells may set multiple definitions for a single name
   lappend ::g_Completes($name) $shell $body
   set ::g_stateCompletes($name) new

   # current module is qualified for refresh evaluation
   lappendState -nodup refresh_qualified [currentState modulename]
}

# undo complete in unload mode
proc complete-un {shell name body} {
   return [uncomplete $name]
}

proc uncomplete {name} {
   if {![string length $name]} {
      knerror "Invalid command name '$name'"
   }
   set ::g_Completes($name) {}
   set ::g_stateCompletes($name) del
}

proc pushenv {var val} {
   # save initial value in pushenv value stack
   set pushvar [getPushenvVarName $var]
   if {![isEnvVarDefined $pushvar] && [isEnvVarDefined $var]} {
      prepend-path $pushvar &$::env($var)
   }

   # clean any previously defined reference counter array
   unset-env [getModshareVarName $var] 1

   # Set the variable for later use during the modulefile evaluation
   set-env $var $val

   # add this value to the stack associated to current module name in order to
   # know what element to remove from stack when unloading
   prepend-path $pushvar [currentState modulename]&$val

   return {}
}

# undo pushenv in unload mode
proc pushenv-un {var val} {
   # clean any existing reference counter array
   unset-env [getModshareVarName $var] 1

   # update value stack
   set pushvar [getPushenvVarName $var]
   if {[isEnvVarDefined $pushvar]} {
      set pushlist [split $::env($pushvar) :]
      # find value pushed by currently evaluated module and remove it
      set popidx [lsearch -exact $pushlist [currentState modulename]&$val]
      if {$popidx != -1} {
         set pushlist [lreplace $pushlist $popidx $popidx]
         remove-path --index $pushvar $popidx
      }

      if {[llength $pushlist]} {
         # fetch value on top of the stack
         set validx [expr {[string first & [lindex $pushlist 0]] + 1}]
         set popval [string range [lindex $pushlist 0] $validx end]

         # restore top value if different from current one
         if {![envVarEquals $var $popval]} {
            set-env $var $popval
         }

         # if last element remaining in stack is the initial value prior first
         # pushenv, then clear the stack totally
         if {$validx == 1} {
            remove-path --index $pushvar 0
         }
      } else {
         unset-env $var 0 $val
      }
   } else {
      # Add variable to the list of variable to unset in shell output code but
      # set it in interp context as done on load mode for later use during the
      # modulefile evaluation
      unset-env $var 0 $val
   }

   return {}
}

# optimized pushenv for whatis mode (same approach than setenv-wh)
proc pushenv-wh {var val} {
   setEnvVarIfUndefined $var {}
   return {}
}

proc modulepath-label {modpath label} {
   set modpath [getAbsolutePath $modpath]
   set ::g_modulepathLabel($modpath) $label
   reportDebug "modpath=$modpath, label='$label'"
}

proc unique-name-conflict {} {
   # skip if unique_name_loaded configuration is disabled
   if {![getConf unique_name_loaded]} {
      return
   }

   # get root name of all names (actual and alternatives) of currently
   # evaluating module
   set root_name_list [list]
   set mod [currentState modulename]
   foreach name [list $mod {*}[getAllModuleResolvedName $mod]] {
      appendNoDupToList root_name_list [lindex [file split $name] 0]
   }

   # declare conflict over all these names
   conflict {*}$root_name_list
}

proc sourceModfileCmd {itrp filename} {
   if {![info exists ::source_cache($filename)]} {
      set ::source_cache($filename) [readFile $filename]
   }

   interp eval $itrp info script $filename
   interp eval $itrp $::source_cache($filename)
   interp eval $itrp info script [currentState modulefile]
}

proc lsb-release {what} {
   return [switch -- $what {
      id {getState lsb_id}
      release {getState lsb_release}
      codename {getState lsb_codename}
      default {knerror "lsb-release $what not supported"}
   }]
}

proc registerCurrentModuleUse {args} {
   if {[string length [currentState modulename]]} {
      setLoadedUse [currentState modulename] {*}$args
   }
}

proc module-help {args} {
   lappend ::g_help_lines [join $args]
}

proc getModuleHelpLines {} {
   if {[info exists ::g_help_lines]} {
      return $::g_help_lines
   }
}

# convert property value as module tag (property name is ignored)
proc add-property {name value} {
   set tag_list [split $value :]
   set mod [currentState modulename]
   foreach tag $tag_list {
      module-tag $tag $mod
   }
}

proc module-warn {args} {
   # parse application criteria arguments to determine if command apply
   lassign [parseApplicationCriteriaArgs 1 0 {*}$args] apply isnearly after\
      otherargs

   # parse remaining argument list, do it even if command does not apply to
   # raise any command specification error
   foreach arg $otherargs {
      if {[info exists nextargisval]} {
         ##nagelfar vartype nextargisval varName
         set $nextargisval $arg
         unset nextargisval
      } else {
         switch -glob -- $arg {
            --message {
               set nextargisval message
            }
            -* {
               knerror "Invalid option '$arg'"
            }
            default {
               lappend modarglist $arg
            }
         }
         set prevarg $arg
      }
   }

   if {[info exists nextargisval]} {
      knerror "Missing value for '$prevarg' option"
   }
   if {![info exists message]} {
      knerror {No message specified in argument}
   }
   if {![info exists modarglist]} {
      knerror {No module specified in argument}
   }

   # skip tag record if application criteria are not met
   if {$apply} {
      ##nagelfar ignore Found constant
      set proplist [list message $message]
      # record each hide spec after parsing them
      foreach modarg [parseModuleSpecification 0 0 0 0 {*}$modarglist] {
         setModspecTag $modarg warning $proplist
      }
   }
}

proc provide {args} {
   if {![llength $args]} {
      knerror {No module specified in argument}
   }
   set current_mod [currentState modulename]
   foreach alias $args {
      setLoadedAltname $current_mod [list al $alias]
   }
}

# optimized variant command for scan mode: init entry in ModuleVariant array
# to avoid variable being undefined when accessed during modulefile evaluation
# record variant definition in structure for extra match search or report
proc variant-sc {itrp args} {
   # parse args
   lassign [parseVariantCommandArgs {*}$args] name values defdflvalue\
      dflvalue isboolean

   # remove duplicate possible values for boolean variant
   if {$isboolean} {
      set values {on off}
      if {$defdflvalue} {
         set dflvalue [expr {$dflvalue ? {on} : {off}}]
      }
   }

   recordScanModuleElt $name variant

   lappend ::g_scanModuleVariant([currentState modulename]) [list $name\
      $values $defdflvalue $dflvalue $isboolean]

   # instantiate variant in modulefile context to an empty value
   reportDebug "Set variant on $itrp: ModuleVariant($name) = ''"
   $itrp eval set "{::ModuleVariant($name)}" "{}"
}

proc setenv-sc {args} {
   lassign [parseSetenvCommandArgs load set {*}$args] bhv var val

   recordScanModuleElt $var setenv envvar

   setEnvVarIfUndefined $var {}
   return {}
}

proc edit-path-sc {cmd args} {
   lassign [parsePathCommandArgs $cmd load noop {*}$args] separator allow_dup\
      idx_val ign_refcount val_set_is_delim glob_match bhv var path_list

   recordScanModuleElt $var $cmd envvar

   # record MODULEPATH edition as "module use" command
   if {$var eq {MODULEPATH} && $cmd in {append-path prepend-path}} {
      recordScanModuleUsePathList $path_list
   }

   setEnvVarIfUndefined $var {}
   return {}
}

proc pushenv-sc {var val} {
   recordScanModuleElt $var pushenv envvar

   setEnvVarIfUndefined $var {}
   return {}
}

proc unsetenv-sc {args} {
   lassign [parseUnsetenvCommandArgs load noop {*}$args] bhv var val

   recordScanModuleElt $var unsetenv envvar

   setEnvVarIfUndefined $var {}
   return {}
}

proc complete-sc {shell name body} {
   if {![string length $name]} {
      knerror "Invalid command name '$name'"
   }

   recordScanModuleElt $name complete
}

proc uncomplete-sc {name} {
   if {![string length $name]} {
      knerror "Invalid command name '$name'"
   }

   recordScanModuleElt $name uncomplete
}

proc set-alias-sc {alias what} {
   recordScanModuleElt $alias set-alias
}

proc unset-alias-sc {alias} {
   recordScanModuleElt $alias unset-alias
}

proc set-function-sc {function what} {
   recordScanModuleElt $function set-function
}

proc unset-function-sc {function} {
   recordScanModuleElt $function unset-function
}

proc chdir-sc {dir} {
   recordScanModuleElt $dir chdir
}

proc family-sc {name} {
   if {![string length $name] || ![regexp {^[A-Za-z0-9_]*$} $name]} {
      knerror "Invalid family name '$name'"
   }
   recordScanModuleElt $name family provided-alias
}

proc provide-sc {args} {
   if {![llength $args]} {
      knerror {No module specified in argument}
   }
   foreach alias $args {
      recordScanModuleElt $alias provide provided-alias
   }
}

proc prereq-sc {args} {
   lassign [parsePrereqCommandArgs prereq {*}$args] tag_list modulepath_list\
      optional opt_list args

   foreach modspec [parseModuleSpecification 0 0 0 0 {*}$args] {
      recordScanModuleElt $modspec prereq prereq-any depends-on-any require
   }
}

proc prereq-all-sc {args} {
   lassign [parsePrereqCommandArgs prereq-all {*}$args] tag_list\
      modulepath_list optional opt_list args

   foreach modspec [parseModuleSpecification 0 0 0 0 {*}$args] {
      recordScanModuleElt $modspec prereq-all depends-on require
   }
}

proc always-load-sc {args} {
   lassign [parsePrereqCommandArgs always-load {*}$args] tag_list\
      modulepath_list optional opt_list args

   foreach modspec [parseModuleSpecification 0 0 0 0 {*}$args] {
      recordScanModuleElt $modspec always-load require
   }
}

proc conflict-sc {args} {
   foreach modspec [parseModuleSpecification 0 0 0 0 {*}$args] {
      recordScanModuleElt $modspec conflict incompat
   }
}

proc module-sc {command args} {
   lassign [parseModuleCommandName $command help] command cmdvalid cmdempty
   # ignore sub-commands that do not either load or unload
   if {$command in {load load-any switch try-load unload use}} {
      # parse options to distinguish them from module version spec
      lassign [parseModuleCommandArgs 0 $command 0 {*}$args] show_oneperline\
         show_mtime show_filter search_filter search_match dump_state\
         addpath_pos not_req tag_list args
      set modspeclist [parseModuleSpecification 0 0 0 0 {*}$args]

      # no require/incompat extra specifier alias if --not-req option is set
      if {$not_req} {
         set xtaliasinc {}
         set xtaliasreq {}
      } else {
         set xtaliasinc [list incompat]
         set xtaliasreq [list require]
      }

      if {$command eq {switch}} {
         # distinguish switched-off module spec from switched-on
         # ignore command without or with too much argument
         switch -- [llength $modspeclist] {
            {1} {
               # no switched-off module with one-arg form
               recordScanModuleElt $modspeclist switch switch-on\
                  {*}$xtaliasreq
            }
            {2} {
               lassign $modspeclist swoffarg swonarg
               recordScanModuleElt $swoffarg switch switch-off {*}$xtaliasinc
               recordScanModuleElt $swonarg switch switch-on {*}$xtaliasreq
            }
         }
      } elseif {$command eq {use}} {
         recordScanModuleUsePathList $modspeclist
      } else {
         set xtalias [expr {$command eq {unload} ? $xtaliasinc : $xtaliasreq}]
         # record each module spec
         foreach modspec $modspeclist {
            recordScanModuleElt $modspec $command {*}$xtalias
         }
      }
   }
}

proc recordScanModuleUsePathList {path_list} {
   foreach path $path_list {
      set resolved_abs_path [getAbsolutePath [resolvStringWithEnv $path]]
      recordScanModuleElt $resolved_abs_path use
   }
}

proc recordScanModuleElt {name args} {
   set mod [currentState modulename]
   set modpath [currentState modulepath]
   if {![info exists ::g_scanModuleElt]} {
      set ::g_scanModuleElt [dict create]
   }
   foreach elt $args {
      if {![dict exists $::g_scanModuleElt $modpath $elt $name]} {
         dict set ::g_scanModuleElt $modpath $elt $name [list $mod]
      } else {
         ##nagelfar ignore Suspicious variable name
         dict with ::g_scanModuleElt $modpath $elt {lappend $name $mod}
      }
      reportDebug "Module $mod defines $elt:$name"
   }
}

proc getScanModuleElt {modpath elt} {
   if {[info exists ::g_scanModuleElt]} {
      if {[dict exists $::g_scanModuleElt $modpath $elt]} {
         return [dict get $::g_scanModuleElt $modpath $elt]
      }
   }
}

# test given variant specification matches what scanned module defines
proc doesModVariantMatch {mod pvrlist} {
   set ret 1
   if {[info exists ::g_scanModuleVariant($mod)]} {
      foreach availvr $::g_scanModuleVariant($mod) {
         set availvrarr([lindex $availvr 0]) [lindex $availvr 1]
         set availvrisbool([lindex $availvr 0]) [lindex $availvr 4]
      }
   }
   # no match if a specified variant is not found among module variants or
   # if the value is not available
   foreach pvr $pvrlist {
      set pvrvallist [lassign $pvr vrname pvrnot pvrisbool]
      # check at least one variant value from specification matches defined
      # available variant values
      set one_vrval_match 0
      foreach pvrval $pvrvallist {
         # if variant is a boolean, specified value should be a boolean too
         # any value accepted for free-value variant
         if {[info exists availvrarr($vrname)] && (($pvrisbool &&\
            $availvrisbool($vrname)) || (!$availvrisbool($vrname) &&\
            (![llength $availvrarr($vrname)] || $pvrval in\
            $availvrarr($vrname))))} {
            set one_vrval_match 1
            break
         }
      }
      # toggle result if negation set for this pattern
      if {$pvrnot} {
         set one_vrval_match [expr {!$one_vrval_match}]
      }
      if {!$one_vrval_match} {
         set ret 0
         break
      }
   }
   return $ret
}

# test given tag specification matches tags defined over module
proc doesModTagMatch {mod modfile ptaglist} {
   set ret 1
   foreach ptag $ptaglist {
      set namelist [lassign $ptag elt pnot]
      # check if at least one tag name from specifier value is applied on mod
      set one_name_match 0
      foreach name $namelist {
         if {[isModuleTagged $mod $name 1 $modfile]} {
            set one_name_match 1
            break
         }
      }
      # toggle result if negation set for this pattern
      if {$pnot} {
         set one_name_match [expr {!$one_name_match}]
      }
      # no tag name from specifier match mod mean no match on extra query
      if {!$one_name_match} {
         set ret 0
         break
      }
   }
   return $ret
}

# collect list of modules matching all extra specifier criteria
proc getModMatchingExtraSpec {modpath pxtlist} {
   set res [list]
   if {[info exists ::g_scanModuleElt] && [dict exists $::g_scanModuleElt\
      $modpath]} {
      foreach pxt $pxtlist {
         set namelist [lassign $pxt elt pnot]
         set one_crit_res [list]
         foreach name $namelist {
            if {$elt in {require incompat load unload prereq conflict\
               prereq-all prereq-any depends-on depends-on-any always-load\
               load-any try-load switch switch-on switch-off}} {
               if {[dict exists $::g_scanModuleElt $modpath $elt]} {
                  foreach {modspec values} [dict get $::g_scanModuleElt\
                     $modpath $elt] {
                     # modEq proc has been initialized in getModules phase #2
                     if {[modEq $modspec $name eqstart 1 5 1]} {
                        # possible duplicate module entry in result list
                        lappend one_crit_res {*}[dict get $::g_scanModuleElt\
                           $modpath $elt $modspec]
                     }
                  }
               }
            } else {
               # get mods matching one value of one extra specifier criterion
               if {[dict exists $::g_scanModuleElt $modpath $elt $name]} {
                  lappend one_crit_res {*}[dict get $::g_scanModuleElt\
                     $modpath $elt $name]
               }
            }
         }
         # result is all other modules if negation set for this pattern
         if {$pnot} {
            set modpath_mod_list [dict get $::g_scanModuleElt $modpath all\
               modulename]
            lassign [getDiffBetweenList $modpath_mod_list $one_crit_res]\
               one_crit_res
         }
         lappend all_crit_res $one_crit_res
         # no match on one criterion means no match globally, no need to test
         # further criteria
         if {![llength $one_crit_res]} {
            break
         }
      }
      # matching modules are those found in every criteria result
      set res [getIntersectBetweenList {*}$all_crit_res]
   }
   return $res
}

# determine if current module search requires an extra match search
proc isExtraMatchSearchRequired {mod} {
   # an extra match search is required if not currently inhibited and:
   # * variant or provided-alias should be reported in output
   # * mod specification contains variant during avail/paths/whatis
   # * mod specification contains extra specifier during avail/paths/whatis
   return [expr {![getState inhibit_ems 0] && ([isEltInReport variant 0] ||\
      [isEltInReport provided-alias 0] || (([llength\
      [getVariantListFromVersSpec $mod]] || [llength\
      [getExtraListFromVersSpec $mod]]) && [currentState commandname] in\
      {avail paths whatis spider}))}]
}

proc insertProvidedAliases {modpath res_arrname} {
   upvar $res_arrname found_list
   foreach {alias target_mod} [getScanModuleElt $modpath provided-alias] {
      if {![info exists found_list($alias)]} {
         ##nagelfar ignore Found constant
         set found_list($alias) [list alias $target_mod]
      }
   }
}

# scan modulefiles from currently being built module search result if extra
# match search is needed
proc scanExtraMatchSearch {modpath mod res_arrname} {
   upvar $res_arrname found_list

   # disable error reporting to avoid modulefile errors (not coping with
   # scan evaluation for instance) to pollute result
   set alreadyinhibit [getState inhibit_errreport]
   if {!$alreadyinhibit} {
      inhibitErrorReport
   }
   # evaluate all modules found in scan mode to gather content information
   lappendState mode scan

   foreach elt [array names found_list] {
      switch -- [lindex $found_list($elt) 0] {
         modulefile - virtual {
            # skip evaluation of fully forbidden modulefile
            if {![isModuleTagged $elt forbidden 0 [lindex $found_list($elt)\
               2]]} {
               ##nagelfar ignore Suspicious variable name
               execute-modulefile [lindex $found_list($elt) 2] $elt $elt $elt\
                  0 0 0 $modpath
            }
         }
      }
   }

   lpopState mode
   # re-enable error report only is it was disabled from this procedure
   if {!$alreadyinhibit} {
      setState inhibit_errreport 0
   }
}

# perform extra match search on currently being built module search result
proc filterExtraMatchSearch {modpath mod res_arrname versmod_arrname} {
   # link to variables/arrays from upper context
   upvar $res_arrname found_list
   upvar $versmod_arrname versmod_list

   # get extra match query properties
   set spec_vr_list [getVariantListFromVersSpec $mod]
   set check_variant [llength $spec_vr_list]
   lassign [getSplitExtraListFromVersSpec $mod] spec_tag_list spec_xt_list
   set check_extra [llength $spec_xt_list]
   set check_tag [llength $spec_tag_list]
   set filter_res [expr {$check_variant || $check_extra || $check_tag}]

   if {$check_tag} {
      # load tags from loaded mods prior collecting tags found during rc eval
      cacheCurrentModules 0
   }

   set unset_list {}
   set keep_list {}
   foreach elt [array names found_list] {
      if {$check_tag} {
         collectModuleTags $elt
      }

      # unset elements that do not match extra query
      if {$filter_res} {
         switch -- [lindex $found_list($elt) 0] {
            alias {
               # module alias does not hold properties to match extra query
               lappend unset_list $elt
            }
            modulefile - virtual {
               if {$check_variant && ![doesModVariantMatch $elt\
                  $spec_vr_list]} {
                  lappend unset_list $elt
               } elseif {$check_tag && ![doesModTagMatch $elt [lindex\
                  $found_list($elt) 2] $spec_tag_list]} {
                  lappend unset_list $elt
               } elseif {$check_extra} {
                  # know currently retained modules to later compute those
                  # to withdrawn
                  lappend keep_list $elt
               }
            }
         }
      }
   }

   if {$check_tag} {
      # indicate tags have been collected for this modulepath
      lappendState tags_collected_in $modpath
   }

   # get list of modules matching extra specifiers to determine those to not
   # matching that need to be withdrawn from result
   if {$check_extra} {
      set extra_keep_list [getModMatchingExtraSpec $modpath $spec_xt_list]
      lassign [getDiffBetweenList $keep_list $extra_keep_list]\
         extra_unset_list
      lappend unset_list {*}$extra_unset_list
   }

   # unset marked elements
   foreach elt $unset_list {
      unset found_list($elt)
      # also unset any symbolic version pointing to unset elt
      if {[info exists versmod_list($elt)]} {
         set eltsym_list $versmod_list($elt)
         for {set i 0} {$i < [llength $eltsym_list]} {incr i} {
            set eltsym [lindex $eltsym_list $i]
            # getModules phase #2 may have already withdrawn symbol
            if {[info exists found_list($eltsym)]} {
               unset found_list($eltsym)
            }
            # also unset symbolic version applying to dir name if removing
            # default symbol
            if {[file tail $eltsym] eq {default}} {
               set eltdir [file dirname $eltsym]
               if {[info exists versmod_list($eltdir)]} {
                  lappend eltsym_list {*}$versmod_list($eltdir)
               }
            }
         }
      }
   }
}

# 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} {
   # fetch variable references in string
   set 0match_list [regexp -all -inline {\$[{]?([A-Za-z_][A-Za-z0-9_]*)[}]?}\
      ${0str}]
   if {[llength ${0match_list}]} {
      # 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}]
         ##nagelfar vartype 0varname varName
         if {![info exists ${0varname}]} {
            set ${0varname} [get-env ${0varname}]
         }
      }
      # resolve variable reference with values (now in local scope)
      set 0res [subst -nobackslashes -nocommands ${0str}]
   } else {
      set 0res ${0str}
   }

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

   return ${0res}
}

# in each string of a list, convert their variable reference into actual value
proc resolvStringListWithEnv {str_list} {
   set res_list {}
   foreach str $str_list {
      lappend res_list [resolvStringWithEnv $str]
   }
   return $res_list
}

# Return first string in list matching given resolved string. Strings in list
# are resolved to help comparison in case they contain an env variable ref.
proc getStringFromListMatchingResString {str_list resolved_str} {
   foreach str $str_list {
      if {$resolved_str eq [resolvStringWithEnv $str]} {
         return $str
      }
   }
}

# 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]
}

# check if given modulefile is hosted in one the given modulepaths
proc isModulefileInModulepathList {mod_file modulepath_list} {
   set ret 0
   foreach modulepath $modulepath_list {
      if {[string first $modulepath/ $mod_file] == 0} {
         set ret 1
         break
      }
   }
   return $ret
}

# return list of all enabled modulepaths matching given path
proc getMatchingModulepathList {path} {
   set ret {}
   foreach modulepath [getModulePathList] {
      if {[string first $path/ $modulepath] == 0} {
         lappend ret $modulepath
      }
   }
   return $ret
}

# 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 [currentState modulename]
   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
      if {[currentState modulefile] ne $curmod && $curmod ne {.modulerc}} {
         # 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}\
   {auto_symver 0}} {
   global g_moduleResolved g_resolvedHash g_resolvedPath g_symbolHash

   # find end-point module and register step-by-step path to get to it
   set res $target
   lappend 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; auto symbol are defined
   # only if no default is pre-existing
   if {$symver eq {default} && !$auto_symver} {
      # 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)
         # there may not be a default in case of auto symbol
         if {[info exists g_symbolHash($prev)] && [set idx [lsearch -exact\
            $g_symbolHash($prev) default]] != -1} {
            reportDebug "remove symbol 'default' from '$prev'"
            set g_symbolHash($prev) [lreplace $g_symbolHash($prev) $idx $idx]
         }
      }
   }

   # register end-point resolution
   reportDebug "$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 "$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)]} {
      lappend relmod_list {*}$g_resolvedHash($modalt)
      unset g_resolvedHash($modalt)
   }
   foreach relmod $relmod_list {
      set g_moduleResolved($relmod) $res
      reportDebug "$relmod now resolved to $res"
      lappend g_resolvedHash($res) $relmod
   }

   # register and propagate symbols to the resolution path, exception made for
   # auto symbol which are stored separately and not propagated
   if {[info exists g_symbolHash($mod)]} {
      set sym_list $g_symbolHash($mod)
   } else {
      set sym_list {}
   }
   if {$symver ne {} && $auto_symver} {
      reportDebug "define auto symbolic version '$mod' targeting $target"
      set ::g_autoSymbol($mod) $target
   } elseif {$symver ne {} && !$auto_symver} {
      # merge symbol definitions in case of alternative name
      if {[info exists modalt] && [info exists g_symbolHash($modalt)]} {
         set sym_list [lsort -dictionary -unique [list {*}$sym_list\
            {*}$g_symbolHash($modalt)]]
         reportDebug "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 [list {*}$sym_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 [list\
                  {*}$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 "set symbols '$modres_sym_list' to $modres and\
                  $altmodres"
               set g_symbolHash($altmodres) $modres_sym_list
            } else {
               reportDebug "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 "module-version $modvers = $modres"
               set ::g_moduleVersion($modvers) $modres
               set ::g_sourceVersion($modvers) [currentState modulefile]
               # record eventual missing resolution
               if {![info exists g_moduleResolved($modvers)]} {
                  set g_moduleResolved($modvers) $modres
                  reportDebug "$modvers resolved to $modres"
                  lappend g_resolvedHash($modres) $modvers
               }
            }
         # 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 "set resolution for $modvers"
         setModuleResolution $modvers $modres $symelt 0
      }
   }

   return 1
}

# retrieve all names that resolve to passed mod
proc getAllModuleResolvedName {mod {flag_type 0} {modspec {}} {filter_default\
   0}} {
   set namelist {}
   set resmodlist {}
   set icase [isIcase]
   defineModEqProc $icase [getConf extended_default]

   # get parent directories of mod
   foreach modelt [split $mod /] {
      if {[info exists modroot]} {
         append modroot /
      }
      append modroot $modelt
      lappend resmodlist $modroot
   }

   set modpar [file dirname $mod]
   set moddfl $modpar/default

   # add additionally all the altnames set on directories, parents of mod
   # or on distant directories whose default version resolves to mod
   for {set i 0} {$i < [llength $resmodlist]} {incr i 1} {
      set modelt [getArrayKey ::g_resolvedHash [lindex $resmodlist $i] $icase]
      if {[info exists ::g_resolvedHash($modelt)]} {
         foreach resmod $::g_resolvedHash($modelt) {
            # if alternative name corresponds to one root name test if default
            # symbol is hidden
            set resmoddfl [expr {[lsearch -exact $resmodlist $resmod] != -1 ?\
               "$resmod/default" : {}}]
            # if alternative name corresponds to default symbol and is hidden
            # test if query matches bare module name
            set resmodpar [expr {[file tail $resmod] eq {default} ? [file\
               dirname $resmod] : {}}]

            # if modelt is not a parent directory of mod, check its resolution
            # points to mod (directly for alias/sym or indirectly for dir
            # whose default version bridge resolution toward mod)
            # if modspec arg is set, exclude hidden entries not explicitly
            # matching modspec. auto symbols cannot be hidden
            # if filter_default is asked, skip parent module name and /default
            # symbol name from result list
            if {($modspec eq {} || ([info exists ::g_autoSymbol($resmod)] &&\
               $::g_autoSymbol($resmod) eq $mod) || (![isModuleHidden $resmod\
               $modspec] && ($resmoddfl eq {} || ![isModuleHidden $resmoddfl\
               $modspec])) || [modEq $modspec $resmod eqspec] || ($resmodpar\
               ne {} && [modEq $modspec $resmodpar eqspec])) && ([modEq\
               $modelt $mod eqstart] || $::g_moduleResolved($resmod) eq $mod\
               || $mod eq [lindex [getPathToModule\
               $::g_moduleResolved($resmod) {} 0] 1]) && (!$filter_default ||\
               (![modEq $resmod $modpar] && ![modEq $resmod $moddfl]))} {
               # prefix automatically generated syms with type flag if asked
               if {$flag_type} {
                  set name_entry [list]
                  if {[info exists ::g_moduleAlias($resmod)]} {
                     lappend name_entry al
                  } elseif {[info exists ::g_autoSymbol($resmod)]} {
                     lappend name_entry as
                  }
                  lappend name_entry $resmod
               } else {
                  set name_entry $resmod
               }
               appendNoDupToList namelist $name_entry

               unset modroot
               foreach reselt [split [file dirname $resmod] /] {
                  if {[info exists modroot]} {
                     append modroot /
                  }
                  append modroot $reselt
                  appendNoDupToList resmodlist $modroot
               }
            }
         }
      }
   }
   return $namelist
}

# build list for a given loaded module of its alternative names plus its
# simplified names (name minus version or any subdir if this version or subdir
# are the implicit default for module)
proc getLoadedAltAndSimplifiedName {mod} {
   if {[isModuleFullPath $mod]} {
      set namelist [list $mod]
   # use already computed name list if any
   } elseif {[info exists ::g_loadedAltAndSimplifiedName($mod)]} {
      set namelist $::g_loadedAltAndSimplifiedName($mod)
   } else {
      # get recorded alternative name
      set namelist [getLoadedAltname $mod]
      # also look through modpaths for simplified mod name
      set modpathlist [getModulePathList]
      if {[llength $modpathlist]} {
         set modfile [getModulefileFromLoadedModule $mod]
         set parentmod [file dirname $mod]
         # simplify to parent name as long as it resolves to current mod
         while {$parentmod ne {.}} {
            lassign [getPathToModule $parentmod $modpathlist 0] parentfile
            if {$parentfile eq $modfile} {
               lappend namelist $parentmod
               set parentmod [file dirname $parentmod]
            } else {
               set parentmod .
            }
         }
      }
      # recorded computed name list for later use
      set ::g_loadedAltAndSimplifiedName($mod) $namelist
   }
   return $namelist
}

# return value list of a loaded module property by parsing corresponding
# environment variable
proc getEnvLoadedModulePropertyParsedList {prop} {
   set unserialize_depth [getEnvLoadedModulePropertyStructDepth $prop]
   set unserialize_proc unserialize${unserialize_depth}ModulePropertyValueList
   set char_unmap_list [getEnvLoadedModulePropertyCharUnmapList $prop]

   set serialized_value_list [getEnvLoadedModulePropertyList $prop]
   set structured_value_list [$unserialize_proc $serialized_value_list\
      $char_unmap_list]
   return $structured_value_list
}

proc getEnvLoadedModulePropertyList {prop} {
   set env_var_name [getLoadedModulePropertyEnvVarName $prop]
   return [split [get-env $env_var_name] [getState path_separator]]
}

proc getLoadedModulePropertyEnvVarName {prop} {
   switch -- $prop {
      name {set env_var_name LOADEDMODULES}
      file {set env_var_name _LMFILES_}
      modulepath {set env_var_name MODULEPATH}
      default {set env_var_name __MODULES_LM[string toupper $prop]}
   }
   return $env_var_name
}

# 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 environment variable references in path elements
# should be resolved or passed as-is in result list
# set_abs param applies an absolute path name conversion to path elements
# if enabled
proc getModulePathList {{behavior returnempty} {resolv_var 1} {set_abs 1}} {
   if {![isEnvVarDefined MODULEPATH] && $behavior eq {exiterronundef}} {
      reportErrorAndExit {No module path defined}
   }

   set modpath_list [list]
   foreach modpath [getEnvLoadedModulePropertyParsedList modulepath] {
      if {$resolv_var} {
         set modpath [resolvStringWithEnv $modpath]
      }
      if {$set_abs} {
         set modpath [getAbsolutePath $modpath]
      }
      appendNoDupToList modpath_list $modpath
   }
   return $modpath_list
}

proc getModulepathLabel {modpath} {
   if {[info exists ::g_modulepathLabel($modpath)]} {
      return $::g_modulepathLabel($modpath)
   } else {
      return $modpath
   }
}

# return list of the configured and existing global RC files
proc getGlobalRcFileList {} {
   set rclist {}

   set rcfilelist [getConfList rcfile]
   if {[llength $rcfilelist]} {
      foreach rcfile $rcfilelist {
         # if rcfile is a dir, look at a modulerc file in it
         if {[file isdirectory $rcfile] && [file isfile $rcfile/modulerc]} {
            lappend rclist $rcfile/modulerc
         } elseif {[file isfile $rcfile]} {
            lappend rclist $rcfile
         }
      }
   }
   if {[file isfile {/etc/environment-modules/rc}]} {
      lappend rclist {/etc/environment-modules/rc}
   }
   # ignore user rc if relative configuration is enabled
   if {![getConf ignore_user_rc] && [isEnvVarDefined HOME] && [file isfile\
      $::env(HOME)/.modulerc]} {
      lappend rclist $::env(HOME)/.modulerc
   }

   set readable_rclist {}
   foreach rc $rclist {
      if {[file readable $rc]} {
         lappend readable_rclist $rc
      }
   }

   return $readable_rclist
}

proc getModuleTag {mod full_path_mod tag} {
   # check if tag is set on full path module designation (no prop there)
   lassign [getFullPathModuleTagSpec $full_path_mod $tag] tag_mod_spec\
      tag_props
   if {[string length $tag_mod_spec]} {
      return [list $tag $tag_props]
   }
   # look if mod matches one of the module specs applying to mod root
   foreach {tmodspec tag_props} [getModuleRootTagSpecList $mod $tag] {
      # first matching module spec wins with its properties
      if {[modEq $tmodspec $mod eqstart 1 0 1]} {
         return [list $tag $tag_props]
      }
   }
   return {}
}

proc getModuleTagRuleList {mod full_path_mod tag} {
   set rule_list {}
   lassign [getFullPathModuleTagSpec $full_path_mod $tag] tag_mod_spec\
      tag_props
   if {[string length $tag_mod_spec]} {
      lappend rule_list $tag_mod_spec
   }
   foreach {tag_mod_spec tag_props} [getModuleRootTagSpecList $mod $tag] {
      if {$tag_mod_spec ni $rule_list && [modEq $tag_mod_spec $mod eqstart 1\
         0 1]} {
         lappend rule_list $tag_mod_spec
      }
   }
   return $rule_list
}

proc getModuleRootTagSpecList {mod tag} {
   set tag_spec_list {}
   set mod_root [getModuleRootFromVersSpec $mod]
   if {[info exists ::g_moduleTagRoot($mod_root)]} {
      set idx [lsearch -exact $::g_moduleTagRoot($mod_root) $tag]
      set tag_spec_list [lindex $::g_moduleTagRootSpec($mod_root) $idx]
   }
   return $tag_spec_list
}

proc getFullPathModuleTagSpec {full_path_mod tag} {
   set tag_spec {}
   if {$full_path_mod ne {} && [info exists\
      ::g_moduleTagFullPath($full_path_mod)]} {
      set idx [lsearch -exact $::g_moduleTagFullPath($full_path_mod) $tag]
      if {$idx != -1} {
         set tag_spec [list $full_path_mod [lindex\
            $::g_moduleTagFullPathSpec($full_path_mod) $idx]]
      }
   }
   return $tag_spec
}

proc isModuleTagged {mod tag {collected 0} {fpmod {}}} {
   # retrieve tag information from collected tags or raw info
   if {$collected} {
      return [expr {$tag in [getTagList $mod $fpmod]}]
   } else {
      return [llength [getModuleTag $mod $fpmod $tag]]
   }
}

proc getTaggedLoadedModuleList {tag} {
   set modlist [list]
   foreach mod [getEnvLoadedModulePropertyParsedList name] {
      if {[isModuleTagged $mod $tag 1]} {
         lappend modlist $mod
      }
   }
   return $modlist
}

proc getModuleTagProp {mod fpmod tag prop} {
   array set tags [getModuleTag $mod $fpmod $tag]

   if {[info exists tags($tag)]} {
      array set props $tags($tag)
      if {[info exists props($prop)]} {
         return $props($prop)
      }
   }
}

# Gather all tags applying to a given module
proc collectModuleTags {mod} {
   if {[info exists ::g_tagHash($mod)]} {
      set known_tag_list $::g_tagHash($mod)
   } else {
      set known_tag_list {}
   }
   set tag_list [getMatchingTagList $mod $known_tag_list]
   if {[llength $tag_list]} {
      setModuleTag $mod {*}$tag_list
   }
}

proc getMatchingTagList {mod {known_tag_list {}}} {
   set tag_list {}
   set modroot [getModuleRootFromVersSpec $mod]
   # look if mod matches one of the module specs applying to mod root
   if {[info exists ::g_moduleTagRoot($modroot)]} {
      for {set i 0} {$i < [llength $::g_moduleTagRoot($modroot)]} {incr i} {
         set tag [lindex $::g_moduleTagRoot($modroot) $i]
         if {$tag ni $tag_list} {
            foreach {tmodspec tprops} [lindex\
               $::g_moduleTagRootSpec($modroot) $i] {
               # add tag if one modspec matches mod
               if {[modEq $tmodspec $mod eqstart 1 0 1]} {
                  lappend known_tag_list $tag
                  lappend tag_list $tag
                  break
               }
            }
         }
      }
   }
   return $tag_list
}

proc setModuleTag {mod args} {
   appendNoDupToList ::g_tagHash($mod) {*}$args
}

proc setModuleAndVariantsTag {mod mod_with_vr args} {
   setModuleTag $mod {*}$args
   if {$mod_with_vr ne {} && $mod ne $mod_with_vr} {
      setModuleTag $mod_with_vr {*}$args
   }
}

proc setModuleExtraTag {mod args} {
   appendNoDupToList ::g_extraTagHash($mod) {*}$args
}

proc setModuleAndVariantsExtraTag {mod mod_with_vr args} {
   setModuleExtraTag $mod {*}$args
   if {$mod_with_vr ne {} && $mod ne $mod_with_vr} {
      setModuleExtraTag $mod_with_vr {*}$args
   }
}

proc unsetModuleTag {mod args} {
   if {[info exists ::g_tagHash($mod)]} {
      lassign [getDiffBetweenList $::g_tagHash($mod) $args] diff_list
      if {[llength $args] && [llength $diff_list]} {
         set ::g_tagHash($mod) $diff_list
      } else {
         unset ::g_tagHash($mod)
      }
   }
}

proc unsetModuleAndVariantsTag {mod mod_with_vr args} {
   unsetModuleTag $mod {*}$args
   if {$mod_with_vr ne {} && $mod ne $mod_with_vr} {
      unsetModuleTag $mod_with_vr {*}$args
   }
}

proc unsetModuleExtraTag {mod args} {
   if {[info exists ::g_extraTagHash($mod)]} {
      lassign [getDiffBetweenList $::g_extraTagHash($mod) $args] diff_list
      if {[llength $args] && [llength $diff_list]} {
         set ::g_extraTagHash($mod) $diff_list
      } else {
         unset ::g_extraTagHash($mod)
      }
   }
}

proc unsetModuleAndVariantsExtraTag {mod mod_with_vr args} {
   unsetModuleExtraTag $mod {*}$args
   if {$mod_with_vr ne {} && $mod ne $mod_with_vr} {
      unsetModuleExtraTag $mod_with_vr {*}$args
   }
}

proc getTagList {mod {fpmod {}} {sort 1}} {
   set tag_list {}
   # get tags applied over module full path designation
   if {$fpmod ne {} && [info exists ::g_moduleTagFullPath($fpmod)]} {
      appendNoDupToList tag_list {*}$::g_moduleTagFullPath($fpmod)
   }
   # recompute tags applying from module specification if mod is loaded but
   # not the same modulefile. tags applying to loaded mod are recorded in
   # g_tagHash but should not apply to same mod from a different modulepath
   # do not make this distinction if modulefile is not provided
   # directly check on g_loadedModules array to avoid triggering env check
   if {[string length $fpmod] && [info exists ::g_loadedModules($mod)] &&\
      $fpmod ne [getModulefileFromLoadedModule $mod]} {
      appendNoDupToList tag_list {*}[getMatchingTagList $mod]
   # get collected tags applied on short designation
   } elseif {[info exists ::g_tagHash($mod)]} {
      appendNoDupToList tag_list {*}$::g_tagHash($mod)
   }
   if {[llength $tag_list]} {
      if {$sort} {
         set tag_list [lsort -dictionary $tag_list]
      }
      reportDebug "'$mod' has tag list '$tag_list'"
   }
   return $tag_list
}

# return tags applying to mod that can be exported
proc getExportTagList {mod {fpmod {}}} {
   # remove loaded/hidden tags from the list to register
   lassign [getDiffBetweenList [getTagList $mod $fpmod 0] [list loaded\
      hidden]] ret
   return $ret
}

# return extra tags set on mod
proc getExtraTagList {mod} {
   if {[info exists ::g_extraTagHash($mod)]} {
      return $::g_extraTagHash($mod)
   }
}

# return rule definition of sticky/super-sticky tag on given module
proc getStickyRuleList {mod {fpmod {}}} {
   set ret {}
   foreach tag {sticky super-sticky} {
      if {[isModuleTagged $mod $tag 1]} {
         set export_sticky_rule 1
         set tag_rule_list [getModuleTagRuleList $mod $fpmod $tag]

         # skip rule export if one matches module name and version or full
         # path designation
         foreach tag_rule $tag_rule_list {
            if {[modEq $tag_rule $mod equal 1 0 1] || [modEq $tag_rule $fpmod\
               equal 1 0 1]} {
               set export_sticky_rule 0
               break
            }
         }

         if {[llength $tag_rule_list] && $export_sticky_rule} {
            lappend ret [list $tag {*}$tag_rule_list]
         }
      }
   }
   return $ret
}

# return tags set on mod to record in collection
proc getSaveTagList {mod} {
   # return all tags if pin tag is enabled, except nearly-forbidden that is
   # not intended for save
   if {[getConf collection_pin_tag]} {
      lassign [getDiffBetweenList [getExportTagList $mod] [list\
         nearly-forbidden]] ret
   } else {
      set ret [getExtraTagList $mod]
      # in addition to those set with --tag option, add tags obtained by the
      # way mod has been loaded
      if {[isModuleTagged $mod auto-loaded 1]} {
         lappend ret auto-loaded
      }
      if {[isModuleTagged $mod keep-loaded 1]} {
         lappend ret keep-loaded
      }
   }
   return $ret
}

proc abbrevTagList {taglist} {
   set ret [list]
   foreach tag $taglist {
      if {[info exists ::g_tagAbbrev($tag)]} {
         # empty string abbrev means no tag report
         if {$::g_tagAbbrev($tag) ne {}} {
            lappend ret $::g_tagAbbrev($tag)
         }
      } else {
         lappend ret $tag
      }
   }
   return [lsort -dictionary $ret]
}

proc getTagFromAbbrev {abbrev} {
   if {![array exists ::g_abbrevTag]} {
      getConf tag_abbrev
   }
   if {[info exists ::g_abbrevTag($abbrev)]} {
      return $::g_abbrevTag($abbrev)
   }
}

proc getVariantList {mod {report 0} {excl_dflval 0} {from 0}} {
   set vrspeclist {}
   set loadedsgrkey {}
   # fetch variant information from
   switch -- $from {
      0 {
         # variant set after module being loaded
         if {[info exists ::g_loadedModuleVariant($mod)]} {
            set vrspeclist $::g_loadedModuleVariant($mod)
         }
      }
      1 {
         # module specification
         set vrspeclist {}
         foreach vrspec [getVariantListFromVersSpec $mod] {
            lassign $vrspec vrname vrnot vrvalisbool vrvalue
            lappend vrspeclist [list $vrname $vrvalue $vrvalisbool]
         }
      }
      2 {
         # variant definition collected during scan evaluation
         if {[info exists ::g_scanModuleVariant($mod)]} {
            set vrspeclist $::g_scanModuleVariant($mod)
         }
         # if module is currently loaded, gather its variant values
         if {[info exists ::g_loadedModuleVariant($mod)]} {
            foreach lovrspec $::g_loadedModuleVariant($mod) {
               lassign $lovrspec lovrname lovrvalue lovrvalisbool lovrvalisdfl
               if {$lovrvalisbool} {
                  set lovrvalue [expr {$lovrvalue ? {on} : {off}}]
               }
               set lovrarr($lovrname) $lovrvalue
            }
            # get sgr key corresponding to loaded tag (if tag reported)
            if {$report == 7 && [isEltInReport tag]} {
               set loadedkind [expr {[isModuleTagged $mod auto-loaded 1] ?\
                  {auto-loaded} : {loaded}}]
               lassign [abbrevTagList [list $loadedkind]] loadedsgrkey
            }
         }
      }
   }

   # use array to make variant unique
   foreach vrspec $vrspeclist {
      if {$from == 2} {
         # report all available variant values on avail
         lassign $vrspec vrname vrvalues vrdefdflval vrdflval
         set vrvalisbool 0
         set vrvalisdfl 0
         # build value list to output for free-variant value
         if {![llength $vrvalues]} {
            # if module is loaded, add loaded variant value
            if {[info exists lovrarr($vrname)]} {
               lappend vrvalues $lovrarr($vrname)
            }
            # if a default value is defined, add it to possible value list
            if {$vrdefdflval && $vrdflval ni $vrvalues} {
               lappend vrvalues $vrdflval
            }
            # indicate on free-value variant that all values are possible
            lappend vrvalues *
         }
      } else {
         lassign $vrspec vrname vrvalue vrvalisbool vrvalisdfl
         set vrvalues [list $vrvalue]
      }
      # correct is-default value if invalid
      if {![string is integer -strict $vrvalisdfl]} {
         set vrvalisdfl 0
      }
      # correct is-boolean value if invalid
      if {![string is boolean -strict $vrvalisbool]} {
         set vrvalisbool 0
      }
      set vrisbool($vrname) $vrvalisbool
      # correct boolean value if invalid
      if {$vrvalisbool && ![string is boolean -strict $vrvalue]} {
         set vrvalue 0
      }
      # do not return variant if value is default, unless if variant was
      # specifically set to default value (isdfl=1) and we only want to
      # exclude the automatically set default value (excl_dflval=2)
      if {$excl_dflval > 0} {
         if {$vrvalisdfl == 0 || ($excl_dflval == 2 && $vrvalisdfl == 1)} {
            set vrarr($vrname) $vrvalue
         # nullify previous duplicate definition if this one is default
         } elseif {[info exists vrarr($vrname)]} {
            unset vrarr($vrname)
         }
      } else {
         if {$report == 7} {
            # indicate what value in the list is the default one
            if {[info exists vrdefdflval]} {
               set vrdflidxarr($vrname) [expr {$vrdefdflval ? [lsearch -exact\
                  $vrvalues $vrdflval] : {-1}}]
            } else {
               set vrdflidxarr($vrname) [expr {$vrvalisdfl ? {0} : {-1}}]
            }
            # indicate what value in the list is the loaded one
            set vrloidxarr($vrname) [expr {[info exists lovrarr($vrname)] ?\
               [lsearch -exact $vrvalues $lovrarr($vrname)] : {-1}}]
            if {$vrvalisbool} {
               set vrarr($vrname) $vrvalue
            } else {
               set vrarr($vrname) $vrvalues
            }
         # is-default hint has to be transmitted on report mode '3'
         } elseif {$report == 3} {
            set vrarr($vrname) [list $vrvalue [expr {$vrvalisdfl > 0 &&\
               $vrvalisdfl < 3}]]
         } else {
            set vrarr($vrname) $vrvalue
         }
      }
   }
   # sort variant to report
   set ret {}
   foreach vrname [lsort -dictionary [array names vrarr]] {
      switch -- $report {
         4 {lappend ret $vrname $vrarr($vrname) $vrisbool($vrname)}
         3 {lappend ret $vrname [lindex $vrarr($vrname) 0] [lindex\
            $vrarr($vrname) 1]}
         2 {lappend ret [list $vrname 0 $vrisbool($vrname) $vrarr($vrname)]}
         1 - 5 - 6 {
            if {$vrisbool($vrname)} {
               if {$vrarr($vrname)} {
                  lappend ret +$vrname
                  # track if +var variant has been reported to build key
                  # unless if key should not be updated (report mode '6')
                  if {$report != 6 && ![info exists ::g_used_va(on)]} {
                     set ::g_used_va(on) 1
                  }
               } else {
                  lappend ret -$vrname
                  # track if -var variant has been reported to build key
                  # unless if key should not be updated (report mode '6')
                  if {$report != 6 && ![info exists ::g_used_va(off)]} {
                     set ::g_used_va(off) 1
                  }
               }
            # use defined shortcut to report if any set for this variant
            # ignore shortcut if report=5 (when saving collection)
            } elseif {$report != 5 && [info exists\
               ::g_variantShortcut($vrname)]} {
               set sc $::g_variantShortcut($vrname)
               lappend ret $sc$vrarr($vrname)
               # track if variant shortcut has been reported to build key
               # unless if key should not be updated (report mode '6')
               if {$report != 6 && ![info exists ::g_used_va($sc)]} {
                  set ::g_used_va($sc) $vrname
               }
            } else {
               lappend ret $vrname=$vrarr($vrname)
               # track if var=val variant has been reported to build key
               # unless if key should not be updated (report mode '6')
               if {$report != 6 && ![info exists ::g_used_va(val)]} {
                  set ::g_used_va(val) 1
               }
            }
         }
         7 {
            if {$vrisbool($vrname)} {
               if {$vrarr($vrname)} {
                  lappend ret [list $vrname {} [list +$vrname]\
                     $vrdflidxarr($vrname) $vrloidxarr($vrname) $loadedsgrkey]
                  # track if +var variant has been reported to build key
                  if {![info exists ::g_used_va(on)]} {
                     set ::g_used_va(on) 1
                  }
               } else {
                  lappend ret [list $vrname {} [list -$vrname]\
                     $vrdflidxarr($vrname) $vrloidxarr($vrname) $loadedsgrkey]
                  # track if -var variant has been reported to build key
                  if {![info exists ::g_used_va(off)]} {
                     set ::g_used_va(off) 1
                  }
               }
            # use defined shortcut to report if any set for this variant
            # ignore shortcut if report=5 (when saving collection)
            } elseif {$report != 5 && [info exists\
               ::g_variantShortcut($vrname)]} {
               set sc $::g_variantShortcut($vrname)
               lappend ret [list $vrname $sc $vrarr($vrname)\
                  $vrdflidxarr($vrname) $vrloidxarr($vrname) $loadedsgrkey]
               # track if variant shortcut has been reported to build key
               # unless if key should not be updated (report mode '6')
               if {$report != 6 && ![info exists ::g_used_va($sc)]} {
                  set ::g_used_va($sc) $vrname
               }
            } else {
               lappend ret [list $vrname $vrname= $vrarr($vrname)\
                  $vrdflidxarr($vrname) $vrloidxarr($vrname) $loadedsgrkey]
               # track if var=val variant has been reported to build key
               # unless if key should not be updated (report mode '6')
               if {$report != 6 && ![info exists ::g_used_va(val)]} {
                  set ::g_used_va(val) 1
               }
            }
         }
         0 {lappend ret $vrname $vrarr($vrname)}
      }
   }

   return $ret
}

proc isOtherVariantOfModuleLoading {mod} {
   set mod_name [getModuleNameAndVersFromVersSpec $mod]
   set mod_has_variant [expr {$mod ne $mod_name}]

   return [expr {$mod_has_variant && [isModuleLoading $mod_name] && ![string\
      length [getLoadedMatchingName $mod {} 1]]}]
}

proc isOtherVariantOfModuleLoaded {mod} {
   set mod_name [getModuleNameAndVersFromVersSpec $mod]
   set mod_has_variant [expr {$mod ne $mod_name}]

   return [expr {$mod_has_variant && [isModuleLoaded $mod_name] && ![string\
      length [getLoadedMatchingName $mod]]}]
}

proc isModuleDotHidden {mod} {
   foreach elt [split $mod /] {
      if {[string index $elt 0] eq {.}} {
         return 1
      }
   }
   return 0
}

proc getModuleHidingLevel {mod fpmod} {
   # if full path module has been hidden, this hidden level is the base level
   # value we will return
   set baselvl [expr {$fpmod ne {} && [info exists\
      ::g_moduleHideFullPath($fpmod)] ? $::g_moduleHideFullPath($fpmod) :\
      {-1}}]

   set modroot [lindex [file split $mod] 0]
   # look if mod matches one of the hidden module specs applying to mod root
   if {[info exists ::g_moduleHideRoot($modroot)]} {
      for {set lvl 2} {$lvl > -1} {incr lvl -1} {
         # no need to search anymore if we have reached same hiding level as
         # one defined on full path module designation
         if {$lvl == $baselvl} {
            break
         }
         foreach hmodspec [lindex $::g_moduleHideRoot($modroot) $lvl] {
            if {[modEq $hmodspec $mod eqstart 1 0 1 {}]} {
               return $lvl
            }
         }
      }
   }
   return $baselvl
}

proc setModspecHidingLevel {modspec lvl} {
   # skip record if an higher hiding level is already set
   if {![info exists ::g_moduleHide($modspec)] || $lvl >\
      $::g_moduleHide($modspec)} {
      reportDebug "Record hidden module specification '$modspec' (lvl=$lvl)"
      set ::g_moduleHide($modspec) $lvl

      if {[isModuleFullPath $modspec]} {
         set ::g_moduleHideFullPath($modspec) $lvl
      } else {
         # record hidden mod spec for mod root to optimize search
         set modroot [getModuleRootFromVersSpec $modspec]
         if {![info exists ::g_moduleHideRoot($modroot)]} {
            set ::g_moduleHideRoot($modroot) [list [list] [list] [list]]
         }
         # record in list corresponding to hidden level to search from
         # strongest to weakest hidden level
         set hiderootlvllist [lindex $::g_moduleHideRoot($modroot) $lvl]
         lappend hiderootlvllist $modspec
         lset ::g_moduleHideRoot($modroot) $lvl $hiderootlvllist
      }
   }
}

# test if mod is declared hidden or has one element in its name starting with
# dot character. mod is considered hidden depending on their hiding level,
# current search query and hiding threshold. when retdetails option is
# enabled, mod hiding level and query match hind are also returned
# also checks full path module designation if fpmod is set
proc isModuleHidden {mod {modspec {}} {retdetails 0} {fpmod {}}} {
   set defhidlvl [set hidlvl [getModuleHidingLevel $mod $fpmod]]
   if {$hidlvl >= [getState hiding_threshold]} {
      # soft hidden mods are considered matched if their root name matches
      # search query, other kind of hidden mods must fully matches query
      set hidmatch [expr {$hidlvl == 0 ? [modStartNb $mod $modspec] > 0 :\
         [modEq $modspec $mod eqspec]}]
   } else {
      set hidlvl -1
      set hidmatch 0
   }
   if {$hidlvl < 1 && [set isdot [isModuleDotHidden $mod]] && [getState\
      hiding_threshold] < 1} {
      set hidlvl 1
      # dot hidden are considered matched if remaining string part after
      # search query is not dot hidden
      set hidmatch [expr {[set i [modStartNb $mod $modspec]] > 0 &&\
         ![isModuleDotHidden [join [lrange [file split $mod] $i end] /]]}]
   }
   # hidden if hiding level greater or equal hiding threshold and not
   # matched or if matched hard hiding level are kept hidden
   set ishid [expr {$hidlvl != -1 && (!$hidmatch || $hidlvl > 1)}]

   # is module hidden by definition (whatever hiding threshold or match)
   set hidbydef [expr {$defhidlvl > 0 || ([info exists isdot] && $isdot)}]

   return [expr {$retdetails ? [list $hidlvl $hidmatch $hidbydef $ishid]\
      : $ishid}]
}

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

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

# 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 {}} {report_issue 1} {look_loaded no}\
   {excdir {}}} {
   reportDebug "finding '$mod' in '$indir' (report_issue=$report_issue,\
      look_loaded=$look_loaded, excdir='$excdir')"

   set vrlist [getVariantList $mod 1 0 1]
   if {$mod eq {}} {
      set retlist [list {} {} {} none [getEmptyNameMsg module]]
   # try first to look at loaded modules if enabled to find matching module
   # or to find a closest match (used when switching with single name arg)
   } elseif {$look_loaded ne {no}} {
      switch -- $look_loaded {
         exact {set getLoadedNameProc getLoadedExactName}
         match {set getLoadedNameProc getLoadedEqstartName}
         close {set getLoadedNameProc getLoadedWithClosestName}
      }
      if {[set lm [$getLoadedNameProc $mod $indir]] ne {}} {
         set vrlist [getVariantList $lm 1]
         set retlist [list [getModulefileFromLoadedModule $lm] $lm]
      } else {
         set retlist [list {} [getModuleNameAndVersFromVersSpec $mod]\
            $mod notloaded]
      }
   # 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 $mod $check_valid $check_msg $mod]
         }
      }
   } else {
      set dir_list [expr {$indir ne {} ? $indir : [getModulePathList\
         exiterronundef]}]
      # remove excluded directories (already searched)
      foreach dir $excdir {
         set dir_list [replaceFromList $dir_list $dir]
      }

      set icase [isIcase]
      defineGetEqArrayKeyProc $icase [getConf extended_default] [getConf\
         implicit_default]

      # Now search for $mod in module paths
      set modspec $mod
      foreach dir $dir_list {
         # get list of modules corresponding to searched query
         array unset mod_list
         array set mod_list [getModules $dir $mod 0 [list rc_defs_included\
            resolve]]

         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} {
            ##nagelfar ignore Found constant
            set mod [getEqArrayKey mod_list $mod]
            set prevmod $mod
            if {[info exists mod_list($mod)]} {
               switch -- [lindex $mod_list($mod) 0] {
                  alias - version {
                     set newmod [resolveModuleVersionOrAlias $mod $icase]
                     if {[info exists mod_list($newmod)]} {
                        set mod $newmod
                     } else {
                        # add specified variants to current resolution
                        set newmod [getAndParseModuleWithVariant $newmod\
                           $vrlist]
                        # restart search on new modulename, constrained to
                        # specified dir if set, if not found in current res
                        return [getPathToModule $newmod $indir $report_issue]
                     }
                  }
                  directory {
                     # is implicit default disabled and none explicitly set?
                     if {[lindex $mod_list($mod) 1] eq {}} {
                        set retlist [list {} $mod $mod none "No default\
                           version defined for '$mod'"]
                     } else {
                        # Move to default element in directory
                        set mod $mod/[lindex $mod_list($mod) 1]
                        # restart search if default element is an hidden dir
                        if {![info exists mod_list($mod)] && [isModuleHidden\
                           $mod $modspec 0 $dir/$mod]} {
                           # add specified variants to current resolution
                           set mod [getAndParseModuleWithVariant $mod $vrlist]
                           return [getPathToModule $mod $indir $report_issue]
                        }
                     }
                  }
                  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 [list {} $mod $mod {*}$mod_list($mod)]
                  }
               }
            }
         }
         # break loop if found something (valid or invalid module)
         # elsewhere go to next path
         if {[info exists retlist]} {
            break
         }
      }
   }

   # set result if nothing found
   if {![info exists retlist]} {
      set retlist [list {} $mod $mod none "Unable to locate a modulefile for\
         '$mod'"]
   } else {
      # build module name and variant with variant specified
      set modnamevr [getAndParseModuleWithVariant [lindex $retlist 1] $vrlist]
      # update result if forbidden
      if {[isModuleTagged $modnamevr forbidden 0 [lindex $retlist 0]]} {
         set retlist [list {} [lindex $retlist 1] [lindex $retlist 2]\
            accesserr [getForbiddenMsg $modnamevr [lindex $retlist 0]]]
      }
   }
   if {[lindex $retlist 0] ne {}} {
      lappend retlist $modnamevr
      reportTrace "'[lindex $retlist 1]' ([lindex $retlist 0]) matching\
         '$mod'" {Select module}
   # no error if we look at loaded modules and passed mod not found loaded
   } elseif {[lindex $retlist 3] ne {notloaded} && $report_issue} {
      reportIssue {*}[lrange $retlist 3 5]
   }
   return $retlist
}

proc isModuleLoaded {mod} {
   cacheCurrentModules

   return [info exists ::g_loadedModules($mod)]
}

proc getModulefileFromLoadedModule {mod} {
   if {[isModuleLoaded $mod]} {
      return $::g_loadedModules($mod)
   }
}

proc getModulepathFromLoadedOrLoadingModule {mod} {
   set modfile [getModulefileFromLoadingModule $mod]
   if {![string length $modfile]} {
      set modfile [getModulefileFromLoadedModule $mod]
   }
   if {[isModuleVirtual $mod $modfile]} {
      # an empty string is returned if no enabled modulepath matches modfile
      return [findModulepathFromModulefile $modfile]
   } else {
      return [getModulepathFromModuleName $modfile $mod]
   }
}

proc isModulefileLoaded {modfile} {
   cacheCurrentModules

   return [info exists ::g_loadedModuleFiles($modfile)]
}

proc getModuleFromLoadedModulefile {modfile {idx all}} {
   if {[isModulefileLoaded $modfile]} {
      if {$idx eq {all}} {
         return $::g_loadedModuleFiles($modfile)
      } else {
         return [lindex $::g_loadedModuleFiles($modfile) $idx]
      }
   }
}

proc isModuleLoading {mod} {
   return [expr {$mod in [getLoadingModuleList]}]
}

proc isModulefileLoading {modfile} {
   return [expr {$modfile in [getLoadingModuleFileList]}]
}

proc getModulefileFromLoadingModule {mod} {
   if {[isModuleLoading $mod]} {
      set idx [lsearch -exact [getLoadingModuleList] $mod]
      return [lindex [getLoadingModuleFileList] $idx]
   }
}

proc getModuleFromLoadingModulefile {modfile {idx all}} {
   if {[isModulefileLoading $modfile]} {
      set loadingmodlist [getLoadingModuleList]
      foreach i [lsearch -all -exact [getLoadingModuleFileList] $modfile] {
         lappend modlist [lindex $loadingmodlist $i]
      }

      if {$idx eq {all}} {
         return $modlist
      } else {
         return [lindex $modlist $idx]
      }
   }
}

proc isModuleRefreshQualified {mod} {
   return $::g_loadedModulesRefresh($mod)
}

proc setLoadedModule {mod modfile uasked modvr refresh} {
   set ::g_loadedModules($mod) $modfile
   # a loaded modfile may correspond to multiple loaded virtual modules
   lappend ::g_loadedModuleFiles($modfile) $mod

   # record if mod has been asked by user and relative loaded/auto-loaded tag
   set loadedtag [expr {$uasked ? {loaded} : {auto-loaded}}]
   setModuleAndVariantsTag $mod $modvr $loadedtag

   # is module qualified for refresh evaluation
   set ::g_loadedModulesRefresh($mod) $refresh

   # build dependency chain
   setModuleDependency $mod
}

proc unsetLoadedModule {mod modfile} {
   unset ::g_loadedModules($mod)
   # a loaded modfile may correspond to multiple loaded virtual modules
   if {[llength $::g_loadedModuleFiles($modfile)] == 1} {
      unset ::g_loadedModuleFiles($modfile)
   } else {
      set ::g_loadedModuleFiles($modfile) [replaceFromList\
         $::g_loadedModuleFiles($modfile) $mod]
   }

   unset ::g_loadedModulesRefresh($mod)

   # update dependencies
   unsetModuleDependency $mod
}

# 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 modulepath_list} {
   set ret {}
   set retmax 1

   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 nametosplit $shortname
      # or look at lmfile names to return the eventual exact match
      } else {
         # module may be loaded with its full path name
         if {[isModuleLoaded $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 {[isModulefileLoaded $fullname]} {
            set ret [getModuleFromLoadedModulefile $fullname end]
         }
      }
   } else {
      set nametosplit $name
   }

   if {[info exists nametosplit]} {
      cacheCurrentModules
      set icase [isIcase]
      defineModStartNbProc $icase
      defineModEqProc $icase [getConf extended_default]
      # compare name to each currently loaded module name
      ##nagelfar ignore Found constant
      foreach mod [getEnvLoadedModulePropertyParsedList name] {
         # 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 {}} {
            # no alt name to retrieve if module has been loaded full path
            set matchmodlist [list $modname]
         } else {
            # add alternative names of mod to the matching list
            set matchmodlist [list $mod {*}[getLoadedAltname $mod]]
         }

         # 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
         foreach matchmod $matchmodlist {
            if {[set i [modStartNb $matchmod $nametosplit]] >= $retmax} {
               set retmax $i
               set ret $mod
               break
            }
         }
      }
   }

   reportDebug "'$ret' closest to '$name'"
   return $ret
}

proc getLoadedExactName {name modulepath_list} {
   return [getLoadedMatchingName $name {} 0 {} $modulepath_list eqspec]
}

proc getLoadedEqstartName {name modulepath_list} {
   return [getLoadedMatchingName $name {} 0 {} $modulepath_list]
}

# 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.
# loading: look at currently loading modules instead of loaded if loading == 1
# lmlist: only take into account passed loaded module list not all loaded mods
proc getLoadedMatchingName {name {behavior {}} {loading 0} {lmlist {}}\
   {modulepath_list {}} {eqtest eqstart}} {
   set ret {}
   set retmax 0
   # get default behavior from unload_match_order config
   if {$behavior eq {}} {
      set behavior [getConf unload_match_order]
   }

   # use loading-specific procedures instead of loaded-specific ones
   if {$loading} {
      set isModulefileLoaded isModulefileLoading
      set getModuleFromLoadedModulefile getModuleFromLoadingModulefile
      set isLoadedMatchSpecificPath isLoadingMatchSpecificPath
      set getLoadedModuleList [list getLoadingModuleList]
   } else {
      ##nagelfar ignore #4 Found constant
      set isModulefileLoaded isModulefileLoaded
      set getModuleFromLoadedModulefile getModuleFromLoadedModulefile
      set isLoadedMatchSpecificPath isLoadedMatchSpecificPath
      set getLoadedModuleList [list getEnvLoadedModulePropertyParsedList name]
   }

   # fetch currently loaded/loading module name is no list provided
   if {![llength $lmlist]} {
      set lmlist [{*}$getLoadedModuleList]
   }

   # if module is passed as full modulefile path name, look at lmfile names
   # to return the eventual exact match
   if {[isModuleFullPath $name]} {
      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 {[$isModulefileLoaded $mod]} {
         # a loaded modfile may correspond to multiple loaded virtual modules
         switch -- $behavior {
            returnlast {
               # the last loaded/loading module will be returned
               set ret [$getModuleFromLoadedModulefile $mod end]
            }
            returnfirst {
               # the first loaded/loading module will be returned
               set ret [$getModuleFromLoadedModulefile $mod 0]
            }
            returnall {
               # all loaded/loading modules will be returned
               set ret [$getModuleFromLoadedModulefile $mod]
            }
         }
      }
   } elseif {$name ne {}} {
      defineModEqProc [isIcase] [getConf extended_default] [expr {!$loading}]
      # compare name to each currently loaded/loading module name, if multiple
      # mod match name:
      foreach mod $lmlist {
         # 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
         }
         # test module matches specified modulepaths
         if {![$isLoadedMatchSpecificPath $mod $modulepath_list]} {
            continue
         }
         if {[modEq $name $matchmod $eqtest 1 [expr {$loading ? 1 : 2}] 1]} {
            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 "'$ret' matches '$name'"
   return $ret
}

# return if loaded mod is part of modulepath from specified constrained list
proc isLoadedMatchSpecificPath {mod modulepath_list} {
   set mod_file [getModulefileFromLoadedModule $mod]
   return [isModulefileMatchSpecificPath $mod_file $modulepath_list]
}

proc isLoadingMatchSpecificPath {mod modulepath_list} {
   set mod_file [getModulefileFromLoadingModule $mod]
   return [isModulefileMatchSpecificPath $mod_file $modulepath_list]
}

proc isModulefileMatchSpecificPath {mod_file modulepath_list} {
   if {[llength $modulepath_list]} {
      return [isModulefileInModulepathList $mod_file $modulepath_list]
   } else {
      return 1
   }
}

proc setLoadedSourceSh {mod args} {
   foreach arg $args {
      # each arg is a list with source-sh call string at index 0 and resulting
      # modulefile commands at all later index positions
      set shtomodargs [lindex $arg 0]
      set modcontent [lrange $arg 1 end]
      if {![llength [getLoadedSourceShScriptContent $mod $shtomodargs]]} {
         # filter alias/function/complete definition not to record them
         set shtomod_entry [list $shtomodargs]
         foreach modcmdlist $modcontent {
            set modcmd [lindex $modcmdlist 0]
            switch -- $modcmd {
               set-alias - set-function {
                  # set an empty body to make valid unset-* call
                  lappend shtomod_entry [list {*}[lrange $modcmdlist 0 1] {}]
               }
               complete {
                  set complist [lrange $modcmdlist 0 2]
                  # ensure only one order is recorded for each shell-name
                  if {![info exists filtcomp($complist)]} {
                     lappend shtomod_entry [list {*}$complist {}]
                     set filtcomp($complist) 1
                  }
               }
               default {
                  lappend shtomod_entry $modcmdlist
               }
            }
         }
         lappend ::g_loadedModuleSourceSh($mod) $shtomod_entry
      }
   }
}

proc unsetLoadedSourceSh {mod} {
   if {[info exists ::g_loadedModuleSourceSh($mod)]} {
      unset ::g_loadedModuleSourceSh($mod)
   }
}

proc getLoadedSourceSh {mod} {
   if {[info exists ::g_loadedModuleSourceSh($mod)]} {
      return $::g_loadedModuleSourceSh($mod)
   }
}

proc getLoadedSourceShScriptContent {mod script} {
   set content {}
   foreach shtomod_entry [getLoadedSourceSh $mod] {
      if {$script eq [lindex $shtomod_entry 0]} {
         set content [lrange $shtomod_entry 1 end]
         break
      }
   }
   return $content
}

proc setLoadedConflict {mod args} {
   appendNoDupToList ::g_loadedModuleConflict($mod) {*}$args
}

proc unsetLoadedConflict {mod} {
   if {[info exists ::g_loadedModuleConflict($mod)]} {
      unset ::g_loadedModuleConflict($mod)
   }
}

proc getLoadedConflict {mod} {
   if {[info exists ::g_loadedModuleConflict($mod)]} {
      return $::g_loadedModuleConflict($mod)
   }
}

proc getModuleLoadedConflict {mod {modulepath_list {}}} {
   set mod_con_list {}
   defineModEqProc [isIcase] [getConf extended_default] 1
   # get module short name if loaded by its full pathname
   if {[set isfullpath [isModuleFullPath $mod]]} {
      set smod [findModuleNameFromModulefile $mod]
   }

   # check if any loaded module has declared a conflict
   foreach mod_con [array names ::g_loadedModuleConflict] {
      # look if some loaded or loading modules correspond to conflict defined
      # by mod
      if {$mod_con eq $mod || ($isfullpath && $mod_con eq $smod)} {
         foreach withmod $::g_loadedModuleConflict($mod_con) {
            # skip own reflexive conflict (look at mod main and alternative
            # names) and those already known
            if {![modEq $withmod $mod eqstart 1 2 1] && (!$isfullpath ||\
               ![modEq $withmod $smod eqstart 1 2 1]) && ([set lm_mod_list\
               [getLoadedMatchingName $withmod returnall]] ne {} || [set\
               lm_mod_list [getLoadedMatchingName $withmod returnall 1]] ne\
               {})} {
               # multiple loaded module may match conflict declared name
               appendNoDupToList mod_con_list {*}$lm_mod_list
            }
         }
      # other loaded module declared conflicts (skipping those already known)
      } elseif {$mod_con ni $mod_con_list} {
         foreach withmod $::g_loadedModuleConflict($mod_con) {
            # check if mod or one of its alt name match conflict
            if {[modEq $withmod $mod eqstart 1 2 1] || ($isfullpath &&\
               [modEq $withmod $smod eqstart 1 2 1])} {
               lappend mod_con_list $mod_con
               break
            }
         }
      }
   }

   if {[isOtherVariantOfModuleLoaded $mod] || ([isModuleLoaded $mod] &&\
      ![isLoadedMatchSpecificPath $mod $modulepath_list])} {
      lappend mod_con_list [getModuleNameAndVersFromVersSpec $mod]
   }

   reportDebug "'$mod' conflicts with '$mod_con_list'"
   return $mod_con_list
}

proc setLoadedPrereq {mod args} {
   appendNoDupToList ::g_loadedModulePrereq($mod) {*}$args
}

proc unsetLoadedPrereq {mod} {
   if {[info exists ::g_loadedModulePrereq($mod)]} {
      unset ::g_loadedModulePrereq($mod)
   }
}

proc getLoadedPrereq {mod} {
   if {[info exists ::g_loadedModulePrereq($mod)]} {
      return $::g_loadedModulePrereq($mod)
   }
}

proc setLoadedPrereqPath {mod args} {
   appendNoDupToList ::g_loadedModulePrereqPath($mod) {*}$args
}

proc unsetLoadedPrereqPath {mod} {
   if {[info exists ::g_loadedModulePrereqPath($mod)]} {
      unset ::g_loadedModulePrereqPath($mod)
   }
}

proc getLoadedPrereqPath {mod {prereq {}}} {
   if {[info exists ::g_loadedModulePrereqPath($mod)]} {
      if {![string length $prereq]} {
         return $::g_loadedModulePrereqPath($mod)
      }
      # return specific entry for given prereq
      array set prereq_path_arr $::g_loadedModulePrereqPath($mod)
      if {[info exists prereq_path_arr($prereq)]} {
         return $prereq_path_arr($prereq)
      }
   }
}

proc setLoadedAltname {mod args} {
   foreach arg $args {
      switch -- [lindex $arg 0] {
         al {
            appendNoDupToList ::g_loadedModuleAliasAltname($mod) [lindex $arg\
               1]
         }
         as {
            appendNoDupToList ::g_loadedModuleAutoAltname($mod) [lindex $arg\
               1]
         }
         default {
            appendNoDupToList ::g_loadedModuleAltname($mod) [lindex $arg 0]
         }
      }
   }
}

proc unsetLoadedAltname {mod} {
   if {[info exists ::g_loadedModuleAltname($mod)]} {
      unset ::g_loadedModuleAltname($mod)
   }
   if {[info exists ::g_loadedModuleAliasAltname($mod)]} {
      unset ::g_loadedModuleAliasAltname($mod)
   }
   if {[info exists ::g_loadedModuleAutoAltname($mod)]} {
      unset ::g_loadedModuleAutoAltname($mod)
   }
}

proc getLoadedAltname {mod {typelist {sym alias autosym}} {with_type 0}} {
   set ret {}
   if {[info exists ::g_loadedModuleAltname($mod)] && {sym} in $typelist} {
      if {$with_type} {
         foreach altname $::g_loadedModuleAltname($mod) {
            lappend ret [list $altname]
         }
      } else {
         set ret $::g_loadedModuleAltname($mod)
      }
   }
   if {[info exists ::g_loadedModuleAliasAltname($mod)] && {alias} in\
      $typelist} {
      if {$with_type} {
         foreach altname $::g_loadedModuleAliasAltname($mod) {
            lappend ret [list al $altname]
         }
      } else {
         lappend ret {*}$::g_loadedModuleAliasAltname($mod)
      }
   }
   if {[info exists ::g_loadedModuleAutoAltname($mod)] && {autosym} in\
      $typelist} {
      if {$with_type} {
         foreach altname $::g_loadedModuleAutoAltname($mod) {
            lappend ret [list as $altname]
         }
      } else {
         lappend ret {*}$::g_loadedModuleAutoAltname($mod)
      }
   }
   return $ret
}

proc setLoadedVariant {mod args} {
   lappend ::g_loadedModuleVariant($mod) {*}$args
}

proc unsetLoadedVariant {mod} {
   if {[info exists ::g_loadedModuleVariant($mod)]} {
      unset ::g_loadedModuleVariant($mod)
   }
}

proc getLoadedVariant {mod} {
   if {[info exists ::g_loadedModuleVariant($mod)]} {
      return $::g_loadedModuleVariant($mod)
   }
}

proc getLoadedModuleWithVariantList {} {
   set modvrlist [list]
   foreach mod [getEnvLoadedModulePropertyParsedList name] {
      set modvr [list $mod]
      set vrlist [getVariantList $mod 5]
      if {[llength $vrlist]} {
         lappend modvr {*}$vrlist
      }
      lappend modvrlist $modvr
   }
   return $modvrlist
}

proc getLoadedModuleWithVariantSaveTagArrayList {} {
   array set tag_arr {}
   foreach mod [getEnvLoadedModulePropertyParsedList name] {
      set modvr [list $mod]
      set vrlist [getVariantList $mod 1]
      if {[llength $vrlist]} {
         lappend modvr {*}$vrlist
      }
      # create entry in array only if tags set
      set tag_list [getSaveTagList $mod]
      if {[llength $tag_list]} {
         set tag_arr($modvr) $tag_list
      }
   }
   return [array get tag_arr]
}

proc getAndParseLoadedModuleWithVariant {mod} {
   set vr_list [getVariantList $mod 1]
   return [getAndParseModuleWithVariant $mod $vr_list]
}

proc getAndParseModuleWithVariant {mod vr_list} {
   if {[llength $vr_list]} {
      lassign [parseModuleSpecification 0 0 0 0 $mod {*}$vr_list] mod_vr
   } else {
      set mod_vr $mod
   }
   return $mod_vr
}

proc setLoadedUse {mod args} {
   appendNoDupToList ::g_loadedModuleUse($mod) {*}$args
}

proc unsetLoadedUse {mod} {
   if {[info exists ::g_loadedModuleUse($mod)]} {
      unset ::g_loadedModuleUse($mod)
   }
}

proc getLoadedUse {mod} {
   if {[info exists ::g_loadedModuleUse($mod)]} {
      return $::g_loadedModuleUse($mod)
   }
}

proc getViaModuleForModulepath {modpath} {
   lassign [getAllLoadedModuleUsingModulepath $modpath] via
   if {![string length $via] && $modpath ni [getModulePathList]} {
      # get via information collected from spider processing
      set via [getModuleUsingModulepath $modpath]
   }
   return $via
}

proc getModuleUsingModulepath {modpath} {
   foreach mod [array names ::g_loadedModuleUse] {
      # no need to resolve variable reference in used path as this resolution
      # as already been performed when recording in global structure during
      # spider processing
      if {$modpath in $::g_loadedModuleUse($mod)} {
         return $mod
      }
   }
}

# Return all loaded module that enables given modulepath. Reference counter
# is checked to detect if modulepath was enabled prior loading the modules in
# which case modulepath is considered not enabled by loaded modules. modpath
# may not be currently enabled.
proc getAllLoadedModuleUsingModulepath {modpath} {
   set mod_list {}
   foreach mod [getEnvLoadedModulePropertyParsedList name] {
      if {$modpath in [resolvStringListWithEnv [getLoadedUse $mod]]} {
         lappend mod_list $mod
      }
   }
   set ref_count [getPathReferenceCount MODULEPATH $modpath [getState\
      path_separator] 1]
   if {$ref_count <= [llength $mod_list]} {
      return $mod_list
   }
}

proc unsetEnvLoadedModuleProperty {mod_name prop} {
   set prop_env_var [getLoadedModulePropertyEnvVarName $prop]
   set env_loaded_prop_list [getEnvLoadedModulePropertyList $prop]
   set mod_pattern_in_env_loaded_prop [escapeGlobChars $mod_name][getState\
      sub1_separator]*

   set mod_env_loaded_prop [lsearch -glob -inline $env_loaded_prop_list\
      $mod_pattern_in_env_loaded_prop]

   if {[string length $mod_env_loaded_prop]} {
      remove-path $prop_env_var $mod_env_loaded_prop
   }
}

proc setEnvLoadedModuleProperty {mod_name prop value_list} {
   set serialize_depth [getEnvLoadedModulePropertyStructDepth $prop]
   set serialize_proc serialize${serialize_depth}ModulePropertyValue
   set char_map_list [getEnvLoadedModulePropertyCharMapList $prop]
   set prop_env_var [getLoadedModulePropertyEnvVarName $prop]

   if {[llength $value_list]} {
      set serialized_value_list [$serialize_proc $mod_name $value_list\
         $char_map_list]
      append-path $prop_env_var $serialized_value_list
   }
}

proc getEnvLoadedModulePropertyStructDepth {prop} {
   switch -- $prop {
      file - init - modulepath - name - refresh {
         return 0Lvl
      }
      conflict - extratag - tag - use {
         return 1Lvl
      }
      altname - prereq - prereqpath - sourcesh - stickyrule - variant {
         return 2Lvl
      }
   }
}

proc getEnvLoadedModulePropertyCharUnmapList {prop} {
   set char_unmap_list {}
   foreach {char mapped} [getEnvLoadedModulePropertyCharMapList $prop] {
      lappend char_unmap_list $mapped $char
   }
   return $char_unmap_list
}

proc getEnvLoadedModulePropertyCharMapList {prop} {
   return [switch -- $prop {
      sourcesh {
         list [getState path_separator] <EnvModEscPS>\
            [getState sub1_separator] <EnvModEscS1>\
            [getState sub2_separator] <EnvModEscS2>
      }
      default {
         list : <
      }
   }]
}

proc serialize2LvlModulePropertyValue {mod structured_value_list\
   char_map_list} {
   set sub2_sepa [getState sub2_separator]
   set serialized_value_list [list]
   foreach structured_value $structured_value_list {
      set mapped_structured_value [string map $char_map_list\
         $structured_value]
      lappend serialized_value_list [join $mapped_structured_value $sub2_sepa]
   }
   # no character translation as it already occurred here
   return [serialize1LvlModulePropertyValue $mod $serialized_value_list {}]
}

proc serialize1LvlModulePropertyValue {mod structured_value_list\
   char_map_list} {
   set serialized_value [join [list $mod {*}$structured_value_list] [getState\
      sub1_separator]]
   return [string map $char_map_list $serialized_value]
}

proc unserialize2LvlModulePropertyValueList {serialized_value_list\
   char_unmap_list} {
   set lvl1_serialized_value_list [unserialize1LvlModulePropertyValueList\
      $serialized_value_list {}]
   set structured_value_list {}
   set sub2_sepa [getState sub2_separator]
   foreach lvl1_serialized_value $lvl1_serialized_value_list {
      set lvl1_strutured_value {}
      set first 1
      foreach lvl2_serialized_value $lvl1_serialized_value {
         # keep first element as string (module name)
         if {$first} {
            set lvl2_structured_value [string map $char_unmap_list\
               $lvl2_serialized_value]
            set first 0
         } else {
            set lvl2_structured_value {}
            foreach value [split $lvl2_serialized_value $sub2_sepa] {
               lappend lvl2_structured_value [string map $char_unmap_list\
                  $value]
            }
         }
         lappend lvl1_strutured_value $lvl2_structured_value
      }
      lappend structured_value_list $lvl1_strutured_value
   }
   return $structured_value_list
}

proc unserialize1LvlModulePropertyValueList {serialized_value_list\
   char_unmap_list} {
   set structured_value_list {}
   set sub1_sepa [getState sub1_separator]
   foreach serialized_value $serialized_value_list {
      set structured_value {}
      foreach value [split $serialized_value $sub1_sepa] {
         if {[string length $value]} {
            lappend structured_value [string map $char_unmap_list $value]
         }
      }
      # ignore empty element (1 is meaningless as first elt is loaded mod)
      if {[llength $structured_value] > 1} {
         lappend structured_value_list $structured_value
      }
   }
   return $structured_value_list
}

proc unserialize0LvlModulePropertyValueList {serialized_value_list\
   char_unmap_list} {
   set value_list {}
   foreach serialized_value $serialized_value_list {
      if {[string length $serialized_value]} {
         lappend value_list [string map $char_unmap_list $serialized_value]
      }
   }
   return $value_list

}

# get current loaded state to record it as the virtual init collection
proc getLoadedInit {} {
   set ret [formatCollectionContent [getModulePathList returnempty 0]\
      [getLoadedModuleWithVariantList]\
      [getLoadedModuleWithVariantSaveTagArrayList] {}]

   # remove ending newline in collection output
   set ret [string trimright $ret \n]

   # get init state as a string that can be registered in an env var translate
   # tag list separator ':' into '<' and line split '\n' into ':'
   return [string map {: < \n :} $ret]
}

# get all modules from all enabled modulepaths that matches a specification
proc getAllAvailModule {mod} {
   array set mod_list {}
   # search is done from lowest to greatest priority path (if result array
   # content is kept later on, it will hold accurate priority information)
   foreach dir [lreverse [getModulePathList]] {
      array set mod_list [getModules $dir $mod 0 [list rc_defs_included]]
   }
   return [array names mod_list]
}

# register conflict violation state between loaded modules
proc setModuleConflictViolation {mod modconlist} {
   reportDebug "set conflict violation state for '$mod'"
   set ::g_conflictViolation($mod) $modconlist
   # also update violation state for loaded mod conflicting with mod
   foreach lmmod $modconlist {
      if {[appendNoDupToList ::g_conflictViolation($lmmod) $mod]} {
         reportDebug "set/update conflict violation state for '$lmmod'"
      }
   }
}

# unregister conflict violation state between modules
proc unsetModuleConflictViolation {mod} {
   if {[info exists ::g_conflictViolation($mod)]} {
      # also update violation state for loaded mod conflicting with mod
      foreach lmmod $::g_conflictViolation($mod) {
         set convio [replaceFromList\
            $::g_conflictViolation($lmmod) $mod]
         reportDebug "unset/update conflict violation state for '$lmmod'"
         if {![llength $convio]} {
            unset ::g_conflictViolation($lmmod)
         } else {
            set ::g_conflictViolation($lmmod) $convio
         }
      }
      reportDebug "unset conflict violation state for '$mod'"
      unset ::g_conflictViolation($mod)
   }
}

# build dependency chain between loaded modules based on registered prereqs
proc setModuleDependency {mod} {
   set deplist {}
   set depnpolist {}

   foreach prereq_match_aftmatch [getLoadedModulePrereqListAndLoadedMatch\
      $mod] {
      set prereq_item_match_aftmatch_list [lassign $prereq_match_aftmatch\
         prereq prereq_path_list]

      set lmprelist {}
      set lmnpolist {}
      foreach {prereq_mod_item bef_loaded_mod_match aft_loaded_mod_match}\
         $prereq_item_match_aftmatch_list {
         if {[string length $bef_loaded_mod_match]} {
            appendNoDupToList lmprelist $bef_loaded_mod_match
            appendNoDupToList lmnpolist $bef_loaded_mod_match
         } else {
            reportDebug "set an unmet requirement on '$prereq_mod_item' for\
               '$mod'"
            lappend ::g_moduleUnmetDep($mod) $prereq_mod_item
            lappend ::g_unmetDepHash($prereq_mod_item) $mod $prereq_path_list
         }

         # is requirement loaded after mod (in *No Particular Order*)
         if {[string length $aft_loaded_mod_match]} {
            appendNoDupToList lmnpolist $aft_loaded_mod_match
         }
      }

      switch -- [llength $lmprelist] {
         0 {
            # prereq not satisfied
            reportDebug "set prereq violation state for '$mod'"
            lappend ::g_prereqViolation($mod) $prereq
         }
         1 {
            set lmmod [lindex $lmprelist 0]
            lappend deplist [list $lmmod]
            # set 'is depended by' relations
            lappend ::g_dependHash($lmmod) [list $mod]
         }
         default {
            lappend deplist $lmprelist
            # many modules in prereq list, means they all set an optional dep
            foreach lmmod $lmprelist {
               lappend ::g_dependHash($lmmod) [list $mod 1]
            }
         }
      }

      # build 'is depended by' relations not taking loading order into account
      switch -- [llength $lmnpolist] {
         0 {
            # even on No Particular Order mode, prereq is not satisfied
            reportDebug "set NPO prereq violation state for '$mod'"
            lappend ::g_prereqNPOViolation($mod) $prereq
         }
         1 {
            set lmmod [lindex $lmnpolist 0]
            lappend depnpolist [list $lmmod]
            # set 'is depended by' relations
            lappend ::g_dependNPOHash($lmmod) [list $mod]
         }
         default {
            lappend depnpolist $lmnpolist
            # many modules in prereq list, means they all set an optional dep
            foreach lmmod $lmnpolist {
               lappend ::g_dependNPOHash($lmmod) [list $mod 1]
            }
         }
      }
   }

   # conflict not satisfied
   set modconlist [getModuleLoadedConflict $mod]
   if {[llength $modconlist]} {
      setModuleConflictViolation $mod $modconlist
   }

   # update eventual registered unmet dependencies
   foreach modpre [array names ::g_unmetDepHash] {
      if {[modEq $modpre $mod eqstart 1 2 1]} {
         reportDebug "refresh requirements targeting '$modpre'"
         foreach {lmmod prereq_path_list} $::g_unmetDepHash($modpre) {
            if {![isLoadedMatchSpecificPath $mod $prereq_path_list]} {
               continue
            }
            if {$mod in [getDependentLoadedModuleList [list $lmmod] 0 0]} {
               reportDebug "skip deps refresh for '$lmmod' as dep cycle\
                  detected with '$mod'"

               # remove dependency link in no particular order structs to
               # avoid cycle first in 'is depended by' struct
               if {[info exists ::g_dependNPOHash($mod)]} {
                  set depmodlist $::g_dependNPOHash($mod)
                  for {set i 0} {$i < [llength $depmodlist]} {incr i 1} {
                     if {[lindex [lindex $depmodlist $i] 0] eq $lmmod} {
                        set depmodlist [lreplace $depmodlist $i $i]
                        break
                     }
                  }
                  set ::g_dependNPOHash($mod) $depmodlist
                  reportDebug "update NPO dependent of '$mod' to\
                     '$depmodlist'"
               }
               # then update 'depend on' struct
               set lmmoddepnpolist {}
               foreach depmodlist $::g_moduleNPODepend($lmmod) {
                  if {[set depidx [lsearch -exact $depmodlist $mod]] != -1} {
                     set depmodlist [lreplace $depmodlist $depidx $depidx]
                     # implies to update consistently alternate requirement or
                     # violation state if no alternative loaded
                     switch -- [llength $depmodlist] {
                        0 {
                           # do not know exact prereq name, so use correspond.
                           # loaded module matching it
                           lappend ::g_prereqNPOViolation($lmmod) $mod
                           reportDebug "set NPO prereq violation state for\
                              '$lmmod'"
                        }
                        1 {
                           # update alternate loaded mod which became a strong
                           # requirement
                           set altmod [lindex $depmodlist 0]
                           set ::g_dependNPOHash($altmod) [replaceFromList\
                              $::g_dependNPOHash($altmod) [list $lmmod 1]\
                              $lmmod]
                           reportDebug "update NPO dependent of '$altmod' to\
                              '$::g_dependNPOHash($altmod)'"
                        }
                     }
                  }
                  lappend lmmoddepnpolist $depmodlist
               }
               reportDebug "update NPO requirement of '$lmmod' to\
                  '$lmmoddepnpolist'"
               set ::g_moduleNPODepend($lmmod) $lmmoddepnpolist
            } else {
               # refresh actual dependencies of targeting mod
               unsetModuleDependency $lmmod
               setModuleDependency $lmmod
            }
         }
      }
   }

   # set 'depends on' relation
   reportDebug "set requirements of '$mod' to '$deplist'"
   set ::g_moduleDepend($mod) $deplist
   reportDebug "set NPO requirements of '$mod' to '$depnpolist'"
   set ::g_moduleNPODepend($mod) $depnpolist
}

# update dependency chain when unloading module
proc unsetModuleDependency {mod} {
   foreach lmmodlist $::g_moduleDepend($mod) {
      set manymod [expr {[llength $lmmodlist] > 1}]

      # unset 'is depended by' mod relations
      foreach lmmod $lmmodlist {
         if {[info exists ::g_dependHash($lmmod)]} {
            if {$manymod} {
               set hashdep [list $mod 1]
            } else {
               set hashdep [list $mod]
            }
            set ::g_dependHash($lmmod) [replaceFromList\
               $::g_dependHash($lmmod) $hashdep]
            if {![llength $::g_dependHash($lmmod)]} {
               unset ::g_dependHash($lmmod)
            }
         }
      }
   }
   # unset mod's 'depends on' relation
   reportDebug "unset requirements of '$mod'"
   unset ::g_moduleDepend($mod)

   foreach lmmodlist $::g_moduleNPODepend($mod) {
      set manymod [expr {[llength $lmmodlist] > 1}]

      # unset 'is depended by' mod relations
      foreach lmmod $lmmodlist {
         if {[info exists ::g_dependNPOHash($lmmod)]} {
            if {$manymod} {
               set hashdep [list $mod 1]
            } else {
               set hashdep [list $mod]
            }
            set ::g_dependNPOHash($lmmod) [replaceFromList\
               $::g_dependNPOHash($lmmod) $hashdep]
            if {![llength $::g_dependNPOHash($lmmod)]} {
               unset ::g_dependNPOHash($lmmod)
            }
         }
      }
   }
   # unset mod's No Particular Order 'depends on' relation
   reportDebug "unset NPO requirements of '$mod'"
   unset ::g_moduleNPODepend($mod)

   # unset eventual violation states
   if {[info exists ::g_prereqViolation($mod)]} {
      reportDebug "unset prereq violation state for '$mod'"
      unset ::g_prereqViolation($mod)
   }
   if {[info exists ::g_prereqNPOViolation($mod)]} {
      reportDebug "unset NPO prereq violation state for '$mod'"
      unset ::g_prereqNPOViolation($mod)
   }
   unsetModuleConflictViolation $mod

   # unset eventual registered unmet dependencies
   if {[info exists ::g_moduleUnmetDep($mod)]} {
      foreach ummod $::g_moduleUnmetDep($mod) {
         if {[info exists ::g_unmetDepHash($ummod)]} {
            set idx [lsearch -exact $::g_unmetDepHash($ummod) $mod]
            set ::g_unmetDepHash($ummod) [lreplace $::g_unmetDepHash($ummod)\
               $idx $idx+1]
            if {![llength $::g_unmetDepHash($ummod)]} {
               unset ::g_unmetDepHash($ummod)
            }
         }
      }
      reportDebug "unset unmet requirements for '$mod'"
      unset ::g_moduleUnmetDep($mod)
   }

   # unset mod's 'is depended by' relations
   set hashdeplist [getDirectDependentList $mod]
   if {[llength $hashdeplist]} {
      reportDebug "refresh dependent of '$mod'"
      foreach lmmod $hashdeplist {
         # refresh actual dependencies of targeting mod
         unsetModuleDependency $lmmod
         setModuleDependency $lmmod
      }
   }
}

# return the list of prereqs of a loaded or loading module and the associated
# loaded module corresponding to these prereq definitions. The list of each
# prereq is returned, each entry is a list of each prereq item definition
# associated to the matching module loaded prior passed mod and loaded after
proc getLoadedModulePrereqListAndLoadedMatch {mod} {
   set prereq_match_aftmatch_list {}
   set prereq_list [getLoadedPrereq $mod]

   set via_mod_list {}
   if {[getConf require_via]} {
      set modpath [getModulepathFromLoadedOrLoadingModule $mod]
      set via_mod_list [getAllLoadedModuleUsingModulepath $modpath]
   }

   # skip processing if no prereq defined
   if {![llength $prereq_list] && ![llength $via_mod_list]} {
      return
   }

   set loaded_mod_list [getEnvLoadedModulePropertyParsedList name]
   set mod_load_idx [lsearch -exact $loaded_mod_list $mod]
   # consider mod as the lastly loaded if not found in module list
   if {$mod_load_idx == -1} {
      set mod_load_idx [llength $loaded_mod_list]
   }

   # distinguish modules loaded before or after passed loaded mod
   set bef_loaded_mod_list [lrange $loaded_mod_list 0 $mod_load_idx]
   set aft_loaded_mod_list [lrange $loaded_mod_list $mod_load_idx+1 end]
   # reverse list to get closest match if returning lastly loaded module
   if {[getConf unload_match_order] eq {returnlast}} {
      set bef_loaded_mod_list [lreverse $bef_loaded_mod_list]
   }

   defineModEqProc [isIcase] [getConf extended_default] 1

   foreach prereq $prereq_list {
      set prereq_path_list [getLoadedPrereqPath $mod $prereq]
      set prereq_match_aftmatch [list $prereq $prereq_path_list]

      foreach prereq_mod_item $prereq {
         # look at modules loaded before to find requirements
         set bef_loaded_mod_match {}
         foreach loaded_mod $bef_loaded_mod_list {
            if {[isLoadedMatchSpecificPath $loaded_mod $prereq_path_list] &&\
               [modEq $prereq_mod_item $loaded_mod eqstart 1 2 1]} {
               set bef_loaded_mod_match $loaded_mod
               break
            }
         }

         # modules loaded afterward are unmet dependencies as dependent have
         # not been reloaded after them
         set aft_loaded_mod_match {}
         foreach loaded_mod $aft_loaded_mod_list {
            if {[isLoadedMatchSpecificPath $loaded_mod $prereq_path_list] &&\
               [modEq $prereq_mod_item $loaded_mod eqstart 1 2 1]} {
               set aft_loaded_mod_match $loaded_mod
               break
            }
         }

         lappend prereq_match_aftmatch $prereq_mod_item $bef_loaded_mod_match\
            $aft_loaded_mod_match
      }

      lappend prereq_match_aftmatch_list $prereq_match_aftmatch
   }

   # add via module requirement if enabled and if any
   if {[llength $via_mod_list]} {
      set bef_loaded_mod_match {}
      foreach loaded_mod $bef_loaded_mod_list {
         if {$loaded_mod in $via_mod_list} {
            set bef_loaded_mod_match $loaded_mod
            break
         }
      }
      set aft_loaded_mod_match {}
      foreach loaded_mod $aft_loaded_mod_list {
         if {$loaded_mod in $via_mod_list} {
            set aft_loaded_mod_match $loaded_mod
            break
         }
      }
      # use "<VIA>" string as prereq specification, this value is used for
      # unsatisfied requirement, which does not exist for via requirement
      lappend prereq_match_aftmatch_list [list <VIA> {} <VIA>\
         $bef_loaded_mod_match $aft_loaded_mod_match]
   }

   return $prereq_match_aftmatch_list
}

# returns if any loaded module (if passed mod is empty) or passed mod and all
# its requirement chain satisfy their loading constraints (prereq & conflict)
proc areModuleConstraintsSatisfied {{mod {}} {nporeq 0}} {
   set ret 1
   cacheCurrentModules

   # are requirements loaded after their dependent included or not
   if {$nporeq} {
      set reqVioVar ::g_prereqNPOViolation
      set reqListVar ::g_moduleNPODepend
   } else {
      set reqVioVar ::g_prereqViolation
      set reqListVar ::g_moduleDepend
   }

   # check if any loaded module violates its prereq or conflict constraints
   ##nagelfar vartype reqVioVar varName
   if {$mod eq {}} {
      if {[array size ::g_conflictViolation] || [array size $reqVioVar]} {
         set ret 0
      }
   } else {
      set fulllist [list $mod]
      for {set i 0} {$i < [llength $fulllist]} {incr i 1} {
         set depmod [lindex $fulllist $i]

         # check if depmod violates its prereq or conflict constraints
         ##nagelfar ignore +2 Suspicious variable name
         if {[info exists ::g_conflictViolation($depmod)] || [info exists\
            ${reqVioVar}($depmod)]} {
            # found violation among the requirement chain of mod so the
            # constraint of mod are not satisfied
            set ret 0
            break
         }
         # add requirements of depmod to the module to check list
         ##nagelfar ignore #2 Suspicious variable name
         if {[info exists ${reqListVar}($depmod)]} {
            foreach lmmodlist [set ${reqListVar}($depmod)] {
               appendNoDupToList fulllist {*}$lmmodlist
            }
         }
      }
   }

   return $ret
}

proc cacheCurrentModules {{exitonerr 1}} {
   # parse loaded modules information only once, global arrays are updated
   # afterwards when module commands update loaded modules state
   if {![isStateDefined lm_info_cached]} {
      setState lm_info_cached 1
      # mark specific as well as generic modules as loaded
      set i 0
      set modfilelist [getEnvLoadedModulePropertyParsedList file]
      set modlist [getEnvLoadedModulePropertyParsedList name]
      set refreshlist [getEnvLoadedModulePropertyParsedList refresh]

      if {[llength $modlist] == [llength $modfilelist]} {
         # cache declared variant of loaded modules
         foreach modvrspec [getEnvLoadedModulePropertyParsedList variant] {
            setLoadedVariant {*}$modvrspec
            # prepare modvr designation of loaded mod needed in next steps
            set mod [lindex $modvrspec 0]
            set vrlist [getVariantList $mod 6]
            set modvr [list $mod {*}$vrlist]
            set modvrarr($mod) $modvr
         }

         # cache declared tags of loaded modules
         foreach modtag [getEnvLoadedModulePropertyParsedList tag] {
            set tag_list [lassign $modtag mod]
            if {[info exists modvrarr($mod)]} {
               set mod_with_vr $modvrarr($mod)
            } else {
               set mod_with_vr {}
            }
            setModuleAndVariantsTag $mod $mod_with_vr {*}$tag_list
         }
         foreach modtag [getEnvLoadedModulePropertyParsedList extratag] {
            set tag_list [lassign $modtag mod]
            if {[info exists modvrarr($mod)]} {
               set mod_with_vr $modvrarr($mod)
            } else {
               set mod_with_vr {}
            }
            setModuleAndVariantsExtraTag $mod $mod_with_vr {*}$tag_list
         }
         foreach modstickyrule [getEnvLoadedModulePropertyParsedList\
            stickyrule] {
            set mod [lindex $modstickyrule 0]
            foreach modtagspeclist [lrange $modstickyrule 1 end] {
               set modspec_list [lassign $modtagspeclist modtag]
               foreach modspec $modspec_list {
                  lassign [parseModuleSpecification 0 0 0 0 {*}$modspec]\
                     parsed_modspec
                  # record tag definition on given module specification
                  setModspecTag $parsed_modspec $modtag
               }
            }
         }

         # cache declared alternative names of loaded modules
         foreach modalt [getEnvLoadedModulePropertyParsedList altname] {
            setLoadedAltname {*}$modalt
         }

         # cache declared source-sh of loaded modules
         foreach modsrcsh [getEnvLoadedModulePropertyParsedList sourcesh] {
            setLoadedSourceSh {*}$modsrcsh
         }

         # cache declared conflict of loaded modules
         foreach modcon [getEnvLoadedModulePropertyParsedList conflict] {
            # parse module version specification to record translation
            foreach modconelt [lrange $modcon 1 end] {
               parseModuleSpecification 0 0 0 0 {*}$modconelt
            }
            setLoadedConflict {*}$modcon
         }

         # cache declared prereq of loaded modules, prior to setLoadedModule
         # which triggers dependency chain build
         foreach modpre [getEnvLoadedModulePropertyParsedList prereq] {
            # parse module version specification to record translation
            foreach modpreeltlist [lrange $modpre 1 end] {
               foreach modpreelt $modpreeltlist {
                  parseModuleSpecification 0 0 0 0 {*}$modpreelt
               }
            }
            setLoadedPrereq {*}$modpre
         }
         foreach modpre_path [getEnvLoadedModulePropertyParsedList\
            prereqpath] {
            setLoadedPrereqPath {*}$modpre_path
         }

         foreach moduse [getEnvLoadedModulePropertyParsedList use] {
            setLoadedUse {*}$moduse
         }

         foreach mod $modlist {
            # get all tags also recorded on mod and vr designation
            if {[info exists modvrarr($mod)]} {
               set modvr $modvrarr($mod)
            } else {
               set modvr {}
            }
            setLoadedModule $mod [lindex $modfilelist $i] [expr\
               {![isModuleTagged $mod auto-loaded 1]}] $modvr [expr {$mod in\
               $refreshlist}]
            incr i
         }

         reportDebug "$i loaded"
      } else {
         set errproc [expr {$exitonerr ? {reportErrorAndExit} :\
            {reportError}}]
         $errproc "Loaded environment state is\
            inconsistent\nLOADEDMODULES=$modlist\n_LMFILES_=$modfilelist"
      }
   }
}

# This proc resolves module aliases or version aliases to the real module name
# and version.
proc resolveModuleVersionOrAlias {name icase} {
   set name [getArrayKey ::g_moduleResolved $name $icase]
   if {[info exists ::g_moduleResolved($name)]} {
      set ret $::g_moduleResolved($name)
   } else {
      set ret $name
   }

   reportTrace "'$name' into '$ret'" Resolve
   return $ret
}

proc parseAccessIssue {modfile} {
   # 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} {
   # test file only once, cache result obtained to minimize file query
   # consider modfile valid without reading it if mcookie_check < always
   return [expr {[info exists ::g_modfileValid($modfile)]\
      ? $::g_modfileValid($modfile)\
      : [set ::g_modfileValid($modfile) [expr {[getConf mcookie_check] eq\
         {always} ? [getModuleValidy $modfile] : [list true {}]}]]}]
}

# get file modification time, cache it at first query, use cache afterward
proc getFileMtime {fpath} {
   if {[info exists ::g_fileMtime($fpath)]} {
      return $::g_fileMtime($fpath)
   } else {
      # protect 'file mtime' call in case we do not know what we are checking
      # when mcookie is not checked
      if {[catch {
         set mtime [file mtime $fpath]
      } errMsg ]} {
         reportError [parseAccessIssue $fpath]
         set mtime {}
      }
      return [set ::g_fileMtime($fpath) $mtime]
   }
}

# define proc that will be used as fallback to command provided by extension
# library in case this library is not loaded
proc __readFile {filename {firstline 0} {must_have_cookie 0}} {
   set fid [open $filename r]
   if {$firstline} {
      set fdata [gets $fid]
   } else {
      # read a first data chunk and check if magic cookie is there if
      # mandatory. skip read of any additional content if mandatory cookie is
      # not found
      set fdata [read $fid 4096]
      if {![eof $fid] && (!$must_have_cookie || [string equal -length 8\
         $fdata {#%Module}])} {
         append fdata [read $fid]
      }
   }
   close $fid
   return $fdata
}

proc getModuleValidy {modfile} {
   if {[catch {lassign [getModuleHeaderAndContent $modfile 1] header}]} {
      set msg [parseAccessIssue $modfile]
      set valid accesserr
   } else {
      lassign [getModuleHeaderValidity $header] valid msg
   }
   return [list $valid $msg]
}

proc getModuleHeaderAndContent {modfile {only_get_header 0}} {
   if {[info exists ::g_modfileContent($modfile)]} {
      set res $::g_modfileContent($modfile)
   } else {
      # only read beginning of file if just checking validity and not asked to
      # always fully read files. when full file should be read cookie is
      # checked if asked to avoid to fully read non modfile
      set content [readFile $modfile [expr {$only_get_header &&\
         ![currentState always_read_full_file]}] 1]
      # extract magic cookie (first word of modulefile)
      set header [string trimright [lindex [split [string range $content 0\
         32]] 0] #]
      set res [list $header $content]
      # cache full file read to minimize file operations
      if {!$only_get_header || [currentState always_read_full_file]} {
         set ::g_modfileContent($modfile) $res
      }
   }
   return $res
}

proc getModuleHeaderValidity {header} {
   if {![string equal -length 8 $header {#%Module}]} {
      set msg {Magic cookie '#%Module' missing}
      set valid invalid
   # check if specified min version requirement is met
   } elseif {[string length $header] > 8 && [getConf mcookie_version_check]\
      && [versioncmp [getState modules_release] [string range $header 8 end]]\
      < 0} {
      set msg "Modulefile requires at least Modules version [string range\
         $header 8 end]"
      set valid invalid
   } else {
      set msg {}
      set valid true
   }
   return [list $valid $msg]
}

proc getModuleContent {mod_file} {
   if {[catch {lassign [getModuleHeaderAndContent $mod_file] mod_header\
      mod_content}]} {
      error [parseAccessIssue $mod_file] {} MODULES_ERR_READ
   }
   # raise error if module is not valid
   lassign [getModuleHeaderValidity $mod_header] mod_valid valid_msg
   if {$mod_valid ne {true}} {
      error $valid_msg {} MODULES_ERR_VALIDITY
   }
   return $mod_content
}

# 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} {
   set sym_list {}
   if {[info exists ::g_symbolHash($mod)]} {
      set sym_list $::g_symbolHash($mod)
      # withdraw hidden symbol from list
      if {[info exists ::g_hiddenSymHash($mod)]} {
         lassign [getDiffBetweenList $sym_list $::g_hiddenSymHash($mod)]\
            sym_list
      }
   }

   reportDebug "'$mod' has symbolic version list '$sym_list'"
   return $sym_list
}

proc doesModuleHaveSym {mod} {
   # is there any non-hidden symbol for mod
   return [expr {[info exists ::g_symbolHash($mod)] && (![info exists\
      ::g_hiddenSymHash($mod)] || [llength [lindex [getDiffBetweenList\
      $::g_symbolHash($mod) $::g_hiddenSymHash($mod)] 0]])}]
}

# get list of elements located in a directory passed as argument. a flag is
# set after each element to know if it is considered hidden or not. a
# fetch_dotversion argument controls whether .version file should be looked at
# in directory .proc will be used as a fallback to command provided by
# extension library
proc __getFilesInDirectory {dir fetch_dotversion} {
   set dir_list [list]

   set elt_list [glob -nocomplain -directory $dir *]

   # Add each element in the current directory to the list
   foreach elt $elt_list {
      lappend dir_list $elt 0
   }

   # search for hidden files
   foreach elt [glob -nocomplain -types hidden -directory $dir -tails *] {
      switch -- $elt {
         . - .. { }
         .modulerc - .version {
            if {($fetch_dotversion || $elt ne {.version}) && [file readable\
               $dir/$elt]} {
               lappend dir_list $dir/$elt 0
            }
         }
         default {
            lappend dir_list $dir/$elt 1
         }
      }
   }

   return $dir_list
}

# check if an existing findModules cache entry matches current search by
# evaluating search ids. if an exact match cannot be found, look at saved
# searches that contains current search (superset of looked elements), extra
# elements will be filtered-out by GetModules
proc findModulesInMemCache {searchid} {
   # exact same search is cached
   if {[info exists ::g_foundModulesMemCache($searchid)]} {
      set match_searchid $searchid
      set mod_list $::g_foundModulesMemCache($searchid)
   # look for a superset search
   } else {
      set match_searchid {}
      set mod_list {}
      foreach cacheid [array names ::g_foundModulesMemCache] {
         # cache id acts as pattern to check if it contains current search
         if {[string match $cacheid $searchid]} {
            set match_searchid $cacheid
            set mod_list $::g_foundModulesMemCache($cacheid)
            break
         }
      }
   }

   return [list $match_searchid $mod_list]
}

# Walk through provided list of directories and files to find modules
proc findModulesFromDirsAndFiles {dir full_list depthlvl fetch_mtime\
   res_arrname {indir_arrname {}} {hidden_listname {}} {fknown_arrname {}}\
   {dknown_arrname {}}} {
   # link to variables/arrays from upper context
   upvar $res_arrname mod_list
   if {$indir_arrname ne {}} {
      upvar $indir_arrname modfile_indir
   }
   if {$hidden_listname ne {}} {
      upvar $hidden_listname hidden_list
   }
   if {$fknown_arrname ne {}} {
      upvar $fknown_arrname fknown_arr
   }
   if {$dknown_arrname ne {}} {
      upvar $dknown_arrname dknown_arr
   }

   foreach igndir [getConf ignored_dirs] {
      set ignored_dirs($igndir) 1
   }
   array set mod_list {}
   for {set i 0} {$i < [llength $full_list]} {incr i 1} {
      set element [lindex $full_list $i]
      set tail [file tail $element]
      set modulename [getModuleNameFromModulepath $element $dir]
      set parentname [file dirname $modulename]
      set moddepthlvl [llength [file split $modulename]]
      # check if element is a directory if we do not already know if it is a
      # dir or a file from transmitted structures
      if {[info exists dknown_arr($modulename)] || (![info exists\
         fknown_arr($modulename)] && [file isdirectory $element])} {
         if {![info exists ignored_dirs($tail)]} {
            if {[catch {
               set elt_list [getFilesInDirectory $element 1]
            } errMsg]} {
               set mod_list($modulename) [list accesserr [parseAccessIssue\
                  $element] $element]
            } else {
               # Add each element in the current directory to the list
               foreach {fpelt hid} $elt_list {
                  lappend full_list $fpelt
                  # Flag hidden files
                  if {$hid} {
                     set hidden_list($fpelt) 1
                  }
               }
            }
         }
      } else {
         switch -glob -- $tail {
            .modulerc {
               set mod_list($modulename) [list modulerc]
            }
            .version {
               # skip .version file from different depth level than search
               # targets if no in depth mode is enabled
               if {$depthlvl == 0 || $moddepthlvl == $depthlvl} {
                  set mod_list($modulename) [list modulerc]
               }
            }
            .modulecache - *~ - *,v - \#*\# { }
            default {
               # skip modfile in no in depth mode search if it does not relate
               # to targeted depth level and one valid modfile has already be
               # found for the dirs lying at other depth level
               if {$depthlvl == 0 || $moddepthlvl == $depthlvl || ![info\
                  exists modfile_indir($parentname)]} {
                  lassign [checkValidModule $element] check_valid check_msg
                  switch -- $check_valid {
                     true {
                        set mtime [expr {$fetch_mtime ? [getFileMtime\
                           $element] : {}}]
                        set mod_list($modulename) [list modulefile $mtime\
                           $element]

                        # a valid modfile has been found in directory
                        if {![info exists hidden_list($element)]} {
                           set modfile_indir($parentname) 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]
                     }
                  }
               }
            }
         }
      }
   }
}

# finds all module-related files matching mod in the module path dir
proc findModules {dir mod depthlvl fetch_mtime} {
   reportDebug "finding '$mod' in $dir (depthlvl=$depthlvl,\
      fetch_mtime=$fetch_mtime)"

   # generated search id (for cache search/save) by compacting given args
   set searchid $dir:$mod:$depthlvl:$fetch_mtime

   # look at memory cache for a compatible result
   lassign [findModulesInMemCache $searchid] cache_searchid cache_list
   if {$cache_searchid ne {}} {
      reportDebug "use cache entry '$cache_searchid'"
      return $cache_list
   }

   # look at modulepath cache file
   lassign [findModulesInCacheFile $dir $mod $depthlvl $fetch_mtime] cache_ok\
      cache_list
   if {$cache_ok} {
      # record cache file findinds in memory cache
      reportDebug "create cache entry '$searchid'"
      set ::g_foundModulesMemCache($searchid) $cache_list
      return $cache_list
   }

   defineModEqStaticProc [isIcase] [getConf extended_default] $mod

   # every entries are requested
   set findall [expr {$mod eq {} || $mod eq {*}}]

   # use catch protection to handle non-readable and non-existent dir
   if {[catch {
      set full_list {}
      foreach {fpelt hid} [getFilesInDirectory $dir 0] {
         set elt [file tail $fpelt]
         # include any .modulerc file found at the modulepath root
         if {$elt eq {.modulerc} || $findall || [modEqStatic $elt match]} {
            lappend full_list $fpelt
         }
      }
   }]} {
      return {}
   }

   # walk through list of dirs and files to find modules
   findModulesFromDirsAndFiles $dir $full_list $depthlvl $fetch_mtime mod_list

   reportDebug "found [array names mod_list]"

   # cache search results
   reportDebug "create cache entry '$searchid'"
   set found_list [array get mod_list]
   set ::g_foundModulesMemCache($searchid) $found_list

   return $found_list
}

proc getModules {dir {mod {}} {fetch_mtime 0} {search {}} {filter {}}} {
   global g_sourceAlias g_sourceVersion g_sourceVirtual g_rcAlias\
      g_moduleAlias g_rcVersion g_moduleVersion g_rcVirtual g_moduleVirtual\
      g_rcfilesSourced

   reportDebug "get '$mod' in $dir (fetch_mtime=$fetch_mtime, search=$search,\
      filter=$filter)"

   # generated search id (for cache search/save) by compacting given args
   set searchid $dir:$mod:$fetch_mtime:$search:$filter

   # look at memory cache for a compatible result
   if {[info exists ::g_gotModulesMemCache($searchid)]} {
      reportDebug "use cache entry '$searchid'"
      return $::g_gotModulesMemCache($searchid)
   }

   # extract one module name from query
   set modqe [getOneModuleFromVersSpec $mod]

   # perform an in depth search or not
   set indepth [expr {{noindepth} ni $search}]

   # set a default if none defined on directory entries
   set implicitdfl [getConf implicit_default]

   # automatically define latest and default sym for all modules
   # disable when implicit default or advanced version spec are disabled or
   # if search query does not contain a module name and version but variant
   set autosymbol [expr {$implicitdfl && [getConf advanced_version_spec] &&\
      ![isSpecWildWithVariant $mod]}]

   # match passed name against any part of avail module names
   set contains [expr {{contains} in $search}]
   set mtest [expr {$contains ? {matchin} : {match}}]

   set icase [isIcase]

   set wild [expr {{wild} in $search}]

   # will only keep default or latest elts in the end or remove plain dirs
   set filtering [expr {$filter eq {noplaindir}}]
   set keeping [expr {!$filtering && $filter ne {}}]

   # check search query string corresponds to directory
   set querydir [string trimright $modqe *]
   set isquerydir [expr {[string index $querydir end] eq {/}}]
   set querydir [string trimright $querydir /]
   set querydepth [countChar $modqe /]

   # get directory relative to module name
   set moddir [getModuleNameFromVersSpec $mod]
   set hasmoddir [expr {$moddir ne {.}}]
   set modroot [getModuleRootFromVersSpec $mod]

   # get all in case of contains search or if provided-aliases are included
   set find_all [expr {$contains || [isEltInReport provided-alias 0]}]

   set earlyfilter [expr {!$find_all && !$wild && $modroot eq [string\
      map {* {} ? {}} $modroot]}]

   # are result entries gathered in a resolution context ?
   set resctx [expr {{resolve} in $search}]

   # need to perform an extra match search?
   set ems_required [isExtraMatchSearchRequired $mod]

   # if search for global or user rc alias only, no dir lookup is performed
   # and aliases from g_rcAlias are returned
   if {{rc_alias_only} in $search} {
      set add_rc_defs 1
      array set found_list {}
   } else {
      # find modules by searching mod root name in order to catch all module
      # related entries to correctly computed auto symbols afterward

      if {$find_all} {
         set findmod *
      } else {
         set findmod $modroot
         # if searched mod is an empty or flat element append wildcard
         # character to match anything starting with mod
         if {$wild && !$hasmoddir && [string index $findmod end] ne {*}} {
            append findmod *
         }
      }

      # add alias/version definitions from global or user rc to result
      set add_rc_defs [expr {{rc_defs_included} in $search}]

      # if no indepth mode search, pass the depth level of the search query
      # unless EMS need to be performed (findModules should fetch everything)
      set depthlvl [expr {$indepth || $ems_required ? 0 : $querydepth + 1}]

      array set found_list [findModules $dir $findmod $depthlvl $fetch_mtime]
   }

   # Phase #1: consolidate every kind of entries (directory, modulefile,
   # symbolic version, alias and virtual module) in found_list

   array set err_list {}
   array set versmod_list {}

   foreach elt [lsort [array names found_list]] {
      switch -- [lindex $found_list($elt) 0] {
         modulerc {
            # process rc files them remove them from found_list
            if {![info exists g_rcfilesSourced($dir/$elt)]} {
               execute-modulerc $dir/$elt $elt $elt
               # Keep track of already sourced rc files not to run them again
               set g_rcfilesSourced($dir/$elt) 1
            }
            unset found_list($elt)
         }
         modulefile {
         }
         default {
            # flag entries with error
            set err_list($elt) 1
         }
      }
   }

   # add all versions found when parsing .version or .modulerc files in this
   # directory or in global or user rc definitions
   foreach vers [array names g_moduleVersion] {
      set versmod $g_moduleVersion($vers)
      if {($dir ne {} && [string first $dir/ $g_sourceVersion($vers)] == 0)\
         || [info exists g_rcVersion($vers)]} {
         set found_list($vers) [list version $versmod]

         # build module symbol list
         lappend versmod_list($versmod) $vers
      # add global/user rc def to module symbol list in any cases
      } elseif {!$add_rc_defs && [info exists g_rcVersion($vers)]} {
         lappend versmod_list($versmod) $vers
      }
   }

   # 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)
   foreach alias [array names g_moduleAlias] {
      if {($dir ne {} && [string first $dir/ $g_sourceAlias($alias)] == 0)\
         || ($add_rc_defs && [info exists g_rcAlias($alias)])} {
         ##nagelfar ignore Found constant
         set found_list($alias) [list alias $g_moduleAlias($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)
   foreach virt [array names g_moduleVirtual] {
      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 {
               set mtime [expr {$fetch_mtime ? [getFileMtime\
                  $g_moduleVirtual($virt)] : {}}]
               # set mtime at index 1 like a modulefile entry
               set found_list($virt) [list virtual $mtime\
                  $g_moduleVirtual($virt)]
            }
            default {
               # register check error and relative message to get it in
               # case of direct access of this module element
               set found_list($virt) [list $check_valid $check_msg\
                  $g_moduleVirtual($virt)]
               set err_list($virt) 1
            }
         }
      }
   }

   # Phase #2: early filtering of non-matching elements

   defineModEqProc $icase [getConf extended_default]

   if {$earlyfilter} {
      foreach elt [array names found_list] {
         if {![modEq $modroot $elt eqstart]} {
            unset found_list($elt)
         }
      }
   }

   # Phase #3: scan modulefiles if extra match search is needed

   if {$ems_required} {
      scanExtraMatchSearch $dir $mod found_list
      if {[isEltInReport provided-alias 0]} {
         insertProvidedAliases $dir found_list
      }
   }

   # Phase #4: filter-out dynamically hidden or expired elements

   # define module name and version comparison procs
   defineModStartNbProc $icase
   defineModEqStaticProc $icase [getConf extended_default] $mod

   # remove hidden elements unless they are (or their symbols) targeted by
   # search query.
   foreach elt [array names found_list] {
      if {[lassign [isModuleHidden $elt $mod 1 [lindex $found_list($elt) 2]]\
         hidlvl hidmatch hidbydef]} {
         # is there a symbol that matches query (bare module name query
         # matches default symbol on resolve context or if onlydefaults filter
         # is applied)
         if {!$hidmatch && [info exists versmod_list($elt)]} {
            foreach eltsym $versmod_list($elt) {
               if {[modEqStatic $eltsym] || (($resctx || $filter eq\
                  {onlydefaults}) && "$mod/default" eq $eltsym)} {
                  set hidmatch 1
                  break
               }
            }
         }
         # consider 'default' symbols are explicitly specified if
         # onlydefaults filter applied or resolving bare module name
         if {!$hidmatch && [lindex $found_list($elt) 0] eq {version} && [file\
            tail $elt] eq {default} && ($filter eq {onlydefaults} || ($resctx\
            && "$mod/default" eq $elt))} {
            set hidmatch 1
         }

         # not hidden if matched unless if hard hiding apply
         if {!$hidmatch || $hidlvl > 1} {
            # record hidden symbol, not to display it in listModules
            if {[lindex $found_list($elt) 0] eq {version}} {
               lappend ::g_hiddenSymHash([lindex $found_list($elt) 1]) [file\
                  tail $elt]
            }
            # transform forbidden module in error entry if it specifically
            # matches search query
            if {$hidlvl == 2 && $hidmatch && [isModuleTagged $elt forbidden 0\
               $dir/$elt]} {
               set found_list($elt) [list accesserr [getForbiddenMsg $elt\
                  $dir/$elt]]
               set err_list($elt) 1
            } else {
               unset found_list($elt)
            }
         }
      }
      # apply hidden tag if an hidden definition apply to module
      if {$hidbydef} {
         setModuleTag $elt hidden
      }
   }

   # Phase #5: elaborate directory content with default element selection

   array set dir_list {}
   array set autosym_list {}

   # build list of elements contained in each directory
   foreach elt [array names found_list] {
      # add a ref to element in its parent directory unless element has error
      # or is a symbolic version then recursively add parent element until
      # reaching top directory
      if {![info exists err_list($elt)] && [lindex $found_list($elt) 0] ne\
         {version}} {
         set direlt $elt
         while {[set pardir [file dirname $direlt]] ne {.}} {
            appendNoDupToList dir_list($pardir) [file tail $direlt]
            set direlt $pardir
         }
      }
   }

   # determine default element for each directory and record sorted elt list
   # unless if an alias or a virtual module has overwritten directory entry
   # but override alias entry if extra match search is required (as it
   # withdrawn module aliases)
   foreach elt [array names dir_list] {
      if {![info exists found_list($elt)] || ($ems_required && [lindex\
         $found_list($elt) 0] eq {alias})} {
         set dir_list($elt) [lsort -dictionary $dir_list($elt)]
         # get default element: explicitly defined default (whether it exists
         # or is in error) or implicit default if enabled
         if {[info exists found_list($elt/default)] && [lindex\
            $found_list($elt/default) 0] eq {version}} {
            set dfl [file tail [lindex $found_list($elt/default) 1]]
         } elseif {$implicitdfl} {
            set dfl [lindex $dir_list($elt) end]
         } else {
            set dfl {}
         }
         # record directory properties
         set found_list($elt) [list directory $dfl {*}$dir_list($elt)]

         # automatically define symbols for all modules matching query if
         # these names do not exist yet or if in error, in which case only
         # auto symbol resolution is set
         if {$autosymbol && (!$hasmoddir || [modEq $modroot $elt eqstart])} {
            if {![info exists found_list($elt/default)] || [info exists\
               err_list($elt/default)]} {
               if {![info exists found_list($elt/default)]} {
                  set found_list($elt/default) [list version $elt/$dfl]
                  lappend versmod_list($elt/$dfl) $elt/default
                  set autosym_list($elt/default) 1
               }
               setModuleResolution $elt/default $elt/$dfl default 1 1
            }
            if {![info exists found_list($elt/latest)] || [info exists\
               err_list($elt/latest)]} {
               set lat [lindex $dir_list($elt) end]
               if {![info exists found_list($elt/latest)]} {
                  set found_list($elt/latest) [list version $elt/$lat]
                  lappend versmod_list($elt/$lat) $elt/latest
                  set autosym_list($elt/latest) 1
               }
               setModuleResolution $elt/latest $elt/$lat latest 1 1
            }
         }
      }
   }

   # Phase #6: perform extra match search filtering

   if {$ems_required} {
      filterExtraMatchSearch $dir $mod found_list versmod_list
   }

   # Phase #7: filter results to keep those matching search query

   # define module name and version comparison procs
   defineDoesModMatchAtDepthProc $contains $querydepth $mtest

   # element to include in output
   set report_indesym [isEltInReport indesym 0]
   set report_dirwsym [expr {!$report_indesym && [isEltInReport dirwsym]}]

   array set mod_list {}
   array set fdir_list {}
   array set keep_list {}

   # keep element matching query, add directory of element matching query,
   # also add directory to result if query name finishes with trailing slash;
   # only keep auto syms if fully matched or version not specified in query;
   # (hidden elements have been filtered on phase 2)
   foreach elt [array names found_list] {
      set elt_type [lindex $found_list($elt) 0]
      if {(($wild && [doesModMatchAtDepth $elt]) || (!$wild && ([modEqStatic\
         $elt match /*] || [modEqStatic $elt match] || ($hasmoddir &&\
         $elt_type eq {directory} && [modEq $moddir $elt]))) || ($isquerydir\
         && $elt_type eq {directory} && [modEq $querydir $elt match 0])) &&\
         (![info exists autosym_list($elt)] || ([countChar $elt /]\
         != $querydepth && !$contains) || [modEqStatic $elt]) && ![info\
         exists mod_list($elt)]} {
         if {$elt_type eq {directory}} {
            # add matching directory to the result list, its entries will be
            # computed in a second time and directory will be dropped if it
            # has no entry in the end
            set mod_list($elt) [list directory]
            # add dir to the filter dir list to enable its removal in next
            # step if dir is empty
            if {![info exists fdir_list($elt)]} {
               set fdir_list($elt) {}
            }
         } else {
            set mod_list($elt) $found_list($elt)
         }

         # version may matches query but not its target, so it should be in
         # this case manually added to result (if it exists and unless if
         # versions should be reported independently)
         if {$elt_type eq {version} && !$report_indesym} {
            # resolve eventual icase target
            set versmod [getArrayKey found_list [lindex $mod_list($elt) 1]\
               $icase]
            # add target to dir struct (not version) if not already recorded
            set direlt .

            # recursively add targets to result (process directory content if
            # target is a directory
            set tgt_list [list $versmod]
            for {set i 0} {$i < [llength $tgt_list]} {incr i} {
               set tgt [lindex $tgt_list $i]

               if {![info exists mod_list($tgt)]} {
                  if {[info exists found_list($tgt)]} {
                     set mod_list($tgt) $found_list($tgt)
                     # version target is directory: recursively add content
                     if {[lindex $mod_list($tgt) 0] eq {directory}} {
                        foreach tgtelt $dir_list($tgt) {
                           lappend tgt_list $tgt/$tgtelt
                        }
                        # add dir to the filter dir list to enable its removal
                        # in next step if dir is empty
                        if {![info exists fdir_list($tgt)]} {
                           set fdir_list($tgt) {}
                        }
                     }
                  }

                  # record target in dir struct if part of found elts or if
                  # hidden but should not be in error
                  set pardir [file dirname $tgt]
                  # do not test if full path module is hidden as full path
                  # designation cannot be computed (target not in found_list)
                  if {([info exists found_list($tgt)] || ($pardir ne {.} &&\
                     ![info exists found_list($pardir)] && [isModuleHidden\
                     $tgt $mod])) && ![info exists err_list($tgt)]} {
                     # create parent directory if it does not exist
                     if {$pardir ne {.} && ![info exists\
                        found_list($pardir)]} {
                        set found_list($pardir) [list directory]
                        set mod_list($pardir) [list directory]
                     }

                     if {$i == 0} {
                        set direlt $tgt
                     } else {
                        lappend fdir_list([file dirname $tgt]) [file tail\
                           $tgt]
                     }
                  }
               }
            }
         # skip adding element to directory content if in error
         } elseif {[info exists err_list($elt)]} {
            set direlt .
         } else {
            set direlt $elt
         }

         # track directory content, as directory are also reported to their
         # parent directory the directory structure is also tracked
         if {[set pardir [file dirname $direlt]] ne {.}} {
            lappend fdir_list($pardir) [file tail $direlt]
         # track top level entries that will be kept if result is filtered
         } elseif {$keeping && $direlt ne {.} && $elt_type ne {directory}} {
            set keep_list($elt) 1
         }
      }
   }

   # determine default element for each directory and record sorted element
   # list unless directory entry has been overwritten by a different module
   # kind or unless only matching directory should be part of result
   foreach elt [lsort -decreasing [array names fdir_list]] {
      if {[lindex $found_list($elt) 0] eq {directory} && ([info exists\
         mod_list($elt)] || $keeping)} {
         set fdir_list($elt) [lsort -dictionary $fdir_list($elt)]
         # get default element: explicitly defined default if included in
         # result or not found or implicit default if enabled
         if {[info exists found_list($elt/default)] && [lindex\
            $found_list($elt/default) 0] eq {version} && ([info exists\
            mod_list([set versmod [lindex $found_list($elt/default) 1]])] ||\
            ![info exists found_list($versmod)])} {
            set dfl [file tail $versmod]
         } elseif {$implicitdfl} {
            set dfl [lindex $fdir_list($elt) end]
         } else {
            set dfl {}
         }
         # remove empty dirs
         if {![llength $fdir_list($elt)]} {
            unset mod_list($elt)
            unset fdir_list($elt)
            # remove unset dir reference in parent directory. parent dir
            # will be treated after unset dir (due to decreasing sort) if it
            # needs to get in turn unset
            if {[set pardir [file dirname $elt]] ne {.}} {
               set fdir_list($pardir) [replaceFromList $fdir_list($pardir)\
                  [file tail $elt]]
            }
         } else {
            # record directory properties
            set mod_list($elt) [list directory $dfl {*}$fdir_list($elt)]

            # list elements to keep for filtering step
            if {$keeping} {
               if {$filter eq {onlylatest}} {
                  set keepelt $elt/[lindex $fdir_list($elt) end]
               } elseif {$dfl ne {}} {
                  set keepelt $elt/$dfl
               } else {
                  set keepelt {}
               }

               # keep directory if its element depth is deeper than query
               if {!$indepth && [countChar $keepelt /] > $querydepth} {
                  set keep_list($elt) 1
               # otherwise only keep existing modules (not directories)
               } elseif {[info exists mod_list($keepelt)] && [lindex\
                  $mod_list($keepelt) 0] ne {directory}} {
                  set keep_list($keepelt) 1
               }
            # when noplaindir filtering, only keep dirs with syms when indepth
            # enabled or if corresponds to query depth when indepth disabled
            } elseif {$filtering && $filter eq {noplaindir} &&\
               (($indepth && (!$report_dirwsym || ![doesModuleHaveSym $elt]))\
               || (!$indepth && [countChar $elt /] != $querydepth))} {
               unset mod_list($elt)
            }
         }
      }
   }

   # now all matching modulefiles are settled, only keep those found at search
   # query depth level if 'noindepth' mode asked
   if {!$indepth} {
      # remove entries with more filename path separator than query pattern
      foreach elt [array names mod_list] {
         if {[countChar $elt /] > $querydepth} {
            unset mod_list($elt)
         }
      }
   }

   # if result should be filtered, only keep marked elements
   if {$keeping} {
      foreach elt [array names mod_list] {
         if {![info exists keep_list($elt)]} {
            unset mod_list($elt)
         }
      }
   }

   # Phase #6: consolidate tags set for retained modules

   # skip collecting tags if already performed for this modulepath or command
   # is different than avail/spider
   if {[currentState commandname] in {avail spider} && $dir ni [getState\
      tags_collected_in]} {
      # load tags from loaded modules in the global structure prior collecting
      # tags found during this getModules evaluation
      cacheCurrentModules 0

      foreach elt [array names mod_list] {
         switch -- [lindex $mod_list($elt) 0] {
            alias - modulefile - virtual {
               # gather all tags applying to elt
               collectModuleTags $elt
            }
         }
      }
   }

   reportTrace "{[array names mod_list]} matching '$mod' in '$dir'" {Get\
      modules}

   # cache search results
   reportDebug "create cache entry '$searchid'"
   set got_list [array get mod_list]
   set ::g_gotModulesMemCache($searchid) $got_list

   return $got_list
}

proc getMatchingAnyModules {dir pattern_list fetch_mtime search filter} {
   set mod_list {}
   foreach pattern $pattern_list {
      lappend mod_list {*}[getModules $dir $pattern $fetch_mtime $search\
         $filter]
   }
   return $mod_list
}

# gather for the current top evaluation the information on all evaluations
# happening under its umbrella
proc registerModuleEval {context msgrecid {failedmod {}} {failedcontext {}}} {
   set evalid [topState evalid]
   set unset [expr {$failedmod eq {} ? 0 : 1}]
   set contextset 0
   # unload dependent reload evaluations are mixed with dependent unload ones
   if {$context eq {depre_un}} {
      set context depun
   }

   # add msgrecid to existing evaluation context list
   if {[info exists ::g_moduleEval($evalid)]} {
      for {set i 0} {$i < [llength $::g_moduleEval($evalid)]} {incr i 1} {
         set contextevallist [lindex $::g_moduleEval($evalid) $i]
         if {[lindex $contextevallist 0] eq $context} {
            if {$unset} {
               set contextevallist [replaceFromList $contextevallist\
                  $msgrecid]
            } else {
               lappend contextevallist $msgrecid
            }
            set ::g_moduleEval($evalid) [expr {[llength $contextevallist] > 1\
               ? [lreplace $::g_moduleEval($evalid) $i $i $contextevallist]\
               : [lreplace $::g_moduleEval($evalid) $i $i]}]
            set contextset 1
            break
         }
      }
   }

   # add msgrecid to new evaluation context list
   if {!$unset && !$contextset} {
      lappend ::g_moduleEval($evalid) [list $context $msgrecid]
   }

   # add mod to failed evaluation list
   if {$unset} {
      lappend ::g_moduleFailedEval($evalid) $failedcontext $failedmod
      if {[depthState reportholdid]} {
         lappend ::g_holdModuleFailedEval([currentState reportholdid])\
            $evalid $failedcontext $failedmod
      }
   }
}

# record that module evaluation is set hidden
proc registerModuleEvalHidden {context msgrecid} {
   set evalid [topState evalid]
   # unload dependent reload evaluations are mixed with dependent unload ones
   if {$context eq {depre_un}} {
      set context depun
   }
   lappend ::g_moduleHiddenEval($evalid:$context) $msgrecid
}

proc changeContextOfModuleEval {mod old_context new_context} {
   set evalid [topState evalid]
   # find old and new context evaluations
   for {set i 0} {$i < [llength $::g_moduleEval($evalid)]} {incr i 1} {
      set context_eval_list [lindex $::g_moduleEval($evalid) $i]
      if {[lindex $context_eval_list 0] eq $old_context} {
         set old_context_idx $i
         set old_context_eval_list $context_eval_list
      } elseif {[lindex $context_eval_list 0] eq $new_context} {
         set new_context_idx $i
         set new_context_eval_list $context_eval_list
      }
      if {[info exists old_context_idx] && [info exists new_context_idx]} {
         break
      }
   }

   # module evaluation not found
   if {![info exists old_context_idx]} {
      return
   }

   # new context has no evaluation yet
   if {![info exists new_context_idx]} {
      set new_context_idx end+1
      set new_context_eval_list [list $new_context]
   }

   # find evaluation id of module
   foreach msgrecid [lrange $old_context_eval_list 1 end] {
      if {$mod eq [getModuleFromEvalId $msgrecid]} {
         set mod_evalid $msgrecid
         break
      }
   }
   if {![info exists mod_evalid]} {
      return
   }

   # remove evaluation from old context
   set old_context_eval_list [replaceFromList $old_context_eval_list\
      $mod_evalid]
   lset ::g_moduleEval($evalid) $old_context_idx $old_context_eval_list

   # insert evaluation at start of new context
   set new_context_eval_list [linsert $new_context_eval_list 1 $mod_evalid]
   # on Tcl 8.5, lset command cannot append a list
   if {$new_context_idx eq {end+1}} {
      lappend ::g_moduleEval($evalid) $new_context_eval_list
   } else {
      lset ::g_moduleEval($evalid) $new_context_idx $new_context_eval_list
   }

   # change context of hidden evaluation
   set old_hidden_evalid $evalid:$old_context
   if {[info exists ::g_moduleHiddenEval($old_hidden_evalid)]} {
      if {$mod_evalid in $::g_moduleHiddenEval($old_hidden_evalid)} {
         set ::g_moduleHiddenEval($old_hidden_evalid) [replaceFromList\
            $::g_moduleHiddenEval($old_hidden_evalid) $mod_evalid]
         set new_hidden_evalid $evalid:$new_context
         lappend ::g_moduleHiddenEval($new_hidden_evalid) $mod_evalid
      }
   }
}

# get context of currently evaluated module
proc currentModuleEvalContext {} {
   return [lindex $::g_moduleEvalAttempt([currentState modulenamevr]) end]
}

# record module evaluation attempt and corresponding context
proc registerModuleEvalAttempt {context mod mod_file} {
   appendNoDupToList ::g_moduleEvalAttempt($mod) $context
   appendNoDupToList ::g_moduleFileEvalAttempt($mod) $mod_file
}

proc unregisterModuleEvalAttempt {context mod mod_file} {
   set ::g_moduleEvalAttempt($mod) [replaceFromList\
      $::g_moduleEvalAttempt($mod) $context]
   set ::g_moduleFileEvalAttempt($mod) [replaceFromList\
      $::g_moduleFileEvalAttempt($mod) $mod_file]
}

# is at least one module passed as argument evaluated in passed context
proc isModuleEvaluated {context exclmod modulepath_list args} {
   set ret 0
   # look at all evaluated mod except excluded one (currently evaluated mod)
   foreach evalmod [lsearch -all -inline -not [array names\
      ::g_moduleEvalAttempt] $exclmod] {
      # test evaluated module matches specified modulepaths
      if {[llength $modulepath_list]} {
         set eval_mod_file_match 0
         foreach eval_mod_file $::g_moduleFileEvalAttempt($evalmod) {
            if {[isModulefileInModulepathList $eval_mod_file\
               $modulepath_list]} {
               set eval_mod_file_match 1
               break
            }
         }
         if {!$eval_mod_file_match} {
            continue
         }
      }
      set evalmatch 0
      # test arguments against all names of evaluated module (translate
      # eventual modspec in evalmod into module names, in case module
      # evaluation stopped prior module name setup)
      # retrieve variants to pass them directly to modEq
      set modvrlist [getVariantList $evalmod 0 0 1]
      foreach mod [getAllModulesFromVersSpec $evalmod] {
         foreach name $args {
            # indicate module is loading to also compare against all alt names
            if {[modEq $name $mod eqstart 1 1 1 $modvrlist]} {
               set evalmatch 1
               if {$context eq {any} || $context in\
                  $::g_moduleEvalAttempt($evalmod)} {
                  set ret 1
               }
               break
            }
         }
         if {$evalmatch} {
            break
         }
      }
      if {$ret} {
         break
      }
   }
   return $ret
}

# was passed mod already evaluated for context and failed
proc isModuleEvalFailed {context mod} {
   set ret 0
   set evalid [topState evalid]
   if {[info exists ::g_moduleFailedEval($evalid)]} {
      foreach {curcon curmod} $::g_moduleFailedEval($evalid) {
         if {$context eq $curcon && $mod eq $curmod} {
            set ret 1
            break
         }
      }
   }
   return $ret
}

# return list of currently loading modules in stack
proc getLoadingModuleList {} {
   set modlist [list]
   for {set i 0} {$i < [depthState modulename]} {incr i 1} {
      if {[lindex [getState mode] $i] eq {load}} {
         lappend modlist [lindex [getState modulename] $i]
      }
   }
   return $modlist
}

# return list of currently loading modulefiles in stack
proc getLoadingModuleFileList {} {
   set modlist [list]
   for {set i 0} {$i < [depthState modulefile]} {incr i 1} {
      if {[lindex [getState mode] $i] eq {load}} {
         lappend modlist [lindex [getState modulefile] $i]
      }
   }
   return $modlist
}

# return list of currently unloading modules in stack
proc getUnloadingModuleList {} {
   set modlist [list]
   for {set i 0} {$i < [depthState modulename]} {incr i 1} {
      if {[lindex [getState mode] $i] eq {unload}} {
         lappend modlist [lindex [getState modulename] $i]
      }
   }
   return $modlist
}

# sort passed module list following both loaded and dependency orders
proc sortModulePerLoadedAndDepOrder {modlist {nporeq 0} {loading 0}} {
   # sort per loaded order
   set sortlist {}
   if {[llength $modlist]} {
      foreach lmmod [getEnvLoadedModulePropertyParsedList name] {
         if {$lmmod in $modlist} {
            lappend sortlist $lmmod
         }
      }
      # also sort eventual loading modules if asked
      if {$loading} {
         foreach loadingmod [lreverse [getLoadingModuleList]] {
            if {$loadingmod in $modlist} {
               lappend sortlist $loadingmod
            }
         }
      }
   }

   # then refine sort with dependencies between loaded modules: a dependent
   # module should be placed prior the loaded module requiring it
   set reqListVar [expr {$nporeq ? {::g_moduleNPODepend} :\
      {::g_moduleDepend}}]
   set i 0
   set imax [llength $sortlist]
   while {$i < $imax} {
      set mod [lindex $sortlist $i]
      set jmin $imax

      ##nagelfar ignore #4 Suspicious variable name
      if {[info exists ${reqListVar}($mod)]} {
         # goes over all dependent modules to find the first one in the loaded
         # order list located after requiring mod
         foreach lmmodlist [set ${reqListVar}($mod)] {
            foreach lmmod $lmmodlist {
               set j [lsearch -exact $sortlist $lmmod]
               if {$j > $i && $j < $jmin} {
                  set jmin $j
                  set jminmod $lmmod
               }
            }
         }
      }

      # move first dependent module found after currently inspected mod right
      # before it
      if {$jmin != $imax} {
         set sortlist [linsert [lreplace $sortlist $jmin $jmin] $i $jminmod]
      # or go to next element in list if current element has not been changed
      } else {
         incr i
      }
   }

   return $sortlist
}

# return list of loaded modules having an unmet requirement on passed mod
# and their recursive dependent
proc getUnmetDependentLoadedModuleList {modnamevr mod_file} {
   set unmetdeplist {}
   set depmodlist {}
   defineModEqProc [isIcase] [getConf extended_default] 1
   set mod [getModuleNameAndVersFromVersSpec $modnamevr]
   set vrlist [getVariantList $modnamevr 0 0 1]

   # skip dependent analysis if mod has a conflict with a loaded module
   set modconlist [getModuleLoadedConflict $mod]
   if {![llength $modconlist]} {
      foreach ummod [array names ::g_unmetDepHash] {
         if {[modEq $ummod $mod eqstart 1 2 1 $vrlist]} {
            foreach {depmod prereq_path_list} $::g_unmetDepHash($ummod) {
               if {![isModulefileMatchSpecificPath $mod_file\
                  $prereq_path_list]} {
                  continue
               }

               lappend depmodlist $depmod
               # temporarily remove prereq violation of depmod if mod
               # load solves it (no other prereq is missing)
               if {[info exists ::g_prereqViolation($depmod)]} {
                  foreach prereq $::g_prereqViolation($depmod) {
                     foreach modpre $prereq {
                        # also temporarily remove prereq violation for
                        # requirements loaded after dependent module
                        if {[modEq $modpre $mod eqstart 1 2 1 $vrlist] ||\
                           [is-loaded $modpre]} {
                           # backup original violation to restore it later
                           if {![info exists preunvioarr($depmod)]} {
                              set preunvioarr($depmod)\
                                 $::g_prereqViolation($depmod)
                           }
                           # temporarily remove matching violation
                           set ::g_prereqViolation($depmod) [replaceFromList\
                              $::g_prereqViolation($depmod) $prereq]
                           if {![llength $::g_prereqViolation($depmod)]} {
                              unset ::g_prereqViolation($depmod)
                           }
                           break
                        }
                     }
                  }
               }
            }
         }
      }
   }

   # select dependent if all its constraint are now satisfied (after removing
   # eventual prereq violation toward mod)
   foreach depmod $depmodlist {
      if {[areModuleConstraintsSatisfied $depmod]} {
         appendNoDupToList unmetdeplist $depmod
      }
   }

   # get dependent of dependent
   set deplist [getDependentLoadedModuleList $unmetdeplist 0 0 0 0 1]

   # restore temporarily lift prereq violation
   if {[array exists preunvioarr]} {
      foreach depmod [array names preunvioarr] {
         set ::g_prereqViolation($depmod) $preunvioarr($depmod)
      }
   }

   set sortlist [sortModulePerLoadedAndDepOrder [list {*}$unmetdeplist\
      {*}$deplist]]
   reportDebug "got '$sortlist'"
   return $sortlist
}

# return list of loaded modules declaring a prereq on passed mod with
# distinction made with strong prereqs (no alternative loaded) or weak and
# also with prereq loaded after their dependent module
proc getDirectDependentList {mod {strong 0} {nporeq 0} {loading 0}\
   {othmodlist {}}} {
   set deplist {}

   # include or not requirements loaded after their dependent
   if {$nporeq} {
      set depListVar ::g_dependNPOHash
      set reqListVar ::g_moduleNPODepend
   } else {
      set depListVar ::g_dependHash
      set reqListVar ::g_moduleDepend
   }

   ##nagelfar ignore #2 Suspicious variable name
   if {[info exists ${depListVar}($mod)]} {
      foreach depmod [set ${depListVar}($mod)] {
         set add 1
         # skip optional dependency if only looking for strong ones
         # look at an additionally processed mod list to determine if all
         # mods from a dependent list (composed of optional parts) are part
         # of the search, which means mod is not optional but strong dependent
         if {$strong && [llength $depmod] > 1} {
            ##nagelfar ignore Suspicious variable name
            foreach lmmodlist [set ${reqListVar}([lindex $depmod 0])] {
               if {$mod in $lmmodlist} {
                  foreach lmmod $lmmodlist {
                     # other mod part of the opt list is not there so mod
                     # is considered optional
                     if {$lmmod ni $othmodlist} {
                        set add 0
                        break
                     }
                  }
                  break
               }
            }
         }

         if {$add} {
            lappend deplist [lindex $depmod 0]
         }
      }
   }

   # take currently loading modules into account if asked
   if {$loading} {
      foreach loading_mod [getLoadingModuleList] {
         foreach prereq_match [getLoadedModulePrereqListAndLoadedMatch\
            $loading_mod] {
            set lmprelist {}
            set item_match_list [lassign $prereq_match prereq\
               prereq_path_list]
            foreach {prereq_mod_item loaded_mod_match ign} $item_match_list {
               if {[string length $loaded_mod_match]} {
                  lappend lmprelist $loaded_mod_match
               }
            }
            if {$mod in $lmprelist && (!$strong || [llength $lmprelist]\
               == 1)} {
               lappend deplist $loading_mod
               break
            }
         }
      }
   }

   return $deplist
}

# gets the list of all loaded modules which are dependent of passed modlist
# ordered by load position. strong argument controls whether only the active
# dependent modules should be returned or also those that are optional. direct
# argument controls if only dependent module directly requiring passed mods
# should be returned or its full dependent tree. nporeq argument tells if
# requirement loaded after their dependent should be returned. sat_constraint
# argument controls whether only the loaded module satisfying their constraint
# should be part or not of the resulting list. being_unload argument controls
# whether loaded modules in conflict with one or multiple modules from modlist
# should be added to the dependent list as these modules are currently being
# unloaded and these conflicting loaded modules should be refreshed.
proc getDependentLoadedModuleList {modlist {strong 1} {direct 1} {nporeq 0}\
   {loading 1} {sat_constraint 0} {being_unload 0}} {
   reportDebug "get loaded mod dependent of '$modlist' (strong=$strong,\
      direct=$direct, nporeq=$nporeq, loading=$loading,\
      sat_constraint=$sat_constraint, being_unload=$being_unload)"

   set deplist {}
   set fulllist $modlist
   # look at consistent requirements for unloading modules
   set unlonporeq [expr {$being_unload ? 0 : $nporeq}]
   foreach mod $modlist {
      # no duplicates or modules from query list
      appendNoDupToList fulllist {*}[getDirectDependentList $mod $strong\
         $unlonporeq $loading $fulllist]
   }

   if {$being_unload} {
      # invite modules in violation with mods to be part of the dependent list
      # with their own dependent modules as mod is being unloaded. Achieve so
      # by faking that conflict violation is gone
      foreach mod $modlist {
         set modconlist [getModuleLoadedConflict $mod]
         if {[llength $modconlist]} {
            unsetModuleConflictViolation $mod
            set conunvioarr($mod) $modconlist
            appendNoDupToList fulllist {*}$modconlist
         }
      }
   }
   set unloadingmodlist [getUnloadingModuleList]
   for {set i [llength $modlist]} {$i < [llength $fulllist]} {incr i 1} {
      set depmod [lindex $fulllist $i]

      # skip already added mod or mod violating constraints if asked
      if {!$sat_constraint || [areModuleConstraintsSatisfied $depmod\
         $nporeq]} {
         # get dependent mod of dep mod when looking at full dep tree
         if {!$direct} {
            appendNoDupToList fulllist {*}[getDirectDependentList $depmod\
               $strong $nporeq 0 $fulllist]
         }
         # avoid module currently unloading from result list
         if {$depmod ni $unloadingmodlist} {
            lappend deplist $depmod
         }
      }
   }

   # restore conflict violation if any
   if {[array exists conunvioarr]} {
      foreach conunvio [array names conunvioarr] {
         setModuleConflictViolation $conunvio $conunvioarr($conunvio)
      }
   }

   # sort complete result list to match both loaded and dependency orders
   set sortlist [sortModulePerLoadedAndDepOrder $deplist $nporeq $loading]
   reportDebug "got '$sortlist'"
   return $sortlist
}

# may given module be automatically unloaded
# unmodlist: pass a list of modules that are going to be unloaded
proc isModuleUnloadable {mod {unmodlist {}}} {
   set ret 1

   # module may be unloaded if it has been automatically loaded, it is not
   # tagged keep-loaded and it is not sticky.
   if {[isModuleTagged $mod loaded 1] || [isModuleTagged $mod keep-loaded\
      1] || [isModuleSticky $mod]} {
      set ret 0
   # there should no one requiring module or these dependent should be part of
   # the unloaded/unloading list
   } else {
      foreach depmod [getDirectDependentList $mod] {
         if {$depmod ni $unmodlist} {
            set ret 0
            break
         }
      }
   }

   return $ret
}

# gets the list of all loaded modules which are required by passed mod_list
# ordered by load position. a list of modules to exclude may be provided
# (usually to skip requirements that will be unloaded)
proc getRequiredLoadedModuleList {mod_list {excluded_mod_list {}}} {
   # search over all list of loaded modules, starting with passed module
   # list, then adding in turns their requirements
   set full_list $mod_list
   for {set i 0} {$i < [llength $full_list]} {incr i 1} {
      # gets the list of loaded modules which are required by depmod
      foreach req_mod_list $::g_moduleDepend([lindex $full_list $i]) {
         foreach req_mod $req_mod_list {
            if {$req_mod ni $excluded_mod_list} {
               appendNoDupToList full_list $req_mod
            }
         }
      }
   }

   # sort complete result list to match both loaded and dependency orders
   set sort_list [sortModulePerLoadedAndDepOrder [lrange $full_list [llength\
      $mod_list] end]]
   reportDebug "got '$sort_list'"
   return $sort_list
}

# how many settings bundle are currently saved
proc getSavedSettingsStackDepth {} {
   return [llength $::g_SAVE_g_loadedModules]
}

# 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_clearedEnvVars g_Aliases g_stateEnvVars g_stateAliases\
      g_stateFunctions g_Functions g_stateCompletes g_Completes\
      g_newXResources g_delXResources g_loadedModules g_loadedModuleFiles\
      g_loadedModuleVariant g_loadedModuleConflict g_loadedModulePrereq\
      g_loadedModulesRefresh g_loadedModuleAltname g_loadedModuleAutoAltname\
      g_loadedModuleAliasAltname g_moduleDepend g_dependHash\
      g_moduleNPODepend g_dependNPOHash g_prereqViolation\
      g_prereqNPOViolation g_conflictViolation g_moduleUnmetDep\
      g_unmetDepHash g_moduleEval g_moduleHiddenEval g_scanModuleVariant\
      g_savedLoReqOfReloadMod g_savedLoReqOfUnloadMod\
      g_loadedModulePrereqPath g_tagHash} {
      ##nagelfar ignore Suspicious variable name
      lappend ::g_SAVE_$var [array get ::$var]
   }
   # save non-array variable and indication if it was set
   foreach var {g_changeDir g_stdoutPuts g_prestdoutPuts g_return_text\
      g_uReqUnFromDepReList} {
      ##nagelfar ignore #4 Suspicious variable name
      if {[info exists ::$var]} {
         lappend ::g_SAVE_$var [list 1 [set ::$var]]
      } else {
         lappend ::g_SAVE_$var [list 0 {}]
      }
   }
   reportDebug "settings saved (#[getSavedSettingsStackDepth])"
}

proc popSettings {} {
   set flushedid [getSavedSettingsStackDepth]
   foreach var {env g_clearedEnvVars g_Aliases g_stateEnvVars g_stateAliases\
      g_stateFunctions g_Functions g_stateCompletes g_Completes\
      g_newXResources g_delXResources g_changeDir g_stdoutPuts\
      g_prestdoutPuts g_return_text g_loadedModules g_loadedModuleFiles\
      g_loadedModuleVariant g_loadedModuleConflict g_loadedModulePrereq\
      g_loadedModulesRefresh g_loadedModuleAltname g_loadedModuleAutoAltname\
      g_loadedModuleAliasAltname g_moduleDepend g_dependHash\
      g_moduleNPODepend g_dependNPOHash g_prereqViolation\
      g_prereqNPOViolation g_conflictViolation g_moduleUnmetDep\
      g_unmetDepHash g_moduleEval g_moduleHiddenEval g_scanModuleVariant\
      g_savedLoReqOfReloadMod g_savedLoReqOfUnloadMod g_uReqUnFromDepReList\
      g_loadedModulePrereqPath g_tagHash} {
      ##nagelfar ignore Suspicious variable name
      set ::g_SAVE_$var [lrange [set ::g_SAVE_$var] 0 end-1]
   }
   reportDebug "previously saved settings flushed (#$flushedid)"
}

proc restoreSettings {} {
   foreach var {g_clearedEnvVars g_Aliases g_stateEnvVars g_stateAliases\
      g_stateFunctions g_Functions g_stateCompletes g_Completes\
      g_newXResources g_delXResources g_loadedModules g_loadedModuleFiles\
      g_loadedModuleVariant g_loadedModuleConflict g_loadedModulePrereq\
      g_loadedModulesRefresh g_loadedModuleAltname g_loadedModuleAutoAltname\
      g_loadedModuleAliasAltname g_moduleDepend g_dependHash\
      g_moduleNPODepend g_dependNPOHash g_prereqViolation\
      g_prereqNPOViolation g_conflictViolation g_moduleUnmetDep\
      g_unmetDepHash g_moduleEval g_moduleHiddenEval g_scanModuleVariant\
      g_savedLoReqOfReloadMod g_savedLoReqOfUnloadMod\
      g_loadedModulePrereqPath g_tagHash} {
      # clear current $var arrays
      ##nagelfar ignore #5 Suspicious variable name
      if {[info exists ::$var]} {
         unset ::$var
         array set ::$var {}
      }
      array set ::$var [lindex [set ::g_SAVE_$var] end]
   }
   # specific restore mechanism for ::env as unsetting this array will make
   # Tcl stop monitoring env accesses and not update env variables anymore
   set envvarlist [list]
   foreach {var val} [lindex $::g_SAVE_env end] {
      lappend envvarlist $var
      interp-sync-env set $var $val
   }
   foreach var [array names ::env] {
      if {$var ni $envvarlist} {
         interp-sync-env unset $var
      }
   }
   # restore non-array variable if it was set
   foreach var {g_changeDir g_stdoutPuts g_prestdoutPuts g_return_text\
      g_uReqUnFromDepReList} {
      ##nagelfar ignore #6 Suspicious variable name
      if {[info exists ::$var]} {
         unset ::$var
      }
      lassign [lindex [set ::g_SAVE_$var] end] isdefined val
      if {$isdefined} {
         set ::$var $val
      }
   }
   reportDebug "previously saved settings restored\
      (#[getSavedSettingsStackDepth])"
}

# clear environment change related variables to undo modifications produced by
# evaluated modulefile(s)
proc flushEnvSettings {} {
   foreach var {g_Aliases g_stateEnvVars g_stateAliases g_stateFunctions\
      g_Functions g_stateCompletes g_Completes g_newXResources\
      g_delXResources g_changeDir g_stdoutPuts g_prestdoutPuts\
      g_return_text} {
      ##nagelfar ignore #2 Suspicious variable name
      if {[info exists ::$var]} {
         unset ::$var
      }
   }
}

# load modules passed as args designated as requirement
proc loadRequirementModuleList {tryload optional tag_list modulepath_list\
   args} {
   set ret 0
   set prereqloaded 0

   # calling procedure must have already parsed module specification in args
   set loadedmod_list {}
   foreach mod $args {
      # get all loaded or loading mod in args list
      if {[set loadedmod [getLoadedMatchingName $mod returnfirst 0 {}\
         $modulepath_list]] ne {} || [set loadedmod [getLoadedMatchingName\
         $mod returnfirst 1 {} $modulepath_list]] ne {}} {
         lappend loadedmod_list $loadedmod
      }
   }

   if {![llength $loadedmod_list]} {
      set imax [llength $args]
      # if prereq list specified, try to load first then
      # try next if load of first module not successful
      for {set i 0} {$i<$imax && !$prereqloaded} {incr i 1} {
         set arg [lindex $args $i]

         # hold output from current evaluation to catch 'module not found'
         # message that occurs outside of sub evaluation
         lappendState reportholdrecid [currentState msgrecordid]
         # hold output of each sub evaluation until they are all done to drop
         # those that failed if one succeed or if optional
         set curholdid load-$i-$arg
         lappendState reportholdid $curholdid
         if {[catch {set retlo [cmdModuleLoad reqlo 0 $tryload 0 $tag_list\
            $modulepath_list $arg]} errorMsg]} {
            # if an error is raised, release output and rethrow the error
            # (could be raised if no modulepath defined for instance)
            lpopState reportholdid
            lpopState reportholdrecid
            lappend holdidlist $curholdid report
            releaseHeldReport {*}$holdidlist
            knerror $errorMsg
         }
         # update return value if an issue occurred in cmdModuleLoad
         if {$retlo != 0} {
            set ret $retlo
         }
         lpopState reportholdid
         lpopState reportholdrecid

         if {[string length [getLoadedMatchingName $arg returnfirst 0 {}\
            $modulepath_list]]} {
            set prereqloaded 1
            # set previous reports to be dropped as this one succeed
            if {[info exists holdidlist]} {
               foreach {holdid action} $holdidlist {
                  lappend newholdidlist $holdid drop
               }
               set holdidlist $newholdidlist
            }
         }
         # drop report if not loaded and optional
         set action [expr {$prereqloaded || !$optional ? {report} : {drop}}]
         lappend holdidlist $curholdid $action
      }
      # output held messages
      releaseHeldReport {*}$holdidlist
   } else {
      set prereqloaded 1
      # apply missing tag to all loaded module found
      cmdModuleTag 0 0 $tag_list {*}$loadedmod_list
   }

   return [list $ret $prereqloaded]
}

# save reloading module properties before they vanish with unload phase
proc savePropsOfReloadingModule {mod} {
   set is_user_asked [isModuleTagged $mod loaded 1]
   set vr_list [getVariantList $mod 1 2]
   set tag_list [getTagList $mod]
   set extra_tag_list [getExtraTagList $mod]
   set conflict_list [getLoadedConflict $mod]
   set prereq_list [getLoadedPrereq $mod]
   set prereq_path_list [getLoadedPrereqPath $mod]
   set modpath [getModulepathFromLoadedOrLoadingModule $mod]
   set ::g_savedPropsOfReloadMod($mod) [list $is_user_asked $vr_list\
      $tag_list $extra_tag_list $conflict_list $prereq_list $prereq_path_list\
      $modpath]
}

proc getSavedPropsOfReloadingModule {mod} {
   return $::g_savedPropsOfReloadMod($mod)
}

# unload phase of a list of modules reload process
proc reloadModuleListUnloadPhase {mod_list {err_msg_tpl {}} {context\
   unload}} {
   # unload one by one to ensure same behavior whatever auto_handling state
   foreach mod [lreverse $mod_list] {
      if {[reloadModuleUnloadPhase $mod $err_msg_tpl $context]} {
         set mod_list [replaceFromList $mod_list $mod]
      }
   }
   return $mod_list
}

proc reloadModuleUnloadPhase {mod {err_msg_tpl {}} {context unload}} {
   # record hint that mod will be reloaded (useful in case mod is sticky)
   lappendState reloading_sticky $mod
   lappendState reloading_supersticky $mod
   savePropsOfReloadingModule $mod
   if {[set ret [cmdModuleUnload $context match 0 s 0 $mod]]} {
      # avoid failing module on load phase
      # if force state is enabled, cmdModuleUnload returns 0
      set err_msg [string map [list _MOD_ [getModuleDesignation loaded $mod]]\
         $err_msg_tpl]
      lpopState reloading_sticky
      lpopState reloading_supersticky
      # no process stop if ongoing reload command in continue behavior
      if {![isStateEqual commandname reload] || [commandAbortOnError]} {
         knerror $err_msg
      }
   }
   lpopState reloading_sticky
   lpopState reloading_supersticky
   return $ret
}

# load phase of a list of modules reload process
proc reloadModuleListLoadPhase {mod_list {errmsgtpl {}} {context load}} {
   if {![llength $mod_list]} {
      return
   }
   # loads are made with auto handling mode disabled to avoid disturbances
   # from a missing prereq automatically reloaded, so these module loads may
   # fail as prereq may not be satisfied anymore
   setConf auto_handling 0
   foreach mod $mod_list {
      lassign [getSavedPropsOfReloadingModule $mod] is_user_asked vr_list\
         tag_list extra_tag_list conflict_list prereq_list prereq_path_list\
         modpath
      # if an auto set default was excluded, module spec need parsing
      lassign [parseModuleSpecification 0 0 0 0 $mod {*}$vr_list] modnamevr

      set is_sticky [isModuleStickyFromTagList {*}$tag_list\
         {*}$extra_tag_list]

      # do not try to reload DepRe module if requirements are not satisfied
      # unless if sticky
      if {$context eq {depre} && ![isModuleLoadable $mod $modnamevr\
         $conflict_list $prereq_list $prereq_path_list $modpath] &&\
         !$is_sticky} {
         continue
      }

      # reload module with user asked property and extra tags preserved
      if {[cmdModuleLoad $context $is_user_asked 0 0 $extra_tag_list {}\
         $modnamevr]} {
         set errMsg [string map [list _MOD_ [getModuleDesignation spec\
            $modnamevr]] $errmsgtpl]
         if {$is_sticky} {
            set errMsg [string map {dependent {sticky dependent}} $errMsg]
         }
         # unless sticky no process stop if forced, or ongoing reload or
         # switch cmd in continue behavior
         if {!$is_sticky && ([getState force] || (([isStateEqual commandname\
            reload] || [isStateEqual commandname switch]) &&\
            ![commandAbortOnError]))} {
            # no msg for reload sub-cmd which provides an empty msg template
            reportWarning $errMsg
         # stop if one load fails unless force mode enabled
         } else {
            knerror $errMsg
         }
      }
   }
   setConf auto_handling 1
}

proc isModuleLoadable {mod mod_vr conflict_list prereq_list\
   prereq_path_list modpath} {
   setLoadedConflict $mod {*}$conflict_list
   set is_conflicting [llength [getModuleLoadedConflict $mod]]
   unsetLoadedConflict $mod

   if {$is_conflicting} {
      return 0
   }

   array set prereq_path_arr $prereq_path_list
   foreach prereq_arg $prereq_list {
      if {[info exists prereq_path_arr($prereq_arg)]} {
         set prereq_arg_path $prereq_path_arr($prereq_arg)
      } else {
         set prereq_arg_path {}
      }
      set is_requirement_loaded 0
      foreach req_mod $prereq_arg {
         # is requirement loaded, loading or optional
         if {[string length [getLoadedMatchingName $req_mod returnfirst 0 {}\
            $prereq_arg_path]] || [string length [getLoadedMatchingName\
            $req_mod returnfirst 1 {} $prereq_arg_path]] || $req_mod eq\
            $mod} {
            set is_requirement_loaded 1
            break
         }
      }
      if {!$is_requirement_loaded} {
         return 0
      }
   }

   # module is loadable if its modulepath is still enabled or if it can be
   # found in another modulepath (variant availability is currently not
   # checked by is-avail procedure)
   if {![is-used $modpath] && ![is-avail $mod_vr]} {
      return 0
   }

   return 1
}

proc isModuleStickyFromTagList {args} {
   return [expr {{super-sticky} in $args || ({sticky} in $args && ![getState\
      force])}]
}

# test if loaded module 'mod' is sticky and if stickiness definition applies
# to one of the reloading module
proc isStickinessReloading {mod reloading_mod_list {tag sticky}} {
   set res 0
   set mod_name_vers [getModuleNameAndVersFromVersSpec $mod]
   if {[isModuleTagged $mod_name_vers $tag 1]} {
      # sticky rules (module-tag definitions) applying to loaded module have
      # been evaluated when charging loaded environment
      set full_path_mod [getModulefileFromLoadedModule $mod_name_vers]
      set tag_rule_list [getModuleTagRuleList $mod $full_path_mod $tag]

      # no rule found (in env), means sticky applies to exact same module
      if {![llength $tag_rule_list]} {
         set mod_should_reload 1
      } else {
         set mod_should_reload 0
         # if tag specifically applies to fully qualified module or module
         # name and version, exact same module should be found in reload list
         foreach tag_rule $tag_rule_list {
            if {[modEq $tag_rule $mod equal 1 0 1] || [modEq $tag_rule\
               $full_path_mod equal 1 0 1]} {
               set mod_should_reload 1
               break
            }
         }
      }

      if {$mod_should_reload} {
         set res [expr {$mod in $reloading_mod_list}]
      } else {
         # check if a reloading module satisfies each sticky rules
         foreach tag_rule $tag_rule_list {
            set res 0
            foreach reloading_mod $reloading_mod_list {
               if {[modEq $tag_rule $reloading_mod eqstart 1 0 1]} {
                  set res 1
                  break
               }
            }
            if {!$res} {
               break
            }
         }
      }
      reportDebug "stickiness ($tag), applying to $tag_rule_list, is\
         reloading=$res"
   }

   return $res
}

proc isModuleSticky {mod} {
   return [expr {[isModuleTagged $mod super-sticky 1] || ([isModuleTagged\
      $mod sticky 1] && ![getState force])}]
}

proc saveLoadedReqOfUnloadingModule {unload_mod} {
   # fetch requirements of unloading module
   set ::g_savedLoReqOfUnloadMod($unload_mod)\
      [getRequiredLoadedModuleList [list $unload_mod]]
}

proc getLoadedReqOfUnloadingModuleList {} {
   set unloading_req_mod_list {}
   foreach unloading_mod [array names ::g_savedLoReqOfUnloadMod] {
      appendNoDupToList unloading_req_mod_list\
         {*}$::g_savedLoReqOfUnloadMod($unloading_mod)
   }
   return [sortModulePerLoadedAndDepOrder $unloading_req_mod_list]
}

proc clearLoadedReqOfUnloadingModuleList {} {
   array unset ::g_savedLoReqOfUnloadMod
}

proc saveLoadedReqOfReloadingModuleList {reload_mod_list unload_mod_list} {
   # fetch requirements of reloading modules skipping unloading ones
   foreach reload_mod $reload_mod_list {
      set ::g_savedLoReqOfReloadMod($reload_mod) [getRequiredLoadedModuleList\
         [list $reload_mod] $unload_mod_list]
   }
}

proc getLoadedReqOfReloadingModuleList {} {
   set reloading_req_mod_list {}
   foreach reloading_mod [array names ::g_savedLoReqOfReloadMod] {
      appendNoDupToList reloading_req_mod_list\
         {*}$::g_savedLoReqOfReloadMod($reloading_mod)
   }
   return [sortModulePerLoadedAndDepOrder $reloading_req_mod_list]
}

proc unsetLoadedReqOfReloadingModule {mod} {
   unset -nocomplain ::g_savedLoReqOfReloadMod($mod)
}

proc clearLoadedReqOfReloadingModuleList {} {
   array unset ::g_savedLoReqOfReloadMod
}

proc identityUReqUnFromDepRe {depre_list unload_mod_list} {
   foreach depre [lreverse $depre_list] {
      if {[isModuleUnloadable $depre $unload_mod_list]} {
         lappend unload_mod_list $depre
         lappend ::g_uReqUnFromDepReList $depre
      }
   }
}

proc clearUReqUnFromDepReList {} {
   unset -nocomplain ::g_uReqUnFromDepReList
}

proc getIdentifiedUReqUnFromDepRe {} {
   if {[info exists ::g_uReqUnFromDepReList]} {
      return $::g_uReqUnFromDepReList
   }
}

proc removeUReqUnFromDepReAndConvertEval {} {
   set depre_list [getDepReList]
   set urequn_from_depre_list [getIdentifiedUReqUnFromDepRe]
   lassign [getDiffBetweenList $depre_list $urequn_from_depre_list] depre_list
   setDepReList $depre_list
   foreach urequn_from_depre [lreverse $urequn_from_depre_list] {
      changeContextOfModuleEval $urequn_from_depre depun urequn
   }
}

proc getUReqUnModuleList {} {
   set unloadable_mod_list {}

   # add DepRe modules that are UReqUn modules and unset their requirements
   # from reloading list (to check them for potential UReqUn modules)
   foreach urequn_from_depre [getIdentifiedUReqUnFromDepRe] {
      lappend unloadable_mod_list $urequn_from_depre
      unsetLoadedReqOfReloadingModule $urequn_from_depre
   }

   set reloading_req_mod_list [getLoadedReqOfReloadingModuleList]
   # useless requirement unload modules are unloadable req of unloaded mods
   # treat lastly loaded module first to build unloadable module list
   foreach unloading_req_mod [lreverse [getLoadedReqOfUnloadingModuleList]] {
      if {$unloading_req_mod ni $reloading_req_mod_list &&\
         [isModuleUnloadable $unloading_req_mod $unloadable_mod_list]} {
         lappend unloadable_mod_list $unloading_req_mod
      }
   }
   # return result in loaded order
   return [lreverse $unloadable_mod_list]
}

proc unloadUReqUnModules {} {
   set urequn_list [getUReqUnModuleList]
   reportDebug "urequn mod list is '$urequn_list'"

   if {[llength $urequn_list]} {
      # DepRe: Dependent to Reload (modules optionally dependent or in
      # conflict with modname, DepUn or UReqUn modules + modules dependent of
      # a module part of this DepRe batch)
      set urequn_depre_list [getDependentLoadedModuleList $urequn_list 0 0 1\
         0 1 1]
      set urequn_depre_list [reloadModuleListUnloadPhase $urequn_depre_list\
         {Unload of dependent _MOD_ failed} depre_un]
      lprependDepReList $urequn_depre_list

      set urequn_list [lreverse $urequn_list]
      for {set i 0} {$i < [llength $urequn_list]} {incr i 1} {
         set unmod [lindex $urequn_list $i]
         if {[cmdModuleUnload urequn match 0 s 0 $unmod]} {
            # main unload process continues, but the UReqUn modules that are
            # required by unmod (whose unload failed) are withdrawn from
            # UReqUn module list
            lassign [getDiffBetweenList $urequn_list\
               [getRequiredLoadedModuleList [list $unmod]]] urequn_list
         }
      }
   }
}

proc getDepUnModuleList {mod} {
   set depun_npo_list [getDependentLoadedModuleList [list $mod] 1 0 1 0]
   set depun_list [getDependentLoadedModuleList [list $mod] 1 0 0 0]
   # look at both regular dependencies or No Particular Order dependencies:
   # use NPO result if situation can be healed with NPO dependencies, which
   # will be part of DepRe list to restore the correct loading order for them
   if {[llength $depun_npo_list] <= [llength $depun_list]} {
      set depun_list $depun_npo_list
   }
   return $depun_list
}

proc clearDepReList {} {
   unset -nocomplain ::g_depReList
}

proc getDepReList {} {
   if {[info exists ::g_depReList]} {
      return $::g_depReList
   }
}

proc setDepReList {mod_list} {
   set ::g_depReList $mod_list
}

proc lprependDepReList {mod_list} {
   lprepend ::g_depReList {*}$mod_list
}

proc unloadDepUnDepReModules {unload_mod_list reload_mod_list} {
   set err_msg_tpl {Unload of dependent _MOD_ failed}
   set unload_mod_list [sortModulePerLoadedAndDepOrder [list\
      {*}$unload_mod_list {*}$reload_mod_list]]
   lprependDepReList $reload_mod_list

   foreach unload_mod [lreverse $unload_mod_list] {
      if {$unload_mod in $reload_mod_list} {
         # an error is raised if an unload eval fails
         reloadModuleUnloadPhase $unload_mod $err_msg_tpl depre_un
      } else {
         if {[cmdModuleUnload depun match 0 s 0 $unload_mod]} {
            # stop if one unload fails unless force mode enabled
            set err_msg [string map [list _MOD_ [getModuleDesignation loaded\
               $unload_mod]] $err_msg_tpl]
            knerrorOrWarningIfForced $err_msg
         }
      }
   }
}

proc reloadDepReModules {} {
   set err_msg_tpl {Reload of dependent _MOD_ failed}
   set depre_list [getDepReList]
   reloadModuleListLoadPhase $depre_list $err_msg_tpl depre
}

# Fail unload attempt if module is sticky, unless if forced or reloading
# Also fail unload if mod is super-sticky even if forced, unless reloading
proc failOrSkipUnloadIfSticky {modname modfile} {
   # when loaded, tags applies to mod name and version (not with variant)
   set is_supersticky_not_reloading [expr {[isModuleTagged $modname\
      super-sticky 1 $modfile] && [currentState reloading_supersticky] ne\
      $modname}]
   set is_sticky_not_reloading [expr {[isModuleTagged $modname sticky 1\
      $modfile] && [currentState reloading_sticky] ne $modname &&\
      [currentState unloading_sticky] ne $modname}]
   set sticky_purge [expr {[getState commandname] eq {purge} ? [getConf\
      sticky_purge] : {}}]

   if {!$is_supersticky_not_reloading && $is_sticky_not_reloading &&\
      [getState force]} {
      reportWarning [getStickyForcedUnloadMsg]
   } elseif {$is_supersticky_not_reloading || $is_sticky_not_reloading} {
      set msg [getStickyUnloadMsg [expr {$is_supersticky_not_reloading ?\
         {super-sticky} : {sticky}}]]
      # no message if sticky_purge is set to silent
      switch -- $sticky_purge {
         error - {} {knerror $msg}
         warning {reportWarning $msg}
      }

      # skip unload without raising error
      return 1
   }

   return 0
}

# Define procedure to get how many parts between passed name and mod are equal
# Adapt procedure code whether icase is enabled or disabled
proc defineModStartNbProc {icase} {
   set procname modStartNbProc
   if {$icase} {
      append procname Icase
   }
   # define proc if not done yet or if it was defined for another context
   if {[info procs modStartNb] eq {} || $::g_modStartNb_proc ne $procname} {
      if {[info exists ::g_modStartNb_proc]} {
         # remove existing debug trace if any
         initProcReportTrace remove modStartNb
         rename ::modStartNb ::$::g_modStartNb_proc
      }
      ##nagelfar syntax modStartNb x x
      rename ::$procname ::modStartNb
      # set report traces if some debug mode enabled
      initProcReportTrace add modStartNb
      set ::g_modStartNb_proc $procname
   }
}

# alternative definitions of modStartNb proc
proc modStartNbProc {mod name} {
   # first compare against name's parent chunk by chunk
   set modname [getModuleNameFromVersSpec $name]
   if {$modname eq {.}} {
      set i 0
      set imax 0
   } else {
      set namesplit [split $modname /]
      set modsplit [split $mod /]
      set imax [tcl::mathfunc::min [llength $namesplit] [llength $modsplit]]
      for {set i 0} {$i < $imax} {incr i} {
         if {![string equal [lindex $modsplit $i] [lindex $namesplit $i]]} {
            break
         }
      }
   }
   # if name's parent matches check if full name also matches
   if {$i == $imax && [modEq $name $mod eqstart]} {
      incr i
   }
   return $i
}
proc modStartNbProcIcase {mod name} {
   set modname [getModuleNameFromVersSpec $name]
   if {$modname eq {.}} {
      set i 0
      set imax 0
   } else {
      set namesplit [split $modname /]
      set modsplit [split $mod /]
      ##nagelfar ignore #2 Badly formed if statement
      set imax [if {[llength $namesplit] < [llength $modsplit]} {llength\
         $namesplit} {llength $modsplit}]
      for {set i 0} {$i < $imax} {incr i} {
         if {![string equal -nocase [lindex $modsplit $i] [lindex $namesplit\
            $i]]} {
            break
         }
      }
   }
   if {$i == $imax && [modEq $name $mod eqstart]} {
      incr i
   }
   return $i
}

# Define procedure to compare module names set as array keys against pattern.
# Adapt procedure code whether implicit_default is enabled or disabled
proc defineGetEqArrayKeyProc {icase extdfl impdfl} {
   set procname getEqArrayKeyProc
   if {$impdfl} {
      append procname Impdfl
   }

   # define proc if not done yet or if it was defined for another context
   if {[info procs getEqArrayKey] eq {} || $::g_getEqArrayKey_proc ne\
      $procname} {
      if {[info exists ::g_getEqArrayKey_proc]} {
         # remove existing debug trace if any
         initProcReportTrace remove getEqArrayKey
         rename ::getEqArrayKey ::$::g_getEqArrayKey_proc
      }
      ##nagelfar syntax getEqArrayKey x x
      rename ::$procname ::getEqArrayKey
      # set report traces if some debug mode enabled
      initProcReportTrace add getEqArrayKey
      set ::g_getEqArrayKey_proc $procname
   }

   # also define modEq which is called by getEqArrayKey
   defineModEqProc $icase $extdfl
}

# alternative definitions of getEqArrayKey proc
proc getEqArrayKeyProcImpdfl {arrname name} {
   set icase [isIcase]
   upvar $arrname arr

   # extract single module specified if any
   lassign [getModuleVersSpec $name] mod modname
   # check name eventual icase match
   set mod [getArrayKey arr [string trimright $mod /] $icase]

   if {$mod ne {} && [info exists arr($mod)]} {
      set match $mod
   } else {
      set mlist {}
      foreach elt [array names arr] {
         if {[modEq $name $elt]} {
            lappend mlist $elt
         }
      }
      if {[llength $mlist] == 1} {
         set match [lindex $mlist 0]
      # in case multiple modules match query, check directory default and
      # return it if it is part of match list, elsewhere return highest result
      } elseif {[llength $mlist] > 1} {
         # get corresponding icase parent directory
         set pname [getArrayKey arr $modname $icase]
         if {[info exists arr($pname)]} {
            set dfl $pname/[lindex $arr($pname) 1]
         }
         # resolve symbolic version entries
         foreach elt $mlist {
            if {[lindex $arr($elt) 0] eq {version}} {
               lappend mrlist [lindex $arr($elt) 1]
            } else {
               lappend mrlist $elt
            }
         }
         if {[info exists dfl] && $dfl in $mrlist} {
            set match $dfl
         } else {
            set match [lindex [lsort -dictionary $mrlist] end]
         }
      }
   }
   if {[info exists match]} {
      reportDebug "key '$match' in array '$arrname' matches '$name'"
      set name $match
   }
   return $name
}
proc getEqArrayKeyProc {arrname name} {
   set icase [isIcase]
   upvar $arrname arr

   lassign [getModuleVersSpec $name] mod modname cmpspec versspec modnamere\
      modescglob modroot variantlist modnvspec
   # check name eventual icase match
   set mod [getArrayKey arr [string trimright $mod /] $icase]

   if {$mod ne {} && [info exists arr($mod)]} {
      set match $mod
   } else {
      set mlist {}
      foreach elt [array names arr] {
         if {[modEq $name $elt]} {
            lappend mlist $elt
         }
      }
      # must have a default part of result even if only one result
      if {[llength $mlist] >= 1} {
         # get corresponding icase parent directory
         set pname [getArrayKey arr $modname $icase]
         if {[info exists arr($pname)]} {
            set dfl $pname/[lindex $arr($pname) 1]
         }
         # resolve symbolic version entries
         foreach elt $mlist {
            if {[lindex $arr($elt) 0] eq {version}} {
               lappend mrlist [lindex $arr($elt) 1]
            } else {
               lappend mrlist $elt
            }
         }
         if {[info exists dfl] && $dfl in $mrlist} {
            set match $dfl
         } else {
            # raise error as no default part of result
            upvar retlist retlist
            set retlist [list {} $modnvspec $name none "No default version\
               defined for '$name'"]
         }
      }
   }
   if {[info exists match]} {
      reportDebug "key '$match' in array '$arrname' matches '$name'"
      set name $match
   }
   return $name
}

# Check a module name does match query at the depth level expressed in query
# when search mode is not contains. Define procedure on the fly to adapt its
# code to search configuration option and querydepth and test mode params.
proc defineDoesModMatchAtDepthProc {contains querydepth test} {
   set procprops $contains:$querydepth:$test

   # define proc if not done yet or if it was defined for another context
   if {[info procs doesModMatchAtDepth] eq {} ||\
      $::g_doesModMatchAtDepth_procprops ne $procprops} {
      if {[info exists ::g_doesModMatchAtDepth_procprops]} {
         # remove existing debug trace if any
         initProcReportTrace remove doesModMatchAtDepth
         rename ::doesModMatchAtDepth {}
      }
      set ::g_doesModMatchAtDepth_procprops $procprops

      # define optimized procedure
      if {$contains} {
         set atdepth {$mod}
      } else {
         set atdepth "\[join \[lrange \[split \$mod /\] 0 $querydepth\] /\]"
      }
      ##nagelfar syntax doesModMatchAtDepth x
      ##nagelfar ignore Non constant argument to proc
      proc doesModMatchAtDepth {mod} "return \[modEqStatic $atdepth $test *\]"
      # set report traces if some debug mode enabled
      initProcReportTrace add doesModMatchAtDepth
   }
}

# Define procedure to check module version equals pattern. Adapt procedure
# code whether icase and extended_default are enabled or disabled
proc defineModVersCmpProc {icase extdfl} {
   set procname modVersCmpProc
   if {$icase} {
      append procname Icase
   }
   if {$extdfl} {
      append procname Extdfl
   }

   # define proc if not done yet or if it was defined for another context
   if {[info procs modVersCmp] eq {} || $::g_modVersCmp_proc ne $procname} {
      if {[info exists ::g_modVersCmp_proc]} {
         # remove existing debug trace if any
         initProcReportTrace remove modVersCmp
         rename ::modVersCmp ::$::g_modVersCmp_proc
      }
      ##nagelfar syntax modVersCmp x x x x x?
      rename ::$procname ::modVersCmp
      # set report traces if some debug mode enabled
      initProcReportTrace add modVersCmp
      set ::g_modVersCmp_proc $procname
   }
}

# alternative definitions of modVersCmp proc
proc modVersCmpProc {cmpspec versspec modvers test {psuf {}}} {
   set ret 0
   switch -- $cmpspec {
      in {
         # check each verspec in list until match
         foreach inspec $versspec {
            lassign $inspec incmp invers
            if {[set ret [modVersCmp $incmp $invers $modvers $test $psuf]]} {
               break
            }
         }
      }
      eq {
         append versspec $psuf
         if {$test eq {eqstart}} {
            set ret [string equal -length [string length $versspec/]\
               $versspec/ $modvers/]
         } else {
            ##nagelfar ignore Non static subcommand
            set ret [string $test $versspec $modvers]
         }
      }
      ge {
         # as we work here on a version range: psuf suffix is ignored, checks
         # are always extended_default-enabled (as 1.2 includes 1.2.12 for
         # instance) and equal, eqstart and match tests are equivalent
         set ret [expr {[isVersion $modvers] && ([versioncmp $modvers\
            $versspec] != -1 || [string match $versspec.* $modvers])}]
      }
      le {
         # 'ge' comment also applies here
         set ret [expr {[isVersion $modvers] && ([versioncmp $versspec\
            $modvers] != -1 || [string match $versspec.* $modvers])}]
      }
      be {
         # 'ge' comment also applies here
         lassign $versspec lovers hivers
         set ret [expr {[isVersion $modvers] && ([versioncmp $modvers\
            $lovers] != -1 || [string match $lovers.* $modvers]) &&\
            ([versioncmp $hivers $modvers] != -1 || [string match\
            $hivers.* $modvers])}]
      }
   }
   return $ret
}
proc modVersCmpProcIcase {cmpspec versspec modvers test {psuf {}}} {
   set ret 0
   switch -- $cmpspec {
      in {
         foreach inspec $versspec {
            lassign $inspec incmp invers
            if {[set ret [modVersCmp $incmp $invers $modvers $test $psuf]]} {
               break
            }
         }
      }
      eq {
         append versspec $psuf
         if {$test eq {eqstart}} {
            set ret [string equal -nocase -length [string length $versspec/]\
               $versspec/ $modvers/]
         } else {
            ##nagelfar ignore Non static subcommand
            set ret [string $test -nocase $versspec $modvers]
         }
      }
      ge {
         set ret [expr {[isVersion $modvers] && ([versioncmp $modvers\
            $versspec] != -1 || [string match -nocase $versspec.* $modvers])}]
      }
      le {
         set ret [expr {[isVersion $modvers] && ([versioncmp $versspec\
            $modvers] != -1 || [string match -nocase $versspec.* $modvers])}]
      }
      be {
         lassign $versspec lovers hivers
         set ret [expr {[isVersion $modvers] && ([versioncmp $modvers\
            $lovers] != -1 || [string match $lovers.* $modvers]) &&\
            ([versioncmp $hivers $modvers] != -1 || [string match -nocase\
            $hivers.* $modvers])}]
      }
   }
   return $ret
}
proc modVersCmpProcExtdfl {cmpspec versspec modvers test {psuf {}}} {
   set ret 0
   switch -- $cmpspec {
      in {
         foreach inspec $versspec {
            lassign $inspec incmp invers
            if {[set ret [modVersCmp $incmp $invers $modvers $test $psuf]]} {
               break
            }
         }
      }
      eq {
         append versspec $psuf
         if {$test eq {eqstart}} {
            set ret [string equal -length [string length $versspec/]\
               $versspec/ $modvers/]
         } else {
            ##nagelfar ignore Non static subcommand
            set ret [string $test $versspec $modvers]
         }
         if {!$ret && [string match $versspec.* $modvers]} {
            set ret 1
         }
      }
      ge {
         set ret [expr {[isVersion $modvers] && ([versioncmp $modvers\
            $versspec] != -1 || [string match $versspec.* $modvers])}]
      }
      le {
         set ret [expr {[isVersion $modvers] && ([versioncmp $versspec\
            $modvers] != -1 || [string match $versspec.* $modvers])}]
      }
      be {
         lassign $versspec lovers hivers
         set ret [expr {[isVersion $modvers] && ([versioncmp $modvers\
            $lovers] != -1 || [string match $lovers.* $modvers]) &&\
            ([versioncmp $hivers $modvers] != -1 || [string match\
            $hivers.* $modvers])}]
      }
   }
   return $ret
}
proc modVersCmpProcIcaseExtdfl {cmpspec versspec modvers test {psuf {}}} {
   set ret 0
   switch -- $cmpspec {
      in {
         foreach inspec $versspec {
            lassign $inspec incmp invers
            if {[set ret [modVersCmp $incmp $invers $modvers $test $psuf]]} {
               break
            }
         }
      }
      eq {
         append versspec $psuf
         if {$test eq {eqstart}} {
            set ret [string equal -nocase -length [string length $versspec/]\
               $versspec/ $modvers/]
         } else {
            ##nagelfar ignore Non static subcommand
            set ret [string $test -nocase $versspec $modvers]
         }
         if {!$ret && [string match -nocase $versspec.* $modvers]} {
            set ret 1
         }
      }
      ge {
         set ret [expr {[isVersion $modvers] && ([versioncmp $modvers\
            $versspec] != -1 || [string match -nocase $versspec.* $modvers])}]
      }
      le {
         set ret [expr {[isVersion $modvers] && ([versioncmp $versspec\
            $modvers] != -1 || [string match -nocase $versspec.* $modvers])}]
      }
      be {
         lassign $versspec lovers hivers
         set ret [expr {[isVersion $modvers] && ([versioncmp $modvers\
            $lovers] != -1 || [string match $lovers.* $modvers]) &&\
            ([versioncmp $hivers $modvers] != -1 || [string match -nocase\
            $hivers.* $modvers])}]
      }
   }
   return $ret
}

proc modVariantCmp {pvrlist modvrlist {missmean 0}} {
   set ret 1
   # missing variant in mod spec means default value
   if {$missmean == 1} {
      foreach {modvrname modvrval modvrisdfl} $modvrlist {
         set modvrarr($modvrname) $modvrval
         set modvrisdflarr($modvrname) $modvrisdfl
      }
   } else {
      array set modvrarr $modvrlist
      # if missmean == 2 pattern is mod, thus missing variant on mod is ok
      # it is used for extra specifier where pattern is definition inside
      # modulefile and mod is extra specifier defined on command line
   }
   foreach pvr $pvrlist {
      set pvrarr([lindex $pvr 0]) [lindex $pvr 3]
   }

   # no match if a specified variant is not found among module variants (and
   # if miss is not ok) or if the value differs
   foreach vrname [array names pvrarr] {
      if {(![info exists modvrarr($vrname)] && $missmean != 2) || ([info\
         exists modvrarr($vrname)] && $pvrarr($vrname) ne\
         $modvrarr($vrname))} {
         set ret 0
         break
      }
   }

   # if an unset variant on pattern means variant default value pattern and
   # mod are not equal if variant unset on pattern and non-default value is
   # set for variant on mod
   if {$missmean == 1} {
      foreach vrname [array names modvrisdflarr] {
         if {!$modvrisdflarr($vrname) && ![info exists pvrarr($vrname)]} {
            set ret 0
            break
         }
      }
   }

   return $ret
}

# Setup a hardwire version of modEq procedure called modEqStatic. This
# optimized procedure already knows the module pattern to compare to, whose
# specification has already been resolved at procedure definition time, which
# saves lot of processing time.
# modEqStatic does not compare against loaded modules so it has no need to
# compare variants set on module specification
proc defineModEqStaticProc {icase extdfl modspec} {
   set procprops $icase:$extdfl:$modspec

   # define proc if not done yet or if it was defined for another context
   if {[info procs modEqStatic] eq {} || $::g_modEqStatic_procprops ne\
      $procprops} {
      if {[info exists ::g_modEqStatic_procprops]} {
         # remove existing debug trace if any
         initProcReportTrace remove modEqStatic
         rename ::modEqStatic {}
      } else {
         # also define modVersCmp which is called by modEqStatic
         defineModVersCmpProc $icase $extdfl
      }
      set ::g_modEqStatic_procprops $procprops

      # define optimized procedure
      lassign [getModuleVersSpec $modspec] pmod pmodname cmpspec versspec\
         pmodnamere pmodescglob
      # trim dup trailing / char and adapt pmod suffix if it starts with /
      if {[string index $pmod end] eq {/}} {
         set pmod [string trimright $pmod /]/
         set endwslash 1
      } else {
         set endwslash 0
      }
      set nocasearg [expr {$icase ? {-nocase } : {}}]
      set pmodnameslen [string length $pmodname/]
      if {$pmod ne {} || $modspec eq {}} {
         set procbody "
            set pmod {$pmod}
            if {\$psuf ne {}} {
               if {$endwslash && \[string index \$psuf 0\] eq {/}} {
                  append pmod \[string range \$psuf 1 end\]
               } else {
                  append pmod \$psuf
               }
            }
            if {\$test eq {eqstart}} {
               set ret \[string equal $nocasearg-length \[string length\
                  \$pmod/\] \$pmod/ \$mod/\]
            } else {
               if {\$test eq {matchin}} {
                  set test match
                  set pmod *\$pmod
               }
               set ret \[string \$test $nocasearg\$pmod \$mod\]
            }"
         if {$extdfl} {
            append procbody "
               if {!\$ret && \[string first / \$pmod\] != -1} {
                  if {\$test eq {match}} {
                     set pmodextdfl \$pmod.*
                  } else {
                     set pmodextdfl {$pmodescglob.*}
                  }
                  set ret \[string match $nocasearg\$pmodextdfl \$mod\]
               }"
         }
      } else {
         set procbody "
            set pmodname {$pmodname}
            set pmodnamere {$pmodnamere}
            if {\$test eq {matchin}} {
               set test match
               if {\$pmodnamere ne {}} {
                  set pmodnamere .*\$pmodnamere
               } else {
                  set pmodnamere {.*$pmodname}
               }
            }
            if {(\$pmodnamere ne {} && \$test eq {match} && \[regexp\
               $nocasearg (^\$pmodnamere)/ \$mod/ rematch pmodname\]) ||\
               \[string equal $nocasearg -length $pmodnameslen {$pmodname/}\
               \$mod/\]} {
               set modvers \[string range \$mod \[string length \$pmodname/\]\
                  end\]
               set ret \[modVersCmp {$cmpspec} {$versspec} \$modvers \$test\
                  \$psuf\]
            } else {
               set ret 0
            }"
      }
      append procbody "
         return \$ret"
      ##nagelfar syntax modEqStatic x x? x?
      ##nagelfar ignore Non constant argument to proc
      proc modEqStatic {mod {test equal} {psuf {}}} $procbody
      # set report traces if some debug mode enabled
      initProcReportTrace add modEqStatic
   }
}

# Define procedure to check module name equals pattern. Adapt procedure
# code whether icase and extended_default are enabled or disabled
proc defineModEqProc {icase extdfl {loadedmod 0}} {
   set procname modEqProc
   if {$icase} {
      append procname Icase
   }
   if {$extdfl} {
      append procname Extdfl
   }

   # define proc if not done yet or if it was defined for another context
   if {[info procs modEq] eq {} || $::g_modEq_proc ne $procname} {
      if {[info exists ::g_modEq_proc]} {
         # remove existing debug trace if any
         initProcReportTrace remove modEq
         rename ::modEq ::$::g_modEq_proc
      }
      ##nagelfar syntax modEq x x x? x? x? x? x? x?
      rename ::$procname ::modEq
      # set report traces if some debug mode enabled
      initProcReportTrace add modEq
      set ::g_modEq_proc $procname
   }

   # also define modVersCmp which is called by modEq
   defineModVersCmpProc $icase $extdfl

   # comparing against loaded modules requires to know their alternative names
   if {$loadedmod} {
      cacheCurrentModules
   }
}

# alternative definitions of modEq proc
proc modEqProc {pattern mod {test equal} {trspec 1} {ismodlo 0} {vrcmp 0}\
   {modvrlist 0} {psuf {}}} {
   # extract specified module name from name and version spec
   if {$trspec} {
      lassign [getModuleVersSpec $pattern] pmod pmodname cmpspec versspec\
         pmodnamere pmodescglob pmodroot pvrlist
   } else {
      set pmod $pattern
   }
   # trim dup trailing / char and adapt pmod suffix if it starts with /
   if {[string index $pmod end] eq {/}} {
      set pmod [string trimright $pmod /]/
      set endwslash 1
   } else {
      set endwslash 0
   }
   # get alternative names if mod is loading(1) or loaded(2)
   set altlist [switch -- $ismodlo {
      7 {getLoadedAltname $mod {alias}}
      6 {getLoadedAltname $mod {sym autosym}}
      5 {getAvailListFromVersSpec $mod}
      4 {getAllModuleResolvedName $mod 0 {} 1}
      3 {getLoadedAltAndSimplifiedName $mod}
      2 {getLoadedAltname $mod}
      1 {getAllModuleResolvedName $mod}
      0 {list}}]
   # fetch variant definition from spec if not loaded/loading
   if {$vrcmp && $ismodlo in {0 5}} {
      set modvrlist [getVariantList $mod 0 0 1]
      set mod [getModuleNameAndVersFromVersSpec $mod]
   }
   # specified module can be translated in a simple mod name/vers or is empty
   if {$pmod ne {} || $pattern eq {}} {
      if {$psuf ne {}} {
         if {$endwslash && [string index $psuf 0] eq {/}} {
            append pmod [string range $psuf 1 end]
         } else {
            append pmod $psuf
         }
      }
      if {$test eq {eqstart}} {
         set ret [string equal -length [string length $pmod/] $pmod/ $mod/]
         # apply comparison to alternative names if any and no match for mod
         if {!$ret && [llength $altlist]} {
            foreach alt $altlist {
               if {[set ret [string equal -length [string length $pmod/]\
                  $pmod/ $alt/]]} {
                  break
               }
            }
         }
      } else {
         # contains test
         if {$test eq {matchin}} {
            set test match
            set pmod *$pmod
         } elseif {$test eq {eqspec}} {
            set test equal
         }
         ##nagelfar ignore Non static subcommand
         set ret [string $test $pmod $mod]
         # apply comparison to alternative names if any and no match for mod
         if {!$ret && [llength $altlist]} {
            foreach alt $altlist {
               ##nagelfar ignore Non static subcommand
               if {[set ret [string $test $pmod $alt]]} {
                  break
               }
            }
         }
      }
   } elseif {$test eq {eqspec}} {
      # test equality against all version described in spec (list or range
      # boundaries), trspec is considered enabled and psuf empty
      foreach pmod [getAllModulesFromVersSpec $pattern] {
         if {[set ret [string equal $pmod $mod]]} {
            break
         }
      }
   } else {
      # contains test
      if {$test eq {matchin}} {
         set test match
         if {$pmodnamere ne {}} {
            set pmodnamere .*$pmodnamere
         } else {
            set pmodnamere .*$pmodname
         }
      }
      # for more complex specification, first check if module name matches
      # use a regexp test if module name contains wildcard characters
      if {($pmodnamere ne {} && $test eq {match} && [regexp (^$pmodnamere)/\
         $mod/ rematch pmodname]) || [string equal -length [string length\
         $pmodname/] $pmodname/ $mod/]} {
         # then compare versions
         set modvers [string range $mod [string length $pmodname/] end]
         set ret [modVersCmp $cmpspec $versspec $modvers $test $psuf]
      } else {
         set ret 0
      }
      # apply comparison to alternative names if any and no match for mod
      if {!$ret && [llength $altlist]} {
         foreach alt $altlist {
            if {($pmodnamere ne {} && $test eq {match} && [regexp\
               (^$pmodnamere)/ $alt/ rematch pmodname]) || [string equal\
               -length [string length $pmodname/] $pmodname/ $alt/]} {
               # then compare versions
               set modvers [string range $alt [string length $pmodname/] end]
               if {[set ret [modVersCmp $cmpspec $versspec $modvers $test\
                  $psuf]]} {
                  break
               }
            }
         }
      }
   }
   # check if variant specified matches those of selected loaded/ing module
   if {$ret && $vrcmp && $ismodlo ni {3 5} && [llength $pvrlist]} {
      if {$modvrlist eq {0}} {
         set modvrlist [getVariantList $mod]
      }
      set ret [modVariantCmp $pvrlist $modvrlist]
   # variant miss means variant default val when comparing collection content
   } elseif {$ret && $vrcmp && $ismodlo == 3} {
      set ret [modVariantCmp $pvrlist [getVariantList $mod 3] 1]
   # variant miss is ok when comparing an extra specifier passed as mod
   } elseif {$ret && $vrcmp && $ismodlo == 5} {
      set ret [modVariantCmp $pvrlist $modvrlist 2]
   }
   return $ret
}
proc modEqProcIcase {pattern mod {test equal} {trspec 1} {ismodlo 0} {vrcmp\
   0} {modvrlist 0} {psuf {}}} {
   if {$trspec} {
      lassign [getModuleVersSpec $pattern] pmod pmodname cmpspec versspec\
         pmodnamere pmodescglob pmodroot pvrlist
   } else {
      set pmod $pattern
   }
   if {[string index $pmod end] eq {/}} {
      set pmod [string trimright $pmod /]/
      set endwslash 1
   } else {
      set endwslash 0
   }
   set altlist [switch -- $ismodlo {
      7 {getLoadedAltname $mod {alias}}
      6 {getLoadedAltname $mod {sym autosym}}
      5 {getAvailListFromVersSpec $mod}
      4 {getAllModuleResolvedName $mod 0 {} 1}
      3 {getLoadedAltAndSimplifiedName $mod}
      2 {getLoadedAltname $mod}
      1 {getAllModuleResolvedName $mod}
      0 {list}}]
   if {$vrcmp && $ismodlo in {0 5}} {
      set modvrlist [getVariantList $mod 0 0 1]
      set mod [getModuleNameAndVersFromVersSpec $mod]
   }
   if {$pmod ne {} || $pattern eq {}} {
      if {$psuf ne {}} {
         if {$endwslash && [string index $psuf 0] eq {/}} {
            append pmod [string range $psuf 1 end]
         } else {
            append pmod $psuf
         }
      }
      if {$test eq {eqstart}} {
         set ret [string equal -nocase -length [string length $pmod/] $pmod/\
            $mod/]
         if {!$ret && [llength $altlist]} {
            foreach alt $altlist {
               if {[set ret [string equal -nocase -length [string length\
                  $pmod/] $pmod/ $alt/]]} {
                  break
               }
            }
         }
      } else {
         # contains test
         if {$test eq {matchin}} {
            set test match
            set pmod *$pmod
         } elseif {$test eq {eqspec}} {
            set test equal
         }
         ##nagelfar ignore Non static subcommand
         set ret [string $test -nocase $pmod $mod]
         if {!$ret && [llength $altlist]} {
            foreach alt $altlist {
               ##nagelfar ignore Non static subcommand
               if {[set ret [string $test -nocase $pmod $alt]]} {
                  break
               }
            }
         }
      }
   } elseif {$test eq {eqspec}} {
      # test equality against all version described in spec (list or range
      # boundaries), trspec is considered enabled and psuf empty
      foreach pmod [getAllModulesFromVersSpec $pattern] {
         if {[set ret [string equal -nocase $pmod $mod]]} {
            break
         }
      }
   } else {
      # contains test
      if {$test eq {matchin}} {
         set test match
         if {$pmodnamere ne {}} {
            set pmodnamere .*$pmodnamere
         } else {
            set pmodnamere .*$pmodname
         }
      }
      # for more complex specification, first check if module name matches
      # use a regexp test if module name contains wildcard characters
      if {($pmodnamere ne {} && $test eq {match} && [regexp -nocase\
         (^$pmodnamere)/ $mod/ rematch pmodname]) || [string equal -nocase\
         -length [string length $pmodname/] $pmodname/ $mod/]} {
         # then compare versions
         set modvers [string range $mod [string length $pmodname/] end]
         set ret [modVersCmp $cmpspec $versspec $modvers $test $psuf]
      } else {
         set ret 0
      }
      if {!$ret && [llength $altlist]} {
         foreach alt $altlist {
            if {($pmodnamere ne {} && $test eq {match} && [regexp -nocase\
               (^$pmodnamere)/ $alt/ rematch pmodname]) || [string equal\
               -nocase -length [string length $pmodname/] $pmodname/ $alt/]} {
               # then compare versions
               set modvers [string range $alt [string length $pmodname/] end]
               if {[set ret [modVersCmp $cmpspec $versspec $modvers $test\
                  $psuf]]} {
                  break
               }
            }
         }
      }
   }
   if {$ret && $vrcmp && $ismodlo ni {3 5} && [llength $pvrlist]} {
      if {$modvrlist eq {0}} {
         set modvrlist [getVariantList $mod]
      }
      set ret [modVariantCmp $pvrlist $modvrlist]
   } elseif {$ret && $vrcmp && $ismodlo == 3} {
      set ret [modVariantCmp $pvrlist [getVariantList $mod 3] 1]
   } elseif {$ret && $vrcmp && $ismodlo == 5} {
      set ret [modVariantCmp $pvrlist $modvrlist 2]
   }
   return $ret
}
proc modEqProcExtdfl {pattern mod {test equal} {trspec 1} {ismodlo 0} {vrcmp\
   0} {modvrlist 0} {psuf {}}} {
   if {$trspec} {
      lassign [getModuleVersSpec $pattern] pmod pmodname cmpspec versspec\
         pmodnamere pmodescglob pmodroot pvrlist
   } else {
      set pmod $pattern
   }
   if {[string index $pmod end] eq {/}} {
      set pmod [string trimright $pmod /]/
      set endwslash 1
   } else {
      set endwslash 0
   }
   set altlist [switch -- $ismodlo {
      7 {getLoadedAltname $mod {alias}}
      6 {getLoadedAltname $mod {sym autosym}}
      5 {getAvailListFromVersSpec $mod}
      4 {getAllModuleResolvedName $mod 0 {} 1}
      3 {getLoadedAltAndSimplifiedName $mod}
      2 {getLoadedAltname $mod}
      1 {getAllModuleResolvedName $mod}
      0 {list}}]
   if {$vrcmp && $ismodlo in {0 5}} {
      set modvrlist [getVariantList $mod 0 0 1]
      set mod [getModuleNameAndVersFromVersSpec $mod]
   }
   if {$pmod ne {} || $pattern eq {}} {
      if {$psuf ne {}} {
         if {$endwslash && [string index $psuf 0] eq {/}} {
            append pmod [string range $psuf 1 end]
         } else {
            append pmod $psuf
         }
      }
      if {$test eq {eqstart}} {
         set ret [string equal -length [string length $pmod/] $pmod/ $mod/]
         if {!$ret && [llength $altlist]} {
            foreach alt $altlist {
               if {[set ret [string equal -length [string length $pmod/]\
                  $pmod/ $alt/]]} {
                  break
               }
            }
         }
      } else {
         # contains test
         if {$test eq {matchin}} {
            set test match
            set pmod *$pmod
         } elseif {$test eq {eqspec}} {
            set test equal
            set eqspec 1
         }
         ##nagelfar ignore Non static subcommand
         set ret [string $test $pmod $mod]
         if {!$ret && [llength $altlist]} {
            foreach alt $altlist {
               ##nagelfar ignore Non static subcommand
               if {[set ret [string $test $pmod $alt]]} {
                  break
               }
            }
         }
      }
      # try the extended default match if not root module and not eqspec test
      if {![info exists eqspec] && !$ret && [string first / $pmod] != -1} {
         if {$test eq {match}} {
            set pmodextdfl $pmod.*
         } else {
            set pmodextdfl $pmodescglob.*
         }
         set ret [string match $pmodextdfl $mod]
         if {!$ret && [llength $altlist]} {
            foreach alt $altlist {
               if {[set ret [string match $pmodextdfl $alt]]} {
                  break
               }
            }
         }
      }
   } elseif {$test eq {eqspec}} {
      # test equality against all version described in spec (list or range
      # boundaries), trspec is considered enabled and psuf empty
      foreach pmod [getAllModulesFromVersSpec $pattern] {
         if {[set ret [string equal $pmod $mod]]} {
            break
         }
      }
   } else {
      # contains test
      if {$test eq {matchin}} {
         set test match
         if {$pmodnamere ne {}} {
            set pmodnamere .*$pmodnamere
         } else {
            set pmodnamere .*$pmodname
         }
      }
      # for more complex specification, first check if module name matches
      # use a regexp test if module name contains wildcard characters
      if {($pmodnamere ne {} && $test eq {match} && [regexp (^$pmodnamere)/\
         $mod/ rematch pmodname]) || [string equal -length [string length\
         $pmodname/] $pmodname/ $mod/]} {
         # then compare versions
         set modvers [string range $mod [string length $pmodname/] end]
         set ret [modVersCmp $cmpspec $versspec $modvers $test $psuf]
      } else {
         set ret 0
      }
      if {!$ret && [llength $altlist]} {
         foreach alt $altlist {
            if {($pmodnamere ne {} && $test eq {match} && [regexp\
               (^$pmodnamere)/ $alt/ rematch pmodname]) || [string equal\
               -length [string length $pmodname/] $pmodname/ $alt/]} {
               # then compare versions
               set modvers [string range $alt [string length $pmodname/] end]
               if {[set ret [modVersCmp $cmpspec $versspec $modvers $test\
                  $psuf]]} {
                  break
               }
            }
         }
      }
   }
   if {$ret && $vrcmp && $ismodlo ni {3 5} && [llength $pvrlist]} {
      if {$modvrlist eq {0}} {
         set modvrlist [getVariantList $mod]
      }
      set ret [modVariantCmp $pvrlist $modvrlist]
   } elseif {$ret && $vrcmp && $ismodlo == 3} {
      set ret [modVariantCmp $pvrlist [getVariantList $mod 3] 1]
   } elseif {$ret && $vrcmp && $ismodlo == 5} {
      set ret [modVariantCmp $pvrlist $modvrlist 2]
   }
   return $ret
}
proc modEqProcIcaseExtdfl {pattern mod {test equal} {trspec 1} {ismodlo 0}\
   {vrcmp 0} {modvrlist 0} {psuf {}}} {
   if {$trspec} {
      lassign [getModuleVersSpec $pattern] pmod pmodname cmpspec versspec\
         pmodnamere pmodescglob pmodroot pvrlist
   } else {
      set pmod $pattern
   }
   if {[string index $pmod end] eq {/}} {
      set pmod [string trimright $pmod /]/
      set endwslash 1
   } else {
      set endwslash 0
   }
   set altlist [switch -- $ismodlo {
      7 {getLoadedAltname $mod {alias}}
      6 {getLoadedAltname $mod {sym autosym}}
      5 {getAvailListFromVersSpec $mod}
      4 {getAllModuleResolvedName $mod 0 {} 1}
      3 {getLoadedAltAndSimplifiedName $mod}
      2 {getLoadedAltname $mod}
      1 {getAllModuleResolvedName $mod}
      0 {list}}]
   if {$vrcmp && $ismodlo in {0 5}} {
      set modvrlist [getVariantList $mod 0 0 1]
      set mod [getModuleNameAndVersFromVersSpec $mod]
   }
   if {$pmod ne {} || $pattern eq {}} {
      if {$psuf ne {}} {
         if {$endwslash && [string index $psuf 0] eq {/}} {
            append pmod [string range $psuf 1 end]
         } else {
            append pmod $psuf
         }
      }
      if {$test eq {eqstart}} {
         set ret [string equal -nocase -length [string length $pmod/] $pmod/\
            $mod/]
         if {!$ret && [llength $altlist]} {
            foreach alt $altlist {
               if {[set ret [string equal -nocase -length [string length\
                  $pmod/] $pmod/ $alt/]]} {
                  break
               }
            }
         }
      } else {
         # contains test
         if {$test eq {matchin}} {
            set test match
            set pmod *$pmod
         } elseif {$test eq {eqspec}} {
            set test equal
            set eqspec 1
         }
         ##nagelfar ignore Non static subcommand
         set ret [string $test -nocase $pmod $mod]
         if {!$ret && [llength $altlist]} {
            foreach alt $altlist {
               ##nagelfar ignore Non static subcommand
               if {[set ret [string $test -nocase $pmod $alt]]} {
                  break
               }
            }
         }
      }
      # try the extended default match if not root module and not eqspec test
      if {![info exists eqspec] && !$ret && [string first / $pmod] != -1} {
         if {$test eq {match}} {
            set pmodextdfl $pmod.*
         } else {
            set pmodextdfl $pmodescglob.*
         }
         set ret [string match -nocase $pmodextdfl $mod]
         if {!$ret && [llength $altlist]} {
            foreach alt $altlist {
               if {[set ret [string match -nocase $pmodextdfl $alt]]} {
                  break
               }
            }
         }
      }
   } elseif {$test eq {eqspec}} {
      # test equality against all version described in spec (list or range
      # boundaries), trspec is considered enabled and psuf empty
      foreach pmod [getAllModulesFromVersSpec $pattern] {
         if {[set ret [string equal -nocase $pmod $mod]]} {
            break
         }
      }
   } else {
      # contains test
      if {$test eq {matchin}} {
         set test match
         if {$pmodnamere ne {}} {
            set pmodnamere .*$pmodnamere
         } else {
            set pmodnamere .*$pmodname
         }
      }
      # for more complex specification, first check if module name matches
      # use a regexp test if module name contains wildcard characters
      if {($pmodnamere ne {} && $test eq {match} && [regexp -nocase\
         (^$pmodnamere)/ $mod/ rematch pmodname]) || [string equal -nocase\
         -length [string length $pmodname/] $pmodname/ $mod/]} {
         # then compare versions
         set modvers [string range $mod [string length $pmodname/] end]
         set ret [modVersCmp $cmpspec $versspec $modvers $test $psuf]
      } else {
         set ret 0
      }
      if {!$ret && [llength $altlist]} {
         foreach alt $altlist {
            if {($pmodnamere ne {} && $test eq {match} && [regexp -nocase\
               (^$pmodnamere)/ $alt/ rematch pmodname]) || [string equal\
               -nocase -length [string length $pmodname/] $pmodname/ $alt/]} {
               # then compare versions
               set modvers [string range $alt [string length $pmodname/] end]
               if {[set ret [modVersCmp $cmpspec $versspec $modvers $test\
                  $psuf]]} {
                  break
               }
            }
         }
      }
   }
   if {$ret && $vrcmp && $ismodlo ni {3 5} && [llength $pvrlist]} {
      if {$modvrlist eq {0}} {
         set modvrlist [getVariantList $mod]
      }
      set ret [modVariantCmp $pvrlist $modvrlist]
   } elseif {$ret && $vrcmp && $ismodlo == 3} {
      set ret [modVariantCmp $pvrlist [getVariantList $mod 3] 1]
   } elseif {$ret && $vrcmp && $ismodlo == 5} {
      set ret [modVariantCmp $pvrlist $modvrlist 2]
   }
   return $ret
}

proc modEqAny {pattern_list mod {test equal} {trspec 1} {ismodlo 0} {vrcmp 0}\
   {modvrlist 0} {psuf {}}} {
   foreach pattern $pattern_list {
      if {[modEq $pattern $mod $test $trspec $ismodlo $vrcmp $modvrlist\
         $psuf]} {
         return 1
      }
   }
   return 0
}

# analyze module version specified within module specification
proc parseModuleVersionSpecifier {modspec} {
   set invalidversspec 0
   set invalidversrange 0
   set islist [expr {[string first , $modspec] != -1}]
   set isrange [expr {[string first : $modspec] != -1}]
   # no deep version specification allowed
   if {[string first / $modspec] != -1} {
      set invalidversspec 1
   # ',' separates multiple versions
   } elseif {$islist} {
      set cmpspec in
      set inspeclist [split $modspec ,]
      # empty element in list is erroneous
      set invalidversspec [expr {[lsearch -exact $inspeclist {}] != -1}]
      if {!$invalidversspec} {
         # recursive call to check each element in list (can be range, etc)
         foreach inspec $inspeclist {
            lappend versspec [parseModuleVersionSpecifier $inspec]
         }
      }
   # ':' separates range elements
   } elseif {$isrange} {
      set versspec [split $modspec :]
      set lovers [lindex $versspec 0]
      set hivers [lindex $versspec 1]
      if {[llength $versspec] != 2 || ($lovers eq {} && $hivers eq {})} {
         set invalidversspec 1
      } elseif {($lovers ne {} && ![isVersion $lovers]) || ($hivers ne {} &&\
         ![isVersion $hivers])} {
         set invalidversrange 1
      # greater or equal
      } elseif {$hivers eq {}} {
         set cmpspec ge
         set versspec $lovers
      # lower or equal
      } elseif {$lovers eq {}} {
         set cmpspec le
         set versspec $hivers
      # between or equal
      } elseif {[versioncmp $lovers $hivers] == 1} {
         set invalidversrange 1
      } else {
         set cmpspec be
      }
   } else {
      set cmpspec eq
      set versspec $modspec
   }
   if {$invalidversspec} {
      knerror "Invalid version specifier '$modspec'"
   }
   if {$invalidversrange} {
      knerror "Invalid version range '$modspec'"
   }
   return [list $cmpspec $versspec]
}

# Define procedure to parse modulefile specification passed as argument
# Adapt procedure code whether advanced_version_spec is enabled or disabled
proc defineParseModuleSpecificationProc {advverspec} {
   set procname parseModuleSpecificationProc
   if {$advverspec} {
      append procname AdvVersSpec
      # resolved configured variant shortcut
      getConf variant_shortcut
   }
   # define proc if not done yet or if it was defined for another context
   if {[info procs parseModuleSpecification] eq {} ||\
      $::g_parseModuleSpecification_proc ne $procname} {
      if {[info exists ::g_parseModuleSpecification_proc]} {
         # remove existing debug trace if any
         initProcReportTrace remove parseModuleSpecification
         rename ::parseModuleSpecification\
            ::$::g_parseModuleSpecification_proc
      }
      ##nagelfar syntax parseModuleSpecification x x x x x*
      rename ::$procname ::parseModuleSpecification
      # set report traces if some debug mode enabled
      initProcReportTrace add parseModuleSpecification
      set ::g_parseModuleSpecification_proc $procname
   }
}

# when advanced_version_spec option is enabled, parse argument list to set in
# a global context version specification of modules passed as argument.
# mlspec: specification may vary whether it comes from the ml or another
# command. nonamespec: sometimes specification may omit module name and
# version and just provides variant properties. xtspec: is extra specifier
# specification allowed. getavails: query available modules to get those
# matching module name and version
proc parseModuleSpecificationProc {mlspec nonamespec xtspec getavails args} {
   # skip arg parse if proc was already call with same arg set by an upper
   # proc. check all args to ensure current arglist does not deviate from
   # what was previously parsed
   foreach arg $args {
      if {![info exists ::g_moduleVersSpec($arg)]} {
         set need_parse 1
         break
      }
   }
   if {![info exists need_parse]} {
      return $args
   }

   set unarglist [list]
   set arglist [list]

   foreach arg $args {
      if {$mlspec && [string index $arg 0] eq {-}} {
         set modname [string range $arg 1 end]
         set mlunload 1
      } else {
         set modname $arg
         set mlunload 0
      }
      # keep arg enclosed if composed of several words
      if {[string first { } $modname] != -1} {
         set modarg "{$modname}"
      } else {
         set modarg $modname
      }
      # record spec, especially needed if arg is enclosed
      setModuleVersSpec $modarg $modname eq {} {} {} $arg {} $getavails
      # append to unload list if ml spec and - prefix used
      if {$mlunload} {
         lappend unarglist $modarg
      } else {
         lappend arglist $modarg
      }
   }

   if {$mlspec} {
      return [list $unarglist $arglist]
   } else {
      return $arglist
   }

}
proc parseModuleSpecificationProcAdvVersSpec {mlspec nonamespec xtspec\
   getavails args} {
   foreach arg $args {
      if {![info exists ::g_moduleVersSpec($arg)]} {
         set need_parse 1
         break
      }
   }
   if {![info exists need_parse]} {
      return $args
   }

   # define extra specifier known list, to raise error if argument does not
   # match
   set xtelt_valid_list [list always-load append-path chdir complete conflict\
      depends-on depends-on-any envvar family incompat load load-any\
      prepend-path prereq prereq-all prereq-any provide provided-alias\
      pushenv remove-path require set-alias set-function setenv switch\
      switch-on switch-off tag try-load uncomplete unload unset-alias\
      unset-function unsetenv use variant]
   set xtelt_modspec_list [list always-load conflict depends-on\
      depends-on-any incompat load load-any prereq prereq-all prereq-any\
      require switch switch-on switch-off try-load unload]

   set mlunload 0
   set nextmlunload 0
   set arglist [list]
   set unarglist [list]
   set vrlist [list]
   set vridx -1
   set xtlist [list]
   set rawarg [list]
   foreach arg $args {
      # set each specification element as separate word but preserve space
      # character in each arg
      set curarglist {}
      # skip argument split if extra specifier detected
      if {[regexp {^[a-z-]+:} $arg]} {
         lappend curarglist $arg
      } else {
         set split_chars {@ ~ +}
         lappend split_chars {*}[array names ::g_shortcutVariant]
         set split_arg_list {}
         set previ 0
         for {set i 1} {$i < [string length $arg]} {incr i} {
            if {[string index $arg $i] in $split_chars} {
               lappend split_arg_list [string range $arg $previ $i-1]
               set previ $i
            }
         }
         lappend split_arg_list [string range $arg $previ $i-1]

         # unsplit some arg parts: those starting with boolean variant prefix
         # (+ or ~) but not followed by a valid variant name
         set prev_arg [lindex $split_arg_list 0]
         for {set i 1} {$i < [llength $split_arg_list]} {incr i} {
            set split_arg [lindex $split_arg_list $i]
            if {[string index $split_arg 0] ni {+ ~} || [isVariantNameValid\
               [string range $split_arg 1 end]]} {
               lappend curarglist $prev_arg
               set prev_arg $split_arg
            } else {
               append prev_arg $split_arg
            }
         }
         lappend curarglist $prev_arg
      }

      # parse each specification element
      foreach curarg $curarglist {
         set vrisbool 0

         set xtnot 0
         if {[string equal -length 4 $curarg not:]} {
            if {!$xtspec} {
               knerror "No extra specification allowed on this command"
            }
            set xtnot 1
            set curarg [string range $curarg 4 end]
         }

         set c [string index $curarg 0]
         switch -- $c {
            @ {
               if {$xtnot} {
                  knerror "Invalid extra specification '$arg'"
               }
               set modspec [string range $curarg 1 end]
               lassign [parseModuleVersionSpecifier $modspec] cmpspec versspec
               continue
            }
            + {
               set curarg [string range $curarg 1 end]
               append curarg =1
               set vrisbool 1
            }
            - {
               set curarg [string range $curarg 1 end]
               if {$mlspec} {
                  set nextmlunload 1
               } else {
                  append curarg =0
                  set vrisbool 1
               }
            }
            ~ {
               set curarg [string range $curarg 1 end]
               append curarg =0
               set vrisbool 1
            }
            default {
               # translate shortcut in variant name in arg
               if {[info exists ::g_shortcutVariant($c)]} {
                  set curarg [string replace $curarg 0 0\
                     $::g_shortcutVariant($c)=]
               }
            }
         }

         switch -glob -- $curarg {
            *:* {
               # extra specification may not be accepted on current context
               if {!$xtspec} {
                  knerror "No extra specification allowed on this command"
               }
               # extract extra specifier spec
               set xtsepidx [string first : $curarg]
               set xtelt [string range $curarg 0 $xtsepidx-1]
               set xtnamelist [split [string range $curarg $xtsepidx+1 end] ,]

               # check no other : character is found in argument or element
               # and name are not an empty string
               if {![string length $xtelt] || ![llength $xtnamelist] || {} in\
                  $xtnamelist || ([string last : $curarg] != $xtsepidx &&\
                  $xtelt ni $xtelt_modspec_list)} {
                  knerror "Invalid extra specification '$arg'"
               }
               if {$xtelt ni $xtelt_valid_list} {
                  knerror "Invalid extra specifier '$xtelt'\nValid extra\
                     specifiers are: $xtelt_valid_list"
               }
               set spec_xt [list $xtelt $xtnot]
               # parse and resolve module spec set as extra specifier value
               if {$xtelt in $xtelt_modspec_list} {
                  foreach xtname $xtnamelist {
                     lassign [parseModuleSpecification 0 0 0 1 {*}$xtname]\
                        xtname
                     lappend spec_xt $xtname
                  }
               } else {
                  # For tag extra specifier, resolve each name to get
                  # corresponding tag name if tag abbreviation set as name
                  if {$xtelt eq {tag}} {
                     foreach xtname $xtnamelist {
                        lappend spec_xt $xtname
                        if {[set tag [getTagFromAbbrev $xtname]] ne {}} {
                           lappend spec_xt $tag
                        }
                     }
                  } else {
                     lappend spec_xt {*}$xtnamelist
                  }
               }
               # save extra specifier element and name value, multiple values
               # may be set (means OR operator), same element can appear
               # multiple time (means AND operator)
               lappend xtlist $spec_xt
            }
            *=* {
               # extract valued-variant spec
               set vrsepidx [string first = $curarg]
               set vrname [string range $curarg 0 $vrsepidx-1]
               set vrvaluelist [split [string range $curarg $vrsepidx+1 end]\
                  ,]
               # value is one empty string
               if {![llength $vrvaluelist]} {
                  lappend vrvaluelist {}
               }

               if {$vrname eq {}} {
                  knerror "No variant name defined in argument '$curarg'"
               }
               # check no other = character is found in argument and that only
               # one value is set unless if extra specifier search is enabled
               if {[string last = $curarg] != $vrsepidx || (!$xtspec &&\
                  [llength $vrvaluelist] > 1)} {
                  knerror "Invalid variant specification '$arg'"
               }
               # replace previous value for variant if already set unless if
               # extra specifier search enabled where all variant spec forms
               # an AND operation
               if {[info exists vrnamearr($vrname)] && !$xtspec} {
                  lreplace $vrlist $vrnamearr($vrname) $vrnamearr($vrname)
               } else {
                  incr vridx
               }
               # translate boolean vrvalue in canonical boolean
               if {!$vrisbool} {
                  set vrisbool 1
                  for {set i 0} {$i < [llength $vrvaluelist]} {incr i 1} {
                     set vrvalue [lindex $vrvaluelist $i]
                     if {[string is boolean -strict $vrvalue] && ![string is\
                        integer -strict $vrvalue]} {
                        lset vrvaluelist $i [string is true -strict $vrvalue]
                     } else {
                        # consider variant not a boolean as soon as one value
                        # set is not a boolean
                        set vrisbool 0
                     }
                  }
               }
               # save variant name and value
               set vrnamearr($vrname) $vridx
               lappend vrlist [list $vrname $xtnot $vrisbool {*}$vrvaluelist]
            }
            default {
               if {$xtnot} {
                  knerror "Invalid extra specification '$arg'"
               }
               # save previous mod version spec and transformed arg if any
               if {[info exists modarglist]} {
                  set modarg [join $modarglist]
                  if {![info exists cmpspec]} {
                     set cmpspec eq
                     set versspec {}
                  }
                  # wild search name if no module name allowed
                  if {$nonamespec && ![info exists modname]} {
                     set modname *
                     set modspec *
                  }
                  if {[info exists modname] && ($modname ne {} || $modspec\
                     eq {})} {
                     setModuleVersSpec $modarg $modname $cmpspec $versspec\
                        $modspec $vrlist $rawarg $xtlist $getavails
                     # rework args to have 1 str element for whole mod spec
                     # append to unload list if ml spec and - prefix used
                     if {$mlunload} {
                        lappend unarglist $modarg
                     } else {
                        lappend arglist $modarg
                     }
                  } else {
                     knerror "No module name defined in argument '$modarg'"
                  }
                  unset modarglist
                  set vrlist [list]
                  array unset vrnamearr
                  set vridx -1
                  set xtlist [list]
                  set rawarg [list]
                  unset cmpspec versspec
               }
               set mlunload $nextmlunload
               set nextmlunload 0
               set modname $curarg
               set modspec {}
            }
         }
      }
      lappend rawarg $arg

      # keep arg enclosed if composed of several words
      if {[string first { } $arg] != -1} {
         lappend modarglist "{$arg}"
      } else {
         lappend modarglist $arg
      }
   }
   # transform last args
   set modarg [join $modarglist]
   if {$nonamespec && ![info exists modname]} {
      set modname *
      set modspec *
   }
   if {[info exists modname] && ($modname ne {} || $modspec eq {})} {
      if {![info exists cmpspec]} {
         set cmpspec eq
         set versspec {}
      }
      setModuleVersSpec $modarg $modname $cmpspec $versspec $modspec $vrlist\
         $rawarg $xtlist $getavails
      # rework args to have 1 string element for whole module spec
      # append to unload list if ml spec and - prefix used
      if {$mlunload || $nextmlunload} {
         lappend unarglist $modarg
      } else {
         lappend arglist $modarg
      }
   } else {
      knerror "No module name defined in argument '$modarg'"
   }

   if {$mlspec} {
      return [list $unarglist $arglist]
   } else {
      return $arglist
   }
}

proc setModuleVersSpec {modarg modname cmpspec versspec rawversspec\
   variantlist rawarg extralist getavails} {
   # translate @loaded version into currently loaded mod matching modname
   if {$cmpspec eq {eq} && $versspec eq {loaded}} {
      if {[set lmmod [getLoadedMatchingName $modname]] ne {}} {
         set modname [file dirname $lmmod]
         set versspec [file tail $lmmod]
         set variantlist [getVariantList $lmmod 2]
      } else {
         knerror "No loaded version found for '$modname' module"
      }
   }
   # save module root name
   set modroot [lindex [file split $modname] 0]
   # save module single designation if any and module name
   if {$versspec eq {}} {
      set mod $modname
      set modname [file dirname $modname]
   } else {
      set modname [string trimright $modname /]
      if {$cmpspec ne {eq}} {
         set mod {}
      } else {
         set mod $modname/$versspec
      }
   }
   # save a regexp-ready version of modname (apply
   # non-greedy quantifier to '*', to avoid matching final
   # '/' in string comparison
   set modnamere [string map {. \\. + \\+ * .*? ? .} $modname]
   if {$modname eq $modnamere} {
      set modnamere {}
   }
   # save a glob-special-chars escaped version of mod
   set modescglob [string map {* \\* ? \\?} $mod]

   # save module name and version specification (without variant specs)
   if {$mod eq {} && $rawversspec ne {} && $modname ne {.}} {
      set modnvspec ${modname}@${rawversspec}
   } else {
      set modnvspec $mod
   }

   # record most of the spec now to be able to rely on this record to get
   # matching available modules
   set ::g_moduleVersSpec($modarg) [list $mod $modname $cmpspec $versspec\
      $modnamere $modescglob $modroot $variantlist $modnvspec $rawarg\
      $extralist]

   # get all available module name and version matching module name and
   # version spec (inhibit extra match search not to trigger an infinite loop)
   if {$getavails} {
      setState inhibit_ems 1
      set availlist [getAllAvailModule $modarg]
      setState inhibit_ems 0
   } else {
      set availlist {}
   }
   # finalize module version spec record
   lappend ::g_moduleVersSpec($modarg) $availlist

   reportDebug "Set module '$mod' (escglob '$modescglob'),  module name\
      '$modname' (re '$modnamere'), module root '$modroot', version cmp\
      '$cmpspec', version(s) '$versspec', variant(s) '$variantlist' and\
      module name version spec '$modnvspec' for argument '$modarg' (raw\
      '$rawarg'), extra specifier(s) '$extralist' and matching available\
      module(s) '$availlist'"
}

proc getModuleVersSpec {modarg} {
   if {[info exists ::g_moduleVersSpec($modarg)]} {
      return $::g_moduleVersSpec($modarg)
   } else {
      return [list $modarg [file dirname $modarg] {} {} {} [string map {* \\*\
         ? \\?} $modarg] [lindex [file split $modarg] 0] {} $modarg $modarg\
         {} {}]
   }
}

proc unsetModuleVersSpec {modarg} {
   if {[info exists ::g_moduleVersSpec($modarg)]} {
      unset ::g_moduleVersSpec($modarg)
   }
}

# get module name from module name and version spec if parsed
proc getModuleNameFromVersSpec {modarg} {
   if {[info exists ::g_moduleVersSpec($modarg)]} {
      return [lindex $::g_moduleVersSpec($modarg) 1]
   } else {
      return [file dirname $modarg]
   }
}

proc getCmpSpecFromVersSpec {modarg} {
   if {[info exists ::g_moduleVersSpec($modarg)]} {
      return [lindex $::g_moduleVersSpec($modarg) 2]
   } else {
      return eq
   }
}
# get module root name from module name and version spec if parsed
proc getModuleRootFromVersSpec {modarg} {
   if {[info exists ::g_moduleVersSpec($modarg)]} {
      return [lindex $::g_moduleVersSpec($modarg) 6]
   } else {
      return [lindex [file split $modarg] 0]
   }
}

# translate module name version spec to return all modules mentioned
proc getAllModulesFromVersSpec {modarg} {
   if {[info exists ::g_moduleVersSpec($modarg)]} {
      lassign $::g_moduleVersSpec($modarg) mod modname cmpspec versspec
      if {$mod eq {} && $cmpspec eq {in}} {
         # loop around each spec in list
         foreach inspec $versspec {
            lassign $inspec incmp invers
            foreach vers $invers {
               lappend modlist $modname/$vers
            }
         }
      } elseif {$mod eq {} && $cmpspec ne {eq}} {
         foreach vers $versspec {
            lappend modlist $modname/$vers
         }
      } else {
         # add empty mod specification if cmpspec is 'eq'
         lappend modlist $mod
      }
   } else {
      lappend modlist $modarg
   }

   return $modlist
}

# translate module name version spec to return one module mentioned
proc getOneModuleFromVersSpec {modarg} {
   if {[info exists ::g_moduleVersSpec($modarg)]} {
      lassign $::g_moduleVersSpec($modarg) mod modname cmpspec versspec
      if {$mod eq {} && $cmpspec eq {in}} {
         set inspec [lindex $versspec 0]
         lassign $inspec incmp invers
         set mod $modname/[lindex $invers 0]
      } elseif {$mod eq {} && $cmpspec ne {eq}} {
         set mod $modname/[lindex $versspec 0]
      }
   } else {
      set mod $modarg
   }

   return $mod
}

# translate module name version spec to return the list of variant mentioned
proc getVariantListFromVersSpec {modarg} {
   if {[info exists ::g_moduleVersSpec($modarg)]} {
      return [lindex $::g_moduleVersSpec($modarg) 7]
   }
}

# get module name and version from version spec if parsed
proc getModuleNameAndVersFromVersSpec {modarg} {
   if {[info exists ::g_moduleVersSpec($modarg)]} {
      return [lindex $::g_moduleVersSpec($modarg) 8]
   } else {
      return $modarg
   }
}

# get raw argument specified from parsed version spec
proc getRawArgumentFromVersSpec {modarg} {
   if {[info exists ::g_moduleVersSpec($modarg)]} {
      return [lindex $::g_moduleVersSpec($modarg) 9]
   } else {
      return $modarg
   }
}

# translate module name version spec to return the list of extra specifier
# mentioned
proc getExtraListFromVersSpec {modarg} {
   if {[info exists ::g_moduleVersSpec($modarg)]} {
      return [lindex $::g_moduleVersSpec($modarg) 10]
   }
}

# translate module name version spec to return list of tag specifier and list
# of other extra specifier
proc getSplitExtraListFromVersSpec {modarg} {
   set taglist {}
   set otherlist {}
   if {[info exists ::g_moduleVersSpec($modarg)]} {
      foreach spec_xt [lindex $::g_moduleVersSpec($modarg) 10] {
         if {[lindex $spec_xt 0] eq {tag}} {
            lappend taglist $spec_xt
         } else {
            lappend otherlist $spec_xt
         }
      }
   }
   return [list $taglist $otherlist]
}

# get available modules matching module specification
proc getAvailListFromVersSpec {modarg} {
   if {[info exists ::g_moduleVersSpec($modarg)]} {
      return [lindex $::g_moduleVersSpec($modarg) 11]
   }
}

# is mod spec containing variant but no module name and version
proc isSpecWildWithVariant {mod} {
   return [expr {[getModuleNameAndVersFromVersSpec $mod] eq {*} && [llength\
      [getVariantListFromVersSpec $mod]]}]
}

# Get full path name of module cache file for given modulepath
proc getModuleCacheFilename {modpath} {
   return $modpath/.modulecache
}

# test if a given file path has a limited access by check permission mode set
# for others
proc isFileAccessLimited {fpath isdir} {
   # check if file stat info can be fetched
   if {[file readable $fpath]} {
      # extract permissions for "other"
      set other_perms [string index [file attributes $fpath -permissions] end]
      # test file can be read by other: o+r right, which means file should at
      # least have 0b100 mode, which can be binary-compared against 0b011 (3)
      # if file is a directory, test it can also be searched: o+x right, which
      # means dir should at least have 0b101 mode, which can be compared
      # against 0b010 (2)
      set perms_mask [expr {$isdir ? {2} : {3}}]
      return [expr {($other_perms | $perms_mask) != 7}]
   } else {
      return 1
   }
}

# walk through directory content and return every files and dirs with limited
# access rights
proc getLimitedAccessesInDirectory {modpath} {
   # cannot test limited accesses on Windows platform
   if {[getState is_win]} {
      return {}
   }

   foreach igndir [getConf ignored_dirs] {
      set ignored_dirs($igndir) 1
   }
   # walk through directory to find elements with limited access
   array set limited_arr {}
   lappend full_list $modpath
   for {set i 0} {$i < [llength $full_list]} {incr i 1} {
      set elt [lindex $full_list $i]
      set elttail [file tail $elt]
      set eltname [getModuleNameFromModulepath $elt $modpath]
      if {[file isdirectory $elt]} {
         # skip ignored dirs
         if {![info exists ignored_dirs($elttail)]} {
            if {[isFileAccessLimited $elt 1]} {
               set limited_arr($eltname) d
            # only walk through directory content if its access is not limited
            } else {
               # get all elements found in directory (regular and dot files)
               set direlt_list [glob -nocomplain -directory $elt *]
               foreach eltindir [glob -nocomplain -types hidden -directory\
                  $elt -tails *] {
                  if {$eltindir ni {. ..}} {
                     lappend direlt_list $elt/$eltindir
                  }
               }
               if {[llength $direlt_list]} {
                  lappend full_list {*}$direlt_list
               }
            }
         }
      } else {
         # skip ignored files
         switch -glob -- $elttail {
            *~ - *,v - \#*\# {}
            default {
               if {[isFileAccessLimited $elt 0]} {
                  set limited_arr($eltname) f
               }
            }
         }
      }
   }

   # filter .version/.modulecache files found at the root of modulepath dir
   foreach elt [list .version .modulecache] {
      if {[info exists limited_arr($elt)]} {
         unset limited_arr($elt)
      }
   }

   reportDebug "found [array names limited_arr] ([array size limited_arr])\
      with limited access"
   return [array get limited_arr]
}

# Build cache file content for given modulepath
proc formatModuleCacheContent {modpath} {
   set content {}
   # collect files from modulepath directory
   array set found_list [findModules $modpath * 0 1]

   # collect files and dirs from modulepath directory with limited access
   array set limited_list [getLimitedAccessesInDirectory $modpath]

   # concatenate element found and those with limited access as the second
   # kind may not be part of found list
   set elt_list [lsort -unique [concat [array names found_list] [array names\
      limited_list]]]

   # build cache entry for every file found
   foreach elt $elt_list {
      # ignore element if it has been flagged in skip list (one of its parent
      # directory has limited access) or if modulepath has itself a limited
      # access
      if {[info exists skip_list($elt)] || $elt eq {}} {
         continue
      }

      set entry_list [list]
      set fetch_content 0

      # mention in cache that file or directory has limited access to test if
      # they can be accessed by user when cache is evaluated
      if {[info exists limited_list($elt)]} {
         if {$limited_list($elt) eq {d}} {
            # mark all elements contained in dir to skip them as their parent
            # directory has limited access (due to lsort on foreach loop we
            # are assured to treat parent dir before the entries it contains)
            foreach elttoskip [array names found_list -glob $elt/*] {
               set skip_list($elttoskip) 1
            }
            lappend entry_list limited-access-directory $elt
         } else {
            lappend entry_list limited-access-file $elt
         }
      } else {
         switch -- [lindex $found_list($elt) 0] {
            modulerc {
               lappend entry_list modulerc-content $elt
               set fetch_content 1
            }
            modulefile {
               lappend entry_list modulefile-content $elt [lindex\
                  $found_list($elt) 1]
               set fetch_content 1
            }
            default {
               # also record obtained error to get all the information to
               # cover everything fetched by findModules. only modulefile
               # validity is checked in findModules
               lappend entry_list modulefile-invalid $elt {*}[lrange\
                  $found_list($elt) 0 1]
            }
         }
      }
      # fetch file content
      if {$fetch_content} {
         if {[catch {
            set fcontent [readFile $modpath/$elt]
            # extract module header from the start of the file
            if {![regexp {^#%Module[0-9\.]*} [string range $fcontent 0 32]\
               fheader]} {
               set fheader {}
            }
            lappend entry_list $fheader $fcontent
         } errMsg]} {
            # rethrow read error after parsing message
            knerror [parseAccessIssue $modpath/$elt]
         }
      }
      # format cache entry
      append content "\n$entry_list"
   }

   # prepend header if some content has been generated
   if {[string length $content]} {
      set cache_header "#%Module[getState cache_mcookie_version]"
      set content $cache_header$content
   }

   return $content
}

# read a cache file
proc readCacheContent {cachefile} {
   set res {}
   if {[catch {
      # read full file
      set fid [open $cachefile r]
      # use defined buffer size to limit number of read system call
      fconfigure $fid -buffersize [getConf cache_buffer_bytes]
      set fdata [read $fid]
      close $fid
      # extract magic cookie (first word of cache file)
      set fh [string trimright [lindex [split [string range $fdata 0 32]]\
         0] #]
      set fhvers [string range $fh 8 end]
   } errMsg ]} {
      reportError [parseAccessIssue $cachefile]
   } else {
      # check cache validity
      if {![string equal -length 8 $fh {#%Module}]} {
         reportInternalBug {Magic cookie '#%Module' missing} $cachefile\
            {Cache ERROR}
      # check if version requirement is present
      } elseif {[string length $fh] <= 8} {
         reportInternalBug {Modules version requirement missing} $cachefile\
            {Cache ERROR}
      # check if min version requirement is met
      } elseif {[versioncmp [getState modules_release] $fhvers] <0} {
         reportDebug "Cache file $cachefile requires at least Modules version\
            $fhvers"
      } else {
         # set file content as result
         set res $fdata
      }
   }
   return $res
}

# evaluate cache file
proc execute-cachefile {cachefile modpath} {
   # register current modulepath for cachefile commands to know where they are
   lappendState modulepath $modpath
   lappendState debug_msg_prefix "\[cache:$cachefile\] "

   # initialize cache gathering structures for modulepath
   set ::g_cacheModpath($modpath) {}
   set ::g_cacheFLimitedModpath($modpath) {}
   set ::g_cacheDLimitedModpath($modpath) {}

   # initialize cache file evaluation interp configuration
   if {![info exists ::g_cachefileUntrackVars]} {
      # list variable that should not be tracked for saving
      array set ::g_cachefileUntrackVars [list ModulesCurrentCachefile 1\
         cachecontent 1 env 1]

      # commands that should be renamed before aliases setup
      array set ::g_cachefileRenameCmds [list]

      # list interpreter alias commands to define
      array set ::g_cachefileAliases [list modulefile-content\
         modulefile-content modulerc-content modulerc-content\
         modulefile-invalid modulefile-invalid limited-access-file\
         limited-access-file limited-access-directory\
         limited-access-directory reportInternalBug reportInternalBug\
         readCacheContent readCacheContent formatErrStackTrace\
         formatErrStackTrace]

      # alias commands where an argument should be passed
      array set ::g_cachefileAliasesPassArg [list]

      # trace commands that should be associated to aliases
      array set ::g_cachefileAliasesTraces [list]

   }
   set itrp __cachefile

   reportTrace '$cachefile' {Evaluate cache file}
   # create cachefile interpreter at first interpretation
   if {![interp exists $itrp]} {
      reportDebug "creating interp $itrp"
      interp create $itrp

      # dump initial interpreter state to restore it before each cachefile
      # interpretation.
      dumpInterpState $itrp g_cachefileVars g_cachefileArrayVars\
         g_cachefileUntrackVars g_cachefileProcs

      # interp has just been created
      set fresh 1
   } else {
      set fresh 0
   }

   # reset interp state command before each interpretation
   resetInterpState $itrp $fresh g_cachefileVars g_cachefileArrayVars\
      g_cachefileUntrackVars g_cachefileProcs g_cachefileAliases\
      g_cachefileAliasesPassArg g_cachefileAliasesTraces\
      g_cachefileRenameCmds g_cachefileCommands

   # reset modulefile-specific variable before each interpretation
   interp eval $itrp set ::ModulesCurrentCachefile "{$cachefile}"

   # evaluate cache file
   ##nagelfar ignore +4 Suspicious # char
   set exec_res [interp eval $itrp {
      set cachecontent [readCacheContent $::ModulesCurrentCachefile]
      if {$cachecontent eq {}} {
         # simply skip cache file, no exit on error here
         return 0
      }

      info script $::ModulesCurrentCachefile
      if {[catch {eval $cachecontent} errorMsg]} {
         # format stack trace to report cachefile information only
         reportInternalBug [formatErrStackTrace $::errorInfo\
            $::ModulesCurrentCachefile [list {*}[info procs] {*}[info\
            commands]]] {} {Cache ERROR}
         return 0
      } else {
         return 1
      }
   }]

   reportDebug "exiting $cachefile (result=$exec_res)"

   lpopState debug_msg_prefix
   lpopState modulepath

   return $exec_res
}

# cache file command to record modulefile content
proc modulefile-content {mod mtime header content} {
   set modfile [currentState modulepath]/$mod

   # record modulefile information in memory structures
   set ::g_modfileContent($modfile) [list $header $content]
   set ::g_fileMtime($modfile) $mtime
   set ::g_modfileValid($modfile) [list true {}]

   # gather all modulefiles/modulercs of a modulepath in a global variable
   # with same information structure than findModules result
   lappend ::g_cacheModpath([currentState modulepath]) $mod [list modulefile\
      $mtime $modfile]
}

# cache file command to record modulerc content
proc modulerc-content {modrc header content} {
   set modrcfile [currentState modulepath]/$modrc

   # record modulerc information in memory structures
   set ::g_modfileContent($modrcfile) [list $header $content]

   # gather all modulefiles/modulercs of a modulepath in a global variable
   # with same information structure than findModules result
   lappend ::g_cacheModpath([currentState modulepath]) $modrc [list modulerc]
}

# cache file command to record an invalid modulefile
proc modulefile-invalid {mod type msg} {
   set modfile [currentState modulepath]/$mod

   # record modulefile information in memory structures
   set ::g_modfileValid($modfile) [list $type $msg]

   # also gather all invalid modulefiles of a modulepath in a global variable
   # with same information structure than findModules result
   lappend ::g_cacheModpath([currentState modulepath]) $mod [list $type $msg\
      $modfile]
}

# gather modulepath limited access files in specific structure
proc limited-access-file {mod} {
   lappend ::g_cacheFLimitedModpath([currentState modulepath]) $mod 1
}

# gather modulepath limited access files in specific structure
proc limited-access-directory {dir} {
   lappend ::g_cacheDLimitedModpath([currentState modulepath]) $dir 1
}

# finds all module-related files matching mod in the modulepath dir by looking
# into the cache file
proc findModulesInCacheFile {modpath mod depthlvl fetch_mtime} {
   set cachefile [getModuleCacheFilename $modpath]

   # check if cache should be ignored
   if {[getConf ignore_cache]} {
      return [list 0 {}]
   }

   # check if a cache file is available (exist, readable and not expired)
   if {![info exists ::g_cachefilesAvail($cachefile)]} {
      if {[file readable $cachefile]} {
         reportDebug "cache file '$cachefile' exists and is readable"
         # check expiry if enabled
         if {[set expiry_secs [getConf cache_expiry_secs]] != 0} {
            set cachemtime [getFileMtime $cachefile]
            # cache is also considered expired if its mtime cannot be fetched
            set ::g_cachefilesAvail($cachefile) [expr {$cachemtime ne {} &&\
               $expiry_secs > ([getState clock_seconds] - $cachemtime)}]
            if {!$::g_cachefilesAvail($cachefile)} {
               reportDebug "cache file '$cachefile' has expired"
            }
         } else {
            set ::g_cachefilesAvail($cachefile) 1
         }
      } else {
         reportDebug "cache file '$cachefile' cannot be found or read"
         set ::g_cachefilesAvail($cachefile) 0
      }
   }

   # return if no cache file available
   if {!$::g_cachefilesAvail($cachefile)} {
      return [list 0 {}]
   }

   # evaluate cache file if not yet done
   if {![info exists ::g_cachefilesSourced($cachefile)]} {
      set exec_res [execute-cachefile $cachefile $modpath]
      # keep track of already sourced cache files not to run them again
      set ::g_cachefilesSourced($cachefile) $exec_res
   } else {
      reportDebug "cache file '$cachefile' has already been evaluated"
   }

   # return if cache file has not been correctly sourced
   if {!$::g_cachefilesSourced($cachefile)} {
      return [list 0 {}]
   }

   # tailor cache file content to what is requested
   array set cache_arr $::g_cacheModpath($modpath)
   reportDebug "[array size cache_arr] elements are cached for '$modpath'"
   array set flimited_arr $::g_cacheFLimitedModpath($modpath)
   reportDebug "[array size flimited_arr] limited access files for '$modpath'"
   array set dlimited_arr $::g_cacheDLimitedModpath($modpath)
   reportDebug "[array size dlimited_arr] limited access dirs for '$modpath'"

   # filter entries unless all are requested
   set findall [expr {$mod in {{} *}}]
   defineModEqStaticProc [isIcase] [getConf extended_default] $mod
   if {!$findall || $depthlvl > 0} {
      foreach elt [array names cache_arr] {
         set eltroot [lindex [file split $elt] 0]
         set eltdepthlvl [llength [file split $elt]]
         set elttail [file tail $elt]
         set eltparent [file dirname $elt]

         # exclude if not matching mod pattern (which is always a root name)
         if {$eltroot ne {.modulerc} && !$findall && ![modEqStatic $eltroot\
            match]} {
            unset cache_arr($elt)
         # also exclude if not corresponding to search depth level (or if one
         # modulefile has already been added for directories lying at other
         # depth level)
         } elseif {$depthlvl > 0 && $elttail ne {.modulerc} && $eltdepthlvl\
            != $depthlvl && [info exists modfile_indir($eltparent)]} {
            unset cache_arr($elt)
         } else {
            # track a valid non-hidden modulefile has been added in parent
            # directory
            if {[string index $elttail 0] ne {.} && ![info exists\
               modfile_indir($eltparent)] && [lindex $cache_arr($elt) 0] eq\
               {modulefile}} {
               set modfile_indir($eltparent) 1
            }
         }
      }
   }

   array set hidden_list {}
   set limited_list [list]
   # add limited access files and directories to result if they match query
   foreach elt [concat [array names flimited_arr] [array names\
      dlimited_arr]] {
      set eltroot [lindex [file split $elt] 0]
      if {$eltroot eq {.modulerc} || $findall || [modEqStatic $eltroot\
         match]} {
         set fpelt [file join $modpath $elt]
         lappend limited_list $fpelt
         # indicate file is hidden in structure that will be transmitted to
         # findModulesFromDirsAndFiles for no-indepth module search
         if {[string index [file tail $elt] 0] eq {.} && [info exists\
            flimited_arr($elt)]} {
            set hidden_list($fpelt) 1
         }
      }
   }

   # walk through list of matching limited access dirs and files to find
   # modules available to current user (transmit list of known files and dirs
   # to avoid file stat tests)
   findModulesFromDirsAndFiles $modpath $limited_list $depthlvl $fetch_mtime\
      cache_arr modfile_indir hidden_list flimited_arr dlimited_arr

   reportDebug "found [array names cache_arr] ([array size cache_arr])"
   set cache_list [array get cache_arr]

   return [list 1 $cache_list]
}

# build list of what to undo then do to move from an initial list to a target
# list, eventually checking element presence in extra from/to lists
proc getMovementBetweenList {from to {extfrom {}} {extto {}} {cmp eq}} {
   reportDebug "from($from) to($to) with extfrom($extfrom) extto($extto)"

   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
   ##nagelfar ignore #2 Badly formed if statement
   set imax [if {[llength $to] > [llength $from]} {llength $to} {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]
      # check from/to element presence in extra from/to list
      set in_extfrom [expr {$from_obj in $extfrom}]
      set in_extto [expr {$to_obj in $extto}]
      # are elts the sames and are both part of or missing from extra lists
      # when comparing modules, ask comparison against loaded module
      # alternative and simplified names (modEq will also compare variants)
      if {($cmp eq {modeq} && ![modEq $to_obj $from_obj equal 1 3 1]) ||\
         ($cmp eq {eq} && $to_obj ne $from_obj) || $in_extfrom != $in_extto} {
         set list_equal 0
      }
      if {!$list_equal} {
         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
proc getSimplifiedLoadedModuleList {} {
   set curr_mod_list {}
   array set curr_tag_arr {}
   set modpathlist [getModulePathList]
   foreach mod [getEnvLoadedModulePropertyParsedList name] {
      set altandsimplist [getLoadedAltAndSimplifiedName $mod]

      set parentmod [file dirname $mod]
      set simplemod $mod
      # simplify to parent name as long as it is found in simplified name list
      while {$parentmod ne {.}} {
         if {$parentmod in $altandsimplist} {
            set simplemod $parentmod
            set parentmod [file dirname $parentmod]
         } else {
            set parentmod .
         }
      }

      # add each module specification as list to correctly enclose spaces in
      # module name or variant name or value
      set simplemodvr [list $simplemod {*}[getVariantList $mod 5 1]]
      lappend curr_mod_list $simplemodvr
      # record tags applying to module in simplified version form
      set tag_list [getSaveTagList $mod]
      if {[llength $tag_list]} {
         set curr_tag_arr($simplemodvr) $tag_list
      }
   }

   return [list $curr_mod_list [array get curr_tag_arr]]
}

# return saved collections found in user directory which corresponds to
# enabled collection target if any set. extract one collection specifically
# when search mode is set to exact. only compute collection name if mode is
# set to name. translate collection name to __init__ if not found and
# swap_by_init enabled. if no_other_target enabled, ensure no result from
# other target are returned from glob search
proc findCollections {{coll *} {search glob} {swap_by_init 0} {errnomatch 0}\
   {checkvalid 1} {no_other_target 0}} {
   # initialize description with collection name
   set colldesc $coll

   if {$coll eq {}} {
      reportErrorAndExit [getEmptyNameMsg collection]
   } elseif {$coll eq {__init__}} {
      set collfile $coll
      set colldesc {}
   # is collection a filepath
   } elseif {[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 {[isEnvVarDefined HOME]} {
      set coll_dir [file join $::env(HOME) .module]
      set coll_glob $coll
      # find saved collections (matching target suffix). 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. current target
      # is ignored if --all option is set on savelist command
      set colltarget [getConf collection_target]
      if {$colltarget ne {} && ([getState hiding_threshold] < 2 ||\
         [currentState commandname] ne {savelist})} {
         append coll_glob .$colltarget
         # add knowledge of collection target on description
         append colldesc " (for target \"$colltarget\")"
      }
      set collfile [file join $coll_dir $coll_glob]
   } else {
      reportErrorAndExit {HOME not defined}
   }

   switch -- $search {
      glob {
         # glob excludes by default files starting with "."
         if {[catch {set clist [glob -nocomplain -directory $coll_dir\
            $coll_glob]} errMsg]} {
            reportErrorAndExit "Cannot access collection directory.\n$errMsg"
         } else {
            set res {}
            foreach cfile $clist {
               # test collection is from correct target or no target if
               # no_other_target is enabled
               set cfile_ext [string range [file extension $cfile] 1 end]
               if {(!$no_other_target || $cfile_ext eq [getConf\
                  collection_target]) && [checkValidColl $cfile]} {
                  lappend res $cfile
               }
            }
         }
      }
      exact {
         if {$coll ne {__init__}} {
            # verify that file exists
            if {![file exists $collfile]} {
               if {$errnomatch} {
                  reportErrorAndExit "Collection $colldesc cannot be found"
               } else {
                  set collfile {}
               }
            # error will be raised if collection not valid
            } elseif {$checkvalid && ![checkValidColl $collfile\
               $errnomatch]} {
               set collfile {}
            }
         }
         if {$collfile eq {} && $swap_by_init} {
            set collfile __init__
            set colldesc {}
         }
         # return coll filename and its description for exact and name modes
         set res [list $collfile $colldesc]
      }
      name {
         set res [list $collfile $colldesc]
      }
   }

   return $res
}

proc checkValidColl {collfile {report_issue 0}} {
   set res 0
   if {[catch {
      set fdata [readFile $collfile 1]
      # extract magic cookie (first word)
      set fh [string trimright [lindex [split [string range $fdata 0 32]]\
         0] #]
   } errMsg ]} {
      if {$report_issue} {
         reportErrorAndExit [parseAccessIssue $collfile]
      }
   } else {
      # collection without magic cookie are valid
      # check if min version requirement is met
      if {[string equal -length 8 $fh {#%Module}] && [string length $fh] \
         > 8 && [versioncmp [getState modules_release] [string range $fh 8\
         end]] < 0} {
         if {$report_issue} {
            reportErrorAndExit "Collection $collfile requires at least\
               Modules version [string range $fh 8 end]"
         }
      } else {
         set res 1
      }
   }
   return $res
}

# generate collection content based on provided path and module lists
proc formatCollectionContent {path_list mod_list tag_arrser header {sgr 0}} {
   set content {}
   array set tag_arr $tag_arrser

   # graphically enhance module command if asked
   set modcmd [expr {$sgr ? [sgr cm module] : {module}}]

   # start collection content with modulepaths
   foreach path $path_list {
      # enclose path if space character found in it
      if {[string first { } $path] != -1} {
         set path "{$path}"
      }
      # 'module use' prepends paths by default so we clarify
      # path order here with --append flag
      append content "$modcmd use --append $path" \n
   }

   # then add modules
   foreach mod $mod_list {
      # save tags associated to module (like auto-loaded tag)
      if {[info exists tag_arr($mod)] && [llength $tag_arr($mod)]} {
         set opt "--tag=[join $tag_arr($mod) :] "
      } else {
         set opt {}
      }
      # no need to specifically enclose module specification if space char
      # used in it as $mod is a list so elements including space will be
      # automatically enclosed
      append content "$modcmd load $opt$mod" \n
   }

   # prepend header if defined and some content has been generated
   if {[string length $header] && [string length $content]} {
      set content "$header\n$content"
   }

   return $content
}

# read given collection file and return the path and module lists it defines
proc readCollectionContent {collfile colldesc} {
   # read file
   if {[catch {
      set fdata [split [readFile $collfile] \n]
   } errMsg ]} {
      reportErrorAndExit "Collection $colldesc cannot be read.\n$errMsg"
   }

   return [parseCollectionContent $fdata]
}

proc parseCollectionContent {fdata} {
   # init lists (maybe coll does not set mod to load)
   set path_list {}
   set mod_list {}
   set nuasked_list {}
   array set tag_arr {}

   # analyze collection content
   foreach fline $fdata {
      if {[regexp {module use (.*)$} $fline match patharg]} {
         # paths are appended by default
         set stuff_path append
         # manage multiple paths and path options specified on single line,
         # for instance "module use --append path1 path2 path3", with list
         # representation of patharg (which handles quoted elements containing
         # space in their name)
         foreach path $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 {
                  lprepend path_list $path
               }
            }
         }
      } elseif {[regexp {module load (.*)$} $fline match modarg]} {
         # extract collection-specific flags from module specification
         switch -glob -- [lindex $modarg 0] {
            --notuasked {
               set tag_list [list auto-loaded]
               set cleanlist [lrange $modarg 1 end]
            }
            --tag=* {
               set tag_list [split [string range [lindex $modarg 0] 6 end] :]
               set cleanlist [lrange $modarg 1 end]
            }
            default {
               set tag_list {}
               set cleanlist $modarg
            }
         }
         # parse module specification to distinguish between module + variant
         # specified and multiple modules specified on a single line
         set parsedlist [parseModuleSpecification 0 0 0 0 {*}$cleanlist]
         foreach parsed $parsedlist {
            set tag_arr($parsed) $tag_list
         }
         lappend mod_list {*}$parsedlist
      }
   }
   return [list $path_list $mod_list [array get tag_arr]]
}

# return specified collection content and differences compared to currently
# defined environment
proc getDiffBetweenCurEnvAndColl {collfile colldesc} {
   # read specific __init__ collection from __MODULES_LMINIT env var
   if {$collfile eq {__init__}} {
      lassign [parseCollectionContent [getEnvLoadedModulePropertyParsedList\
         init]] coll_path_list coll_mod_list coll_tag_arrser
   } else {
      lassign [readCollectionContent $collfile $colldesc] coll_path_list\
         coll_mod_list coll_tag_arrser
   }

   # build list of module tagged auto-loaded in collection
   array set coll_tag_arr $coll_tag_arrser
   set coll_nuasked_list {}
   foreach mod [array names coll_tag_arr] {
      if {{auto-loaded} in $coll_tag_arr($mod)} {
         lappend coll_nuasked_list $mod
      }
   }

   # collection should at least define a path or a mod, but initial env may be
   # totally empty
   if {$collfile ne {__init__} && ![llength $coll_path_list] && ![llength\
      $coll_mod_list]} {
      reportErrorAndExit "$colldesc is not a valid collection"
   }

   # load tags from loaded modules
   cacheCurrentModules

   defineModEqProc [isIcase] [getConf extended_default]

   # fetch what is currently loaded
   set curr_path_list [getModulePathList returnempty 0]
   # get current loaded module list
   set curr_mod_list [getEnvLoadedModulePropertyParsedList name]
   set curr_nuasked_list [getTaggedLoadedModuleList auto-loaded]
   # get current save tags of loaded modules
   array set curr_tag_arr [getLoadedModuleWithVariantSaveTagArrayList]

   # determine what module to unload to restore collection from current
   # situation with preservation of the load order (asking for a modeq
   # comparison will help to check against simplified mod name and variants)
   lassign [getMovementBetweenList $curr_mod_list $coll_mod_list\
      $curr_nuasked_list $coll_nuasked_list modeq] mod_to_unload mod_to_load

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

   # indicate if loaded modules that matches modules in collection have
   # different tags set
   if {![llength $mod_to_load]} {
      # consider a not-set entry as an empty element when comparing collection
      # and current environment tags. compare tags as unordered lists
      lassign [getDiffBetweenArray curr_tag_arr coll_tag_arr 1 1] notincoll\
         diff notincurr
      set is_tags_diff [llength $diff]
   # if some module from collection are not yet loaded, consider there is a
   # difference
   } else {
      set is_tags_diff 1
   }

   return [list $coll_path_list $coll_mod_list $coll_tag_arrser\
      $coll_nuasked_list $mod_to_unload $mod_to_load $path_to_unuse\
      $path_to_use $is_tags_diff]
}

proc getCollectionFromStash {stash} {
   if {[string match stash-* $stash]} {
      set coll $stash
   } elseif {[string is integer -strict $stash]} {
      # filter collection from other target (especially if no target set)
      set collfile [lindex [lsort -decreasing [findCollections stash-* glob\
         0 0 1 1]] $stash]
      if {$collfile eq {}} {
         knerror "Invalid stash index '$stash'"
      }
      # extract collection name (without path and target extension)
      set coll [file rootname [file tail $collfile]]
   } else {
      knerror "Invalid stash collection name '$stash'"
   }

   return $coll
}

proc cmdModuleList {show_oneperline show_mtime search_match args} {
   set json [isStateEqual report_format json]

   # load tags from loaded modules
   cacheCurrentModules

   if {[llength $args]} {
      defineModEqProc [isIcase] [getConf extended_default]
      # match passed name against any part of loaded module names
      set mtest [expr {{contains} in $search_match ? {matchin} : {match}}]
      set search_queries $args
      # prepare header message which depend if search is performed
      set loadedmsg {Currently Loaded Matching Modulefiles}
   } else {
      set search_queries {}
      set loadedmsg {Currently Loaded Modulefiles}
   }

   set report_indesym [isEltInReport indesym 0]
   set report_alias [expr {[isEltInReport alias 0] || [isEltInReport\
      provided-alias 0]}]

   # build list of loaded modules and symbolics and aliases if reported
   set loadedmodlist [list]
   foreach mod [getEnvLoadedModulePropertyParsedList name] {
      set modfile [getModulefileFromLoadedModule $mod]
      set mtime [expr {$show_mtime && [file exists $modfile] ?\
         [getFileMtime $modfile] : {}}]
      set mod_list($mod) [list modulefile $mtime $modfile]

      # fetch symbols from loaded environment information
      set modname [file dirname $mod]
      set sym_list {}
      foreach altname [getLoadedAltname $mod sym] {
         # skip non-symbol entry
         if {$altname ne $modname} {
            lappend sym_list [file tail $altname]
            # fill loaded list structure with symbolic versions in case
            # indesym report is activated
            if {$report_indesym} {
               set mod_list($altname) [list version $mod]
               lappend loadedmodlist $altname
            }
         }
      }
      set ::g_symbolHash($mod) [lsort -dictionary $sym_list]

      # fetch aliases from loaded environment information
      if {$report_alias} {
         foreach altname [getLoadedAltname $mod alias] {
            set mod_list($altname) [list alias $mod]
            lappend loadedmodlist $altname
         }
      }

      lappend loadedmodlist $mod
   }

   # filter-out hidden loaded modules unless all module should be seen
   if {[getState hiding_threshold] <= 1} {
      set newloadedmodlist [list]
      foreach mod $loadedmodlist {
         if {![isModuleTagged $mod hidden-loaded 1]} {
            lappend newloadedmodlist $mod
         }
      }
      set loadedmodlist $newloadedmodlist
   }

   # same header msg if no module loaded at all whether search is made or not
   set noloadedmsg [expr {![llength $loadedmodlist] ? {No Modulefiles\
      Currently Loaded.} : {No Matching Modulefiles Currently Loaded.}}]

   # filter loaded modules not matching any of the mod spec passed
   if {[llength $args]} {
      # include alt name comparison (alias/sym) when checking module name
      # depends if alias and/or sym are reported independently
      set modeq_altname_mode [switch -- $report_indesym$report_alias {
         11 {expr {0}}
         10 {expr {7}}
         01 {expr {6}}
         00 {expr {2}}
      }]
      set matchlist [list]
      foreach mod $loadedmodlist {
         foreach pattern $args {
            # compare pattern against loaded module, its variant set and its
            # alternative names
            if {[modEq $pattern $mod $mtest 1 $modeq_altname_mode 1 0 *]} {
               lappend matchlist $mod
               break
            }
         }
      }
      set loadedmodlist $matchlist
   }

   if {![llength $loadedmodlist]} {
      if {!$json && [isEltInReport header]} {
         report $noloadedmsg
      }
   } else {
      set one_per_line [expr {$show_mtime || $show_oneperline}]
      set show_idx [expr {!$show_mtime && [isEltInReport idx]}]
      set header [expr {!$json && [isEltInReport header] ? $loadedmsg :\
         {noheader}}]
      set theader_cols [list hi Package 39 Versions 19 {Last mod.} 19]

      reportModules $search_queries $header {} terse $show_mtime $show_idx\
         $one_per_line $theader_cols loaded $loadedmodlist

      # display output key
      if {!$show_mtime && !$json && [isEltInReport key]} {
         displayKey
      }
   }
}

proc cmdModuleDisplay {args} {
   lappendState mode display
   set first_report 1
   foreach mod $args {
      lassign [getPathToModule $mod] modfile modname modnamevr
      if {$modfile ne {}} {
         # only one separator lines between 2 modules
         if {$first_report} {
            displaySeparatorLine
            set first_report 0
         }
         report [sgr hi $modfile]:\n
         execute-modulefile $modfile $modname modnamevr $mod 1
         displaySeparatorLine
      }
   }
   lpopState mode
}

proc cmdModulePaths {mod} {
   set dir_list [getModulePathList exiterronundef]
   foreach dir $dir_list {
      array unset mod_list
      array set mod_list [getModules $dir $mod 0 [list 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 [list $dir {*}[replaceFromList $dir_list $dir]]

      # forcibly enable implicit_default to resolve alias target when it
      # points to a directory
      setConf implicit_default 1

      # 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 0]\
                  modfile modname modnamevr
               # add module target as result instead of alias
               if {$modfile ne {} && ![info exists mod_list($modname)]} {
                  lappend ::g_return_text $modfile
               }
            }
         }
      }

      # reset implicit_default to restore behavior defined
      unsetConf implicit_default
   }

   # 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} {
   lassign [getPathToModule $mod] modfile modname modnamevr
   # if no result set empty value to return empty
   if {$modfile eq {}} {
      set ::g_return_text {}
   } else {
      lappend ::g_return_text $modfile
   }
}

proc cmdModuleSearch {args} {
   # disable error report to avoid modulefile errors to mix with valid results
   set mod_pattern_list [lassign $args search]

   set json [isStateEqual report_format json]
   set icase [isIcase]
   defineModEqProc $icase [getConf extended_default]

   lappend searchmod rc_defs_included
   if {![llength $mod_pattern_list]} {
      lappend mod_pattern_list {}
      lappend searchmod wild
   } else {
      foreach mod_pattern $mod_pattern_list {
         if {![string length $mod_pattern]} {
            reportError [getEmptyNameMsg module]
            return
         }
      }
   }

   inhibitErrorReport

   set foundmod 0
   lappendState mode whatis
   set dir_list [getModulePathList exiterronundef]
   foreach dir $dir_list {
      array unset mod_list
      array set mod_list [getMatchingAnyModules $dir $mod_pattern_list 0\
         $searchmod {}]
      array unset interp_list
      array set interp_list {}

      # forcibly enable implicit_default to resolve alias target when it
      # points to a directory
      setConf implicit_default 1

      # build list of modulefile to interpret
      foreach elt [array names mod_list] {
         switch -- [lindex $mod_list($elt) 0] {
            modulefile {
               if {[isModuleTagged $elt forbidden 0 $dir/$elt]} {
                  # register any error occurring on element matching search
                  if {[modEqAny $mod_pattern_list $elt]} {
                     set err_list($elt) [list accesserr [getForbiddenMsg\
                        $elt $dir/$elt]]
                  }
               } else {
                  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 {
               if {[isModuleTagged $elt forbidden 0 [lindex $mod_list($elt)\
                  2]]} {
                  # register any error occurring on element matching search
                  if {[modEqAny $mod_pattern_list $elt]} {
                     set err_list($elt) [list accesserr [getForbiddenMsg\
                        $elt [lindex $mod_list($elt) 2]]]
                  }
               } else {
                  set interp_list($elt) [lindex $mod_list($elt) 2]
                  set full_list($elt) 1
               }
            }
            alias {
               # resolve alias target
               set elt_target [lindex $mod_list($elt) 1]
               if {![info exists full_list($elt_target)]} {
                  lassign [getPathToModule $elt_target $dir 0]\
                     modfile modname modnamevr 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 [modEqAny\
                           $mod_pattern_list $elt]]
                     # register resolution error if alias name matches search
                     } elseif {[modEqAny $mod_pattern_list $elt]} {
                        set err_list($modname) [list $issuetype $issuemsg]
                     }
                  }
               }
            }
            version {
               # report error of version target if matching query
               set elt_target [getArrayKey mod_list [lindex $mod_list($elt)\
                  1] $icase]
               if {[info exists mod_list($elt_target)] && [lindex\
                  $mod_list($elt_target) 0] in [list invalid accesserr] &&\
                  [modEqAny $mod_pattern_list $elt]} {
                  set err_list($elt_target) $mod_list($elt_target)
               } elseif {![info exists mod_list($elt_target)]} {
                  set extra_search($elt_target) [list $dir [modEqAny\
                     $mod_pattern_list $elt]]
               }
            }
            invalid - accesserr {
               # register any error occurring on element matching search
               if {[modEqAny $mod_pattern_list $elt]} {
                  set err_list($elt) $mod_list($elt)
               }
            }
         }
      }

      # reset implicit_default to restore behavior defined
      unsetConf implicit_default

      # 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]} {
         set interp_save($dir) [array get interp_list]
      }
   }

   # forcibly enable implicit_default to resolve alias target when it points
   # to a directory
   setConf implicit_default 1

   # find target of aliases in all modulepath except the one already tried
   foreach elt [array names extra_search] {
      lassign [getPathToModule $elt {} 0 no [lindex $extra_search($elt) 0]]\
         modfile modname modnamevr 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]
      }
   }

   # reset implicit_default to restore behavior defined
   unsetConf implicit_default

   # prepare string translation to highlight search query string
   set matchmodmap [prepareMapToHightlightSubstr {*}$mod_pattern_list]
   set matchsearchmap [prepareMapToHightlightSubstr $search]

   # 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 {}
            ##nagelfar ignore Suspicious variable name
            execute-modulefile $interp_list($elt) $elt $elt $elt 0 0

            # treat whatis as a multi-line text
            if {$search eq {} || [regexp -nocase $search $::g_whatis]} {
               if {$json} {
                  lappend display_list [formatListEltToJsonDisplay $elt\
                     whatis a $::g_whatis 1]
               } else {
                  set eltsgr [string map $matchmodmap $elt]
                  foreach line $::g_whatis {
                     set linesgr [string map $matchsearchmap $line]
                     lappend display_list "[string repeat { } [expr {20 -\
                        [string length $elt]}]]$eltsgr: $linesgr"
                  }
               }
            }
         }

         displayElementList $dir mp sepline 1 0 0 $display_list
      }
   }
   lpopState mode

   setState inhibit_errreport 0

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

# Intermediate procedure between module and cmdModuleSwitch
# Adapt options and arguments depending on context to call cmdModuleSwitch
proc cmdModuleIntSwitch {mode tag_list args} {
   # pass 'user asked state' to switch procedure
   set uasked [isTopEvaluation]
   # CAUTION: it is not recommended to use the `switch` sub-command in
   # modulefiles as this command is intended for the command-line for a 2in1
   # operation. Could be removed from the modfile scope in a future release.
   # Use `module unload` and `module load` commands in modulefiles instead.
   if {$uasked || $mode eq {load}} {
      set ret [cmdModuleSwitch $uasked $tag_list {*}$args]
      if {!$uasked && $ret && ![getState force]} {
         knerror {} MODULES_ERR_SUBFAILED
      }
   } else {
      # find what has been asked for unload and load
      lassign $args swunmod swlomod
      if {$swlomod eq {} && $swunmod ne {}} {
         set swlomod $swunmod
      }

      # apply same mechanisms than for 'module load' and 'module unload' for
      # an unload evaluation: nothing done for switched-off module and unload
      # of switched-on module. If auto handling is enabled switched-on module
      # is handled via UReqUn mechanism (unless if implicit_requirement has
      # been inhibited). Also unloads are triggered by ongoing reload, purge,
      # restore, reset, stash or stashpop cmds
      if {(![getConf auto_handling] || [getState inhibit_req_record] eq\
         [currentState evalid]) && $swlomod ne {} && [aboveCommandName] ni\
         [list purge reload restore reset stash stashpop]} {
         # unload mod if it was loaded prior this mod, not user asked and not
         # required by another loaded module
         set modlist [getEnvLoadedModulePropertyParsedList name]
         set modidx [lsearch -exact $modlist [currentState modulename]]
         if {$modidx != 0} {
            set priormodlist [lrange $modlist 0 $modidx]
            if {[set unmod [getLoadedMatchingName $swlomod {} 0\
               $priormodlist]] ne {}} {
               cmdModuleUnload urequn match 1 s 1 $unmod
            }
         }
      }
   }
}

proc cmdModuleSwitch {uasked tag_list 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
   }
   # save orig names to register them as deps if called from modulefile
   set argnew $new
   if {$new eq $old} {
      set argold {}
   } else {
      set argold $old
   }

   reportDebug "old='$old' new='$new' (uasked=$uasked)"

   # extend requirement recording inhibition to switch subcontext
   set inhibit_req_rec [expr {[currentState inhibit_req_record] ==\
      [currentState evalid]}]

   # record sumup messages from underlying unload/load actions under the same
   # switch message record id to report (evaluation messages still go under
   # their respective unload/load block
   if {$uasked} {
      pushMsgRecordId switch-$old-$new-[depthState modulename]
   }
   if {$inhibit_req_rec} {
      lappendState inhibit_req_record [currentState evalid]
   }

   pushSettings

   # enable unload of sticky mod if stickiness is preserved on swapped-on mod
   # need to resolve swapped-off module here to get stickiness details
   lassign [getPathToModule $old {} 0 $unload_match] modfile oldmod oldmodvr
   set swunmod_is_supersticky [isModuleTagged $oldmod super-sticky 1 $modfile]
   lassign [getPathToModule $new {} 0] newmodfile newmod newmodvr
   set sticky_reload [isStickinessReloading $oldmodvr [list $newmodvr]]
   set supersticky_reload [isStickinessReloading $oldmodvr [list $newmodvr]\
      super-sticky]
   set report_newmod_issue 0

   # do not set sticky or supersticky reload states if swap-on module cannot
   # be found
   if {($supersticky_reload || $sticky_reload) && $newmodfile eq {}} {
      set report_newmod_issue 1
      set sticky_reload 0
      set supersticky_reload 0
   }
   if {$sticky_reload} {
      lappendState reloading_sticky $oldmod
   }
   if {$supersticky_reload} {
      lappendState reloading_supersticky $oldmod
   }
   ##nagelfar implicitvarcmd {cmdModuleUnload swunload *} oldhidden olduasked\
      oldmsgrecid
   set ret_unload [cmdModuleUnload swunload $unload_match 1 s 0 $old]
   if {$sticky_reload} {
      lpopState reloading_sticky
   }
   if {$supersticky_reload} {
      lpopState reloading_supersticky
   }

   # register modulefile to unload as conflict if an unload module is
   # mentioned on this module switch command set in a modulefile
   # skip conflict declaration if old spec matches new as in this case switch
   # means *replace loaded version of mod by this specific version*
   if {!$uasked && $argold ne {} && ($newmod eq {} || ![modEq $argold $newmod\
      eqstart])} {
      registerCurrentModuleConflict $argold
   }

   # attempt load and depre reload only if unload succeed (or if top switch
   # evaluation has "continue on error" behavior enabled and switched-off
   # module is not super-sticky or if sub-evaluation is forced)
   if {!$ret_unload || ([isTopEvaluation] && ![commandAbortOnError] &&\
      ![commandAbortOnError switch_unload] && !$swunmod_is_supersticky) ||
      (![isTopEvaluation] && [getState force])} {
      ##nagelfar implicitvarcmd {cmdModuleLoad swload *} newhidden newmsgrecid
      set ret_load [cmdModuleLoad swload $uasked 0 0 $tag_list {} $new]

      # rollback settings if load evaluation went wrong and abort behavior is
      # enabled for this switch phase
      if {$ret_load && [commandAbortOnError]} {
         restoreSettings
      } else {
         set ret_auto 0
         if {[getConf auto_handling] && [isTopEvaluation]} {
            removeUReqUnFromDepReAndConvertEval
            unloadUReqUnModules

            # DepRe load phase now other mechanisms are done
            # Try DepRe load phase: load failure will not make switch fail
            if {[set ret_auto [catch {
               reloadDepReModules
            } errMsg]]} {
               reportError $errMsg
               restoreSettings
            }
         }

         # report a summary of automated evaluations if no error
         if {!$ret_auto && $uasked} {
            reportModuleEval
         }
      }
   } else {
      # re-run switched-on module search to report locating issue
      if {$report_newmod_issue} {
         getPathToModule $new
      }
      # initialize dummy load phase msg rec id to query designation
      set newmsgrecid {}
   }

   popSettings

   # report all recorded sumup messages for this evaluation unless both old
   # and new modules are set hidden, old was auto loaded and this switch is
   # done by a modfile
   if {$uasked} {
      reportMsgRecord "Switching from [getModuleDesignation $oldmsgrecid {}\
         2] to [getModuleDesignation $newmsgrecid $new 2]" [expr {$oldhidden\
         && !$olduasked && $newhidden && !$uasked}]
      popMsgRecordId
   }
   if {$inhibit_req_rec} {
      lpopState inhibit_req_record
   }

   # register modulefile load attempt as prereq when called from modulefile
   if {!$uasked && [info exists ret_load] && $argnew ne {}} {
      prereqAnyModfileCmd 0 0 $argnew
   }

   return [expr {$ret_unload || $ret_load}]
}

proc cmdModuleSave {{coll default}} {
   if {![areModuleConstraintsSatisfied]} {
      reportErrorAndExit {Cannot save collection, some module constraints are\
         not satisfied}
   }

   # format collection content, version number of modulefile are saved if
   # version pinning is enabled
   if {[getConf collection_pin_version]} {
      set curr_mod_list [getLoadedModuleWithVariantList]
      set curr_tag_arrser [getLoadedModuleWithVariantSaveTagArrayList]
   } else {
      lassign [getSimplifiedLoadedModuleList] curr_mod_list curr_tag_arrser
   }
   # generate collection content with header indicating oldest Modules version
   # compatible with collection syntax
   set coll_header [expr {[llength $curr_tag_arrser] ? {#%Module5.1} :\
      {}}]

   set save [formatCollectionContent [getModulePathList returnempty 0]\
      $curr_mod_list $curr_tag_arrser $coll_header]

   if {![string length $save]} {
      reportErrorAndExit {Nothing to save in a collection}
   }

   # get corresponding filename and its directory
   lassign [findCollections $coll name] collfile colldesc
   set colldir [file dirname $collfile]

   if {![file exists $colldir]} {
      reportDebug "Creating $colldir"
      if {[catch {file mkdir $colldir} errMsg]} {
         reportErrorAndExit "Collection directory cannot be created.\n$errMsg"
      }
   } elseif {![file isdirectory $colldir]} {
      reportErrorAndExit "$colldir exists but is not a directory"
   }

   reportDebug "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 {args} {
   # distinguish between zero and one argument provided
   if {![llength $args]} {
      set arg_provided 0
      set coll default
   } else {
      set arg_provided 1
      set coll [lindex $args 0]
   }

   # get corresponding collection, raise error if it does not exist unless
   # if no collection name has been provided or if __init__
   lassign [findCollections $coll exact [expr {!$arg_provided}]\
      $arg_provided] collfile colldesc

   # forcibly enable implicit_default to restore colls saved in this mode
   setConf implicit_default 1

   # fetch collection content and differences compared current environment
   lassign [getDiffBetweenCurEnvAndColl $collfile $colldesc] coll_path_list\
      coll_mod_list coll_tag_arrser coll_nuasked_list mod_to_unload\
      mod_to_load path_to_unuse path_to_use is_tags_diff
   array set coll_tag_arr $coll_tag_arrser

   # create an eval id to track successful/failed module evaluations
   pushMsgRecordId restore-$coll-[depthState modulename] 0

   # unload modules one by one (no dependency auto unload)
   foreach mod [lreverse $mod_to_unload] {
      # test stickiness over full module name version variant designation
      set modvr [getAndParseLoadedModuleWithVariant $mod]
      # sticky modules can be unloaded when restoring collection
      lappendState unloading_sticky $mod
      if {[set supersticky_reload [isStickinessReloading $modvr $mod_to_load\
         super-sticky]]} {
         lappendState reloading_supersticky $mod
      }
      cmdModuleUnload unload match 0 s 0 $mod
      lpopState unloading_sticky
      if {$supersticky_reload} {
         lpopState reloading_supersticky
      }
   }
   # unuse paths
   if {[llength $path_to_unuse]} {
      cmdModuleUnuse load {*}[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]

   set curr_mod_list [getEnvLoadedModulePropertyParsedList name]
   set curr_nuasked_list [getTaggedLoadedModuleList auto-loaded]

   # update tags sets on the modules already loaded at correct position
   # remove extra tags that are not defined in collection
   foreach modvr [getLoadedModuleWithVariantList] {
      if {[info exists coll_tag_arr($modvr)]} {
         set tag_list $coll_tag_arr($modvr)
      } else {
         set tag_list {}
      }
      # indicate if module has been asked by user
      cmdModuleTag 1 [expr {![isModuleTagged $modvr auto-loaded 1]}]\
         $tag_list $modvr
   }

   # determine what module to load to restore collection from current
   # situation with preservation of the load order
   # list of alternative and simplified names for loaded modules has been
   # gathered and cached during the previous getMovementBetweenList call on
   # modules, so here the getMovementBetweenList call will correctly get these
   # alternative names for module comparison even if no modulepath is left set
   lassign [getMovementBetweenList $curr_mod_list $coll_mod_list\
      $curr_nuasked_list $coll_nuasked_list modeq] mod_to_unload mod_to_load

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

   # reset implicit_default to restore behavior defined
   unsetConf implicit_default

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

   # load modules one by one with user asked state preserved
   foreach mod $mod_to_load {
      cmdModuleLoad load [expr {$mod ni $coll_nuasked_list}] 0 0\
         $coll_tag_arr($mod) {} $mod
   }

   popMsgRecordId 0
}

proc cmdModuleSaverm {{coll default}} {
   # 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 corresponding collection, raise error if it does not exist, but do
   # not check if collection is valid
   lassign [findCollections $coll exact 0 1 0] collfile colldesc

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

proc cmdModuleSaveshow {args} {
   # distinguish between zero and one argument provided
   if {![llength $args]} {
      set arg_provided 0
      set coll default
   } else {
      set arg_provided 1
      set coll [lindex $args 0]
   }

   # get corresponding collection, raise error if it does not exist unless
   # if no collection name has been provided or if __init__
   lassign [findCollections $coll exact [expr {!$arg_provided}]\
      $arg_provided] collfile colldesc

   # read specific __init__ collection from __MODULES_LMINIT env var
   if {$collfile eq {__init__}} {
      lassign [parseCollectionContent [getEnvLoadedModulePropertyParsedList\
         init]] coll_path_list coll_mod_list coll_tag_arrser
      set collfile {initial environment}
      set coll __init__
   } else {
      lassign [readCollectionContent $collfile $colldesc] coll_path_list\
         coll_mod_list coll_tag_arrser
   }

   # collection should at least define a path or a mod, but initial env may be
   # totally empty
   if {$coll ne {__init__} && ![llength $coll_path_list] && ![llength\
      $coll_mod_list]} {
      reportErrorAndExit "$colldesc is not a valid collection"
   }

   displaySeparatorLine
   report [sgr hi $collfile]:\n
   report [formatCollectionContent $coll_path_list $coll_mod_list\
      $coll_tag_arrser {} 1]
   displaySeparatorLine
}

proc cmdModuleSavelist {show_oneperline show_mtime search_match args} {
   # if a target is set, only list collection matching this target (means
   # having target as suffix in their name) unless if --all option is set
   set colltarget [getConf collection_target]
   if {$colltarget ne {} && [getState hiding_threshold] < 2} {
      set suffix .$colltarget
      set targetdesc " (for target \"$colltarget\")"
   } else {
      set suffix {}
      set targetdesc {}
   }

   set json [isStateEqual report_format json]

   reportDebug "list collections$targetdesc"

   # if only stash collection are expected, start result index at 0, sort
   # results in reverse order (latest first) and ensure only collection from
   # current target (and no-target if none set) are returned.
   if {[getCallingProcName] eq {cmdModuleStashlist}} {
      set start_idx 0
      set sort_opts [list -dictionary -decreasing]
      set find_no_other_target 1
      set typedesc stash
      # no icase match as stash collections are only lowercases
      set icase 0
   } else {
      set start_idx 1
      set sort_opts [list -dictionary]
      set find_no_other_target 0
      set typedesc named
      set icase [isIcase]
   }

   if {[llength $args]} {
      defineModEqProc $icase 0
      # match passed name against any part of collection names
      set mtest [expr {{contains} in $search_match ? {matchin} : {match}}]
   }

   # prepare header message which depend if search is performed (no search
   # considered if listing stash collections)
   if {[llength $args] && $typedesc ne {stash}} {
      set collmsg "Matching $typedesc collection list$targetdesc:"
   } else {
      set collmsg "[string totitle $typedesc] collection list$targetdesc:"
   }

   foreach collfile [findCollections * glob 0 0 1 $find_no_other_target] {
      # remove target suffix from names to display
      regsub $suffix$ [file tail $collfile] {} coll
      # filter stash collections unless called by stashlist or --all opt set
      if {$typedesc ne {named} || ![regexp {stash-\d+} $coll] || [getState\
         hiding_threshold] >= 2} {
         set coll_arr($coll) $collfile
      }
   }

   # same header msg if no collection at all whether search is made or not
   if {![array exists coll_arr] || $typedesc eq {stash}} {
      set nocollmsg "No $typedesc collection$targetdesc."
   } else {
      set nocollmsg "No matching $typedesc collection$targetdesc."
   }

   # filter collection not matching any of the passed specification
   if {[llength $args]} {
      set matchlist [list]
      foreach coll [array names coll_arr] {
         set match 0
         foreach pattern $args {
            # compare pattern against collections using comparison module proc
            # useful for suffix/prefix/icase checks, disabling module-specific
            # checks (variants, alternative names, etc)
            if {[modEq $pattern $coll $mtest 0 0 0 0 *]} {
               set match 1
               break
            }
         }
         if {!$match} {
            unset coll_arr($coll)
         }
      }
   }

   if {![array size coll_arr]} {
      if {!$json} {
         report $nocollmsg
      }
   } else {
      if {!$json} {
         if {$show_mtime} {
            displayTableHeader hi Collection 59 {Last mod.} 19
         }
         report $collmsg
      }
      set display_list {}
      set len_list {}
      set max_len 0
      set one_per_line [expr {$show_mtime || $show_oneperline}]
      set show_idx [expr {!$one_per_line}]
      # prepare query to highlight
      set himatchmap [prepareMapToHightlightSubstr {*}$args]

      foreach coll [lsort {*}$sort_opts [array names coll_arr]] {
         if {$json} {
            lappend display_list [formatListEltToJsonDisplay $coll target s\
               $colltarget 1 pathname s $coll_arr($coll) 1]
         # no need to test coll consistency as findCollections does not return
         # collection whose name starts with "."
         } else {
            set collsgr [sgr {} $coll $himatchmap]
            if {$show_mtime} {
               set filetime [clock format [getFileMtime $coll_arr($coll)]\
                  -format {%Y/%m/%d %H:%M:%S}]
               lappend display_list [format %-60s%19s $collsgr $filetime]
            } else {
               lappend display_list $collsgr
               lappend len_list [set len [string length $coll]]
               if {$len > $max_len} {
                  set max_len $len
               }
            }
         }
      }

      displayElementList noheader {} {} $one_per_line $show_idx $start_idx\
         $display_list $len_list $max_len
   }
}


proc cmdModuleSource {mode args} {
   foreach mod $args {
      set rawarg [getRawArgumentFromVersSpec $mod]
      if {$mod eq {}} {
         reportErrorAndExit {File name empty}
      # first check if raw specification is an existing file
      } elseif {[file exists [set absfpath [getAbsolutePath $rawarg]]]} {
         set modfile $absfpath
         set modname $absfpath
         set modnamevr $absfpath
         # unset module specification not to confuse specific char in file
         # path (like '+') with variant specification
         unsetModuleVersSpec $mod
         set mod $absfpath
      # if not a path specification, try to resolve a modulefile
      } elseif {![isModuleFullPath $rawarg]} {
         lassign [getPathToModule $mod] modfile modname modnamevr
         # stop if no module found, issue has been reported by getPathToModule
         if {$modfile eq {}} {
            break
         }
      } else {
         reportErrorAndExit "File $rawarg does not exist"
      }

      ##nagelfar ignore Found constant
      lappendState mode $mode
      # sourced file must also have a magic cookie set at their start
      ##nagelfar ignore Suspicious variable name
      execute-modulefile $modfile $modname $modnamevr $mod 1 0 0
      ##nagelfar ignore Found constant
      lpopState mode
   }
}

# Intermediate procedure between module and cmdModuleLoad/prereq
# Adapt options and arguments depending on context to call cmdModuleLoad or
# one of the prereq procedures
proc cmdModuleIntLoad {topcall command mode tag_list args} {
   # ignore flag used in collection to track non-user asked state
   set args [replaceFromList $args --notuasked]
   # no error raised on empty argument list to cope with initadd command that
   # may expect this behavior
   if {![llength $args]} {
      return
   }

   set ret 0
   # if top command is source, consider module load commands made within
   # sourced file evaluation as top load command
   if {[isTopEvaluation]} {
      # is eval a regular attempt or a try (silence not found error)
      set tryload [expr {$command in {try-load load-any}}]
      set loadany [expr {$command eq {load-any}}]
      set ret [cmdModuleLoad load 1 $tryload $loadany $tag_list {} {*}$args]
   } elseif {$mode eq {load}} {
      # auto load is inhibited if currently in DepRe context only register
      # requirement
      set subauto [expr {[currentModuleEvalContext] eq {depre} ? {0} : {1}}]
      if {$command eq {try-load}} {
         # attempt load of not already loaded modules
         if {$subauto} {
            foreach arg $args {
               lassign [loadRequirementModuleList 1 0 $tag_list {} $arg] retlo
               # update return value if an issue occurred unless force mode is
               # enabled
               if {$retlo != 0 && ![getState force]} {
                  set ret $retlo
               }
               # record requirement prior raising error
               prereqAllModfileCmd 1 0 --optional --tag [join $tag_list :]\
                  $arg
               # report error message and raise error
               if {$retlo != 0} {
                  reportMissingPrereqError [currentState modulenamevr] {} $arg
               }
            }
         } else {
            # record requirement as optional: no error if not loaded but
            # reload will be triggered if loaded later on
            prereqAllModfileCmd 1 0 --optional --tag [join $tag_list :]\
               {*}$args
         }
      } elseif {$command eq {load-any}} {
         # load and register requirement in a OR-operation
         prereqAnyModfileCmd 1 $subauto --tag [join $tag_list :] {*}$args
      } else {
         # load and register requirement in a AND-operation
         prereqAllModfileCmd 0 $subauto --tag [join $tag_list :] {*}$args
      }
   # mods unload is handled via UReqUn mechanism when auto enabled (unless if
   # implicit_requirement has been inhibited) also unloads are triggered by
   # ongoing reload, purge, restore, reset, stash or stashpop cmds
   } elseif {(![getConf auto_handling] || [getState inhibit_req_record] eq\
      [currentState evalid]) && [aboveCommandName] ni [list purge reload\
      restore reset stash stashpop]} {
      # on unload mode, unload mods in reverse order, if loaded prior this
      # mod, if not user asked and not required by other loaded mods
      set modlist [getEnvLoadedModulePropertyParsedList name]
      set modidx [lsearch -exact $modlist [currentState modulename]]
      if {$modidx != 0} {
         set priormodlist [lrange $modlist 0 $modidx]
         foreach arg [lreverse $args] {
            if {[set unmod [getLoadedMatchingName $arg {} 0 $priormodlist]]\
               ne {}} {
               cmdModuleUnload urequn match 1 s 1 $unmod
            }
         }
      }
   }
   # sub-module interpretation failed, raise error
   if {$ret && !$topcall} {
      knerror {} MODULES_ERR_SUBFAILED
   }
}

proc cmdModuleLoad {context uasked tryload loadany tag_list modulepath_list\
   args} {
   reportDebug "loading $args (context=$context, uasked=$uasked,\
      tryload=$tryload, loadany=$loadany, tag_list=$tag_list,\
      modulepath_list=$modulepath_list)"

   set ret 0
   set one_mod_loaded 0
   lappendState mode load
   foreach mod $args {
      set mod_load_in_error 0
      # stop when first module in list is loaded if any mode enabled
      if {$one_mod_loaded && $loadany} {
         break
      }

      # if a switch action is ongoing...
      if {$context eq {swload}} {
         set swprocessing 1
         # context is ReqLo if switch is called from a modulefile
         if {![isTopEvaluation]} {
            set context reqlo
         }
         upvar newhidden hidden
         upvar newmsgrecid msgrecid
      }
      lappendState eval_context $context
      # loading module is visible by default
      set hidden 0
      # error if module not found or forbidden
      set notfounderr [expr {!$tryload}]

      # first try to resolve over loaded mods unless spec corresponds to
      # multiple module designations, otherwise search avail mods
      unset -nocomplain modfile
      if {[getCmpSpecFromVersSpec $mod] eq {eq}} {
         lassign [getPathToModule $mod $modulepath_list 0 exact] modfile\
            modname modnamevr
      }
      if {![info exists modfile] || ![string length $modfile]} {
         lassign [getPathToModule $mod $modulepath_list $notfounderr] modfile\
            modname modnamevr
      }

      # record evaluation attempt on specified module name
      registerModuleEvalAttempt $context $mod $modfile

      # set a unique id to record messages related to this evaluation.
      set msgrecid load-$modnamevr-[depthState modulename]

      # go to next module to load if not matching module found
      if {$modfile eq {}} {
         set ret $notfounderr
         continue
      }

      if {[isModuleEvalFailed load $modnamevr]} {
         reportDebug "$modnamevr ($modfile) load was already tried and failed"
         # nullify this evaluation attempt to avoid duplicate issue report
         unregisterModuleEvalAttempt $context $mod $modfile
         continue
      }

      # if a switch action is ongoing...
      if {[info exists swprocessing]} {
         # transmit loaded mod name for switch report summary
         uplevel 1 set new "{$modnamevr}"
      }

      # register record message unique id (now we know mod will be evaluated)
      pushMsgRecordId $msgrecid

      # record evaluation attempt on actual module name
      registerModuleEvalAttempt $context $modnamevr $modfile
      registerModuleEvalAttempt $context $modfile $modfile

      # 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 $modnamevr]
      if {$loadedmodname ne {}} {
         set modname $loadedmodname
         set modnamevr [getAndParseLoadedModuleWithVariant $modname]
      }

      # record module title (with the variant specified on load call, and no
      # tag list) prior module evaluation to get this title ready in case of
      # eval error
      registerModuleDesignation $msgrecid $modname [getVariantList $mod 7 0\
         1] {}

      pushSettings
      if {[set errCode [catch {
         if {[set isloaded [isModuleLoaded $modname]]} {
            set isloading 0
         } else {
            set isloading [isModuleLoading $modname]
         }
         if {$isloaded || $isloading} {
            # stop if same mod is loaded but from a modulepath not part of
            # constrained list
            if {($isloaded && ![isLoadedMatchSpecificPath $modname\
               $modulepath_list]) || ($isloading &&\
               ![isLoadingMatchSpecificPath $modname $modulepath_list])} {
               # no error if ConUn mechanism handles unload of this module
               if {![getConf auto_handling] || ![getConf conflict_unload] ||\
                  $isloading} {
                  knerror [getModFromDiffPathIsLoadedMsg]
               }
            # stop if same mod is loaded but with a different set of variants
            } elseif {[isOtherVariantOfModuleLoaded $modnamevr] ||\
               [isOtherVariantOfModuleLoading $modnamevr]} {
               # no error if ConUn mechanism handles unload of this module
               if {![getConf auto_handling] || ![getConf conflict_unload] ||\
                  $isloading} {
                  knerror [getModWithAltVrIsLoadedMsg $modname $isloading]
               }
            } else {
               reportDebug "$modname ($modfile) already loaded/loading"
               # apply missing tag to loaded module
               set rettag [cmdModuleTag 0 $uasked $tag_list $modname]

               # report module is already loaded if verbose2 or higher level
               # and no new tag set
               if {$isloaded && $rettag != 2 && [isVerbosityLevel verbose2]} {
                  reportInfo "Module '$modname' is already loaded"
                  registerModuleDesignation $msgrecid $modname\
                     [getVariantList $modname 7] [getExportTagList $modname]
                  reportMsgRecord "Loading [getModuleDesignation $msgrecid {}\
                     2]"
               }

               # exit treatment but no need to restore settings
               set one_mod_loaded 1
               continue
            }
         }

         if {[isTopEvaluation] && ![info exists swprocessing]} {
            clearLoadedReqOfReloadingModuleList
            clearLoadedReqOfUnloadingModuleList
            clearUReqUnFromDepReList
            clearDepReList
         }

         set unload_mod_list {}

         # register altname of modname prior any conflict check
         setLoadedAltname $modname {*}[getAllModuleResolvedName $modname 1\
            $mod]

         if {[getConf auto_handling]} {
            if {[getConf conflict_unload]} {
               # get loaded conflicting modules and unload them (ConUn)
               set conun_mod_list [getModuleLoadedConflict $modnamevr\
                  $modulepath_list]
               reportDebug "conun mod list is '$conun_mod_list'"
               if {[llength $conun_mod_list]} {
                  foreach conun_mod [lreverse $conun_mod_list] {
                     if {[cmdModuleUnload conun match 1 s 0 $conun_mod]} {
                        # error message already sent within cmdModuleUnload
                        knerrorOrWarningIfForced {}
                     }
                  }
               }
               lappend unload_mod_list {*}$conun_mod_list
            }

            # get loaded modules holding a requirement on modname and able to
            # be reloaded
            set depre_list [getUnmetDependentLoadedModuleList $modnamevr\
               $modfile]
            reportDebug "depre mod list is '$depre_list'"

            if {[isTopEvaluation]} {
               saveLoadedReqOfReloadingModuleList $depre_list $unload_mod_list
               identityUReqUnFromDepRe $depre_list $unload_mod_list
            }

            # Reload all modules that have declared a prereq on mod as they
            # may take benefit from their prereq availability if it is newly
            # loaded. First perform unload phase of the reload, prior mod load
            # to ensure these dependent modules are unloaded with the same
            # loaded prereq as when they were loaded
            unloadDepUnDepReModules {} $depre_list
         }

         # record additional tags passed through --tag option prior mod eval
         # to make them known within evaluation
         if {[llength $tag_list]} {
            # record tags set with --tag as extra tag excluding tags relative
            # to the way module is loaded (auto, keep)
            lassign [getDiffBetweenList $tag_list [list auto-loaded\
               keep-loaded]] extratag_list
            setModuleAndVariantsTag $modname $modnamevr {*}$tag_list
            if {[llength $extratag_list]} {
               setModuleAndVariantsExtraTag $modname $modnamevr\
                  {*}$extratag_list
            }
         }

         if {!$uasked} {
            # set auto-loaded tag now to be able to query it during eval
            setModuleAndVariantsTag $modname $modnamevr auto-loaded
            # hide auto loaded modules if hide_auto_loaded config is enabled
            if {[getConf hide_auto_loaded]} {
               setModuleAndVariantsTag $modname $modnamevr hidden-loaded
            }
         }

         if {[execute-modulefile $modfile $modname modnamevr $mod $uasked]} {
            break
         }

         # register this evaluation on the main one that triggered it (after
         # load evaluation to report correct order with other evaluations)
         registerModuleEval $context $msgrecid

         # loading visibility depends on hidden-loaded tag
         set hidden [isModuleTagged $modnamevr hidden-loaded 1 $modfile]

         append-path LOADEDMODULES $modname
         # allow duplicate modfile entries for virtual modules
         append-path --duplicates _LMFILES_ $modfile
         # update cache arrays
         setLoadedModule $modname $modfile $uasked $modnamevr [expr {$modname\
            in [getState refresh_qualified]}]

         # register declared source-sh in environment
         setEnvLoadedModuleProperty $modname sourcesh [getLoadedSourceSh\
            $modname]

         # register declared conflict in environment
         setEnvLoadedModuleProperty $modname conflict [getLoadedConflict\
            $modname]

         # declare the prereq of this module
         setEnvLoadedModuleProperty $modname prereq [getLoadedPrereq\
            $modname]
         setEnvLoadedModuleProperty $modname prereqpath [getLoadedPrereqPath\
            $modname]

         setEnvLoadedModuleProperty $modname use [getLoadedUse $modname]

         # declare the alternative names of this module
         setEnvLoadedModuleProperty $modname altname [getLoadedAltname\
            $modname {sym alias autosym} 1]

         # declare the variant of this module
         setEnvLoadedModuleProperty $modname variant [getLoadedVariant\
            $modname]

         # declare the tags of this module
         setEnvLoadedModuleProperty $modname tag [getExportTagList $modnamevr\
            $modfile]
         setEnvLoadedModuleProperty $modname extratag [getExtraTagList\
            $modnamevr]
         setEnvLoadedModuleProperty $modname stickyrule [getStickyRuleList\
            $modnamevr $modfile]

         # declare module qualified for refresh evaluation
         if {[isModuleRefreshQualified $modname]} {
            append-path __MODULES_LMREFRESH $modname
         }

         if {[getConf auto_handling] && [isTopEvaluation] && ![info exists\
            swprocessing]} {
            # UReqUn: Useless Requirement to Unload (autoloaded requirements
            # not required by any remaining mods)
            removeUReqUnFromDepReAndConvertEval
            unloadUReqUnModules

            # Load phase of dependent module reloading. These modules now can
            # adapt since mod is seen loaded.
            reloadDepReModules
         }

         # record module title (name, variants and tags)
         registerModuleDesignation $msgrecid $modname [getVariantList\
            $modname 7] [getExportTagList $modname $modfile]

         # consider evaluation hidden if hidden loaded module is auto loaded
         # and no specific messages are recorded for this evaluation
         if {$hidden && !$uasked && ![isMsgRecorded]} {
            registerModuleEvalHidden $context $msgrecid
         }

         # report a summary of automated evaluations if no error
         reportModuleEval
      } errMsg]] != 0 && $errCode != 4} {
         set mod_load_in_error 1
         # in case of error report module info even if set hidden
         set hidden 0
         reportError $errMsg
         # rollback settings if some evaluation went wrong
         set ret 1
         restoreSettings
         # remove from successfully evaluated module list
         registerModuleEval $context $msgrecid $modnamevr load
      }
      popSettings

      # report all recorded messages for this evaluation except if module were
      # already loaded
      if {$errCode != 4} {
         reportMsgRecord "Loading [getModuleDesignation $msgrecid {} 2]"\
            [expr {$hidden && !$uasked}]
      }
      popMsgRecordId

      if {$mod_load_in_error} {
         # report load issue on the message block of the above action
         switch -- $context {
            swload {
               reportError "Load of switched-on [getModuleDesignation\
                  $msgrecid] failed"
            }
         }
      }

      if {!$mod_load_in_error} {
         set one_mod_loaded 1
      }

      # abort evaluation if error and behavior configured this way (abort
      # means stop to evaluate modulefile and flush changes of previous ones)
      # applies to top evaluation context for supported sub-commands
      if {$ret && $context eq {load} && [commandAbortOnError]} {
         flushEnvSettings
         break
      }
   }
   lpopState eval_context
   lpopState mode

   # raise error if no module has been loaded or has produced an error during
   # its load attempt in case of top-level load-any sub-command
   if {!$ret && !$one_mod_loaded && $context eq {load} && $loadany} {
      knerror "No module has been loaded"
   }

   return $ret
}

# Intermediate procedure between module and cmdModuleUnload
# Adapt options and arguments depending on context to call cmdModuleUnload
proc cmdModuleIntUnload {mode args} {
   # if top command is source, consider module load commands made within
   # sourced file evaluation as top load command
   if {[isTopEvaluation]} {
      set ret [cmdModuleUnload unload match 1 s 0 {*}$args]
   } elseif {$mode eq {load}} {
      # unload mods only on load mode, nothing done on unload mode as the
      # registered conflict guarantees the target module cannot be loaded
      # unless forced
      # enable auto unload and allow force mode if both auto handling and
      # conflict unload features are enabled
      if {[getConf conflict_unload] && [getConf auto_handling]} {
         set conun_mod_list {}
         foreach conun_arg $args {
            # unload attempt in reverse load order
            appendNoDupToList conun_mod_list {*}[lreverse\
               [getLoadedMatchingName $conun_arg returnall]]
         }
         set ret 0
         foreach conun_mod $conun_mod_list {
            if {[cmdModuleUnload conun match 1 s 0 $conun_mod] && ![getState\
               force]} {
               set ret 1
               break
            }
         }
      # otherwise module required by others are not unloaded and force mode is
      # disabled
      } else {
         set ret [cmdModuleUnload conun match 0 0 0 {*}$args]
      }

      # register modulefiles to unload as individual conflicts
      registerCurrentModuleConflict {*}$args

      # sub-module interpretation failed, raise error unless if forced
      if {$ret && ![getState force]} {
         knerror {} MODULES_ERR_SUBFAILED
      }
   }
}

proc cmdModuleUnload {context match auto force onlyureq args} {
   reportDebug "unloading $args (context=$context, match=$match, auto=$auto,\
      force=$force, onlyureq=$onlyureq)"

   # inherit force mode from force state if asked through arg
   if {$force eq {s}} {
      ##nagelfar ignore Found constant
      set force [getState force]
   }
   set ret 0
   lappendState mode unload
   foreach mod $args {
      set mod_unload_in_error 0
      # if a switch action is ongoing...
      if {$context eq {swunload}} {
         set swprocessing 1
         # context is ConUn if switch is called from a modulefile
         if {![isTopEvaluation]} {
            set context conun
         }
         upvar oldhidden hidden
         upvar olduasked uasked
         upvar oldmsgrecid msgrecid
      }
      lappendState eval_context $context
      # unloading module is visible by default
      set hidden 0
      set uasked 1

      # resolve by also looking at matching loaded module and update mod
      # specification to fully match obtained loaded module
      # enable report_issue flag to report empty module name issue
      lassign [getPathToModule $mod {} 1 $match] modfile modname\
         modnamevr errkind

      # record evaluation attempt on specified module name
      registerModuleEvalAttempt $context $mod $modfile

      # set a unique id to record messages related to this evaluation.
      set msgrecid unload-$modnamevr-[depthState modulename]

      # record module title (with the variant specified on unload call, and no
      # tag list) prior module evaluation to get this title ready in case of
      # eval error
      registerModuleDesignation $msgrecid $modname [getVariantList $modnamevr\
         7 0 1] {}

      # if a switch action is ongoing...
      if {[info exists swprocessing]} {
         # transmit unloaded mod name for switch report summary
         uplevel 1 set old "{$modnamevr}"
      }

      if {$modfile eq {}} {
         # no error return if module is not loaded
         if {$errkind eq {notloaded}} {
            reportDebug "$modname is not loaded"
            # report module is not loaded if >=verbose2 and not auto context
            if {[isVerbosityLevel verbose2] && $context ni {urequn}} {
               pushMsgRecordId $msgrecid
               reportInfo "Module '$modname' is not loaded"
               reportMsgRecord "Unloading [getModuleDesignation $msgrecid {}\
                  2]"
               popMsgRecordId
            }
         } else {
            # return error code in case of empty module name
            set ret 1
         }
         # go to next module to unload
         continue
      }

      if {$onlyureq && ![isModuleUnloadable $modname\
         [getUnloadingModuleList]]}  {
         reportDebug "$modname ($modfile) is required by loaded module or\
            asked by user"
         continue
      }

      if {[isModuleEvalFailed unload $modnamevr]} {
         reportDebug "$modnamevr ($modfile) unload was already tried and\
            failed"
         # nullify this evaluation attempt to avoid duplicate issue report
         unregisterModuleEvalAttempt $context $mod $modfile
         set ret 1
         continue
      }

      # register record message unique id (now we know mod will be evaluated)
      pushMsgRecordId $msgrecid

      # record evaluation attempt on actual module name
      registerModuleEvalAttempt $context $modnamevr $modfile
      registerModuleEvalAttempt $context $modfile $modfile

      # record module title (name, variants and tags)
      registerModuleDesignation $msgrecid $modname [getVariantList $modname\
         7] [getExportTagList $modname]

      pushSettings
      if {[set errCode [catch {
         if {[failOrSkipUnloadIfSticky $modname $modfile]} {
            continue
         }

         # stop unless forced or auto handling mode enabled if unloading
         # module violates a registered prereq
         set prereq_list [getDependentLoadedModuleList [list $modname]]
         set prereq_loaded_list [getDependentLoadedModuleList [list $modname]\
            1 1 0 0]
         if {[llength $prereq_loaded_list] && (![getConf auto_handling] ||\
            !$auto)} {
            # force mode should not affect if we only look for mods w/o dep
            if {$force} {
               # in case unload is called for a DepRe mechanism do not warn
               # about prereq violation enforced as it is due to the dependent
               # module which is already in a violation state
               if {$context ne {depre_un}} {
                  reportWarning [getDepLoadedMsg $prereq_loaded_list]
               }
            } else {
               knerror [expr {[isModuleEvaluated any $modnamevr {}\
                  {*}$prereq_loaded_list] ? [getDepLoadedMsg\
                  $prereq_loaded_list] : [getErrPrereqMsg $prereq_loaded_list\
                  0]}]
            }
         }
         # stop unless forced if loading module are part of dependent modules
         # to unload (means module dependencies are inconsistent)
         lassign [getDiffBetweenList $prereq_list $prereq_loaded_list]\
            prereq_loading_list
         if {[llength $prereq_loading_list]} {
            knerrorOrWarningIfForced [getDepLoadingMsg $prereq_loading_list]
         }

         if {[getConf auto_handling] && $auto} {
            # compute lists of modules to update due to modname unload prior
            # unload to get requirement info before it vanishes

            # DepUn: Dependent to Unload (modules actively requiring modname
            # or a module part of this DepUn batch)
            set depun_list [getDepUnModuleList $modname]
            reportDebug "depun mod list is '$depun_list'"

            if {[isTopEvaluation]} {
               clearLoadedReqOfReloadingModuleList
               clearLoadedReqOfUnloadingModuleList
               clearUReqUnFromDepReList
               clearDepReList
            }

            set unload_mod_list [list {*}$depun_list $modname]

            # DepRe: Dependent to Reload (modules optionally dependent or in
            # conflict with modname, DepUn modules + modules dependent of a
            # module part of this DepRe batch)
            set depre_list [getDependentLoadedModuleList $unload_mod_list 0 0\
               1 0 1 1]

            # DepUn mods are merged into the DepRe list if this is a conflict
            # unload or an ongoing switch
            if {[info exists swprocessing] || $context eq {conun}} {
               set depre_list [sortModulePerLoadedAndDepOrder [list\
                  {*}$depun_list {*}$depre_list] 1]
               set depun_list {}
               set unload_mod_list [list $modname]
               reportDebug "updated depun mod list is '$depun_list'"
               reportDebug "updated depre mod list is '$depre_list'"
            }

            if {[isTopEvaluation]} {
               saveLoadedReqOfReloadingModuleList $depre_list $unload_mod_list
               identityUReqUnFromDepRe $depre_list $unload_mod_list
            }

            # Unload DepUn modules and unload DepRe modules (unload phase of
            # their reload process). These modules are unloaded prior main mod
            # unload and they are mixed to be unloaded in their reverse load
            # order. They are unloaded this way prior eventually unloading any
            # of their requirements.
            unloadDepUnDepReModules $depun_list $depre_list
         }

         # register this evaluation on the main one that triggered it (prior
         # unload evaluation to report correct order with other evaluations)
         registerModuleEval $context $msgrecid

         # module was asked by user if tagged loaded instead of auto-loaded
         set uasked [isModuleTagged $modname loaded 1]

         # no need to update modnamevr and tags after evaluation as these
         # information were already complete in persistent environment
         ##nagelfar ignore Suspicious variable name
         if {[execute-modulefile $modfile $modname $modnamevr $mod $uasked 0\
            0]} {
            break
         }

         # unloading visibility depends on hidden-loaded tag
         set hidden [isModuleTagged $modname hidden-loaded 1]

         # unset module from list of loaded modules qualified for refresh eval
         if {[isModuleRefreshQualified $modname]} {
            remove-path __MODULES_LMREFRESH $modname
         }

         saveLoadedReqOfUnloadingModule $modname

         # get module position in loaded list to remove corresponding loaded
         # modulefile (entry at same position in _LMFILES_)
         # need the unfiltered loaded module list to get correct index
         set lmidx [lsearch -exact [getEnvLoadedModulePropertyList name]\
            $modname]
         remove-path LOADEDMODULES $modname
         remove-path --index _LMFILES_ $lmidx
         # update cache arrays
         unsetLoadedModule $modname $modfile

         # unregister declared source-sh
         unsetEnvLoadedModuleProperty $modname sourcesh
         unsetLoadedSourceSh $modname

         # unregister declared conflict
         unsetEnvLoadedModuleProperty $modname conflict
         unsetLoadedConflict $modname

         # unset prereq declared for this module
         unsetEnvLoadedModuleProperty $modname prereq
         unsetLoadedPrereq $modname
         unsetEnvLoadedModuleProperty $modname prereqpath
         unsetLoadedPrereqPath $modname

         unsetEnvLoadedModuleProperty $modname use
         unsetLoadedUse $modname

         # unset alternative names declared for this module
         unsetEnvLoadedModuleProperty $modname altname
         unsetLoadedAltname $modname

         # unset variant declared for this module
         unsetEnvLoadedModuleProperty $modname variant
         unsetLoadedVariant $modname

         # unset tags declared for this module
         unsetEnvLoadedModuleProperty $modname tag
         # also remove tags from in-memory knowledge not to re-apply them if
         # module is reloaded in other conditions
         unsetModuleAndVariantsTag $modname $modnamevr
         unsetEnvLoadedModuleProperty $modname extratag
         unsetModuleAndVariantsExtraTag $modname $modnamevr
         unsetEnvLoadedModuleProperty $modname stickyrule

         if {[getConf auto_handling] && $auto && [isTopEvaluation] &&\
            ![info exists swprocessing]} {
            # UReqUn: Useless Requirement to Unload (autoloaded requirements
            # not required by any remaining mods)
            removeUReqUnFromDepReAndConvertEval
            unloadUReqUnModules

            # DepRe modules load phase now DepUn+UReqUn+main mods are unloaded
            reloadDepReModules
         }

         # consider evaluation hidden if hidden loaded module was auto loaded
         # and no specific messages are recorded for this evaluation
         if {$hidden && !$uasked && ![isMsgRecorded]} {
            registerModuleEvalHidden $context $msgrecid
         }

         # report a summary of automated evaluations if no error
         reportModuleEval
      } errMsg]] != 0 && $errCode != 4} {
         set mod_unload_in_error 1
         # report module error even if set hidden
         set hidden 0
         reportError $errMsg
         # rollback settings if some evaluation went wrong
         set ret 1
         restoreSettings
         # remove from successfully evaluated module list
         registerModuleEval $context $msgrecid $modnamevr unload
      }
      popSettings

      # report all recorded messages for this evaluation (hide evaluation if
      # loaded mod is set hidden, has been automatically loaded and unloaded)
      reportMsgRecord "Unloading [getModuleDesignation $msgrecid {} 2]" [expr\
         {$hidden && !$uasked && [depthState evalid] != 1}]
      popMsgRecordId

      if {$mod_unload_in_error} {
         # report unload issue on the message block of the above action
         switch -- $context {
            swunload {
               reportError "Unload of switched-off [getModuleDesignation\
                  loaded $modname] failed"
            }
            conun {
               setConflictErrorAsReported $modname
               reportErrorOrWarningIfForced [getErrConUnMsg\
                  [getModuleDesignation loaded $modname]]
            }
            urequn {
               # warn and do not count urequn unload error
               reportWarning "Unload of useless requirement\
                  [getModuleDesignation loaded $modname] failed"
               decrErrorCount
            }
         }
      }

      # abort evaluation if error and behavior configured this way (abort
      # means stop to evaluate modulefile and flush changes of previous ones)
      # applies to top evaluation context for supported sub-commands
      if {$ret && $context eq {unload} && [commandAbortOnError]} {
         flushEnvSettings
         break
      }
   }
   lpopState eval_context
   lpopState mode

   return $ret
}

proc cmdModulePurge {} {
   # create an eval id to track successful/failed module evaluations
   pushMsgRecordId purge-[depthState modulename] 0

   # unload one by one to ensure same behavior whatever auto_handling state
   # force it to handle loaded modules in violation state
   # remove dependent modules if force mode enabled
   cmdModuleUnload unload match 0 s 0 {*}[lreverse\
      [getEnvLoadedModulePropertyParsedList name]]

   popMsgRecordId 0
}

proc cmdModuleReload {} {
   # reload all loaded modules
   set loaded_mod_list [getEnvLoadedModulePropertyParsedList name]
   reportDebug "reloading $loaded_mod_list"

   # create an eval id to track successful/failed module evaluations
   pushMsgRecordId reload-[depthState modulename] 0

   # no reload of all loaded modules attempt if constraints are violated
   if {![areModuleConstraintsSatisfied]} {
      reportError {Cannot reload modules, some of their constraints are not\
         satisfied}
   } else {
      pushSettings
      if {[set errCode [catch {
         # run unload then load-again phases
         set loaded_mod_list [reloadModuleListUnloadPhase $loaded_mod_list]
         reloadModuleListLoadPhase $loaded_mod_list
      } errMsg]] == 1} {
         # rollback settings if some evaluation went wrong
         restoreSettings
      }
      popSettings
   }

   popMsgRecordId 0
}

proc cmdModuleAliases {} {
   # 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 {}
   }

   setState inhibit_errreport 0

   set display_list {}
   foreach name [lsort -dictionary [array names ::g_moduleAlias]] {
      # exclude hidden aliases from result
      if {![isModuleHidden $name]} {
         lappend display_list "[sgr al $name] -> $::g_moduleAlias($name)"
      }
   }
   displayElementList Aliases hi sepline 1 0 0 $display_list

   set display_list {}
   foreach name [lsort -dictionary [array names ::g_moduleVersion]] {
      # exclude hidden versions or versions targeting an hidden module
      if {![isModuleHidden $name] && ![isModuleHidden\
         $::g_moduleVersion($name)]} {
         lappend display_list "[sgr sy $name] -> $::g_moduleVersion($name)"
      }
   }
   displayElementList Versions hi sepline 1 0 0 $display_list
}

proc cmdModuleAvail {show_oneperline show_mtime show_filter search_filter\
   search_match modpath_list args} {
   if {![llength $args]} {
      lappend args *
   }

   if {$show_mtime || $show_oneperline} {
      set one_per_line 1
      set hstyle terse
      set theader_cols [list hi Package/Alias 39 Versions 19 {Last mod.} 19]
   } else {
      set one_per_line 0
      set hstyle sepline
      set theader_cols {}
   }

   # set a default filter (do not print dirs with no sym) if none set
   if {$show_filter eq {}} {
      set show_filter noplaindir
   }

   # elements to include in output
   set report_modulepath [isEltInReport modulepath]

   # consolidate search filters
   lappend search_filter $search_match wild
   set search_rc_filter $search_filter
   lappend search_rc_filter rc_alias_only

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

   # look if aliases have been defined in the global or user-specific rc
   array set mod_list [getMatchingAnyModules {} $args $show_mtime\
      $search_rc_filter $show_filter]
   if {$report_modulepath} {
      reportModules $args {global/user modulerc} hi $hstyle $show_mtime 0\
         $one_per_line $theader_cols hidden-loaded
   }

   foreach dir $modpath_list {
      set dir_mod_list [getMatchingAnyModules $dir $args $show_mtime\
         $search_filter $show_filter]
      if {$report_modulepath} {
         array unset mod_list
         array set mod_list $dir_mod_list
         reportModules $args $dir mp $hstyle $show_mtime 0 $one_per_line\
            $theader_cols hidden-loaded
      } else {
         # add result if not already added from an upper priority modpath
         foreach {elt props} $dir_mod_list {
            if {![info exists mod_list($elt)]} {
               set mod_list($elt) $props
            }
         }
      }
   }

   # no report by modulepath, mix all aggregated results
   if {!$report_modulepath} {
      reportModules $args noheader {} {} $show_mtime 0 $one_per_line\
         $theader_cols hidden-loaded
   }

   # display output key
   if {!$show_mtime && ![isStateEqual report_format json] && [isEltInReport\
      key]} {
      displayKey
   }

   setState inhibit_errreport 0
}

proc runModuleUse {cmd mode pos args} {
   if {$args eq {}} {
      showModulePath
   } else {
      if {$pos eq {remove}} {
         # get current module path list
         set modpathlist [getModulePathList returnempty 0 0]
      }

      foreach path $args {
         switch -glob -- $path {
            --remove-on-unload - --append-on-unload - --prepend-on-unload -\
            --noop-on-unload {
               if {$cmd ne {unuse}} {
                  knerror "Invalid option '$path'"
               } else {
                  lappend pathlist $path
               }
            }
            -* {
               knerror "Invalid option '$path'"
            }
            {} {
               reportError [getEmptyNameMsg directory]
            }
            $* {
               lappend pathlist $path
            }
            default {
               if {$pos eq {remove}} {
                  if {$path in $modpathlist} {
                     lappend pathlist $path
                  # 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
                  } elseif {[set abspath [getAbsolutePath $path]] in\
                     $modpathlist} {
                     lappend pathlist $abspath
                  # even if not found, transmit this path to remove-path in
                  # case several path elements have been joined as one string
                  } else {
                     lappend pathlist $path
                  }
               } else {
                  # transform given path in an absolute path to avoid
                  # dependency to the current work directory. except if this
                  # path starts with a variable reference
                  lappend pathlist [getAbsolutePath $path]
               }
            }
         }
      }

      # added directory may not exist at this time
      # pass all paths specified at once to append-path/prepend-path
      if {[info exists pathlist]} {
         set optlist [list]
         # define path command to call
         set pathcmd [expr {$pos eq {remove} ? {unload-path} : {add-path}}]

         # by-pass any reference counter in case use is called from top level
         # not to increase reference counter if paths are already defined
         if {[isTopEvaluation]} {
            lappend optlist --ignore-refcount
         }

         if {[isTopEvaluation]} {
            ##nagelfar ignore Found constant
            lappendState mode load
         }
         $pathcmd $pos-path $mode $pos {*}$optlist MODULEPATH {*}$pathlist
         if {[isTopEvaluation]} {
            ##nagelfar ignore Found constant
            lpopState mode
         }
      }
   }
}

proc cmdModuleUse {mode pos args} {
   if {$mode eq {unload}} {
      set pos remove
   }
   runModuleUse use $mode $pos {*}$args
}

proc cmdModuleUnuse {mode args} {
   runModuleUse unuse $mode remove {*}$args
}

proc cmdModuleAutoinit {} {
   # skip autoinit process if found already ongoing in current environment
   if {[envVarEquals __MODULES_AUTOINIT_INPROGRESS 1]} {
      return
   }

   # set environment variable to state autoinit process is ongoing
   setenv __MODULES_AUTOINIT_INPROGRESS 1

   # flag to make renderSettings define the module command
   setState autoinit 1

   # initialize env variables around module command
   lappendState mode load

   # register command location
   setenv MODULES_CMD [getAbsolutePath $::argv0]

   # define current Modules version if versioning enabled
   ##nagelfar ignore #4 Strange command
   #if {![isEnvVarDefined MODULE_VERSION]} {
   #   setenv MODULE_VERSION 5.6.1
   #   setenv MODULE_VERSION_STACK 5.6.1
   #}

   # initialize MODULEPATH and LOADEDMODULES if found unset
   if {![isEnvVarDefined MODULEPATH]} {
      setenv MODULEPATH {}
   }
   if {![isEnvVarDefined LOADEDMODULES]} {
      setenv LOADEDMODULES {}
   }

   # initialize user environment if found undefined (both MODULEPATH and
   # LOADEDMODULES empty)
   if {[get-env MODULEPATH] eq {} && [get-env LOADEDMODULES] eq {}} {
      # set modpaths defined in modulespath config file if it exists
      # use .modulespath file in initdir if conf file are located in this dir
      if {[file readable {/etc/environment-modules/modulespath}]} {
         set fdata [split [readFile {/etc/environment-modules/modulespath}] \n]
         foreach fline $fdata {
            if {[regexp {^\s*(.*?)\s*(#.*|)$} $fline match patharg] &&\
               $patharg ne {}} {
               foreach path [split $patharg :] {
                  # resolve path directory in case wildcard character used
                  set globlist [glob -types d -nocomplain $path]
                  if {![llength $globlist]} {
                     lappend pathlist $path
                  } else {
                     lappend pathlist {*}$globlist
                  }
               }
            }
         }
         if {[info exists pathlist]} {
            cmdModuleUse load append {*}$pathlist
         }
      }

      # source initialization initrc after modulespaths if it exists
      # use modulerc file in initdir if conf files are located in this dir
      if {[file exists {/etc/environment-modules/initrc}]} {
         lappendState commandname source
         cmdModuleSource load {/etc/environment-modules/initrc}
         lpopState commandname
      }

      # record what has just been loaded in the virtual init collection
      setenv __MODULES_LMINIT [getLoadedInit]
   # if user environment is already initialized, refresh the already loaded
   # modules unless if environment is inconsistent
   } elseif {![catch {cacheCurrentModules}]} {
      cmdModuleRefresh
   }

   # default MODULESHOME
   setenv MODULESHOME [getConf home]

   # append dir where to find module function for ksh (to get it defined in
   # interactive and non-interactive sub-shells). also applies for shells
   # listed in shells_with_ksh_fpath conf
   if {[getState shell] in [list {*}[getConfList shells_with_ksh_fpath] \
      ksh]} {
      append-path FPATH {/usr/share/Modules/init/ksh-functions}
   }

   # define Modules init script as shell startup file
   if {[getConf set_shell_startup] && [getState shelltype] in [list sh csh\
      fish]} {
      # setup ENV variables to get module defined in sub-shells (works for
      # 'sh' and 'ksh' in interactive mode and 'sh' (zsh-compat), 'bash' and
      # 'ksh' (zsh-compat) in non-interactive mode.
      setenv ENV {/usr/share/Modules/init/profile.sh}
      setenv BASH_ENV {/usr/share/Modules/init/bash}
   }

   if {[getState shelltype] in {sh csh fish}} {
      # add Modules bin directory to PATH if enabled but do not increase ref
      # counter variable if already there
      ##nagelfar ignore #26 Strange command
      if {{/usr/share/Modules/bin} ni [split [get-env PATH] :]} {
         prepend-path --ignore-refcount PATH {/usr/share/Modules/bin}
      }

      # add Modules man directory to MANPATH if enabled
      # initialize MANPATH if not set with a value that preserves manpath
      # system configuration even after addition of paths to this variable by
      # modulefiles
      set manpath {}
      # use manpath tool if found at configure step, use MANPATH otherwise
      ##nagelfar ignore +2 Too long line
      ##nagelfar ignore Found constant
      catch {set manpath [exec -ignorestderr 2>/dev/null manpath]}
      #if {[isEnvVarDefined MANPATH]} {
      #   set manpath $::env(MANPATH)
      #}
      if {{/usr/share/man} ni [split $manpath :]} {
         if {![isEnvVarDefined MANPATH]} {
            append-path MANPATH {}
         # ensure no duplicate ':' is set
         } elseif {[envVarEquals MANPATH :]} {
            remove-path MANPATH {}
            append-path MANPATH {}
         }
         prepend-path MANPATH {/usr/share/man}
      }
   }

   # source shell completion script if available, not installed in default
   # completion locations and only if shell is interactive
   if {[getState shell] in {tcsh} && [getState is_stderr_tty]} {
      set compfile "/usr/share/Modules/init/[getState shell]_completion"
      if {[file readable $compfile]} {
         putsModfileCmd dummy "source '$compfile';"
      }
   }

   # clear in progress flag
   unsetenv __MODULES_AUTOINIT_INPROGRESS

   lpopState mode
}

proc cmdModuleInit {args} {
   set init_cmd [lindex $args 0]
   set init_list [lrange $args 1 end]
   set notdone 1
   set nomatch 1

   # 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([getState shell])
   foreach filename $current_files {
      if {$notdone} {
         set filepath $::env(HOME)
         append filepath / $filename

         reportDebug "Looking at $filepath"
         if {[file readable $filepath] && [file isfile $filepath]} {
            set newinit {}
            set thismatch 0

            foreach curline [split [readFile $filepath] \n] {
               # 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 "[getState 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]
                        lappend newinit [expr {$modcount ?\
                           "$cmd$modules$comments" : [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]
                     }
                  }
               } elseif {$curline ne {}} {
                  # copy the line from the old file to the new
                  lappend newinit $curline
               }
            }

            if {$init_cmd ne {list} && $thismatch} {
               reportDebug "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\
         '[getState 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} {
   lappendState mode load
   lappendState commandname $cmd

   set optlist [list]
   switch -- $cmd {
      prepend-path - append-path - remove-path {
         # by-pass any reference counter, as call is from top level
         # append/prepend-path: not to increase reference counter if paths are
         # already defined. remove-path: to ensure paths are removed whatever
         # their reference counter value
         lappend optlist --ignore-refcount
      }
   }

   # run modulefile command and get its result
   if {[catch {$cmd {*}$optlist {*}$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
               setState return_false 1
            }
         }
      }
   }

   lpopState commandname
   lpopState mode
}

proc cmdModuleTest {args} {
   lappendState mode test
   set first_report 1
   foreach mod $args {
      lassign [getPathToModule $mod] modfile modname modnamevr
      if {$modfile ne {}} {
         # only one separator lines between 2 modules
         if {$first_report} {
            displaySeparatorLine
            set first_report 0
         }
         report "Module Specific Test for [sgr hi $modfile]:\n"
         execute-modulefile $modfile $modname modnamevr $mod 1
         displaySeparatorLine
      }
   }
   lpopState mode
}

proc cmdModuleClear {args} {
   # fetch confirmation if no arg passed and force mode disabled
   if {![llength $args] && ![getState force]} {
      # ask for it if stdin is attached to a terminal
      if {![catch {fconfigure stdin -mode}]} {
         report "Are you sure you want to clear all loaded modules!? \[n\] " 1
         flush [getState reportfd]
      }
      # fetch stdin content even if not attached to terminal in case some
      # content has been piped to this channel
      set doit [gets stdin]
   } else {
      set doit [lindex $args 0]
   }

   # should be confirmed or forced to proceed
   if {[string equal -nocase -length 1 $doit y] || [getState force]} {
      lappendState mode load
      # unset all Modules runtime variables
      foreach globvar [getModulesEnvVarGlobList 1] {
         foreach var [array names ::env -glob $globvar] {
            unset-env $var
         }
      }
      lpopState mode
   } else {
      reportInfo "Modules runtime information were not cleared"
   }
}

proc cmdModuleState {args} {
   if {[llength $args]} {
      set name [lindex $args 0]
   }

   if {[info exists name] && $name ni [concat [array names ::g_state_defs]\
      [array names ::g_states]]} {
      knerror "State '$name' does not exist"
   }

   # report module version unless if called by cmdModuleConfig
   if {[getCallingProcName] ne {cmdModuleConfig}} {
      reportVersion
      reportSeparateNextContent
   }

   displayTableHeader hi {State name} 24 {Value} 54

   # fetch specified state or all states
   if {[info exists name]} {
      if {$name in [array names ::g_state_defs]} {
         set stateval($name) [getState $name <undef> 1]
      } else {
         set stateval($name) [getState $name]
      }
   } else {
      # define each attribute/fetched state value pair
      foreach state [array names ::g_state_defs] {
         set stateval($state) [getState $state <undef> 1]
      }
      # also get dynamic states (with no prior definition)
      foreach state [array names ::g_states] {
         if {![info exists stateval($state)]} {
            set stateval($state) [getState $state]
         }
      }
   }

   foreach state [lsort [array names stateval]] {
      append displist [format {%-25s %s} $state $stateval($state)] \n
   }
   report $displist 1
   reportSeparateNextContent

   # only report specified state if any
   if {[info exists name]} {
      return
   }

   # report environment variable set related to Modules
   displayTableHeader hi {Env. variable} 24 {Value} 54
   set envvar_list {}
   foreach var [getModulesEnvVarGlobList] {
      lappend envvar_list {*}[array names ::env -glob $var]
   }
   unset displist
   foreach var [lsort -unique $envvar_list] {
      append displist [format {%-25s %s} $var $::env($var)] \n
   }
   report $displist 1
}

proc cmdModuleConfig {dump_state args} {
   # parse arguments
   set nameunset 0
   switch -- [llength $args] {
      1 {
         lassign $args name
      }
      2 {
         lassign $args name value
         # check if configuration should be set or unset
         if {$name eq {--reset}} {
            set name $value
            set nameunset 1
            unset value
         }
      }
   }

   reportDebug "dump_state='$dump_state', reset=$nameunset,\
      name=[expr {[info exists name] ? "'$name'" : {<undef>}}], value=[expr\
      {[info exists value] ? "'$value'" : {<undef>}}]"

   foreach option [array names ::g_config_defs] {
      lassign $::g_config_defs($option) confvar($option) defval\
         conflockable($option) confkind($option) confvalid($option) vtrans\
         initproc confvalidkind($option)
      set confval($option) [getConf $option <undef>]
      set confvtrans($option) {}
      for {set i 0} {$i < [llength $vtrans]} {incr i} {
         lappend confvtrans($option) [lindex $vtrans $i] [lindex\
            $confvalid($option) $i]
      }
   }

   # catch any environment variable set for modulecmd run-time execution
   foreach runenvvar [array names ::env -glob MODULES_RUNENV_*] {
      set runenvconf [string tolower [string range $runenvvar 8 end]]
      set confval($runenvconf) [get-env $runenvvar]
      # enable modification of runenv conf
      set confvar($runenvconf) $runenvvar
      set confvalid($runenvconf) {}
      set conflockable($runenvconf) {}
      set confkind($runenvconf) s
      set confvtrans($runenvconf) {}
      set confvalidkind($runenvconf) {}
   }

   if {[info exists name] && ![info exists confval($name)]} {
      reportErrorAndExit "Configuration option '$name' does not exist"
   # set configuration
   } elseif {[info exists name] && ($nameunset || [info exists value])} {
      # append or subtract value to existing configuration value if new value
      # starts with '+' or '-' (for colon-separated list option only)
      if {[info exists value] && $confkind($name) eq {l}} {
         set curconfvallist [getConfList $name]
         switch -- [string index $value 0] {
            + {
               appendNoDupToList curconfvallist {*}[split [string range\
                  $value 1 end] :]
               set value [join $curconfvallist :]
            }
            - {
               lassign [getDiffBetweenList $curconfvallist [split [string\
                  range $value 1 end] :]] curconfvallist
               set value [join $curconfvallist :]
            }
         }
      }

      if {$confvar($name) eq {}} {
         reportErrorAndExit "Configuration option '$name' cannot be altered"
      } elseif {$conflockable($name) eq {1} && [isConfigLocked $name]} {
         reportErrorAndExit "Configuration option '$name' is locked"
      } elseif {$nameunset} {
         # unset configuration variable
         lappendState mode load
         unsetenv $confvar($name)
         lpopState mode
      } elseif {[llength $confvalid($name)]} {
         switch -- $confvalidkind($name) {
            eltlist {
               # check each element in value list
               if {[isDiffBetweenList [split $value :] $confvalid($name)]} {
                  reportErrorAndExit "Invalid element in value list for\
                     config. option '$name'\nAllowed elements are:\
                     $confvalid($name) (separated by ':')"
               } else {
                  set validval 1
               }
            }
            intbe {
               if {[string is integer -strict $value] && $value >= [lindex\
                  $confvalid($name) 0] && $value <= [lindex $confvalid($name)\
                  1]} {
                  set validval 1
               } else {
                  reportErrorAndExit "Invalid value for configuration option\
                     '$name'\nValue should be an integer comprised between\
                     [lindex $confvalid($name) 0] and [lindex\
                     $confvalid($name) 1]"
               }
            }
            {} {
               ##nagelfar ignore +2 Non static subcommand
               if {([llength $confvalid($name)] == 1 && ![string is\
                  $confvalid($name) -strict $value]) || ([llength\
                  $confvalid($name)] > 1 && $value ni $confvalid($name))} {
                  reportErrorAndExit "Valid values for configuration option\
                     '$name' are: $confvalid($name)"
               } else {
                  set validval 1
               }
            }
         }
      } else {
         set validval 1
      }

      if {[info exists validval]} {
         # effectively set configuration variable
         lappendState mode load
         setenv $confvar($name) $value
         lpopState mode
      }
      # clear cached value for config if any
      unsetConf $name
   # report configuration
   } else {
      reportVersion
      reportSeparateNextContent
      displayTableHeader hi {Config. name} 24 {Value (set by if default\
         overridden)} 54

      # report all configs or just queried one
      if {[info exists name]} {
         set varlist [list $name]
      } else {
         set varlist [lsort [array names confval]]
      }

      foreach var $varlist {
         ##nagelfar ignore +2 Suspicious variable name
         set valrep [displayConfig $confval($var) $confvar($var) [info exists\
            ::asked_$var] $confvtrans($var) [expr {$conflockable($var) eq {1}\
            && [isConfigLocked $var]}]]
         append displist [format {%-25s %s} $var $valrep] \n
      }
      report $displist 1
      reportSeparateNextContent

      if {$dump_state} {
         cmdModuleState
      }
   }
}

proc cmdModuleShToMod {args} {
   set scriptargs [lassign $args shell script]

   # evaluate script and get the environment changes it performs translated
   # into modulefile commands
   set modcontent [sh-to-mod {} {*}$args]

   # output resulting modulefile
   if {[llength $modcontent]} {
      report "#%Module"
      # format each command with tabs and colors if enabled
      foreach modcmd $modcontent {
         reportCmd -nativeargrep {*}$modcmd
      }
   }
}

proc cmdModuleEdit {mod} {
   lassign [getPathToModule $mod] modfile modname

   # error message has already been produced if mod not found or forbidden
   if {$modfile ne {}} {
      # redirect stdout to stderr as stdout is evaluated by module shell func
      if {[catch {runCommand [getConf editor] $modfile >@stderr 2>@stderr}\
         errMsg]} {
         # re-throw error but as an external one (not as a module issue)
         knerror $errMsg
      }
   }
}

proc cmdModuleRefresh {} {
   lappendState mode refresh
   # create an eval id to track successful/failed module evaluations
   pushMsgRecordId refresh-[depthState modulename] 0

   # load variants from loaded modules
   cacheCurrentModules

   foreach lm [getEnvLoadedModulePropertyParsedList refresh] {
      # prepare info to execute modulefile
      set lmvr [getAndParseLoadedModuleWithVariant $lm]
      set lmfile [getModulefileFromLoadedModule $lm]
      set taglist [getExportTagList $lm]

      # refreshing module is visible by default
      set hidden 0
      set uasked 1

      # set a unique id to record messages related to this evaluation.
      set msgrecid refresh-$lmvr-[depthState modulename]

      # register record message unique id (now we know mod will be evaluated)
      pushMsgRecordId $msgrecid

      # record module title (with the variants and tags of loaded module)
      # prior module evaluation to get this title ready in case of eval error
      registerModuleDesignation $msgrecid $lm [getVariantList $lm 7]\
         $taglist

      # run modulefile, restore settings prior evaluation if error and
      # continue to evaluate the remaining loaded modules
      pushSettings
      if {[set errCode [catch {
         if {[execute-modulefile $lmfile $lm lmvr $lm 0]} {
            break
         }

         # unloading visibility depends on hidden-loaded tag
         set hidden [isModuleTagged $lm hidden-loaded 1]

         # module was asked by user if tagged loaded instead of auto-loaded
         set uasked [isModuleTagged $lm loaded 1]
      } errMsg]] != 0 && $errCode != 4} {
         restoreSettings
      }
      popSettings

      # report all recorded messages for this evaluation (hide evaluation if
      # loaded mod is set hidden, has been automatically loaded and unloaded)
      reportMsgRecord "Refreshing [getModuleDesignation $msgrecid {} 2]"\
         [expr {$hidden && !$uasked && [depthState evalid] != 1}]
      popMsgRecordId
   }

   popMsgRecordId 0
   lpopState mode
}

proc cmdModuleHelp {args} {
   lappendState mode help
   set first_report 1
   foreach arg $args {
      lassign [getPathToModule $arg] modfile modname modnamevr

      if {$modfile ne {}} {
         # only one separator lines between 2 modules
         if {$first_report} {
            displaySeparatorLine
            set first_report 0
         }
         report "Module Specific Help for [sgr hi $modfile]:\n"
         execute-modulefile $modfile $modname modnamevr $arg 1
         displaySeparatorLine
      }
   }
   lpopState mode
   if {![llength $args]} {
      reportUsage
   }
}

proc cmdModuleTag {unset_extra uasked tag_list args} {
   reportDebug "tagging $args (unset_extra=$unset_extra, uasked=$uasked,\
      tag_list=$tag_list)"

   set ret 0
   foreach mod $args {
      # find mod among loaded modules
      lassign [getPathToModule $mod {} 1 match] modfile modname modnamevr\
         errkind

      if {$modfile eq {}} {
         set ret 1
         # go to next module to unload
         continue
      }

      # record tags not already set and if asked unset extra tags not set
      # anymore
      lassign [getDiffBetweenList $tag_list [getTagList $modname]] diff_list
      lassign [getDiffBetweenList [getExtraTagList $modname] $tag_list] \
         unset_list
      if {[llength $diff_list] || ($unset_extra && [llength $unset_list])} {
         # set a unique id to record messages related to this evaluation.
         set msgrecid tag-$modnamevr-[depthState modulename]
         pushMsgRecordId $msgrecid
         # record module title (with the variant but no tag list) prior
         # evaluation to get this title ready in case of error
         registerModuleDesignation $msgrecid $modname [getVariantList\
            $modnamevr 7] {}

         lappendState mode unload
         # first unset tags declared for this module on LM env var
         unsetEnvLoadedModuleProperty $modname tag
         unsetEnvLoadedModuleProperty $modname extratag
         lpopState mode

         # remove extra tags currently set not part of tag list if asked
         if {$unset_extra && [llength $unset_list]} {
            unsetModuleAndVariantsTag $modname $modnamevr {*}$unset_list
            unsetModuleAndVariantsExtraTag $modname $modnamevr {*}$unset_list
         }

         # ensure auto-loaded tag is not preserved if not part of target tags
         if {$unset_extra && {auto-loaded} ni $tag_list} {
            unsetModuleAndVariantsTag $modname $modnamevr auto-loaded
         }

         # record new tags as extra tag excluding tags relative to the way
         # module is loaded (auto, keep)
         lassign [getDiffBetweenList $diff_list [list auto-loaded\
            keep-loaded]] extradiff_list
         setModuleAndVariantsTag $modname $modnamevr {*}$diff_list
         if {[llength $extradiff_list]} {
            setModuleAndVariantsExtraTag $modname $modnamevr\
               {*}$extradiff_list
         }

         # set the new tag set for module on LM env var
         lappendState mode load
         setEnvLoadedModuleProperty $modname tag [getExportTagList $modnamevr]
         setEnvLoadedModuleProperty $modname extratag [getExtraTagList\
            $modnamevr]
         lpopState mode

         # update module designation now the additional tags are set
         registerModuleDesignation $msgrecid $modname [getVariantList\
            $modname 7] [getExportTagList $modname]
         # report tagging evaluation unless hidden and auto-loaded
         set hidden [isModuleTagged $modnamevr hidden-loaded 1]
         reportMsgRecord "Tagging [getModuleDesignation $msgrecid {} 2]"\
            [expr {$hidden && !$uasked}]
         popMsgRecordId

         # indicates that new tags have been applied
         set ret 2
      }
   }

   return $ret
}

proc cmdModuleLint {args} {
   # stop if no linter defined
   if {![llength [getConf tcl_linter]]} {
      knerror {No Tcl linter program configured}
   }

   # extract linter program name
   set linter [file rootname [file tail [lindex [getConf tcl_linter] 0]]]
   # build command line
   set linter_mfile [getConf tcl_linter]
   set linter_mrc [getConf tcl_linter]
   set linter_gmrc [getConf tcl_linter]
   set linter_mcache [getConf tcl_linter]
   # add module-specific syntax database in addition to regular Tcl one
   ##nagelfar ignore #11 Strange command
   if {$linter eq {nagelfar}} {
      lappend linter_mfile -s _\
         -s {/usr/share/Modules/nagelfar/syntaxdb_modulefile.tcl}\
         -plugin {/usr/share/Modules/nagelfar/plugin_modulefile.tcl}
      lappend linter_mrc -s _\
         -s {/usr/share/Modules/nagelfar/syntaxdb_modulerc.tcl}\
         -plugin {/usr/share/Modules/nagelfar/plugin_modulerc.tcl}
      lappend linter_gmrc -s _\
         -s {/usr/share/Modules/nagelfar/syntaxdb_modulefile.tcl}\
         -plugin {/usr/share/Modules/nagelfar/plugin_globalrc.tcl}
      lappend linter_mcache -s _\
         -s {/usr/share/Modules/nagelfar/syntaxdb_modulecache.tcl}\
         -plugin {/usr/share/Modules/nagelfar/plugin_modulecache.tcl}
   }
   set global_rclist [getGlobalRcFileList]

   set modfilelist {}
   # fetch every available modulefiles if no argument provided
   if {![llength $args]} {
      # add global RC files
      foreach rc $global_rclist {
         set tolint($rc) gmrc
      }

      inhibitErrorReport
      foreach dir [getModulePathList exiterronundef] {
         set cachefile [getModuleCacheFilename $dir]
         if {[file readable $cachefile]} {
            set tolint($cachefile) mcache
         }

         # fetch all existing rc file current user has access to
         foreach {elt props} [findModules $dir * 0 0] {
            switch -- [lindex $props 0] {
               modulerc {
                  set tolint($dir/$elt) mrc
               }
            }
         }
         # collect all modulefile from dir that current user has access to
         # getModules will reuse the result collected for findModules
         foreach {elt props} [getModules $dir *] {
            switch -- [lindex $props 0] {
               modulefile - virtual {
                  set tolint([lindex $props 2]) mfile
               }
            }
         }
      }
      setState inhibit_errreport 0
   } else {
      foreach mod $args {
         lassign [getPathToModule $mod] modfile modname modnamevr
         # error mesg has already been produced if mod not found or forbidden
         if {$modfile ne {}} {
            if {$modfile in $global_rclist} {
               set mkind gmrc
            } else {
               switch -- [file tail $modfile] {
                  .modulerc - .version {
                     set mkind mrc
                  }
                  .modulecache {
                     set mkind mcache
                  }
                  default {
                     set mkind mfile
                  }
               }
            }
            set tolint($modfile) $mkind
         }
      }
   }

   # execute linter program over every gathered file
   foreach lintfile [lsort -dictionary [array names tolint]] {
      # set a record message unique id and record modulefile title
      set msgrecid lint-$lintfile
      pushMsgRecordId $msgrecid
      registerModuleDesignation $msgrecid $lintfile {} {}

      ##nagelfar ignore Suspicious variable name
      if {[catch {set out [runCommand {*}[set linter_$tolint($lintfile)]\
         $lintfile]} errMsg]} {
         # re-throw error but as an external one (not as a module issue)
         knerror $errMsg
      }

      # report linting messages
      displayLinterOutput $linter $out

      # report all lint messages for this modulefile
      reportMsgRecord "Linting [getModuleDesignation $msgrecid {} 2]"
      popMsgRecordId
   }
}

proc cmdModuleModToSh {shell args} {
   # save shell modulecmd is initialized to
   ##nagelfar ignore Found constant
   setState modtosh_real_shell [getState shell]

   # set shell and shellType states to mod-to-sh target value
   if {$shell ni [getState supported_shells]} {
      reportErrorAndExit "Unsupported shell type '$shell'"
   }
   ##nagelfar ignore Found constant
   setState shell $shell
   unsetState shelltype

   # silence message report (avoid mix with produced shell code) unless if
   # a debugging mode is set
   if {![isVerbosityLevel trace]} {
      unsetConf verbosity
      set ::asked_verbosity silent
   }

   # modulefile evaluation is done against mod-to-sh target shell which means
   # module-info will return mod-to-sh shell value
   return [cmdModuleLoad load 1 0 0 {} {} {*}$args]

   # after evaluation, renderSettings will produce shell code for mod-to-sh
   # target shell. modtosh_real_shell state helps to know that shell code has
   # to be output on report message channel
}

proc cmdModuleReset {} {
   # use reset_target_state configuration option to know the environment state
   # to restore
   if {[getConf reset_target_state] eq {__purge__}} {
      cmdModulePurge
   } else {
      cmdModuleRestore [getConf reset_target_state]
   }
}

proc cmdModuleStash {} {
   # check if there is something to stash
   if {[getConf reset_target_state] eq {__purge__}} {
      # load tags from loaded modules
      cacheCurrentModules

      # current environment differs from initial 'purge' state when at least
      # a module is loaded and it is not super-sticky and not sticky or force
      # mode is enabled to allow sticky tag unload
      set diff_from_init 0
      foreach mod [getEnvLoadedModulePropertyParsedList name] {
         if {![isModuleSticky $mod]} {
            set diff_from_init 1
            break
         }
      }
   } else {
      # compare current environment against initial collection to check if
      # something differ
      set coll [getConf reset_target_state]
      # get corresponding collection or init, raise error if it does not exist
      lassign [findCollections $coll exact 0 1] collfile colldesc

      # fetch collection content and differences compared current environment
      lassign [getDiffBetweenCurEnvAndColl $collfile $colldesc]\
         coll_path_list coll_mod_list coll_tag_arrser coll_nuasked_list\
         mod_to_unload mod_to_load path_to_unuse path_to_use is_tags_diff
      array set coll_tag_arr $coll_tag_arrser

      set diff_from_init [expr {[llength $mod_to_unload] || [llength\
         $mod_to_load] || [llength $path_to_unuse] || [llength $path_to_use]\
         || $is_tags_diff}]
   }

   if {!$diff_from_init} {
      reportWarning {No specific environment to save}
      return
   }

   # record current environment
   cmdModuleSave stash-[clock milliseconds]

   # restore initial environment
   cmdModuleReset
}

proc cmdModuleStashpop {{stash 0}} {
   # determine stash collection name from argument
   set coll [getCollectionFromStash $stash]

   # restore stash collection environment state
   cmdModuleRestore $coll

   # delete stash collection file
   cmdModuleSaverm $coll
}

proc cmdModuleStashrm {{stash 0}} {
   # determine stash collection name from argument
   set coll [getCollectionFromStash $stash]

   # delete stash collection file
   cmdModuleSaverm $coll
}

proc cmdModuleStashshow {{stash 0}} {
   # determine stash collection name from argument
   set coll [getCollectionFromStash $stash]

   # display stash collection file
   cmdModuleSaveshow $coll
}

proc cmdModuleStashclear {} {
   # get all stash collections (only from current target)
   set collfile_list [findCollections stash-* glob 0 0 1 1]

   # delete all stash collections starting from most recent
   foreach collfile [lsort -decreasing $collfile_list] {
      # extract collection name (without path and target extension)
      set coll [file rootname [file tail $collfile]]

      # delete stash collection file
      cmdModuleSaverm $coll
   }
}

proc cmdModuleStashlist {show_oneperline show_mtime} {
   cmdModuleSavelist $show_oneperline $show_mtime {} stash-*
}

proc cmdModuleCachebuild {args} {
   # use enabled modulepaths when no arg is provided
   if {[llength $args]} {
      set modpath_list $args
   } else {
      set modpath_list [getModulePathList exiterronundef]
   }

   # record cache with module header check options enabled
   setConf mcookie_check always
   setConf mcookie_version_check 1

   # ignore cache when building cache
   setConf ignore_cache 1

   foreach modpath $modpath_list {
      set cachefile [getModuleCacheFilename $modpath]
      # set a record message unique id and record cachefile title
      set msgrecid cachebuild-$cachefile
      pushMsgRecordId $msgrecid
      registerModuleDesignation $msgrecid $cachefile {} {}

      if {[file isdirectory $modpath]} {
         if {[file writable $modpath]} {
            if {[catch {
               # get cache content for modulepath
               set cache [formatModuleCacheContent $modpath]
               if {![string length $cache]} {
                  reportWarning {Nothing to record in cache file}
               } else {
                  # record cache content in file
                  set fid [open $cachefile w]
                  # use defined buffer size to limit num of write system call
                  fconfigure $fid -buffersize [getConf cache_buffer_bytes]
                  puts $fid $cache
                  close $fid
               }
            } errMsg]} {
               # report error occurring during cache content format or cache
               # file write
               reportError $errMsg
            }
         } else {
            reportWarning {Cannot build cache file, directory is not writable}
         }
      } else {
         reportError "'$modpath' is not a directory"
      }

      # report all messages for this cachefile creation
      reportMsgRecord "Creating [getModuleDesignation $msgrecid {} 2]"
      popMsgRecordId
   }
}

proc cmdModuleCacheclear {} {
   foreach modpath [getModulePathList exiterronundef] {
      set cachefile [getModuleCacheFilename $modpath]
      if {[file exists $cachefile]} {
         # set a record message unique id and record cachefile title
         set msgrecid cacheclear-$cachefile
         pushMsgRecordId $msgrecid
         registerModuleDesignation $msgrecid $cachefile {} {}

         if {[file writable $modpath]} {
            if {[catch {file delete $cachefile} errMsg]} {
               reportError $errMsg
            }
         } else {
            reportWarning {Cannot remove cache file, directory is not\
               writable}
         }

         # report all messages for this cachefile deletion
         reportMsgRecord "Deleting [getModuleDesignation $msgrecid {} 2]"
         popMsgRecordId
      }
   }
}

proc cmdModuleSpider {show_oneperline show_mtime show_filter search_filter\
   search_match args} {

   # recursively collect all modulepath used among available modules
   inhibitErrorReport
   setConf advanced_version_spec 1
   defineParseModuleSpecificationProc 1
   set modpath_list [getModulePathList exiterronundef]
   set use_xt_query [parseModuleSpecification 0 1 1 0 use:*]
   for {set i -1} {$i < [llength $modpath_list]} {incr i} {
      if {$i < 0} {
         set modpath {}
         getModules $modpath $use_xt_query 0 {rc_alias_only}
      } else {
         set modpath [lindex $modpath_list $i]
         getModules $modpath $use_xt_query
      }
      foreach {new_modpath from_mod_list} [getScanModuleElt $modpath use] {
         if {[string length $new_modpath] && $new_modpath ni $modpath_list} {
            lappend modpath_list $new_modpath
            setLoadedUse [lindex $from_mod_list 0] $new_modpath
         }
      }
   }
   unsetConf advanced_version_spec
   defineParseModuleSpecificationProc [getConf advanced_version_spec]
   setState inhibit_errreport 0
   reportTrace $modpath_list {Collect modulepaths}

   # perform an avail on all collected modulepaths
   cmdModuleAvail $show_oneperline $show_mtime $show_filter $search_filter\
      $search_match $modpath_list {*}$args
}

# exit in a clean manner by flushing and closing interaction with external
# components
proc flushAndExit {} {
   # output all shell code generated on stdout
   renderFlush
   # send messages to log if any and enabled
   logFlush
   # output last messages on the report file descriptor and close it
   reportFlush

   exit [expr {[getState error_count] > 0}]
}

# runs the global RC files if they exist
proc runModulerc {} {
   setState rc_running 1
   foreach rc [getGlobalRcFileList] {
      reportDebug "Executing $rc"
      cmdModuleSource load $rc
      lappendState rc_loaded $rc
   }
   unsetState rc_running

   # 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]
}

proc aboveCommandName {} {
   return [lindex [getState commandname] end-1]
}

proc ongoingCommandName {commandName} {
   return [expr {[lsearch -exact [getState commandname] $commandName] != -1}]
}

# Know if we are currently at the top evaluation level: (1) at the modulecmd
# level (module command written by user in terminal or script) or (2) during
# the evaluation of rc or modulefile by a source or autoinit sub-command
# triggered from modulecmd level or (3) during the evaluation of global rc
# file. (2) and (3) are considered "extended" top evaluation contexts.
proc isTopEvaluation {{extended 1}} {
   return [expr {([depthState modulename] == 0 && [currentState eval_context]\
      in {{} load unload swload swunload}) || ($extended && [depthState\
      modulename] == 1 && ([aboveCommandName] in {source autoinit} ||\
      [isStateDefined rc_running]))}]
}

# analyze/translate command name passed to module
proc parseModuleCommandName {command defaultcmd} {
   set cmdempty 0

   # resolve command if alias or shortcut name used
   switch -- $command {
      add               {set command load}
      try-add           {set command try-load}
      add-any           {set command load-any}
      rm - remove       {set command unload}
      show              {set command display}
      apropos - keyword {set command search}
      {} {
         # if empty string supplied translate to default command
         set command $defaultcmd
         set cmdempty 1
      }
      default {
         # specific match for shortcut names
         set cmdlen [string length $command]
         foreach {match minlen sccmd} {load 2 load unload 4 unload delete 3\
            unload refresh 3 refresh reload 3 reload switch 2 switch swap 2\
            switch display 2 display available 2 avail aliases 2 aliases list\
            2 list whatis 2 whatis purge 2 purge initadd 5 initadd initload 6\
            initadd initprepend 5 initprepend initswitch 6 initswitch\
            initswap 6 initswitch initunload 8 initrm initlist 5 initlist\
            spider 3 spider update 2 reload disable 4 saverm describe 3\
            saveshow} {
            if {$cmdlen >= $minlen && [string equal -length $cmdlen $command\
               $match]} {
               set command $sccmd
               break
            }
         }
      }
   }

   set cmdvalid [expr {$command in [list load unload reload use unuse source\
      switch display avail aliases path paths list whatis search purge save\
      restore saverm saveshow savelist initadd initprepend initswitch initrm\
      initlist initclear autoinit clear config help test prepend-path\
      append-path remove-path is-loaded is-saved is-used is-avail info-loaded\
      sh-to-mod edit try-load refresh state load-any lint mod-to-sh reset\
      stash stashpop stashrm stashshow stashclear stashlist cachebuild\
      cacheclear spider]}]

   reportDebug "(command=$command, cmdvalid=$cmdvalid, cmdempty=$cmdempty)"
   return [list $command $cmdvalid $cmdempty]
}

# analyze arg list passed to a module cmd to set options
proc parseModuleCommandArgs {topcall cmd ignerr args} {
   set show_oneperline 0
   set show_mtime 0
   set show_filter {}
   set indepth_opt [expr {$cmd eq {spider} ? {spider_indepth} :\
      {avail_indepth}}]
   set search_filter [expr {[getConf $indepth_opt] ? {} : {noindepth}}]
   set search_match [getConf search_match]
   set dump_state 0
   set addpath_pos prepend
   set not_req 0
   set tag_list {}
   set otherargs {}

   # parse argument list
   foreach arg $args {
      if {[info exists nextargisval]} {
         ##nagelfar vartype nextargisval varName
         set $nextargisval $arg
         unset nextargisval
      } elseif {[info exists nextargisvaltosplit]} {
         ##nagelfar vartype nextargisvaltosplit varName
         set $nextargisvaltosplit [split $arg :]
         unset nextargisvaltosplit
      } elseif {[info exists ignore_next_arg]} {
         unset ignore_next_arg
      } else {
         switch -glob -- $arg {
            -j - --json {
               # enable json output only on supported command
               if {$cmd in [list avail savelist stashlist list search\
                  whatis spider]} {
                  setState report_format json
                  set show_oneperline 0
                  set show_mtime 0
               }
            }
            -t - --terse {
               set show_oneperline 1
               set show_mtime 0
               setState report_format terse
            }
            -l - --long {
               set show_mtime 1
               set show_oneperline 0
               setState report_format long
            }
            -o {
               # option is only valid for specific sub-commands
               if {$cmd in [list avail list spider]} {
                  set nextargisval asked_output
                  set output_arg -o
               } else {
                  if {!$ignerr} {
                     knerror "Unsupported option '$arg' on $cmd sub-command"
                  }
                  set ignore_next_arg 1
               }
            }
            --output=* {
               # option is only valid for specific sub-commands
               if {$cmd in [list avail list spider]} {
                  set asked_output [string range $arg 9 end]
                  set output_arg --output
               } elseif {!$ignerr} {
                  knerror "Unsupported option '--output' on $cmd sub-command"
               }
            }
            --tag=* - --tag {
               # option is only valid for specific sub-commands
               # unload allowed not to raise error on unload/load mixed ml cmd
               if {$cmd in [list load try-load load-any switch unload]} {
                  if {$arg eq {--tag}} {
                     ##nagelfar ignore Found constant
                     set nextargisvaltosplit tag_list
                  } else {
                     set tag_list [split [string range $arg 6 end] :]
                     if {![llength $tag_list]} {
                        knerror "Missing value for '--tag' option"
                     }
                  }
               } elseif {!$ignerr} {
                  knerror "Unsupported option '--tag' on $cmd sub-command"
               }
            }
            --append - -append {
               if {$cmd eq {use}} {
                  set addpath_pos append
               } else {
                  lappend otherargs $arg
               }
            }
            -p - --prepend - -prepend {
               if {$cmd eq {use}} {
                  set addpath_pos prepend
               } else {
                  lappend otherargs $arg
               }
            }
            --all - --show_hidden {
               # include hidden modules only on a limited set of command
               if {$cmd in [list avail aliases search whatis ml list lint\
                  savelist spider]} {
                  set ::asked_hiding_threshold 2
               } else {
                  lappend otherargs $arg
               }
            }
            -a {
               # -a option has a different meaning whether sub-command is use
               # or one of the search/listing sub-commands
               if {$cmd eq {use}} {
                  set addpath_pos append
               } elseif {$cmd in [list avail aliases search whatis ml list\
                  lint savelist spider]} {
                  set ::asked_hiding_threshold 2
               } else {
                  lappend otherargs $arg
               }
            }
            -d - --default {
               # in case of *-path command, -d means --delim
               if {$arg eq {-d} && [string match *-path $cmd]} {
                  lappend otherargs $arg
               } else {
                  set show_filter onlydefaults
               }
            }
            -L - --latest {
               set show_filter onlylatest
            }
            -C - --contains {
               set search_match contains
            }
            -S - --starts-with {
               set search_match starts_with
            }
            --indepth {
               # empty value means 'in depth' as it is default behavior
               set search_filter {}
            }
            --no-indepth {
               set search_filter noindepth
            }
            --dump-state {
               set dump_state 1
            }
            --auto - --no-auto - -f - --force {
               reportWarning "Unsupported option '$arg'"
            }
            --not-req {
               if {!$topcall && $cmd in [list load try-load load-any unload\
                  switch]} {
                  set not_req 1
               } else {
                  knerror "Unsupported option '$arg' on $cmd sub-command"
               }
            }
            --output {
               knerror "Missing value for '$arg' option"
            }
            default {
               lappend otherargs $arg
            }
         }
         set prevarg $arg
      }
   }

   if {[info exists nextargisval] || [info exists nextargisvaltosplit]} {
      knerror "Missing value for '$prevarg' option"
   }

   foreach tag $tag_list {
      if {$tag in [list loaded auto-loaded forbidden nearly-forbidden\
         hidden warning]} {
         knerror "Tag '$tag' cannot be manually set"
      }
   }

   if {[info exists asked_output]} {
      if {[getState report_format] in [list long json]} {
         knerror "Unsupported option '$output_arg' on [getState\
            report_format] output mode"
      } else {
         # get config name relative to current sub-command and output format
         set outputconf $cmd
         if {[getState report_format] ne {regular}} {
            append outputconf _[getState report_format]
         }
         append outputconf _output

         # check option value is coherent with current sub-command (remove
         # append/subtract operator at the start if any)
         set asked_output_list [split [expr {[string index $asked_output 0]\
            in {+ -} ? [string range $asked_output 1 end] : $asked_output}] :]
         if {[isDiffBetweenList $asked_output_list [lindex\
            $::g_config_defs($outputconf) 4]]} {
            knerror "Invalid element in value list for '$output_arg' option\
               on $cmd sub-command\nAllowed elements are: [lindex\
               $::g_config_defs($outputconf) 4] (separated by ':')"
         } else {
            ##nagelfar ignore Suspicious variable name
            set ::asked_$outputconf $asked_output
         }
      }
   }

   reportDebug "(show_oneperline=$show_oneperline, show_mtime=$show_mtime,\
      show_filter=$show_filter, search_filter=$search_filter,\
      search_match=$search_match, dump_state=$dump_state,\
      addpath_pos=$addpath_pos, not_req=$not_req, tag_list=$tag_list,\
      otherargs=$otherargs)"
   return [list $show_oneperline $show_mtime $show_filter $search_filter\
      $search_match $dump_state $addpath_pos $not_req $tag_list $otherargs]
}

proc module {command args} {
   # guess if called from top level
   set topcall [isTopEvaluation 0]
   set tryhelpmsg [expr {$topcall ? "\nTry 'module --help' for more\
      information." : {}}]
   if {$topcall} {
      set msgprefix {}
   } else {
      set msgprefix {module: }
   }

   # get mode, set to load if called from top level
   set mode [expr {$topcall ? {load} : [currentState mode]}]

   # resolve and check command name
   lassign [parseModuleCommandName $command help] command cmdvalid cmdempty
   # clear other args if no command name supplied
   if {$cmdempty} {
      set args {}
   }
   # raise error if supplied command is not known
   if {!$cmdvalid} {
      knerror "${msgprefix}Invalid command '$command'$tryhelpmsg"
   }

   # define states needed by module spec parsing and sub-command processing
   # define if modfile should always be fully read even for validity check
   lappendState always_read_full_file [expr {$command ni [list path paths\
      list avail aliases edit spider]}]
   lappendState commandname $command

   # parse options, do that globally to ignore options not related to a given
   # module sub-command (exclude them from arg list)
   lassign [parseModuleCommandArgs $topcall $command 0 {*}$args]\
      show_oneperline show_mtime show_filter search_filter search_match\
      dump_state addpath_pos not_req tag_list parsed_args

   # parse module version specification
   defineParseModuleSpecificationProc [getConf advanced_version_spec]
   if {$command in [list avail paths whatis load unload switch help test\
      display path is-avail edit try-load load-any list lint mod-to-sh\
      source spider]} {
      # some cmds allow to express variant without module name
      set allow_noname_spec [expr {$command in {avail list paths whatis\
         spider}}]
      # some cmds allow extra specifier in query
      set allow_xt_spec [expr {$command in {avail paths whatis spider}}]
      set parsed_args [parseModuleSpecification 0 $allow_noname_spec\
         $allow_xt_spec 0 {*}$parsed_args]
   }

   if {!$topcall} {
      # some commands can only be called from top level, not within modulefile
      switch -- $command {
         path - paths - autoinit - help - prepend-path - append-path -\
         remove-path - is-loaded - is-saved - is-used - is-avail -\
         info-loaded - clear - sh-to-mod - edit - refresh - source - state -\
         lint - mod-to-sh - reset - stash - stashpop - stashrm - stashshow -\
         stashclear - stashlist - cachebuild - cacheclear - spider {
            knerror "${msgprefix}Command '$command' not supported$tryhelpmsg"
         }
      }
      # other commands can only be called from modulefile evaluated from
      # command acting as top-level context (source and autoinit)
      if {([depthState modulename] > 1 || [aboveCommandName] ni [list source\
         autoinit]) && $command eq {config}} {
         knerror "${msgprefix}Command '$command' not supported$tryhelpmsg"
      }
      # no requirement should be recorded this module load/unload/switch cmd
      if {$not_req || ![getConf implicit_requirement]} {
         lappendState inhibit_req_record [currentState evalid]
      }
   }

   # argument number check
   switch -- $command {
      unload - source - display - initadd - initprepend - initrm - test -\
      is-avail - try-load - load-any {
         if {![llength $parsed_args]} {
            set argnberr 1
         }
      }
      refresh - reload - aliases - purge - initlist - initclear - autoinit -\
         reset - stash - stashclear - stashlist - cacheclear {
         if {[llength $parsed_args]} {
            set argnberr 1
         }
      }
      switch {
         if {![llength $parsed_args] || [llength $parsed_args] > 2} {
            set argnberr 1
         }
      }
      path - paths - info-loaded - edit {
         if {[llength $parsed_args] != 1} {
            set argnberr 1
         }
      }
      search - save - restore - saverm - saveshow - clear - state - stashpop\
         - stashrm - stashshow {
         if {[llength $parsed_args] > 1} {
            set argnberr 1
         }
      }
      initswitch {
         if {[llength $parsed_args] != 2} {
            set argnberr 1
         }
      }
      prepend-path - append-path - remove-path - sh-to-mod - mod-to-sh {
         if {[llength $parsed_args] < 2} {
            set argnberr 1
         }
      }
      config {
         if {[llength $parsed_args] > 2} {
            set argnberr 1
         }
      }
   }
   if {[info exists argnberr]} {
      knerror "Unexpected number of args for '$command' command$tryhelpmsg"
   }

   if {$topcall} {
      logEvent requested_cmd {command} $command arguments $args
      # Find and execute any global rc file found
      runModulerc
   }

   # define procedure to launch and associated options
   set cmdopts [list]
   switch -- $command {
      load - try-load - load-any {
         set cmdprocsuffix IntLoad
      }
      unload {
         set cmdprocsuffix IntUnload
      }
      switch {
         set cmdprocsuffix IntSwitch
      }
      initadd - initprepend - initswitch - initrm - initlist - initclear {
         set cmdprocsuffix Init
         lappend cmdopts [string range $command 4 end]
      }
      sh-to-mod {
         set cmdprocsuffix ShToMod
      }
      mod-to-sh {
         set cmdprocsuffix ModToSh
      }
      prepend-path - append-path - remove-path - is-loaded - is-saved -\
      is-used - is-avail {
         set cmdprocsuffix Resurface
         lappend cmdopts $command
      }
      info-loaded {
         set cmdprocsuffix Resurface
         lappend cmdopts module-info loaded
      }
      whatis {
         set cmdprocsuffix Search
         lappend cmdopts {}
      }
      default {
         set cmdprocsuffix [string toupper $command 0 0]
      }
   }
   set cmdprocname cmdModule$cmdprocsuffix
   switch -- $command {
      load - try-load - load-any {
         lappend cmdopts $topcall $command $mode $tag_list
      }
      unload {
         lappend cmdopts $mode
      }
      switch {
         lappend cmdopts $mode $tag_list
      }
      use {
         lappend cmdopts $mode $addpath_pos
      }
      unuse {
         lappend cmdopts $mode
      }
      source {
         lappend cmdopts load
      }
      avail {
         lappend cmdopts $show_oneperline $show_mtime $show_filter\
            $search_filter $search_match [getModulePathList exiterronundef]
      }
      spider {
         lappend cmdopts $show_oneperline $show_mtime $show_filter\
            $search_filter $search_match
      }
      list - savelist {
         lappend cmdopts $show_oneperline $show_mtime $search_match
      }
      config {
         lappend cmdopts $dump_state
      }
      stashlist {
         lappend cmdopts $show_oneperline $show_mtime
      }
   }

   $cmdprocname {*}$cmdopts {*}$parsed_args
   lpopState commandname
   lpopState always_read_full_file

   if {!$topcall && ($not_req || ![getConf implicit_requirement])} {
      lpopState inhibit_req_record
   }

   # if called from top level render settings if any
   if {$topcall} {
      renderSettings
   }

   return {}
}

proc ml {args} {
   # filter out all known options from argument list to guess command name
   # without them in the way
   lassign [parseModuleCommandArgs 1 ml 1 {*}$args] show_oneperline\
      show_mtime show_filter search_filter search_match dump_state\
      addpath_pos not_req tag_list fargs

   # determine if first argument is a known module sub-command
   lassign [parseModuleCommandName [lindex $fargs 0] list] command cmdvalid\
      cmdempty

   if {$cmdempty} {
      # consider empty string supplied as first argument as module name
      if {[llength $fargs]} {
         set cmdvalid 0
      }
      set margs $args
   } else {
      # first argument was command name
      set margs [lrange $args 1 end]
   }

   # directly call module procedure if sub-command spotted as first argument
   # or no argument supplied
   if {$cmdvalid} {
      module $command {*}$margs
   } else {
      # define if modfile should always be fully read even for validity check
      lappendState always_read_full_file 1
      lappendState commandname ml

      # parse specified module and get list of mods to unload and mods to load
      defineParseModuleSpecificationProc [getConf advanced_version_spec]
      lassign [parseModuleSpecification 1 0 0 0 {*}$fargs] modunlist modlolist

      # main procedure has already raised error for badly written argument
      # like '-' or '--', but we need here to replay module-specific argument
      # parsing to raise error if some arg are not allowed on unload/load cmd
      set mlcmd [expr {[llength $modunlist] ? {unload} : {load}}]
      lassign [parseModuleCommandArgs 1 $mlcmd 0 {*}$args] show_oneperline\
         show_mtime show_filter search_filter search_match dump_state\
         addpath_pos not_req tag_list fargs

      logEvent requested_cmd {command} ml arguments $args

      # Find and execute any global rc file found
      runModulerc

      set ret 0
      pushSettings

      # first unload specified modules
      if {[llength $modunlist]} {
         set ret [cmdModuleUnload unload match 1 s 0 {*}$modunlist]
      }
      # then load other modules unless unload phase failed and abort mode
      if {(!$ret || ![commandAbortOnError]) && [llength $modlolist]} {
         set ret [cmdModuleLoad load 1 0 0 $tag_list {} {*}$modlolist]
      }

      # rollback changes if abort mode and any load or unload failed
      if {$ret && [commandAbortOnError]} {
         restoreSettings
      }
      popSettings

      lpopState commandname
      lpopState always_read_full_file

      renderSettings
   }

   return {}
}

#
# 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
   set show_name 0
   setState cmdline "$argv0 $argv"

   # Load extension library if enabled
   ##nagelfar ignore #6 Strange command
   ##nagelfar ignore +2 Too long line
   if {[file readable [getConf tcl_ext_lib]]} {
      reportDebug "Load Tcl extension library ([getConf tcl_ext_lib])"
      load [file normalize [getConf tcl_ext_lib]] Envmodules
      setState tcl_ext_lib_loaded 1
   }
   # use fallback procs if extension library is not loaded
   if {[info commands readFile] eq {}} {
      rename ::__readFile ::readFile
      rename ::__getFilesInDirectory ::getFilesInDirectory
      rename ::__initStateUsergroups ::initStateUsergroups
      rename ::__initStateUsername ::initStateUsername
      rename ::__initStateClockSeconds ::initStateClockSeconds
      rename ::__parseDateTimeArg ::parseDateTimeArg
   }
   ##nagelfar syntax readFile x x? x?
   ##nagelfar syntax getFilesInDirectory x x
   ##nagelfar syntax initStateUsergroups
   ##nagelfar syntax initStateUsername
   ##nagelfar syntax initStateClockSeconds
   ##nagelfar syntax parseDateTimeArg x x

   # source site configuration script if any
   sourceSiteConfig

   setState supported_shells {sh bash ksh zsh csh tcsh fish cmd tcl perl\
      python ruby lisp cmake r pwsh}

   # Parse shell
   setState shell [lindex $argv 0]
   if {[getState shell] ni [getState supported_shells]} {
      reportErrorAndExit "Unknown shell type '([getState shell])'"
   }

   # extract options and command switches from other args
   set otherargv {}
   set extraargv {}
   set ddelimarg 0
   # split first arg if multi-word string detected for compat with previous
   # doc on module usage with scripting language: module('load mod1 mod2')
   ##nagelfar ignore #2 Badly formed if statement
   set argtoparse [if {[llength [lindex $argv 1]] > 1} {list {*}[split\
      [lindex $argv 1]] {*}[lrange $argv 2 end]} {lrange $argv 1 end}]
   foreach arg $argtoparse {
      if {[info exists ignore_next_arg]} {
         unset ignore_next_arg
      } elseif {[info exists nextargisextraargv]} {
         lappend extraargv $arg
         unset nextargisextraargv
      } elseif {[info exists nextargisval]} {
         ##nagelfar vartype nextargisval varName
         set $nextargisval $arg
         unset nextargisval
      } else {
         switch -glob -- $arg {
            -T - --trace {
               set asked_verbosity trace
            }
            -D - -DD - --debug {
               set asked_verbosity [expr {$arg eq {-DD} || ([info exists\
                  asked_verbosity] && $asked_verbosity in {debug debug2}) ?\
                  {debug2} : {debug}}]
            }
            -s - --silent {
               set asked_verbosity silent
            }
            -v - -vv - --verbose {
               set asked_verbosity [expr {$arg eq {-vv} || ([info exists\
                  asked_verbosity] && $asked_verbosity in {verbose verbose2})\
                  ? {verbose2} : {verbose}}]
            }
            --help - -h {
               set show_help 1
            }
            -V - --version {
               set show_version 1
            }
            --dumpname {
               set show_name 1
            }
            --paginate {
               set asked_paginate 1
            }
            --no-pager {
               set asked_paginate 0
            }
            --redirect {
               if {[getState shelltype] ni {sh fish pwsh}} {
                  reportWarning "Unsupported option '--redirect' on [getState\
                     shell] shell"
               } else {
                  set asked_redirect_output 1
               }
            }
            --no-redirect - --no_redirect {
               set asked_redirect_output 0
            }
            --auto {
               set asked_auto_handling 1
            }
            --no-auto {
               set asked_auto_handling 0
            }
            -f - --force {
               set asked_force 1
            }
            --color* {
               set asked_color [string range $arg 8 end]
               if {$asked_color eq {}} {
                  set asked_color always
               } elseif {$asked_color ni [lindex $::g_config_defs(color) 4]} {
                  unset asked_color
               }
            }
            -o {
               # add with next arg to the command-specific switches
               lappend extraargv $arg
               set nextargisextraargv 1
            }
            --width* {
               set asked_term_width [string range $arg 8 end]
               set term_width_arg --width
               if {$asked_term_width eq {}} {
                  set asked_term_width 0
               }
            }
            -w {
               ##nagelfar ignore Found constant
               set nextargisval asked_term_width
               set term_width_arg -w
            }
            -t - --terse - -l - --long - --default - -L - --latest - -S -\
            --starts-with - -C - --contains - -j - --json - --output=* {
               # command-specific switches that can for compatibility be
               # passed before the command name, so add them to a specific
               # arg list to ensure command name as first position argument
               lappend extraargv $arg
            }
            -d {
               # in case of *-path command, -d means --delim
               if {$ddelimarg} {
                  lappend otherargv $arg
               } else {
                  lappend extraargv $arg
               }
            }
            -a - --append - -append - --all - -p - --prepend - -prepend -\
            --delim - -delim - --delim=* - -delim=* - --duplicates - --index\
            - --notuasked - --indepth - --no-indepth - --dump-state -\
            --reset - --tag - --tag=* - --glob - --show_hidden {
               # 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
            }
            -i - --icase {
               set asked_icase always
            }
            --ignore-cache - --ignore_cache {
               set asked_ignore_cache 1
            }
            --ignore-user-rc {
               set asked_ignore_user_rc 1
            }
            --timer {
               setState timer 1
               set timer_start [clock microseconds]
            }
            --human - -c - --create - --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
            }
            --output {
               reportErrorAndExit "Missing value for '$arg' option\nTry\
                  'module --help' for more information."
            }
            --initial_load {
               # ignore option for compatibility
            }
            {-} - {--} - {--*} {
               reportErrorAndExit "Invalid option '$arg'\nTry 'module --help'\
                  for more information."
            }
            -* {
               # verify current command accepts minus arg (-*)
               if {![info exists accept_minus_arg] && [llength $otherargv]} {
                  set subcmdtest [lindex $otherargv 0]
                  if {$subcmdtest ne {ml}} {
                     lassign [parseModuleCommandName $subcmdtest {}]\
                        subcmdtest
                  }
                  # accepted if command is ml or config or if adv vers spec is
                  # enabled and command can receive boolean variant
                  # specification
                  set accept_minus_arg [expr {$subcmdtest in {ml config} ||\
                     ([getConf advanced_version_spec] && $subcmdtest in\
                     {avail list display help is-avail is-loaded load path\
                     paths switch test unload whatis mod-to-sh source\
                     spider})}]
               }
               # spare argument if minus arg is accepted
               if {[info exists accept_minus_arg] && $accept_minus_arg} {
                  lappend otherargv $arg
               } else {
                  reportErrorAndExit "Invalid option '$arg'\nTry 'module\
                     --help' for more information."
               }
            }
            default {
               lappend otherargv $arg
            }
         }
         set prevarg $arg
      }
   }

   if {[info exists nextargisextraargv]} {
      reportErrorAndExit "Missing value for '$prevarg' option\nTry 'module\
         --help' for more information."
   }
   if {[info exists asked_term_width]} {
      set rangewidth [lindex $::g_config_defs(term_width) 4]
      if {[string is integer -strict $::asked_term_width] && \
         $::asked_term_width >= [lindex $rangewidth 0] &&\
         $::asked_term_width <= [lindex $rangewidth 1]} {
         set validval 1
      } else {
         reportErrorAndExit "Invalid value for option\
            '$term_width_arg'\nValue should be an integer comprised between\
            [lindex $rangewidth 0] and [lindex $rangewidth 1]"
      }
   }

   setState subcmd [lindex $otherargv 0]
   set otherargv [list {*}[lreplace $otherargv 0 0] {*}$extraargv]
   setState subcmd_args $otherargv
   # call ml frontend if it is asked command
   if {[getState subcmd] eq {ml}} {
      set execcmdlist [list ml {*}$otherargv]
   } else {
      set execcmdlist [list module [getState subcmd] {*}$otherargv]
   }

   # 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_QUARANTINE_SET)] &&\
      $env(__MODULES_QUARANTINE_SET) eq {1}} {
      foreach var [split [getConf run_quarantine]] {
         # check variable name is valid
         if {[regexp {^[A-Za-z_][A-Za-z0-9_]*$} $var]} {
            set quarvar __MODULES_QUAR_${var}
            # 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]} {
            reportWarning "Bad variable name set in MODULES_RUN_QUARANTINE\
               ($var)"
         }
      }
   }

   if {$show_help} {
      if {[getState subcmd] eq {ml}} {
         reportMlUsage
      } else {
         reportUsage
      }
      flushAndExit
   }
   if {$show_version} {
      reportVersion
      flushAndExit
   }
   if {$show_name} {
      reportName
      flushAndExit
   }

   # eval needed to pass otherargv as list to module proc
   {*}$execcmdlist
} errMsg ]} {
   # re-enable error report in case it was previously inhibited
   setState inhibit_errreport 0
   # remove any message record id to render next error
   clearAllMsgRecordId
   reportFinalError $errMsg
   # init error report here in case the error raised before the regular init
   initErrorReport
   flushAndExit
}

flushAndExit