#
# <20200209.1509.05>
#
# This is an expansion on auto_execok for windows.  It takes advantage of
# the possibility that there are some *nix utilities that might be
# the right thing to do. For the most part this is an internal routine to
# be used by 'fixMSWcommand'.
#
#
# There are three possible returns here:
# 1) nil, could not find an executable
# 2) Full path to executable
# 3) Full path to an executable that needs the original file inserted 
#    this version returns a value containing "%s" and is a list of two
#    values, the execute part and the modified file that we started from.
#    These will need to be split by the caller for passage to frECF
#  
# In general, we assume that the path to executables may be *nix style
# and others need to be native, we do not attempt the insertion here
# but leave it to the caller.
#
# Return Notation:
# We return (in general) a string and a list. The string is intended to be
# passed to exec with each %s filled in with the next entry from the list.
# We do not do that here as MSW wants parameters (which the list contains) to
# be in 'native' notation if they are file names. We leave the work here to
# frECF which will sort out all of this. So, in general, 

# All of this is windows only.  If by some odd mistake it get loaded
# else where well, just don't let it do anything.

if {$tcl_platform(platform) != "windows"} {
  # This should terminate the "source" command and thus not load the following
  return
}
load_now_auto_execok

namespace eval auto_execNS {
  variable auto_exts
}

# Here is a helper routine to check if a entry is a file name.
#
proc ::auto_execNS::ifFile {name} {
  frputs name
  # the issue here is whether we have a file name here or simple parameter
  # This depends on where it came from. If it is a built-in etc.
  # We use these rules:
  # It is a file if:
  # if it starts with '[a-z]:' or '//'
  # if it contains a backslash
  # if it has a forward slash after the first char
  # if it has more than one "/" its a file
  # if [file exists ..] says it is a file
  # if it is a file or dir at the root of the current volume
  # With these rules "switches" will not be touched but we
  # may pass files as non-files
  expr {
	[string match -nocase "\[a-z\]:/*" $name] ||\
	    [string match {*\\*} $name] ||\
	    [string range $name 0 1] == "//" || \
	    [set tm [string first "/" $name]] > 0 ||\
	    ($tm != -1 && [string last "/" $name] != $tm) ||\
	    [file exists $name] ||\
	    [file exists [string range [pwd] 0 1]$name]
      }
}

# Another helper... this one handles the parameter parts
# These come up in some 'auto_execok' responces as well as
# in the parameter part of the call. 'parms' is a list...

# Each element in 'parms' is tested to see if it is a file
# (using the above 'ifFile' routine). If it is a file,
# " %s" is appended to ncmd and the file is lappended to fileList.
# If it is not a file, it is tested to see if it has wild
# card patterns. If so they are expanded and each resulting
# file is added to the list (as are files as defined above).
# If both thes tests fail the element is appended to 'ncmd'.

# This routine is local and not really useful for others so
# we put it in our namespace.
proc ::auto_execNS::doParms {parms} {
  upvar ncmd ncmd
  upvar fileList fileList
  foreach part $parms {
    if {[string match "*\\**" $part] ||\
	    [string match "*\\?*" $part] ||\
	    [regexp {.*\[.*\].*} $part] ||\
	    [regexp {.*\{.*,+.*\}.*} $part] } {
      frputs part ncmd
      # We found a pattern... carefully normalize and do the glob thing
      # 'file join' does not change relative paths to absolute...
      # but does flip the back slashes
      set gfile [lsort [glob -nocomplain -- [file join $part]]]
      append ncmd [string repeat " %s" [llength $gfile]]
      lappend fileList {*}$gfile
    } elseif {[ifFile $part]} {
      append ncmd " %s"
      lappend fileList [file join $part]
    } else {
      append ncmd " $part"
    }
  }
}


