# -*- tcl -*-
set rev <20240423.1951.44>
# puts "loading frECF $rev"

# The "filerunner Exec Call Formater (frECF).
# the primary purpose of this function is to assemble an exec
# call that will do the "right" thing.
# It treats all parameters as strings, thus (hopefully) avoiding
# triping over special characters of meaning in the tcl syntax.
# But wait, the exec call needs to have special characters escaped
# the specials are "{} []$\". This appears to be true only for MS
# windows. To handle "native" file names we do our own quoting using
# FixFileName.

# frECF parameters:
#     fmat          A prototype of a format string (see below)
#     parms         A list of strings (file names) to convert (format)
#                   This should ALWAYS be a list. If one file, pass
#                   [list <file>]. This protects us (you) from
#                   odd ball strings in <file>
#     args          Options detailing various things. (see below)
#
#  effectively the frECF function calls 'format' with the given string
#  and set of parms and {optionally) executes the result.
#
# Details...
# In general 'format' requires one '%' for each parm.  The proto type
# string 'fmat' may be deficient in this regard due to more entries
# in the 'parms' list than '%'s in 'fmat'. This is corrected by
# adding '%' factors to the string. In the resulting processed
# 'fmat' string (pfmat) we recognize (note that parms with spaces
# will be quoted unless otherwise noted):

#  %s    parm converted with 'file native' (on MS windows system)
#        Otherwise the parm is quoted (") only if needed.
#  %m    converts to a %s with its parm converted with a function
#        provided by the 'args' -m option. defaults to "format %s"
#  %n    Converts to a simple %s i.e. no special conversions
#        Read it a "normal"
#  %a    Replace this with default (%[ns]). This is used if exactly
#        one conversion is needed at this point in the string.
#  %b    Same as %a, except put as many as needed here to get do all
#        remaining conversions. (using blank seperators)
#        (usually followed by & or |)
#  %q    same as %s except enclose in \" (instead of ") The protected
#        quotes are only generated if the parameter would be quoted,
#        i.e. if no quotes are needed/used by FixFileName these will
#        not be generated.
#  %r    same as %s but no enclosing quotes at all
#  %p    percent/URL encode the parm.
#  %!    is a disappearing space. It is used to seperate entries in the
#        format string so they aren't grouped with other entries e.g.:
#        {%s} -> "{s-value}" where as {%!%s%!}-> {"s-value"} (note " location)
#  %\    removes the backslash e.g. " \" " -> { \" } but " %\" " -> { " }
#        This would most likely be used to pass a quote in a quoted string
#        where as the simple quote would run into issues with tcl quote model.
#  %%    converts to %
#  %     (percent space) converts to " " an embedded space (or other white space)
#        (In this discussion %s may be replaced with any of %m %n $q %r or %a.
#        %b, however is special in that if 2 or more conversions are done
#        for a %b, "white space" from the -default  will be inserted between
#        them and will thus terminate a conversion and start a new one.)
#        E.g. { %s } ->  { "s-value" } but { % %s } -> { " s-value" }
#       
#  All other % suffixes are stripped of the % and passed through. Just like
#  % space and %%. In all these case a space between them and the %s will
#  exclude them from the quotes used to protect the %s result, otherwise they
#  are included in the quote. Quotes are only used if a space appears anywhere
#  in the parameter or a '%s ' was added (without unprotected spaces).
#  ALSO NOTE that '{' -> '\{' and '}' -> '\}'  while '%{' -> '{' and '%}' -> '}'
#  

#  Positional conversions are allowed (i.e. %2@s %1@s etc.) except for %a & %b.
#  "@" is used instead of "$" to avoid tcl's desire to dereference the "$".
#  How it works:
#  Characters from pfmat are move to tmp buffer until 'white space' or '%'
#  or the end of fmat is found. If 'white space' or '%!' or end of pfmat
#  is found the required quoting is done and the result is move to the
#  end of the command line. If a '%' if followed by a digit, a positional
#  parameter is processed. If the '%' is followed by one of
#  (a, b, m,n, q, r, or s) and "parms" remain a parm is moved to the tmp
#  buffer (if no "parms" remain, the next fmat character is processed). If
#  the conversion is '%b' and parms remain, the fmat pointer is backed up to
#  the beginning of the '%b' thus processing the next parm. Any other characters
#  (after a '%') are added to the tmp buffer.

