/*This line lets emacs recognize this as -*- C -*- Code
 *-----------------------------------------------------------------------------
 *
 * Project:	Tcl Modules
 * Created:	91/10/23
 * Author:	John L. Furlani<john.furlani@East.Sun.COM>
 *
 * Description:
 *      The path manipulation routines.  Much of the heart of Modules is
 *  contained in this file.  These routines are responsible for adding and
 *  removing directories from given PATH-like variables.
 *	
 * $Log: cmdPath.c,v $
 *
 *
 * Revision 1.4  1993/01/29  06:16:26  jlf
 * Modified the DISPLAY output to align spacing.
 *
 * Revision 1.3  1993/01/25  18:48:13  jlf
 * Changed the method for keeping and unwinding failed modulefiles.
 * The output returns to its optimized state where only the aggregate
 * changes to the environment are sent to the executing shell.
 *
 * Revision 1.2  1993/01/23  01:01:23  jlf
 * Fixed a number of memory leaks and large static arrays.
 *
 * Revision 1.1  1992/11/05  23:35:40  jlf
 * Initial revision
 *
 *---------------------------------------------------------------------------*/
static char Id[] =
    "$Id: cmdPath.c,v 2.0 1993/02/21 00:00:53 jlf Exp jlf $";

#include <regexp.h>
#include "global.h"

int
cmdSetPath(ClientData client_data,
	       Tcl_Interp* interp,
	       int argc,
	       char* argv[])
{
  regexp* chkexpPtr;
  regexp* midexpPtr;
  regexp* endexpPtr;
  char*   oldpath;
  char*   newpath;
  char*   sw_marker = APP_SW_MARKER;  /* arbitrary default */

  if(argc != 3) {
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
		     " path-variable directory\"", (char *) NULL);
    return TCL_ERROR;
  }

  if(flags & M_REMOVE) { 
    return cmdRemovePath(client_data, interp, argc, argv);
  }

  if(!strncmp(argv[0], "pre", 3)) {
      sw_marker = PRE_SW_MARKER;
  }
  
  if(flags & M_DISPLAY) {
    if(!strncmp(argv[0], "pre", 3)) {
      fprintf(stderr, "Prepend %s to %s\n", argv[2], argv[1]);
    } else {
      fprintf(stderr, "Append  %s to %s\n", argv[2], argv[1]);
    }
    return TCL_OK;
  }
  
  oldpath = Tcl_GetVar2(interp, "env", argv[1], TCL_GLOBAL_ONLY);

  if(oldpath == NULL)
    oldpath = chk_nullvars(argv[1]);
  
  /* 
    Check to see if path is already in this path variable.

     It could be at the 
        beginning ... ^path:
	middle    ... :path:
	end       ... :path$
	only one  ... ^path$
   */
  newpath = (char*) malloc(4*(strlen(argv[2]) + 5));
  sprintf(newpath, "(^%s:)|(:%s:)|(:%s$)|(^%s$)", 
	  argv[2], argv[2], argv[2], argv[2]);
  chkexpPtr = regcomp(newpath);
  free(newpath);

  if(regexec(chkexpPtr, oldpath)) {
      free((char*)chkexpPtr);
      return TCL_OK;
  }
  free((char*)chkexpPtr);

  /*
    Some space for our newly created path.
    We size at the oldpath plus the addition.
    */
  if((newpath = (char*) malloc(strlen(oldpath) 
			       + strlen(argv[2]) + 2)) == NULL) {
    Tcl_AppendResult(interp, "Couldn't malloc space for newpath.",
		     (char *) NULL);
    return TCL_ERROR;
  }

  strcpy(newpath, oldpath);

  if(! strcmp(oldpath, "")) {
    sprintf(newpath, "%s", argv[2]);
  } else {
    regexp* markexpPtr = regcomp(sw_marker);

    if(regexec(markexpPtr, oldpath)) {
      if(!strncmp(argv[0], "pre", 3)) { /* Prepend to the path */
	char ch = *markexpPtr->endp[0];
	*markexpPtr->endp[0] = '\0';
	sprintf(newpath, "%s:%s", oldpath, argv[2]);
	*markexpPtr->endp[0] = ch;
	strcat(newpath, markexpPtr->endp[0]);
      } else {
	char ch = *markexpPtr->startp[0];
	*markexpPtr->startp[0] = '\0';
	sprintf(newpath, "%s%s:", oldpath, argv[2]);
	*markexpPtr->startp[0] = ch;
	strcat(newpath, markexpPtr->startp[0]);
      }
    } else {
      if(!strncmp(argv[0], "pre", 3)) { /* Prepend to the path */
	sprintf(newpath, "%s:%s", argv[2], oldpath);
      } else {
	sprintf(newpath, "%s:%s", oldpath, argv[2]);
      }
    }
    free((char*)markexpPtr);
  }

  moduleSetenv(interp, argv[1], newpath, 1);
  
  free(newpath);
  return TCL_OK;
}