# This routine fixes up a command line for MS windows.
# If possible we avoid cmd as it tends to not properly
# set pipes up and we loose the output, usually to a
# flash window. This is where we resolve pipe line stuff.
#
# What we do is to tear the command apart a parameter at a time
# We assume that the line starts with a command so we use the
# above "windowsAutoExecOk" code to flesh it out.
# The other parameters we look at and if they look like
# filenames (testing tries to include the possibility that one
# of them may be an output file and not yet exist) we set up
# a list of such, replacing the name in the string with
# a %s (for frECF).  We then call frECF to put the whole
# command together with the names in native mode. Accepted 'args'
# are the same as frECF accepts. (In fact they are passed to
# frECF.
#
# Heck, we even do glob name expansion....
# So, parameters:
#
# cmd    is the command line. It may have %s places in it to
#        accept filename (or what ever) from:
# parms  a list of file names (or what ever) to combine into
#        the command at the %s locations (or the end). This is
#        combined with filenames generated from the command
#        tear apart in a (hopefully) intelligent manner.
# args   this is passed as 'args' to frECF (see there for
#        what they mean.
#
proc fixMSWcommand {cmd parms args} {
  set ncmd {}
  set fileList {}
  incr eos -1
  # Tried doing this as a list but, well it was just to messy...
  # so we use a more forgiving parser...
  set nextIsCmd 1
  frputs cmd args
  # To make things easy here, lets make sure the "pipe" control
  # characters have white space around them.
  # These characters are:
  # set ctlChars [list |& | << < 2>@1 2>@ 2>> 2> >>& >> >&@ >& >@ > &]
  # To make this a bit easier lets just define the specials
  set spCtlChars [list & | < > @ 2 1 &]
  # and we will do a little magic with the 1 & 2 digits
  set ws $cmd
  set cmd {}
  set ln [string length $ws]
  set last " "
  set lastWasSpec 0
  set indx -1
  while {[incr indx] < $ln} {
    set thisSpec [expr {[set this [string index $ws $indx]] in $spCtlChars}]
    if {$thisSpec && !$lastWasSpec} {
      # a '1' is special only if it follows another special
      if {$last != " " && $this != "1"} {
	append cmd " "
	incr lastWasSpec
      }
    } elseif {!$thisSpec && $lastWasSpec} {
      if {$last == "2"} {
	# false positive on 2 file2<special> becomes file 2<special>
	# but file2<nonspecial> becomes file2<nonspecial> i.e. no change
	set cmd [string range $cmd 0 end-2]
	append cmd $last
      } elseif {$this != " "} {
	append cmd " "
      }
      set lastWasSpec 0
    } elseif {$thisSpec && $lastWasSpec && "$last$this" == "21"} {
      # Don't you love special cases?
      set cmd [string range $cmd 0 end-2]
      append cmd 2
      set lastWasSpec 0
    }
    append cmd $this
    set last $this
  }
  # End of line clean up...
  if {$last == "2"} {
    set cmd [string replace $cmd end-1 end-1]
  }
  frputs ws cmd
  set last -1    
  set eos [string length $cmd]
  while {
	 [set start [incr last]] < 0 ||\
	     [set last [stringGetToken $cmd start]] >= $start} {
    set part [string range $cmd $start $last]
    frputs cmd fileList part ncmd nextIsCmd
    if {[string is space [string range $cmd $last+1 end]] &&\
	    $last > $start &&\
	    [string index $part end] == "&"} {
      string replace $cmd end end " &"
      set part [lindex [string range $part 0 end-1]]
      incr last -1
    }
    set part [lindex $part 0]
    if {$nextIsCmd} {
      if {$ncmd == {} && $part == "exec"} {
	# command follows initial 'exec'
	set ncmd "exec"
	continue
      }
      set nextIsCmd 0
      if {[set try [auto_execok $part]] != {}} {
	set rem [lassign $try cm]
	append ncmd  " [list $cm]"
	# regsub {%s} $ncmd {} ncmd
	::auto_execNS::doParms $rem
	frputs part try cm ncmd rem
	continue
      } else {
	# could it be a tcl command (must be the first thing we find)?
	if {$ncmd == {} && [info commands $part] != {}} {
	  append ncmd "$part "
	  continue
	}
      }	
      error "Command \"$part\" not found."
    }
    # Not a command. 
    set ch [string index $part 0]
    if {$ch == "|"} {
      # looks like a pipe connector
      incr nextIsCmd
      append ncmd " $ch"
      if {[string length $part] > 1} {
	string replace $cmd $start+1 end " [string range $cmd $start+1 end]"
	set last $start
      }
      continue
    }
    if {$part == "%s"} {
      set parms [lassign $parms fi]
      lappend fileList $fi
      append ncmd " %s"
      continue
    }
    # a parameter in the command, check if it is a file... or expansion
    ::auto_execNS::doParms $part
  }
  # The command is done. now do the parms...
  ::auto_execNS::doParms $parms

  # Now put it all together with frECF
  frputs ncmd fileList parms args
  return [frECF $ncmd $fileList [concat {*}$args]]
}