#  args is a list consisting of -<option type>  <option value>
#       args is processed such that you can pass one or more lists and
#       or a series of individual parameters.
#       Unique abbreviations may be used here.
#
#  defined args are () enclose recognized abbreviation:

# (-m) -m   <function>    This function will be called to convert parms
#                         for '%m' pfmat specifiers. The function should take
#                         one parameter (the file name) either at the end of
#                         the sequence given for <function> e.g
#                         <file native 'parm'>
#                         or embeded with a '%s' marking the spot e.g.
#                         <regsub {stuf} %s {stuff)>.

# (-d) -default <conversion>  <conversion> will be added to the 'pfmat'
#                         at '%a' and/or at the end of the pfmat as
#                         many times as need to get the required
#                         number of conversions. If '-d' is not given,
#                         { %s} is used for MS windows, else { %n}
#                         If a '%b' is encountered it is assumed that there 
#                         are no more '%conversions' and it will be replaced
#                         with as many of these conversions as needed
#                         to complete the format string '%' count.
#                         The first %b conversion will complete the
#                         part of pfmat it is found in, including any trailing
#                         characters (see above) after the %b.
#                         Then, if needed, additional conversion is started
#                         with the characters preceeding the % in
#                         the 'default'. Each of these conversions will
#                         be completed with the characters following
#                         the conversion character in 'default'. This
#                         string will ususlly need to be protected with
#                         the braces, e.g. {  %n} In order to not run
#                         %b conversions together the leading char(s) (i.e.
#                         those before the %, must contain at least one
#                         "white space" (possibly a %!) character
#                         (an error is thrown if not).


# (-f) -fonly        1    the result of the format call is returned
#                         without being executed.

# (-b) -background   1    execute asynchronously (done by 'after' command).

# (-a) -alterr <message>  <message> is an alternative error message
#                         to be used on execution failure. <message>
#                         may include up to 3 '%s's which will be replaced 
#                         with as follows:
#                         1 %s the error returned from the command
#                         2 %s the command and the error returned
#                         3 %s the command, ::errorInfo and the output
#                         The default <message> is:
#                         "While executing: \n%s\n%s".
#                         If <message> is {}, no error are reported.
#                         Errors are passed to 'PopError' except if:

# (-e) -error    <efun>   Errors are passed to the function '<efun>'
#                         If <efun> is {} no errors are reported as such,
#                         but the -post function (if one) is called.   

# (-p) -post     <pfun>   function to call with result of the command
#                         this is most useful for async commands. 
#                         <pfun> will only be be called if the command
#                         produces no errors. The variable name 
#                         containing the output (if any) will be  
#                         appended to <pfun> for this call (call by name).

# (-w) -wildcard <bool>   If true glob chars in parts where file names
#                         are expected are expanded.
#                         Defaults to false.

# (-x) -xecok    <bool>   If true use 'auto_execok'to expand executable
#                         names (first token, token after 'exec' or |)
#                         most useful in MSW where scripts are expanded
#                         to prefix their interpreters.
#                         WARNNING: If true it may mess up file names
#                                   that contain 'pipe' characters.
#                         Default false.
#
#     The following options are only meaningful on MS windows systems.
#     For other systems they will be quietly ignored.

# (-s) -short    <bool>   if true and MSW, file names are converted to short
#                         names. Used to eliminate problems caused by spaces
#                         in MS windows file names. Defaults to false.

# (-c) -cmd      <bool>   true if result is to pass through windows cmd.exe
#                         forced false if not MSW. Default to false.

# (-t) -twapi    <bool>   If false frECF will not attempt to convert "exec"
#                         calls to "twapi::shell_execute" (see below).
#                         Defaults to true.

# frECF may be used to format "twapi::shell_execute" calls. In this case you
# should code "fmat" as "twapi::shell_execute -path %s -params { %b }
# and the "parms" list should start with the file name of the desired program.
# You may also add action verbs, the most likely being "-verb runas" which
# will invoke "elevation" as part of the call.