int
cmdRemovePath(ClientData client_data,
	      Tcl_Interp* interp,
	      int argc,
	      char* argv[])
{
  char* oldpath;
  char* tmppath;
  char  ch;
  int   start_offset, end_offset;
  regexp* regexpPtr  = (regexp*) 0;
  regexp* begexpPtr  = (regexp*) 0;
  regexp* midexpPtr  = (regexp*) 0;
  regexp* endexpPtr  = (regexp*) 0;
  regexp* onlyexpPtr = (regexp*) 0;
  regexp* markexpPtr = (regexp*) 0;
  char*   searchpath;
  char*   sw_marker = APP_SW_MARKER;  /* arbitrary default */

  if(argc > 3) {
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
		     " path-variable directory\"", (char *) NULL);
    return TCL_ERROR;
  }

  if(flags & M_DISPLAY) {
    fprintf(stderr, "Removes %s from %s\n", argv[2], argv[1]);
    return TCL_OK;
  }

  if(!strncmp(argv[0], "pre", 3)) {
      sw_marker = PRE_SW_MARKER;
  }

  tmppath = Tcl_GetVar2(interp, "env", argv[1], TCL_GLOBAL_ONLY);
  if(tmppath == NULL) {
    Tcl_AppendResult(interp, "Tcl_GetVar2 of \"env\" returned null -- ", argv[1], 
		     " variable not found", (char *) NULL);
    return TCL_OK;
  }    

  /*
    We want to make a local copy of old path because we're going
    to be butchering it and the environ one doesn't need to be
    changed in this manner.
    */
  oldpath = (char*) malloc(strlen(tmppath) + 1);
  strcpy(oldpath, tmppath);

  /*
    Now we need to find it in the path variable.  So, we create
    space enough to build search expressions.
    */
  searchpath = (char*)  malloc(strlen(argv[2]) + 3);

  /*
    For state3, we're looking to remove the marker.
    */
  if(flags & M_SWSTATE3) {
    argv[2] = sw_marker;
  }

  /*
    This section searches for the oldpath in the PATH variable.
    There are four cases because the colons must be used as markers
    in order to guarantee that a partial match isn't being found.

    The start_offset and end_offset variables indicate how much to 
    cut or not to cut off of each end of the located
    string.  They differ for each case...beginning of
    the path, middle of the path, end of the path, 
    and for the only path.

    This is to ensure the colons get cut off properly.
    */
  sprintf(searchpath, "^%s:", argv[2]);
  begexpPtr = regcomp(searchpath);
  if(regexec(begexpPtr, oldpath)) {
      regexpPtr = begexpPtr;

      /*
	Copy from the beginning of the stringp[0] to one
	character before endp[0]
	*/
      start_offset = 0; end_offset = -1;
    } else {
      sprintf(searchpath, ":%s:", argv[2]);
      midexpPtr = regcomp(searchpath);
      if(regexec(midexpPtr, oldpath)) {
	regexpPtr = midexpPtr;

	/*
	  Copy from one character after stringp[0] to one
	  character before endp[0]
	*/
	start_offset = 1; end_offset = -1;
      } else {
	sprintf(searchpath, ":%s$", argv[2]);
	endexpPtr = regcomp(searchpath);
	if(regexec(endexpPtr, oldpath)) {
	  regexpPtr = endexpPtr;

	  /*
	    Copy from one character after stringp[0] 
	    through endp[0]
	    */
	  start_offset = 0; end_offset = 0;
	} else {
	  sprintf(searchpath, "^%s$", argv[2]);
	  onlyexpPtr = regcomp(searchpath);
	  if(regexec(onlyexpPtr, oldpath)) {
              /*
               *  In this case, I should go ahead and unset the variable from
               *  the environment because I'm removing the very last path.
               *
               *  First I'm going to clear the variable from the
               *  setenvHashTable just in case it has already been altered and
               *  had a significant value at the time.  It's very possible
               *  that I'm removing the only two or three paths from this
               *  variable.  If that's the case, then all the earlier paths
               *  were marked for output in this hashTable.
               *
               *  Secondly, I actually mark the the environment variable to be
               *  unset when output.
               */
              clear_shell_variable(setenvHashTable, argv[1]);
              moduleUnsetenv(interp, argv[1]);

              /*
               *  moduleUnsetenv doesn't unset the variable in the Tcl
               *  space because the $env variable might need to be
               *  used again in the modulefile for locating other
               *  paths.  BUT, since this was a path-type environment
               *  variable, the user is expecting this to be empty
               *  after removing the only remaining path.  So, I set
               *  the variable empty here.
               */
              (void) Tcl_SetVar2(interp, "env", argv[1], "", TCL_GLOBAL_ONLY);
              free(searchpath);
              free(oldpath);
              free((char*)onlyexpPtr);
              free((char*)begexpPtr);
              free((char*)midexpPtr);
              free((char*)endexpPtr);
              return TCL_OK;
	  }
          free((char*)onlyexpPtr);
	}
      }
    }

  free(searchpath);

  /*
    If I couldn't find it assume it wasn't there
    and return.
    */
  if(! regexpPtr) {
      free((char*)begexpPtr);
      free((char*)midexpPtr);
      free((char*)endexpPtr);
      free(oldpath);
      return TCL_OK;
  }

  markexpPtr = regcomp(sw_marker);

  /*
    In state1, we're actually replacing old paths with
    the markers for future appends and prepends.

    We only want to do this once to mark the location
    the module was formed around.
    */
  if((flags & M_SWSTATE1) && ! regexec(markexpPtr, oldpath)) {
    /*
      If I don't have enough space to replace the oldpath with the
      marker, then I must create space for the marker and thus
      recreate the PATH variable.
      */
      char* newenv;
      
      newenv = (char*) malloc(((regexpPtr->startp[0] + start_offset) -
			       oldpath) +
			      strlen(sw_marker) +
			      strlen(argv[1]) +
			      strlen(regexpPtr->endp[0]) + end_offset + 1);

      *(regexpPtr->startp[0] + start_offset) = '\0';

      if(*(regexpPtr->endp[0] + end_offset) == '\0')
          sprintf(newenv, "%s:%s", 
                  oldpath, sw_marker);
      else
          sprintf(newenv, "%s%s%s", 
                  oldpath, sw_marker, regexpPtr->endp[0] + end_offset);

      /*
       *  Just store the value with the marker into the environment.  I'm not
       *  checking to see if it changed because the only case that a PATH-type
       *  variable will be unset is when I remove the only path remaining in
       *  the variable.  So, using moduleSetenv is not necessary here.
       */
      (void) Tcl_SetVar2(interp, "env", argv[1], newenv, TCL_GLOBAL_ONLY);
      free(newenv);
  } else {
    /*
      We must be in SW_STATE3 or not in SW_STATE at all.
      Removing the marker should be just like removing any other path.
      */
    strcpy(oldpath + ((regexpPtr->startp[0] + start_offset) - oldpath), 
	   oldpath + ((regexpPtr->endp[0]) - oldpath));

    /*
     *  Cache the set.  Clear the variable from the unset table just
     *  in case it was previously unset.
     */
    store_shell_variable(setenvHashTable, argv[1], oldpath);
    clear_shell_variable(unsetenvHashTable, argv[1]);

    /*
     *  Store the new PATH value into the environment.
     */
    (void) Tcl_SetVar2(interp, "env", argv[1], oldpath, TCL_GLOBAL_ONLY);
  }

  free((char*)begexpPtr);
  free((char*)midexpPtr);
  free((char*)endexpPtr);
  free((char*)markexpPtr);
  free(oldpath);
  return TCL_OK;
}

char*
chk_nullvars(char* varname)
{
  if(!strcmp(varname, "MANPATH"))
    return "/usr/man";

  return "";
}