# "twapi::shell_execute" will not start pipes, even the stdout connection
# provided by tcl "exec" and "open", however, if these are not needed or used
# "twapi::shell_execute" seems to be more robust "exec" replacement.
# For this reason, if "fmat" starts with "exec ", contains no " | "s and
# ends in "&", it will be converted as follows:
# "exec" -> "twapi::shell_execute -path"
# if "exec" is not followed by %s, the next element is copied to "fmat"
# after the %s or the above element "-params { %b }" is inserted
# "&" -> ""

# There are two types of errors:
# 1) those that come from a bad call and
# 2) those that are caused by attempts to execute an external
#    function which fails
# Type 1) errors are reported with the tcl 'error'
# Type 2) are reported with the -error function
#
# frECF returns:
#  A 2 or 3 element list: {<r> <output> ?<command>?}
#  if <r> is zero,     then there was no error and <output> is the result
#  if <r> is non-zero, then there was an error and <output> is the
#                      error message and <command> is the command that
#                      generated the message

# In an effort to allow this module to stand alone, we will put the
# WSL file handeling code here.  Like wise the test for WSL.


proc frECFwinFile {file} {
  global ::frECF::newcmd
  global frECF::short
  
  if {!$::frECF::MSW} {
    # *nix env. This test will only do somthing in MSW WSL environment.
    # we don't need to test for WSL as only in that case is this combination
    # possible.
    # frputs newcmd "[string range $newcmd [string last "|" $newcmd] end] "
    if {[regexp -nocase {.*\.exe(\}| )}\
		 [string range $newcmd [string last "|" $newcmd] end]]} {
      return [string map $::frECF::wslMap $file]
    }
    # frputs file
    return $file 
    #
    # we eliminate the case where the files directory is "/" which
    # (in MSW refers to the root of the volume). This is needed to
    # prevent calling 'switches' such as '/c' files. This is not a
    # general solution, but in 'filerunner' we try to always use full
    # file names with the volume id (e.g. 'c:').
  } elseif {![file exists $file] &&\
		(![file exist [set dir [file dir $file]]] || $dir == "/") } {
    # frputs file "[file exists $file] [file exist [file dir $file]] "
    return $file
  }
  # We now know it is MSW and that the file or at least it dir exists
  # The short name test will fail if not a file name
  if {$short} {
    if {[catch {file att $file -shortname} result] != 0} {
      if {[catch {file att [file dir $file] -shortname} result] == 0} {
	set file [file join $result/[file tail $file]]
  # we would like to not mess with things that appear not to be file names
      }
    } else {
      set file $result
    }
  }
  # frputs file
  return [file native $file]
}

proc frECF {pfmat {parms {}} args} {
  global ::frECF::short
  global ::frECF::newcmd
  # set up defaults
  set alterr "While executing:\n%s\n%s"
  set error "PopError"
  set fonly 0
  set default $::frECF::default
  set m {format %s }
  set background 0
  set post     {}
  set cmd      0
  set wildcard 0
  set xecok    0
  set openBrace  [string index {{}} 0]
  set closeBrace [string index {{}} 1]
  set dquote    "\""
  set short 0
  set twapi 1
  set possible {-alterr -error -fonly -default -m -background \
		    -post -cmd -short -twapi -wildcard -xecok}
  # This code is to allow a mixture of lists of doublets and just
  # doublets. It depends on the first entity of a double to be
  # of length 1. The second entity may be of any length
  # frputs #1 #2 pfmat parms args
  while {$args != {} && $args != {{}}} {
    set args [lassign $args opt]
     frputs  #2 #3 pfmat parms args opt
    # is this a list?
    if {[llength $opt] > 1} {
      # yes, break it up and push it ahead of the rest
      set args [linsert $args 0 {*}$opt]
      continue
    }
    # not a list, get value part...
    set args [lassign $args value]

    set tar [lsearch -glob -all -inline $possible "$opt*"]
    if {$tar == -1 || [llength $tar] != 1} {
      error "frECF: option ($opt) must be one of $possible"
    }
    set [string range $tar 1 end] $value
  }
  set dInfo [patternStringSearch {{ } %! %s %n %m %b %q %r} $default]
  # dInfo should have at least two entries,
  # the conversion entry {? (>1)} should be proceeded by at least
  # one for white space {? (0 or 1)}
  # Multiple conversion entries could be an issue but we let the user
  # sort that out. Here we insure:
  # a: At least two entries
  # b: The first is for white space
  # c: A conversion is present
  foreach ent $dInfo {
    lassign $ent loc type
    if {$type > 1} {
      set ind $loc
      break
    }
  }
  
  if {![info exists ind] || [lindex $dInfo 0 1] > 1} {
    return -code error "frECF: -default ($default) must have whitespace\
                       followed by one of: %s %n %m %b %q %r %p"
  }
  if {!$::frECF::MSW} {
    set short [set twapi [set cmd 0]]
  }
  if {$m != {} && ![string match "*%s*" $m]} {
      append m " %s "
  }
  set m [regsub { %s | %s$} $m { \"%s\" } ]
  frputs parms
  set len [llength $parms ]
  frputs parms
  # Here we define the prefix conversion
  # This should be some separator character(s) and a quote
  # we only want the conversion char.
  set defaultC [string index $default $ind+1]
  # We don't need to '%!' any more, disappear it.
  set defaultPrefix [regsub -all {%!} [string range $default 0 $ind-1] {}]
  set defaultPostfix [string range $default $ind+2 end]
  # frputs default ind loc type defaultC defaultPrefix defaultPostfix dInfo
  # set pre [string range $default 0 $ind-1]
  # set pos [string range $default $ind+2 end]
  
  # frputs pfmat parms len default defaultPrefix defaultPostfix
  # I explored two ways to go from here:
  # 1) Change 'pfmat' to be a proper 'format' string and call 'format'
  #    which means:
  #    a) Changing all the %a to the current default and
  #    b) Processing the %b to put the needed default conversions
  #       either at the %b spot or at the end of the string.
  #    c) Preprocess each occurance of %n or %s or %m to convert
  #       the 'parms' list into a new, converted list while changing
  #       each entry in the 'pfmat' string to %s.
  #    d) Calling 'format' with the string and the processed 'parms'
  #
  #  or
  #
  #  2) Process the 'pfmat' string and 'parms' in one pass:
  #     a) Find each '%' and do the required conversion of
  #        'parms' directly into the resulting string.
  #
  #  While 2) sounds easier, it really isn't, at least in the
  #  amount of code needed.  However, it allows us to be more
  #  forgiving around the conversion of positional specifiers.
  #  While 'format' requires all or none in this reguard, we only
  #  require not (explicitly or implicity) asking for the same
  #  element twice. (As each conversion is processed we mark it
  #  as done. If no particular element is requested we
  #  take the first un-done element.  If subsequently that
  #  element is requested, we throw an error.)
  

  # now build the command...
  set commandExpected 0
  set used {}
  set this {}
  set thiswas {}
  set foundB 0
  set begin 0
  set twapiSh 0
  set start 0
  set oc \u001a ; # An obscure character... 

  if {[string index $pfmat end] == "&"} {
    if {[string is space [string index $pfmat end-1]]} {
      set pfmat [string replace $pfmat end end "%b &"]
    } else {
      set pfmat [string replace $pfmat end end "%b&"]
    }  
  } else {
    append pfmat " %b"
    
  }
  global reDir
  frputs pfmat parms
  #                2   1 4  5  3  15 12  8 9  6 10  7   14  11  13    
  #                |&  | <@ << < >&@ >@ >& >> > 2>> 2> 2>@1 >>& 2>@
  set reDir {([^%|<>2]|^)(\|&|\||<@|<<|<|>&@|>@|>&|>>|>|2>>|2>|2>@1|>>&|2>@)}
  if {$xecok} {
    set pfmat [regsub -all $reDir $pfmat {\1 \2 }]
  }
  frputs pfmat parms

  set tmp {}
  set chunk {}
  set newcmd {}
  set start 0
  set perCfound {}
  set commandExpected 0
  set sufix {}
  
 
  set pfmatEnd [string length $pfmat]
  
  # So here is an issue. If we have 'chars%nmore' and %n -> '"parm"' do we do
  # a) '"charsparmmore"' or b) 'chars"parm"more'. The answer we chose is a)
  # Even more to the point we don't want to even use the '"'s unless something
  # needs protecting which we leave up to FixFileName.
  # This leaves the question of 'spaces'. If 'chars' (in our example) contains
  # a space it MUST be protected with % (i.e. "% ") and if the whole thing
  # contains such a space we will protected it as a unit. We dismantle { }
  # protections to allow them to be used. This means we need a way to code;
  # %n<something> that results in "parm"<something> when <something> does not
  # start with a space. So we define %! to be a disappearing space allowing:
  # %n%!<something> ->"parm"<something> while %n<something> -> "parm<something>"
  #
  # And then we have the case of {some thing} which may be a file path/name.
  # If a brace is not to be paired, it must be coded as %brace, otherwise
  # we attempt to pair it. Likewise for quotes.
  
  while {1} {
    set this [string index $pfmat $start]
    # The following protects quoted strings ({} or ""). Lets not...
    if {1 && $this in [list $openBrace $dquote]} {
      set endTok [stringGetToken $pfmat start stoponclose 1 protectchar {%}]
      set this [string range $pfmat $start $endTok]
      set start $endTok
    }
    set next [string index $pfmat [incr start]]
    frputs NEWCMD newcmd tmp this next
    # white space or '%!' or end of pfmat string terminates the current production
    if {$start > $pfmatEnd ||\
	    [string is space $this] ||\
	    ($this == "%" && $next == "!")} {
      frputs newcmd tmp this next execFlag commandExpected twapiSh\
	  perCfound pfmat
      # time to finish the production.
      # Time to deal with pipes...
      # here we use an obscure character
      set ttmp $tmp
      if {$xecok} {
	set ttmp [regsub -all $reDir $tmp "\\1$oc\\2$oc"]
      }
      while {$ttmp != {}} {
	if {[set en [string first $oc $ttmp]] != -1} {
	  set tmp [string range $ttmp 0 $en-1]
	  set ttmp [string range $ttmp $en+1 end]
	} else {
	  set tmp $ttmp
	  set ttmp {}
	}
	if {$tmp == {}} {continue}
	frputs tmp commandExpected newcmd xecok frECF::MSW
	# frputs tmp this next "%" (perCfound)
	# Don't mess with "%m" conversions...
	set execFlag [string equal "exec" $tmp]
	if {$::frECF::MSW && $xecok && ($commandExpected || $newcmd == {}) } {
	  if {!$execFlag} {
	    set sufix [lassign [auto_execok [list $tmp]] prog]
	    if {$prog != {}} {
	      set tmp $prog
	    }
	  }
	  frputs prog sufix tmp
	}
	set ntmp {}
	# perCfound is the current % char (after a %) or nil if none
	# we do not want to do more with a %m conversion...
	if { $perCfound != "m"} {
	  if {$twapiSh} {
	    frputs commandExpected wildcard sufix prog tmp newcmd
	    # If useing the twapi shell call we only need to
	    # double up on the backslashes
	    # perCfound is tmp if the % char is to be passed (not replaced)
	    if {$perCfound != $tmp} {
	      if {!$commandExpected && $wildcard} {
		foreach file [expr {$wildcard ? [stringExpand $tmp] : [list $tmp]}] {
		  append ntmp " " [FixFileName $file -p $::frECF::MSW]
		  
		  frputs file ntmp  wildcard
		}
		frputs tmp ntmp newcmd
		set tmp $ntmp
	      } else {
		# set tmp \"[regsub -all {([\\]+)} $tmp {\1\1}]\"
		if {$doParms} {
		  set tmp [FixFileName $tmp -p $::frECF::MSW]
		  append tmp " "  "-params $openBrace "
		  set doParms 0
		}
		# time to add the suffix
		foreach suf $sufix {
		  append tmp " " [FixFileName $suf -p $::frECF::MSW]
		}
	      }
	    }
	  } else {
	    if {$tmp != $perCfound} {
	      # if perCfound is tmp, it is to be passed, not messed with
	      # For others, more protection is needed.
	      frputs tmp sufx ntmp
	      if {$sufix != {}} {
		set ntmp [FixFileName $tmp -p $::frECF::MSW]
		foreach suf $sufix {		
		  append ntmp " " [FixFileName $suf -p $::frECF::MSW]
		}
	      } elseif {$wildcard} {
		foreach file [stringExpand $tmp] {
		  append ntmp [FixFileName $file -p $::frECF::MSW] " "
		}
		# take away the final blank
		set ntmp [string range $ntmp 0 end-1]
	      } else {
		set ntmp [FixFileName $tmp -p $::frECF::MSW]
	      }
	      set tmp {}
	    }
	    append tmp $ntmp
	  }
	  frputs tmp  ntmp sufix
	  set sufix {}
	  if {$perCfound == "r"} {
	    set tmp [regsub -all {^"|"$} $tmp {}]
	  } elseif {$perCfound == "q"} {
	    set tmp [regsub -all {^"|"$} $tmp {\"}]
	  }
	}
	set commandExpected 0
	# here is where we set up the twapiSh replacement.
	frputs newcmd execFlag tmp
	if {$newcmd == {}} {
	  if {$::frECF::MSW && $execFlag} {
	    set pipeFlags [list % %! %% | |& 2>* <* >*]
	    foreach el $pfmat {
	      if {$el in $pipeFlags} {
		incr pipeFound
		break
	      }
	    }
	    # expect a command as the second element
	    # Note: we don't need the 'auto_execok' for unix
	    set commandExpected 1
	    frputs frECF::MSW execFlag commandExpected tmp this
	    
	    if {$twapi &&
		([string index $pfmat end] == "&" || $background ) && \
		    ![info exists pipeFound] } {
	      # Ok, recast to a twapi::shell_execute call
	      set tmp "twapi::shell_execute -path  "
	      
	      incr twapiSh
	      incr doParms
	      # replace the ending '&' with new ending
	      set pfmat "[string trimright $pfmat &] %b %$closeBrace "
	      set pfmatEnd [string length $pfmat]

	    }
	  }
	}
	if {$tmp in {" |" " |&"}} {
	  set commandExpected 1
	}
	append newcmd $tmp
      frputs execFlag commandExpected tmp this
      }
      frputs tmp this start next  perCfound newcmd pfmat
      if {[string is space $this]} {
	append newcmd $this
      } else {
	# this disappears the '!'
	incr start
      }
      set tmp {}
      set perCfound {}
    
      if {$start > $pfmatEnd} {
	break
      }
 	    frputs newcmd tmp frECF::MSW execFlag commandExpected this used
      continue
    }  ; # end of if  $start...
    # frputs frECF::MSW execFlag commandExpected tmp this newcmd used
    if {$this != "%" || [string index $tmp end] == "\\"} {
      append tmp $this
      continue
    }
    
    # if the last char in the string is "%",
    # discard it and clean up.
    if {$start >= $pfmatEnd} {
      continue
    }
    # gobble the %
    set this $next
    # if {$next in [list $openBrace $dquote]} {continue}
    incr start
    # if not an action char, pass it.
    set indx -1
    if {[set indx [string first $this {123456789smnabqrp}]] == -1} {
      append newcmd $this
      if {$perCfound == {}} {
	set perCfound $this
      }
      continue
    }
    
    # frputs this start tmp indx newcmd
    # check for position count (%n@c)
    # 'this' is 'n'
    # start points at char '@'
    if {$indx < 9} {
      if {[string index $pfmat $start] == "@" &&\
	      [set this [string index $pfmat $start+1]] in {s m n q r p}} {
	# this is the index + 1
	#incr indx
	incr start 2
      } else {
	return -code error \
	    "Illegal position code [string range $pfmat $start-2 $start+1]"
      }
    } else {
      set indx -1
    } ; # end of position code
    if {$perCfound == {}} {
      set perCfound $this
    } elseif {$perCfound in {"q" "r"} && $this != $perCfound} {
      error "In compatable conversions in same element: %$perCfound & %$this"
    }
    # Ignore conversions if we are out of parameters
    if {[llength $used] >= $len} {
      continue
    }
    if {$indx == -1} {
      while {[incr indx] in $used} {}
    } elseif {$indx in $used} {
      error "frECF: Conversion # [incr indx] used twice."
    } elseif {$indx >= $len || $indx < 0} {
      error "frECF: Conversion # [incr indx] has no value"
    }
    lappend used $indx
    set thiswas $this
    if {$this in {"a" "b"}} {
      set npfmat {}
      # take care here. If we have not yet found a "%b" we need to
      # make sure the prefix is handled correctly
      # If a character is space, directly append it to the result,
      # otherwise it should be considered part of the parameter.
      foreach char [split $defaultPrefix {}] {
	if {[string is space $char]} {
	  #append newcmd $char
	  append npfmat $char
	} else {
	  append tmp $char
	}
      }
      if {$this == "b"} {
	set foundB 1
	if {[llength $used] < $len} {
	  # set to find  "%b" yet again
	  # This will usually be " "
	  #append npfmat $defaultPrefix "%b"
	  append npfmat "%b"
	}
      }
      append npfmat  [string range $pfmat $start end]
      set pfmat $npfmat
      set pfmatEnd [string length $pfmat]
      set start 0
      set this $defaultC
    }
    frputs this default defaultC defaultPrefix defaultPostfix char pfmat foundB tmp newcmd
    switch $this {
      q -
      r -
      s {
	append tmp [frECFwinFile [lindex $parms $indx]]
      }
      p {append tmp [FixFileName [lindex $parms $indx] -l -1]}
      m {
	set pNam  [list [regsub -all { } [lindex $parms $indx] {\x20}]]
	frputs pNam
	set r [catch {uplevel {*}[format $m $pNam ]} out]
	if {$r == 0} {
	  append tmp $out
	} else {
	  # this might be a type 2 error... but
	  error "frECF: \"m\" conversion error $out"
	}
      }
      default {append tmp [lindex $parms $indx]}
    }
    if {$thiswas in {"a" "b"}} {
      append tmp $defaultPostfix
    }
    frputs m this out tmp pfmat
  } ; # End of while {1}
  
  # If windows and cmd.exe is in the picture (i.e. caller set -cmd 1)
  # do the cmd.exe escapes...
  # Here we DO NOT trust the 'list' command at all!
  # frputs newcmd
  # cmd metacharacters are (, ), %, !, ^, ", <, >, &, and |
  if {$cmd && [regexp -nocase {^ *exec +cmd(.exe +| +).*} $newcmd]} {
    set newcmd [regsub -all {(\(|\)|%|!|\^|\"|<|>|&|\|)} $newcmd {^\1}]
  }
  # End of main While...getToken
  
  frputs NEWCMD cmd newcmd pfmat parms
  # frputs post
  # frputs background
  
  if {[string is true $fonly]} {
    return $newcmd
  }

  if {$background != 0} {
    after 10 "frECF_Async [list $newcmd] \
                        [list $alterr] \
                        [list $error] \
                        [list $post]"
    return 0
  } else {
    return [frECF_Async $newcmd $alterr $error $post]
  }
}
proc frECF_Async {newcmd alterr error post} {
  # This needs to be executed if frECF's callers space, not frECF's space
  set lev [expr {[info level] == 1 ? 0 : 2}]
  set r [catch {uplevel $lev $newcmd} out]
  # frputs  newcmd out r
  if {$r != 0 && $error != {} && $alterr != {}} {
    switch [stringCount "%s" $alterr] {
      3 {set out [format $alterr $newcmd $::errorInfo $out]}
      2 {set out [format $alterr $newcmd $out]}
      1 {set out [format $alterr $out]}
    }
    if {[catch {{*}$error "$out"} outt] != 0} {
      error "frECF: Calling $error: $outt"
    }
  } else {
    if {$post != {}} {
      catch {{*}$post out} hun
      # frputs hun ::errorInfo
    }
  }
  if {$r == 0} {
    return [list $r $out]
  }
  return [list $r $out $newcmd]

}


# To keep the run time simple we set up some stuff at load time
namespace eval frECF {
  variable wslMap {}
  variable short 0
  variable MSW [expr {$::tcl_platform(platform) == "windows"}]
  variable default [expr {$MSW ? { %s} : { %n}}]
# set up the file name translations using the mounts info
# we set this up as a procedure so we can, if needed, redo it.
  if {!$MSW} {
    proc getWslMap {} {
      variable wslMap {}
      if {![file exists /proc/mounts]} {return}
      foreach mount [split [read [set fid [open /proc/mounts r]]] \n] {
	if {[string match {[a-zA-Z]:*} [set pos [string range $mount 0 1]]]} {
	  lappend wslMap [lindex $mount 1] $pos
	}
      }
      close $fid
    }
    getWslMap
  }
}
