/*************************************************************************
** ^FILE: parseargs.c - command line interface to parseargs()
**
** ^DESCRIPTION:
**    This file implements the command-line interface to the parseargs
**    library. Under Unix, the user may use parseargs(1) (this program)
**    to parse command-line arguments for shell scripts.  At the present,
**    time, VMS/DCL is not yet supported (nor are MS-DOS Batch-files).
**
**    Given a command name, a vector of string-valued arguments such as that
**    passed to a shell script, and a specification string describing the
**    possible arguments, parseargs matches actual arguments to possible
**    arguments, converts values to the desired type, and diagnoses problems
**    such as missing arguments, extra arguments, and argument values that
**    are syntactically incorrect.  Other behavior such as prompting the
**    user for missing arguments and ignoring as command-line syntax may be
**    specified on the command-line through the use of various options, or
**    through the use of the "PARSECNTL" environment variable.
**
**    Given the command name and the argument specification string,
**       parsearg -U
**    prints a reasonably friendly version of the usage of the
**    calling program on standard diagnostic output. The "verbosity" of
**    the usage message may be controlled through the use of the
**    "USAGECNTL" environment variable.
**
**    Given the command name and the argument specification string,
**       parsearg -M
**    prints a template of the command-syntax on standard output that is
**    suitable for input to nroff or troff using the -man macro package.
**
** ^SEE_ALSO:
**    argtype(3), parseargs(1), parseargs(3), parsecntl(3),
**    parseargs.pl, parseargs.awk
**    test.sh, test.csh, test.ksh, test.rc, test.awk, test.pl
**
** ^BUGS:
**    It does not make sense to use any arguments of type argTBool since
**    parseargs currently has no way of knowing what the initial value of
**    the variable is. For this reason, argTBool is not recognized as a
**    valid argument type (even though it is used by parseargs(3)). By the
**    same token, since the user cannot create their own arguments types on
**    the fly from a shell-script, ARGNOVAL is not recognized as a valid
**    argument flag.
**
**    Commas will not be interpreted properly if any field in the argument
**    specification string contains double quotes that are nested inside of
**    double quotes, or single quotes that are nested inside of single quotes.
**
**    Inside the argument specification string, any repeated string of
**    commas that does not appear inside of double or single quotes will
**    be treated as a single comma.
**
**    Text descriptions for argument entries are automatically formatted in
**    usage messages. Any attempt by the user to include tabs and/or newlines
**    in the description will cause it to be formatted improperly.
**
**    Parseargs cannot properly preserve any newlines in shell variables if
**    the eval command is used to read its output (this is a shortcoming of
**    the eval command, not of parseargs). If the user is concerned about
**    this particular case, then the user should redirect the output from
**    parseargs to a temporary file and use the source command in csh or the
**    dot command (`.') in sh and ksh, to interpret the results; otherwise,
**    newlines will be translated into spaces, or characters following a
**    newline may be lost, in any variables that are set by parseargs.
**
**    Parseargs(1) is subject to the same caveats as parseargs(3).
**    Refer to the CAVEATS section of the parseargs(3) for more information.
**
** ^HISTORY:
**    07/18/90 	Brad Appleton 	<brad@ssd.csd.harris.com> 	Created
***^^**********************************************************************/

#include <fcntl.h>
#include <stdio.h>
#include <useful.h>
#include "strfuncs.h"

#define PARSEARGS_PRIVATE   /* include private definitions */
#include "parseargs.h"

/*************************************************************************
** ^SECTION: RETURN-CODES
**
** Parseargs may return any of the following status-codes:
*/
#define  e_SYSTEM   -1
/*    -- A system error occurred
*/
#define  e_SUCCESS   0
/*    -- No errors, success!!
*/
#define  e_USAGE     1
/*    -- No errors were encountered. A usage-message (or manual-page-template)
**       was explicitly requested (and printed) by the user.
*/
#define  e_SYNTAX    2
/*    -- A syntax error was encountered on the command-line. The error may
**       be in the argument(s) intended for parseargs(1) or in the argument(s)
**       for the invoking shell-script.
*/
#define  e_NOENV     3
/*    -- The user specified that the argument description was to be found in
**       an environment variable, however the environment variable in question
**       is unset or empty.
*/
#define  e_ARGD      4
/*    -- An error was encountered in the string that describes the arguments
**       for the given command.
*/
/**^^**********************************************************************/

   /* default shell variable values for a boolean argument */
static CONST char  Default_StrTrue[]  = "TRUE";
static CONST char  Default_StrFalse[] = "";

      /* define character sets */
static CONST char  WhiteSpace[] = " \t\n\v\r\f\"'";
static CONST char  ArgTableDelims[] = ",";
static CONST char  ArgFlagsDelims[] = "|+ \t\n\v\r\f";

   /* macros to improve readability of string tests */
#define strEQ(s1,s2)     !strcmp(s1, s2)
#define strnEQ(s1,s2,n)  !strncmp(s1, s2, n)

#define  BUFFER_SIZE  1024       /* start off with 1k buffer & resize it */
#define  ESCAPED_COMMA  '\001'   /* character to replace commas with */

      /* determine the beginning and end of a struct */
#define  c_BEGIN_STRUCT  '{'
#define  c_END_STRUCT    '}'

      /* determine beginning-of-arg-table string */
#define  s_START_ARGS    "STARTOFARGS"
#define  isSTART_ARGS(s)  strnEQ(s, s_START_ARGS, 5)

      /* determine end-of-arg-table string */
#define  s_END_ARGS    "ENDOFARGS"
#define  isEND_ARGS(s)  strnEQ(s, s_END_ARGS, 3)

      /* define #of entries per arg-descriptor */
#define  NFIELDS  5


/**************************************************************************
** ^SECTION: SHELLS
**    After the command line has been parsed, parseargs will print on stan-
**    dard output, a script to set the shell variables which correspond to
**    arguments that were present on the command-line.  This script may be
**    evaluated by redirecting it to a file and then executing the file,
**    or by directly evaluating the output from parseargs (under most UNIX
**    shells, this could be done using eval).  If any arguments on the com-
**    mand line contained any special characters that needed to be escaped
**    from the shell, these characters will remain intact (not be evaluated
**    by the shell) in the corresponding shell variable.
**
**    The -s shell option may be used to tell parseargs which shell syntax
**    to use. At present, parseargs only recognizes "sh", "csh", "ksh",
**    "tcsh", "bash", "rc", "awk", and "perl" as valid command interpreters.
**    Awk output is slightly different from that of the other shells in that
**    the actual variable setting are not printed but each line of an
**    associative array is printed (the first field is the array index, the
**    second is the value for that index).  If no shell is specified, then
**    the Bourne shell ("sh") will be assumed.
**
**    If the user wishes to use a value other than "TRUE" for a boolean
**    flag that is true, this may be done using the "-T string" option.
**    The same may also be done for a boolean flag that is false using the
**    "-F string" option.
**
**    Parseargs will only set the values of variables that correspond to
**    arguments that were given on the command line. If a particular argu-
**    ment was not supplied on the command line, then no assignment is made
**    for the corresponding shell variable and it will have the same value
**    that it had before parseargs was invoked. The only exception to this
**    is that if the -u option is specified, then the positional parameters
**    are unset before any shell variable assignments (which may reset the
**    positional parameters) are made.
***^^*********************************************************************/

      /* #defines for possible shell names and corresponding types */
typedef short  shell_t;
#define  BASH  ((shell_t) 0)
#define  TCSH  ((shell_t) 1)
#define  CSH   ((shell_t) 2)
#define  KSH   ((shell_t) 3)
#define  SH    ((shell_t) 4)
#define  RC    ((shell_t) 5)
#define  AWK   ((shell_t) 6)
#define  PERL  ((shell_t) 7)

#define  BOURNE_AGAIN_SHELL  "bash"
#define  BOURNE_SHELL        "sh"
#define  KORN_SHELL          "ksh"
#define  C_SHELL             "csh"
#define  RC_SHELL            "rc"
#define  TC_SHELL            "tcsh"
#define  AWK_LANG            "awk"
#define  PERL_LANG           "perl"

   /* structure for shell-specific info */
typedef struct {
   char  *varname;   /* name of variable containing the positional parameters */
   char  *setcmd;    /* formatted string (%s is replaced with variable name) */
   char  *prefix;    /* beginning for variable setting */
   char  *suffix;    /* ending for variable setting */
   char  *escape;    /* format to escape chars (%c is the char to escape) */
   char  *metachars; /* special characters that need to be escaped */
} shell_info;

   /* array of shell info - indexed by the #define for the shell type */
static CONST shell_info  Shell[] = {

   /* BASH : Positional parms in -- ; Assignment Syntax: name="value"; */
   { "--", "%s=",   "'",   "';\n",   "'\\%c'",   "'" },

   /* TCSH : Positional parms in argv ; Assignment Syntax: set name="value"; */
   { "argv", "set %s=", "'",   "';\n",  "'\\%c'",   "'" },

   /* CSH : Positional parms in argv ; Assignment Syntax: set name="value"; */
   { "argv", "set %s=", "'",   "';\n",  "'\\%c'",   "'" },

   /* KSH : Positional parms in -- ; Assignment Syntax: name="value"; */
   { "--", "%s=",       "'",   "';\n",  "'\\%c'",   "'" },

   /* SH : Positional parms in -- ; Assignment Syntax: name="value"; */
   { "--", "%s=",       "'",   "';\n",  "'\\%c'",   "'" },

   /* RC : Positional parms in -- ; Assignment Syntax: name="value"; */
   { "*", "%s=",       "'",   "';\n",  "''",   "'" },

   /* AWK : Positional parms in ARGV; Assignment Syntax: name\nvalue\n\n; */
   { "ARGV", "%s\n",    "",   "\n\n",  "'\034'",   "\n" },

   /* PERL : Positional parms in ARGV; Assignment Syntax: $name=value\n; */
   { "ARGV", "$%s = ",  "",   ";\n",  "'.\"%c\".'",   "'" }
};
/*************************************************************************/

   /* define all current arg-vector types */
typedef ARGVEC_T(char *)  strvec_t;
typedef ARGVEC_T(char)    charvec_t;
typedef ARGVEC_T(int)     intvec_t;
typedef ARGVEC_T(short)   shortvec_t;
typedef ARGVEC_T(long)    longvec_t;
typedef ARGVEC_T(float)   floatvec_t;
typedef ARGVEC_T(double)  doublevec_t;
typedef ARGVEC_T(VOID)    genericvec_t;  /* generic vector */

      /* union to hold all possibles values of an argument */
typedef union {
   BOOL    Bool_val;
   short   Short_val;
   int     Int_val;
   long    Long_val;
   float   Float_val;
   double  Double_val;
   char    Char_val;
   char   *Str_val;
   strvec_t      Str_vec;
   charvec_t     Char_vec;
   intvec_t      Int_vec;
   shortvec_t    Short_vec;
   longvec_t     Long_vec;
   floatvec_t    Float_vec;
   doublevec_t   Double_vec;
   genericvec_t  Vector;
} storage_t;

   /* structure to hold a command-line argument name, value, and fmt-string */
typedef struct {
   CONST char *name;   /* name of shell variable to use */
   storage_t   value;  /* storage for value of argument */
} cmdarg_t;
#define CMDARGNULL (cmdarg_t *)NULL

EXTERN  int   eprintf   ARGS((const char *, ...));
EXTERN  VOID  syserr    ARGS((const char *, ...));
EXTERN  VOID  usrerr    ARGS((const char *, ...));
EXTERN  char *getenv    ARGS((const char *));
EXTERN  VOID  manpage   ARGS((const ARGDESC *));
EXTERN  VOID  perror    ARGS((const char *));

extern  int  errno;  /* system wide error level */

/*************************************************************************/
   /*
   ** variables that are set via command-line arguments
   */
static  char   *Cmd_Name;  /* name of this program */

static  ARGDESC   *UsrArgd = ARGDESCNULL;       /* users arg-table */
static  cmdarg_t  *UsrVals = CMDARGNULL;        /* variable names & values */
static  int        UsrArgc = 0;                 /* # of arg-table entries */
static  shell_t    UsrSh;                       /* shell indicator */
static  BOOL       UseStdin = TRUE;             /* read argd from stdin */

static  char   *ShellName  = CHARNULL;  /* name of user's shell */
static  char   *UsrName    = CHARNULL;  /* name of users program */
static  char   *FieldSep   = " ";       /* field-separators for arrays */
static  strvec_t UsrArgv   = ARGVEC_EMPTY(char *);   /* users args */
static  char   *ArgdString = CHARNULL;  /* argd string (with WhiteSpace) */
static  char   *ArgdEnv    = CHARNULL;  /* environment variable for argd */
static  char   *ArgdFname  = CHARNULL;  /* argd input file */
static  BOOL    Unset      = FALSE;     /* ?unset positional parms? */
static  char   *StrTrue    = CHARNULL;  /* string for TRUE values */
static  char   *StrFalse   = CHARNULL;  /* string for FALSE values */
static  char    OptsOnly   = FALSE;     /* parse options only? */
static  char    KwdsOnly   = FALSE;     /* parse keywords only? */
static  BOOL    ModArr     = FALSE;     /* modify array behavior */
static  BOOL    PrUsage    = FALSE;     /* ?just print usage? */
static  BOOL    PrManual   = FALSE;     /* ?just print manual page(s)? */
static  BOOL    Prompt     = FALSE;     /* ?prompt for missing args? */
static  BOOL    Ignore     = FALSE;     /* ?ignore bad syntax and continue? */

/*************************************************************************/
   /* now we are ready to define the command-line */
static
CMD_OBJECT
   Args

CMD_NAME
   "parseargs  --  parse command-line arguments in shell scripts"

CMD_DESCRIPTION
   "Given a description of the command-line and the command-line arguments, \
parseargs will parse all command-line arguments, convert them to their \
desired type, and print on standard output, a script of all the resulting \
shell assignment statements."

CMD_ARGUMENTS
   'U', ARGOPT,  argBool,  __ &PrUsage,
   "usage : just print program usage, dont parse command-line",

   'M', ARGOPT,  argBool,  __ &PrManual,
   "man1 : just print man1 template, dont parse command-line",

   'T', ARGOPT,  argStr,  __ &StrTrue,
   "TRUEstr : string to use for TRUE Booleans   (default=\"TRUE\")",

   'F', ARGOPT,  argStr,  __ &StrFalse,
   "FALSEstr : string to use for FALSE Booleans  (default=\"\")",

   'A', ARGOPT,  argBool,  __ &ModArr,
   "array : modify the behavior of arrays",

   'S', ARGOPT,  argStr,  __ &FieldSep,
   "SEParator : field-separator-string used to delimit array elements \
(default=\" \")",

   'a', ARGOPT,  argStr,  __ &ArgdString,
   "ARGSpec : argument specification string",

   'e', ARGOPT,  argStr,  __ &ArgdEnv,
   "ENVarname : environment variable containing arg-spec",

   'f', ARGOPT,  argStr,  __ &ArgdFname,
   "FILEname : read the arg-spec from <filename> (default=stdin)",

   'l', ARGOPT,  argBool,  __ &KwdsOnly,
   "Long-OPTionS : long-options only - do not parse options",

   'o', ARGOPT,  argBool,  __ &OptsOnly,
   "OPTionS : options only - do not parse long-options",

   's', ARGOPT,  argStr,  __ &ShellName,
   "SHell : use <shell> command syntax        (default=\"sh\")",

   'u', ARGOPT,  argBool,  __ &Unset,
   "unset : unset positional parameters before parsing",

   'p', ARGOPT,  argBool,  __ &Prompt,
   "prompt : prompt the user for missing required arguments",

   'i', ARGOPT,  argBool,  __ &Ignore,
   "ignore : ignore bad command-line syntax and continue processing \
(instead of aborting)",

   '-', ARGOPT,  argDummy, __ NULL,
   "+ : end of options - all remaining arguments are interpreted as \
positional parameters (even if one begins with '-' or '+')",

   ' ', ARGREQ,  argStr, __ &UsrName,
   "name : name of calling program",

   ' ', ARGOPT|ARGVEC,  argStr,  __ &UsrArgv,
   "arguments : arguments to calling program",

   END_ARGUMENTS

CMD_END


/***************************************************************************
** ^FUNCTION: cleanup - deallocate all global storage
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static  VOID cleanup()
#endif  /* !__ANSI_C__ */
/*
** ^PARAMETERS:
**    None.
**
** ^DESCRIPTION:
**    Cleanup is used to deallocate any global storage. It is called
**    before exiting.
**
** ^REQUIREMENTS:
**    None.
**
** ^SIDE-EFECTS:
**    Storage associated with all dynamically allocated global-variables
**    is released and set to NULL.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    Trivial.
***^^**********************************************************************/
#ifdef __ANSI_C__
   static void cleanup( void )
#endif
{
   register ARGDESC *ad;
   register storage_t val;
   
   /* free up any vectors from the command-line */
   for ( ad = ARG_FIRST(UsrArgd) ; !ARG_isEND(ad) ; ARG_ADVANCE(ad) ) {
      if ( ! BTEST(arg_flags(ad), ARGVEC) )  continue;

      val = *((storage_t *) arg_valp(ad));

      if      ( arg_type(ad) == argStr )     vecFree( val.Str_vec, char * );
      else if ( arg_type(ad) == argChar )    vecFree( val.Char_vec, char );
      else if ( arg_type(ad) == argInt )     vecFree( val.Int_vec, int );
      else if ( arg_type(ad) == argShort )   vecFree( val.Short_vec, short );
      else if ( arg_type(ad) == argLong )    vecFree( val.Long_vec, long );
      else if ( arg_type(ad) == argFloat )   vecFree( val.Float_vec, float );
      else if ( arg_type(ad) == argDouble )  vecFree( val.Double_vec, double );
   }

   /* free up tables */
   vecFree( UsrArgv, char * );
   if ( UsrArgd ) {
      free( UsrArgd );
      UsrArgd = ARGDESCNULL;
   }
   if ( UsrVals ) {
      free( UsrVals );
      UsrVals = CMDARGNULL;
   }
   if ( ArgdFname   &&   !ArgdEnv ) {
      free( ArgdString );
      ArgdString = CHARNULL;
   }
}


/***************************************************************************
** ^FUNCTION: ckalloc - allocate space, check for success
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static ARBPTR ckalloc( size )
/*
** ^PARAMETERS:
*/
   size_t  size;
/*    -- the number of bytes to allocate
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Ckalloc will use malloc to attempt to fill the given request 
**    for memory. If The request cannot be met than a message is
**    printed and execution is terminated.
**
** ^REQUIREMENTS:
**    size should be > 0
**
** ^SIDE-EFECTS:
**    Memory is allocated that should later be deallocated using free().
**
** ^RETURN-VALUE:
**    The address of the allocated region.
**
** ^ALGORITHM:
**    - Allocate space, check for success
***^^**********************************************************************/
#ifdef __ANSI_C__
   static ARBPTR ckalloc( size_t size )
#endif
{
   ARBPTR  ptr;

   ptr = malloc( size );
   if ( !ptr ) {
      eprintf( "%s: Fatal Error: out of memory!!", Cmd_Name );
      cleanup();
      if ( errno )  perror( Cmd_Name );
      exit( e_SYSTEM );
   }

   return  ptr;
}


/***************************************************************************
** ^FUNCTION: ckrealloc - reallocate space, check for success
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static ARBPTR ckrealloc( ptr, size )
/*
** ^PARAMETERS:
*/
   ARBPTR  ptr;
/*    -- address of the region to be expanded/shrunk
*/
   size_t  size;
/*    -- the number of bytes to allocate
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Ckrealloc will use realloc to attempt to fill the given request 
**    for memory. If The request cannot be met than a message is
**    printed and execution is terminated.
**
** ^REQUIREMENTS:
**    size should be > 0
**
** ^SIDE-EFECTS:
**    Memory is allocated that should later be deallocated using free().
**
** ^RETURN-VALUE:
**    The address of the (re)allocated region (which may have been moved).
**
** ^ALGORITHM:
**    - Reallocate space, check for success
***^^**********************************************************************/
#ifdef __ANSI_C__
   static ARBPTR ckrealloc( ARBPTR ptr, size_t size )
#endif
{
   ptr = realloc( ptr, (unsigned int)size );
   if ( !ptr ) {
      eprintf( "%s: Fatal Error: out of memory!!", Cmd_Name );
      cleanup();
      if ( errno )  perror( Cmd_Name );
      exit( e_SYSTEM );
   }

   return  ptr;
}


/***************************************************************************
** ^FUNCTION: escape_char - (re)map a character
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static VOID escape_char( str, ch, esc )
/*
** ^PARAMETERS:
*/
   char *str;
/*    -- the string to be translated
*/
   int ch;
/*    --  the character to be replaced/translated
*/
   int esc;
/*    -- the replacement character to use
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Escape_char will escape all occurences of a character by replacing
**    it with <esc> if the character appears in double or single quotes.
**
** ^REQUIREMENTS:
**    Both <ch> and <esc> should be non-zero.
**    <str> should be non-null and non-empty.
**
** ^SIDE-EFECTS:
**    Each occurrence in <str> of <ch> within single or double quotes is
**    replaced with <esc>.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    Trivial.
***^^**********************************************************************/
#ifdef __ANSI_C__
   static void escape_char( char *str, int ch, int esc )
#endif
{
   int   squoted = 0, dquoted = 0;

   for ( ; *str ; str++ ) {
      if ( *str == '\''  &&  !dquoted )
         squoted = ~squoted;
      else if ( *str == '"'  &&  !squoted )
         dquoted = ~dquoted;
      else if ( (squoted || dquoted)  &&  *str == ch )
         *str = esc;
   }
}


/***************************************************************************
** ^FUNCTION: restore_char - restore any chars escaped by escape_char()
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static VOID restore_char( str, ch, esc )
/*
** ^PARAMETERS:
*/
   char *str;
/*    -- the string to be translated
*/
   int ch;
/*    --  the character to be restored
*/
   int esc;
/*    -- the replacement character to use to escape the above character.
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Restore_char will attempt to undo the results of a previous call
**    to escape_char by replacing each occurence of <esc> in <str> with <ch>.
**
** ^REQUIREMENTS:
**    <str> should be the victim of a previous escape_char(str, ch, esc) call.
**    Furthermore, <esc> should be a character that occurs only as a result
**    of this call (it should be VERY uncommon).
**
**    It should be noted that escape_char() only replaces characters in quotes
**    whereas this routine replaces all occurrences.
**
** ^SIDE-EFECTS:
**    Each occurrence of <esc> in <str> is replaced with <ch>.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    Trivial.
***^^**********************************************************************/
#ifdef __ANSI_C__
   void restore_char( char *str, int ch, int esc )
#endif
{
   for ( ; *str ; str++ )
      if ( *str == esc )   *str = ch;
}


/***************************************************************************
** ^FUNCTION: get_arg_type - return function corresponding to given string
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static argTypePtr_t  get_arg_type( type_str )
/*
** ^PARAMETERS:
*/
   char *type_str;
/*    -- string corresponding to the name of an existing argXxxx type function.
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Get_arg_type will attempt to match <type_name> against the name of all
**    known argXxxx argumnent translation routines and routine the address of
**    the corresponding function. If no match is found, then an error message
**    is printed and execution is terminated.
**
** ^REQUIREMENTS:
**    type_str should be non-NULL and non-empty
**
** ^SIDE-EFECTS:
**    None.
**
** ^RETURN-VALUE:
**    Address of the corresponding function
**
** ^ALGORITHM:
**    Trivial.
***^^**********************************************************************/
#ifdef __ANSI_C__
   static  argTypePtr_t  get_arg_type( const char *type_str )
#endif
{
   register  CONST char *str = type_str;

   /* translate all listXxx into argXxx */
   if ( strnEQ( str, "list", 4 ) )
      str += 4;

   if ( strnEQ( str, "arg", 3 ) )
      str += 3;

   if      ( strEQ( str, "Usage" ) )
      return   argUsage;
   else if ( strEQ( str, "Dummy" ) )
      return   argDummy;
   else if ( strEQ( str, "Bool" ) )
      return   argBool;
   else if ( strEQ( str, "SBool" ) )
      return   argSBool;
   else if ( strEQ( str, "UBool" ) )
      return   argUBool;
   else if ( strEQ( str, "Int" ) )
      return   argInt;
   else if ( strEQ( str, "Short" ) )
      return   argShort;
   else if ( strEQ( str, "Long" ) )
      return   argLong;
   else if ( strEQ( str, "Float" ) )
      return   argFloat;
   else if ( strEQ( str, "Double" ) )
      return   argDouble;
   else if ( strEQ( str, "Char" ) )
      return   argChar;
   else if ( strEQ( str, "Str" ) )
      return   argStr;
   else {
      eprintf( "%s: Fatal Error: invalid argument type '%s'\n",
               Cmd_Name, type_str );
      cleanup();
      exit( e_ARGD );
   }
}


/***************************************************************************
** ^FUNCTION: get_arg_flag - return BITMASK corresponding to string
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static argMask_t get_arg_flag( flag_str )
/*
** ^PARAMETERS:
*/
   char flag_str[];
/*    -- name of an ARGXXXXX argument-flag
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Get_arg_flag will attempt to match the given string against the name of
**    all valid argument-flags and return its associated bitmask.  If no match
**    is found, then an error message is printed and execution is terminated.
**
** ^REQUIREMENTS:
**    flag_str should be non-NULL and non-empty
**
** ^SIDE-EFECTS:
**    None.
**
** ^RETURN-VALUE:
**    The bitmask corresponding to named ARGXXXX flag.
**
** ^ALGORITHM:
**    Trivial.
***^^**********************************************************************/
#ifdef __ANSI_C__
   static argMask_t get_arg_flag( const char flag_str[] )
#endif
{
   if ( strnEQ( flag_str, "ARG", 3 ) )   {
      if      ( strEQ( flag_str+3, "OPT" ) )     return   ARGOPT;
      else if ( strEQ( flag_str+3, "REQ" ) )     return   ARGREQ;
      else if ( strEQ( flag_str+3, "POS" ) )     return   ARGPOS;
      else if ( strEQ( flag_str+3, "VALREQ" ) )  return   ARGVALREQ;
      else if ( strEQ( flag_str+3, "VALOPT" ) )  return   ARGVALOPT;
      else if ( strEQ( flag_str+3, "HIDDEN" ) )  return   ARGHIDDEN;
      else if ( strEQ( flag_str+3, "LIST" ) )    return   ARGVEC;
      else if ( strEQ( flag_str+3, "VEC" ) )     return   ARGVEC;
      else {
         eprintf( "%s: Fatal Error: invalid argument flag '%s'\n",
                  Cmd_Name, flag_str );
         cleanup();
         exit( e_ARGD );
      }
   }
   else {
      eprintf( "%s: Fatal Error: invalid argument flag '%s'\n",
               Cmd_Name, flag_str );
      cleanup();
      exit( e_ARGD );
   }
}

/***************************************************************************
** ^FUNCTION: get_argtable_string - read in the argument-table
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static char *get_argtable_string()
#endif
/*
** ^PARAMETERS:
**    None.
**
** ^DESCRIPTION:
**    Get_argtable_string will read (from standard input if UseStdin is set)
**    the entire argument descriptor table into a string and return its address.
**
**    Execution is terminated if there is an error reading STDIN or if the
**    string is too big to fit into memory.
**
** ^REQUIREMENTS:
**    Standard input should be open for reading and be non-interactive.
**
** ^SIDE-EFECTS:
**    Memory is allocated that should later be deallocated using free.
**
** ^RETURN-VALUE:
**    NULL if STDIN is connected to a terminal (after all,
**    this program is for Non-interactive input)
**
** ^ALGORITHM:
**    - start off with a 1k buffer
**    - open the file (if necessary)
**    - while (not eof)
**      - read 1k bytes (or whatever is left).
**      - increase the buffer size by 1k bytes.
**      end-while
**    - shrink the buffer down to the number of bytes used.
**    - close the file (if we had to open it).
**    - return the buffer address
***^^**********************************************************************/
#ifdef __ANSI_C__
   static char *get_argtable_string( void )
#endif
{
   int  isatty  ARGS((int));
   int  fread   ARGS((char *, int, int, FILE *));
   FILE *fp;
   char *buf;
   register int  nchars = 0;   /* # bytes read */
   register int  bufsiz = 0;   /* actual buffer-size needed */

   /* open file if necessary */
   if ( UseStdin )  {
      if ( isatty(STDIN) ) {
            /* we wont read the arg-table from a terminal */
         eprintf( "\
%s: Fatal Error:\n\
\tcannot read arg-descriptor table from stdin\n\
\tif stdin is connected to a terminal\n!",
                  Cmd_Name );
         cleanup();
         exit( e_ARGD );
      }
      errno = 0;   /* reset errno if isatty() was not a terminal */
      fp = stdin;
   }
   else {
      if ( (fp = fopen( ArgdFname, "r")) == FILENULL )   {
         eprintf( "%s: Fatal error: Unable to open %s for reading\n",
                  Cmd_Name, ArgdFname );
         cleanup();
         if ( errno )  perror( Cmd_Name );
         exit( e_SYSTEM );
      }
   }

      /* get initial block for buffer */
   buf = (char *)ckalloc( BUFFER_SIZE * sizeof(char) );

   /*
   ** Loop reading characters into buffer and resizing as needed
   */
   do {
         /* read fildes into the buffer */
      nchars = fread( &(buf[bufsiz]), sizeof(char), BUFFER_SIZE, fp );
      if ( nchars < 0 )   {
         eprintf( "\
%s: Fatal Error:\n\
\tBad status from read() while reading argument descriptor table\n",
                  Cmd_Name );
         free(buf);
         cleanup();
         if ( errno )  perror( "" );
         exit( e_SYSTEM );
      }
      errno = 0;  /* errno is undefined after a succesful fread() */
      bufsiz += nchars;

         /* see if we need to grow the buffer */
      if ( nchars == BUFFER_SIZE )
         buf = (char *)ckrealloc( buf, (bufsiz + BUFFER_SIZE) * sizeof(char) );
   } while ( nchars == BUFFER_SIZE );

      /* shrink the buffer down to the exact size used */
   buf = (char *)ckrealloc( buf, (bufsiz + 1) * sizeof(char) );

      /* close file if necessary */
   if ( !UseStdin )  (VOID) fclose( fp );

   return   buf;
}


/***************************************************************************
** ^FUNCTION: get_shell_type - return shell corresponding to given string
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static shell_t get_shell_type( sh_str )
/*
** ^PARAMETERS:
*/
   char *sh_str;
/*    -- string corresponding tp the basename of a shell/command-interpreter
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Get_shell_type will retrun the shell-type for the named shell. If
**    No corresponding shell is known, then an error message is printed
**    and execution is terminated.
**
** ^REQUIREMENTS:
**    sh_str should be non-NULL and non-empty.
**
** ^SIDE-EFECTS:
**    None.
**
** ^RETURN-VALUE:
**    The corresponding shell-type.
**
** ^ALGORITHM:
**    Trivial.
***^^**********************************************************************/
#ifdef __ANSI_C__
   static shell_t get_shell_type ( const char *sh_str )
#endif
{
    if      ( strEQ( sh_str, BOURNE_SHELL ) )
       return   SH;
    else if ( strEQ( sh_str, TC_SHELL ) )
       return   TCSH;
    else if ( strEQ( sh_str, C_SHELL ) )
       return   CSH;
    else if ( strEQ( sh_str, KORN_SHELL ) )
       return   KSH;
    else if ( strEQ( sh_str, BOURNE_AGAIN_SHELL ) )
       return   BASH;
    else if ( strEQ( sh_str, RC_SHELL ) )
       return   RC;
    else if ( strEQ( sh_str, AWK_LANG ) )
       return   AWK;
    else if ( strEQ( sh_str, PERL_LANG ) )
       return   PERL;

    else {
       eprintf( "%s: Fatal Error: unknown shell '%s'\n",
                Cmd_Name, sh_str );
       eprintf( "\tKnown shells are: %s, %s, %s, %s, %s, %s, %s, and %s\n",
                AWK_LANG, BOURNE_AGAIN_SHELL, C_SHELL, KORN_SHELL, RC_SHELL,
                PERL_LANG, BOURNE_SHELL, TC_SHELL );
       cleanup();
       exit( e_SYNTAX );
    }
}


/***************************************************************************
** ^FUNCTION: build_tables - build the Argument and Value tables
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static int build_tables( argd_str )
/*
** ^PARAMETERS:
*/
   char argd_str[];
/*    -- the comma-separated table of argument descriptions
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Build_tables will read the contents of the argument-descriptor-table
**    string and build the corresponding Argument and Value tables to be
**    used by parseargs(3).
**
** ^REQUIREMENTS:
**    argd_str should be non-NULL and non-empty
**
** ^SIDE-EFECTS:
**    The global variables UsrVals and UsrArgd are allocated and initialized
**
** ^RETURN-VALUE:
**    The number of argument entries interpreted from the given string.
**
** ^ALGORITHM:
**    - split argd_str into a vector of tokens
**    - make sure the first and last tokens are syntactically correct
**    - make sure that the number of tokens is a multiple of 5 (the number
**      of fields in an argument descriptor)
**    - num-args = num-tokens / 5
**    - allocate space for UsrVals and UsrArgd
**    - i = 0
**    - for every 5 tokens
**      - UsrArgd[i].ad_name = token#1
**      - UsrArgd[i].ad_flags = 0
**      - split token#2 into a subvector of '|' separated fields
**      - for each '|' separated token
**        - UsrArgd[i].ad_flags |= bitmask( subfield )
**        end-for
**      - UsrArgd[i].ad_type = argtype( token#3 )
**      - UsrVals[i].name = token#4
**      - UsrArgd[i].ad_valp = &(UsrVals[i].value)
**      - UsrArgd[i].ad_prompt = token#5
**      - increment i by one
**    end-for
**    - Initialize first and last entries in UsrArgd
**    - return  num-args
***^^**********************************************************************/
#ifdef __ANSI_C__
   static int build_tables( char argd_str[] )
#endif
{
   char **token_vec, **flags_vec, *type_name;
   int   i = 0, j = 0, idx, token_num = 0, flags_num = 0;
   int   argc = 0, ad_idx;
   BOOL  start_string_used = FALSE, is_braces = FALSE;

      /* what about NULL or empty-string */
   if ( !argd_str  ||  !*argd_str )  return  0;

      /* escape all commas inside of single or double quotes */
   escape_char( argd_str, ',', ESCAPED_COMMA );

      /* parse Argument Table String */
   token_num = strsplit( &token_vec, argd_str, ArgTableDelims );
   if ( token_num )  (VOID) strtrim( token_vec[ token_num - 1 ], WhiteSpace );

      /* see if we need to build the tables at all */
   if ( token_num == 0 || isEND_ARGS(*token_vec) ) {
      free( token_vec );
      return   0;    /* nothing to parse */
   }

      /* make sure table is properly terminated */
   if ( !isEND_ARGS( token_vec[ --token_num ] ) )  {
      restore_char( token_vec[ token_num ], ',', ESCAPED_COMMA );
      eprintf( "\
%s: Fatal Error:\n\
\tArgument descriptor table is not terminated with the string:\n\
\t\t\"%s\"\n\
\tLast entry in table is: \"%s\"\n",
               Cmd_Name, s_END_ARGS, token_vec[ token_num ] );
      free( token_vec );
      cleanup();
      exit( e_ARGD );
   }

      /* check for optional start-string */
   (VOID) strtrim( *token_vec, WhiteSpace );
   if ( isSTART_ARGS(*token_vec) ) {
      start_string_used = TRUE;
      --token_num;
      ++token_vec;
   }

      /* make sure table has proper number of arguments */
   if ( (token_num % NFIELDS) != 0 ) {
      eprintf( "\
%s: Fatal Error:\n\
\tArgument descriptor table has an invalid number of arguments\n\
\tThe number of comma-separated arguments MUST be a multiple of %d\n\
\t(not including terminating \"%s\")\n",
               Cmd_Name, NFIELDS, s_END_ARGS );
      free( (start_string_used) ? (token_vec - 1) : token_vec );
      cleanup();
      exit( e_ARGD );
   }

      /* determine number of arg-descriptors and allocate arg-tables */
   argc = token_num / NFIELDS;
   UsrArgd = (ARGDESC *) ckalloc( (argc + 2) * sizeof(ARGDESC) );
   UsrVals = (cmdarg_t *) ckalloc( argc * sizeof(cmdarg_t) );

      /* start filling in the tables */
   i = 0;
   while ( i < token_num )  {
      restore_char( token_vec[i], ',', ESCAPED_COMMA );
      (VOID) strtrim( token_vec[i], WhiteSpace );
      idx = (i / NFIELDS); /* save index into UsrVals table */
      ad_idx = (idx + 1);   /* save index into UsrArgd table */
      is_braces = FALSE;

         /* remove first curly-brace if its present (this has the drawback
         ** of disallowing a left curly-brace from being an option character).
         */
      if ( token_vec[i][0] == c_BEGIN_STRUCT ) {
         token_vec[i][0] = ' ';
         (VOID) strltrim( token_vec[i], WhiteSpace );
         is_braces = TRUE;
      }

         /* get argument name */
      UsrArgd[ ad_idx ].ad_name = *(token_vec[i++]);
      if ( !UsrArgd[ ad_idx ].ad_name )  UsrArgd[ ad_idx ].ad_name = ' ';

         /*
         ** get argument flags, flags may be ORed together so I
         ** need to parse the flags for each individual flag used
         */
      UsrArgd[ ad_idx ].ad_flags = (argMask_t) 0;   /* initialize */
      flags_num = strsplit( &flags_vec, token_vec[i++] , ArgFlagsDelims );
      for ( j = 0 ; j < flags_num ; j++ ) {
            (VOID) strtrim( flags_vec[j], WhiteSpace );
            UsrArgd[ ad_idx ].ad_flags |= get_arg_flag( flags_vec[j] );
      }
      free( flags_vec );

         /* get argument type and name for Value table */
      type_name = strtrim( token_vec[i++], WhiteSpace );
      restore_char( token_vec[i], ',', ESCAPED_COMMA );
      UsrVals[ idx ].name = strtrim( token_vec[i++], WhiteSpace );

         /* remove any leading "__" from the name */
      if ( strnEQ("__", UsrVals[ idx ].name, 2) ) {
         (VOID) strltrim( (char *)UsrVals[idx].name, "_ \t\n\r\v\f" );
      }

         /* remove any leading '&', '$', and '@' from the name */
      if ( strchr("&$@", UsrVals[ idx ].name[0]) ) {
         (VOID) strltrim( (char *)UsrVals[idx].name, "&$@ \t\n\r\v\f" );
      }

         /* get type and value pointer for Arg table */
      UsrArgd[ ad_idx ].ad_type = get_arg_type( type_name );
      UsrArgd[ ad_idx ].ad_valp = __ &(UsrVals[ idx ].value);

         /* if we have a vector we need to initialize it */
      if ( ARG_isVEC((UsrArgd + ad_idx)) )  {
         UsrVals[ idx ].value.Vector.count = 0;
         UsrVals[ idx ].value.Vector.array = (VOID *)NULL;
      }

         /* get argument prompt/description */
      restore_char( token_vec[i], ',', ESCAPED_COMMA );
      UsrArgd[ ad_idx ].ad_prompt = strtrim( token_vec[i++], WhiteSpace );

         /* if in curly-braces, remove the trailing brace */
      if ( is_braces ) {
        int  last = strlen( UsrArgd[ad_idx].ad_prompt ) - 1;
        if ( UsrArgd[ ad_idx ].ad_prompt[ last ] == c_END_STRUCT ) {
           *((char *)(UsrArgd[ ad_idx ].ad_prompt) + last) = '\0';
           (VOID) strrtrim( (char *)UsrArgd[ad_idx].ad_prompt, WhiteSpace );
        }
      }/*end-if*/
   }/*while*/

      /* free up token tables (just the arrays, not the actual elements) */
   free( flags_vec );
   free( (start_string_used) ? (token_vec - 1) : token_vec );

      /* set up first & last argument entries */
   (UsrArgd -> ad_name)    =   UsrArgd[ argc+1 ].ad_name    = '\0';
   (UsrArgd -> ad_flags)   =   UsrArgd[ argc+1 ].ad_flags   = (argMask_t) 0;
   (UsrArgd -> ad_type)    =   UsrArgd[ argc+1 ].ad_type    = argNULL;
   (UsrArgd -> ad_valp)    =   UsrArgd[ argc+1 ].ad_valp    = ARBNULL;
   UsrArgd[ argc+1 ].ad_prompt = CHARNULL;

      /* try to get a command-description */
   cmd_description(UsrArgd) = getenv( "DESCRIPTION" );
   if ( !cmd_description(UsrArgd) ) {
      cmd_description(UsrArgd) = getenv( "CMD_DESCRIPTION" );
   }

   return  argc;
}


/***************************************************************************
** ^FUNCTION: put_char_arg - print a character
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static put_char_arg( fp, ch )
/*
** ^PARAMETERS:
*/
   FILE *fp;
/*    -- the output stream to write to.
*/
   int ch;
/*    -- the character to print
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Put_char_arg will write the given character on the specified output
**    stream. If the character is metacharacter for the current shell, then
**    it is "escaped" according to the given shell syntax.
**
** ^REQUIREMENTS:
**    <fp> should be non-NULL and open for writing.
**    <ch> should be a printable character.
**
** ^SIDE-EFECTS:
**    output is written to <fp>.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    print a character argument on standard output
**    and make sure we preserve the evaluation of
**    any special characters such as: double-quotes,
**    back-quotes, back-slash, dollar-signs, etc ....
***^^**********************************************************************/
#ifdef __ANSI_C__
   static void put_char_arg( FILE *fp, int ch )
#endif
{
      /* newline needs to be escaped specially for CSH, TCSH, and PERL */
   if ( ch == '\n' ) {
      if ( UsrSh == CSH   || UsrSh == TCSH ) {
         fprintf( fp, "\\\n" );
         return;
      }
      else if ( UsrSh == PERL ) {
         fprintf( fp, "\\n" );
         return;
      }
   }/*if newline*/

   if ( strchr( Shell[ UsrSh ].metachars, ch ) ) {
      fprintf( fp, Shell[ UsrSh ].escape, ch );
   }
   else {
      fputc( ch, fp );
   }
}


/***************************************************************************
** ^FUNCTION: put_str_arg - same as put_char_arg but for a string!
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static VOID put_str_arg( fp, str )
/*
** ^PARAMETERS:
*/
   FILE *fp;
/*    -- the output stream to write to
*/
   char str[];
/*    -- the string to print
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Put_str_arg will print the given string to the given output stream
**    and will escape any shell meta-characters for the current shell.
**
** ^REQUIREMENTS:
**    <fp> should be non-NULL and open for writing.
**    <str> should be non-NULL and non-empty.
**
** ^SIDE-EFECTS:
**    Output is written to <fp>
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - foreach character in str
**     - put_char_arg(fp, character)
**    end-for
***^^**********************************************************************/
#ifdef __ANSI_C__
   static void put_str_arg( FILE *fp, const char str[] )
#endif
{
   if ( !str )   return;

   for ( ; *str ; str++ )
      put_char_arg( fp, *str );
}


/***************************************************************************
** ^FUNCTION: put_arg - convert & print the given value into the given buffer
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static VOID put_arg( fp, ad, val, idx )
/*
** ^PARAMETERS:
*/
   FILE *fp;
/*    -- the output stream to write to
*/
   ARGDESC *ad;
/*    -- the argument-descriptor of the argument to print.
*/
   cmdarg_t *val;
/*    -- the value of the argument to print
*/
   short idx;
/*    -- the index in the argument-vector of the item to be printed
**       (only used when ad corresponds to an ARGVEC argument).
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Put_arg will print the given variable/array setting on the given
**    output stream using the syntax of the user-sepcified shell.
**
** ^REQUIREMENTS:
**    <val> should be the value corresponing to the argument-descriptor <ad>
**
** ^SIDE-EFECTS:
**    Output is written to <fp>.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - if we have a vector, make sure we were given a valid index.
**    - if we have a vector, then value=val.vec[idx],
**      else value = val.value
**    - print the beginning of the variable setting
**    - case (argument-type) of
**        INTEGRAL-TYPE: print the integer value
**        DECIMAL-TYPE: print the floating point value
**        CHARACTER: print the character value and escape it if necessary
**        STRING: print the string value and escape it if necessary
**        BOOLEAN: print the string StrTrue if value is TRUE
**                 print the string StrFalse if value is FALSE
**    - print the end of the variable setting
***^^**********************************************************************/
#ifdef __ANSI_C__
   static void put_arg(
      FILE *fp, const ARGDESC *ad, const cmdarg_t *val, short idx
   )
#endif
{
   if ( ARG_isVEC(ad) ) {
      if ( idx < 0  ||  idx >= val->value.Vector.count ) {
         return;   /* bad index given */
      }

      if ( arg_type(ad) == argStr ) {
         if ( UsrSh == PERL )  fputc( '\'', fp );
         put_str_arg( fp, val->value.Str_vec.array[idx] );
         if ( UsrSh == PERL )  fputc( '\'', fp );
      }
      else if ( arg_type(ad) == argChar ) {
         if ( UsrSh == PERL )  fputc( '\'', fp );
         put_char_arg( fp, val->value.Char_vec.array[idx] );
         if ( UsrSh == PERL )  fputc( '\'', fp );
      }
      else if ( arg_type(ad) == argDouble ) {
         fprintf( fp, "%lf", val->value.Double_vec.array[idx] );
      }
      else if ( arg_type(ad) == argFloat ) {
         fprintf( fp, "%f", val->value.Float_vec.array[idx] );
      }
      else if ( arg_type(ad) == argLong ) {
         fprintf( fp, "%ld", val->value.Long_vec.array[idx] );
      }
      else if ( arg_type(ad) == argInt ) {
         fprintf( fp, "%d", val->value.Int_vec.array[idx] );
      }
      else if ( arg_type(ad) == argShort ) {
         fprintf( fp, "%ld", val->value.Short_vec.array[idx] );
      }

      /* Boolean vectors are not supported */
   }/*if vector*/
   else {
      if ( arg_type(ad) == argStr ) {
         put_str_arg( fp, val->value.Str_val );
      }
      else if ( arg_type(ad) == argChar ) {
         put_char_arg( fp, val->value.Char_val );
      }
      else if ( arg_type(ad) == argDouble ) {
         fprintf( fp, "%lf", val->value.Double_val );
      }
      else if ( arg_type(ad) == argFloat ) {
         fprintf( fp, "%f", val->value.Float_val );
      }
      else if ( arg_type(ad) == argLong ) {
         fprintf( fp, "%ld", val->value.Long_val );
      }
      else if ( arg_type(ad) == argInt ) {
         fprintf( fp, "%d", val->value.Int_val );
      }
      else if ( arg_type(ad) == argShort ) {
         fprintf( fp, "%ld", val->value.Short_val );
      }
      else if ( ARG_isBOOLEAN(ad) ) {
         fprintf( fp, "%s", (val->value.Bool_val) ? StrTrue : StrFalse );
      }
   }/*else !vector*/
}


/***************************************************************************
** ^FUNCTION: print_argvector - print shell variable settings for an ARGVEC
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static VOID print_argvector( ad, val )
/*
** ^PARAMETERS:
*/
   ARGDESC *ad;
/*    -- the argument-descriptor of the vector to print
*/
   cmdarg_t *val;
/*    -- the value of the vector to print
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Parseargs treats ARGLIST and ARGVEC arguments in a special way. The
**    method used for setting up an argument list depends largely upon the
**    syntax of shell that was specified on the command line via the -s option
**    (although an ARGLIST argument is treated the same as an ARGVEC argument).
**
** ^Resetting_the_Positional_Parameters_to_an_Argument_List:
**    For the Bourne, Bourne-Again, and Korn shells, if the variable name
**    corresponding to the ARGLIST argument is "--", then the positional
**    parameters of the calling program will be re-assigned to the contents
**    of the argument list ($1 will be the first item, $2 the second item,
**    and so on). In this particular case, the calling program may wish to
**    use the -u option to reset the positional parameters to NULL before
**    making any shell-variable assignments (this way, the positional
**    parameters will be unset if the associated list of command line
**    arguments is not encountered).
**
**    Similarly for the C and TC shells, if the variable name corresponding
**    to the ARGLIST argument is "argv", then the positional parameters
**    of the calling program will be re-assigned to the contents of the
**    argument list.
**
**    For the Plan 9 shell (rc), if the variable name corresponding to the
**    ARGLIST argument is "*", then the positional parameters of the calling
**    program will be re-assigned to the contents of the argument list.
**
**    For awk and perl, if the variable name corresponding to the ARGLIST
**    argument is "ARGV", then the positional parameters of the calling
**    program will be re-assigned to the contents of the argument list.
**
** ^Bourne_Shell_Argument_Lists:
**    For the Bourne shell, if the associated variable name is NOT "--"
**    and the -A option was NOT specified, then that variable is treated as
**    a regular shell variable and is assigned using the following syntax:
**
**         name='arg1 arg2  ...'
**
**    After invoking parseargs, if you wish to go through all the words in
**    the variable name and one of the words in name contains an IFS charac-
**    ter (such as a space or a tab), then that particular word will be
**    treated by the Bourne shell as two distinct words.
**
**    Also for the Bourne shell, If the associated variable name is NOT
**    "--" and the -A option WAS specified, then that variable is treated
**    as the root name of an array that is set using the following syntax:
**
**         name1='arg1'
**         name2='arg2'
**             ...
**
**    and the variable "name_count" will be set to contain the number of
**    items in the array.  The user may then step through all the items in
**    the array using the following syntax:
**
**         i=1
**         while [ $i -le $name_count ] ; do
**           eval echo "item #$i is: " \$name$i
**           i=`expr $i + 1`
**         done
**
** ^Korn_Shell_Argument_Lists:
**    For the Korn shell, if the associated variable name is NOT "--",
**    then that variable is treated as an array and is assigned using the -A
**    option of the set command. The first item will be in ${name[0]}, the
**    second item will be in ${name[1]}, etc ..., and all items may be given
**    by ${name[*]} or ${name[@]}.  If the associated variable name is NOT
**    "--" and the -A option WAS specified, then that variable is assigned
**    using the +A option of the set command (which preserves any array
**    elements that were not overwritten by the set command).
**
**    It should be noted that there is a bug in versions of the Korn shell
**    earlier than 11/16/88a, in which the following:
**
**         set  -A  name  'arg1'  'arg2'  ...
**
**    causes the positional parameters to be overwritten as an unintentional
**    side-effect. If your version of the Korn shell is earlier than this
**    and you wish to keep the contents of your positional parameters after
**    invoking parseargs than you must save them yourself before you call
**    parseargs. This may be accomplished by the following:
**
**         set  -A  save_parms  "$@"
**
** ^C_and_TC_Shell_Argument_Lists:
**    For the C and TC shells, ARGLIST variables are treated as word-lists
**    and are assigned using the following syntax:
**
**         set  name = ( 'arg1'  'arg2'  ... )
**
**    The first item will be in $name[1], the second item will be in
**    $name[2], etc ..., and all items may be given by $name.  Notice that
**    Korn shell arrays start at index zero whereas C and TC shell word-
**    lists start at index one.
**
** ^Bourne-Again_Shell_Argument_Lists:
**    At present, the Free Software Foundation's Bourne-Again shell is
**    treated exactly the same as the Bourne Shell. This will change when
**    bash supports arrays.
**
** ^Plan_9_Shell_Argument_Lists:
**    For the Plan 9 shell, if the associated variable name is not "*" then
**    it is considered to be word-list and set using the following syntax:
**
**         name=( 'arg1'  'arg2'  ... )
**
** ^Awk_Argument_Lists:
**    For awk, if the -A option is not given, then the output for thes
**    variable-list will be a line with the variable name, followed by a
**    line with each of the values (each value will be separated with the
**    field separator specified using the -S option - which defaults to a
**    space character):
**
**         name
**         arg1  arg2  ...
**
**    If the -A option is given, then the associated variable is considered
**    the root name of an array. The ouput for the array will consist of two
**    lines for each item in the list (as in the following expample):
**
**         name1
**         arg1
**
**         name2
**         arg2
**
**    and the variable "name_count" will have an output line showing the
**    number of items in the array.
**
** ^Perl_Argument_Lists:
**    For perl, each argument list is considered an array and is set using
**    the following syntax:
**
**         @name=( arg1 , arg2 ,  ... )
**
** ^A_Final_Note_on_Argument_Lists:
**    The word-lists used by the C shell, the arrays used by the Korn shell,
**    The Plan 9 shell, awk, & perl, and the positional parameters used by
**    all shells (if overwritten by parseargs) will preserve any IFS
**    characters in their contents.  That us to say that if an item in one
**    of the aforementioned multi-word lists contains any IFS characters,
**    it will not be split up into multiple items but will remain a single
**    item which contains IFS characters.
**
** ^REQUIREMENTS:
**    <val> should correspond to the vlue of the argument indicated by <ad>
**
** ^SIDE-EFECTS:
**    prints the array assignment statement on standard output
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - print the beginning of the array assigment statement
**    - print each item in the array (escaping characters where needed)
**    - print the end of the array assignment statement
***^^**********************************************************************/
#ifdef __ANSI_C__
   static void print_argvector( const ARGDESC *ad, const cmdarg_t *val )
#endif
{
   BOOL   is_array = TRUE;
   int    i;
   char   *varname;

   switch( UsrSh ) {
      case KSH :
         if ( strEQ( val->name, Shell[ UsrSh ].varname ) ) {
            fputs( "set -- ", stdout );
         }
         else {
            printf( "set %cA %s ", ((ModArr) ? '+' : '-'), val->name );
         }
         break;

      case TCSH : case CSH : case RC: case PERL :
         if ( UsrSh == PERL )
            printf( "@%s = ", val->name );
         else if ( UsrSh == RC )
            printf( "%s=", val->name );
         else /* UsrSh == CSH/TCSH */
            printf( "set %s=", val->name );
         fputc( '(', stdout );
         break;

      case BASH: case SH : case AWK:
         if ( UsrSh == AWK )  is_array = FALSE;
         if ( strEQ( val->name, Shell[ UsrSh ].varname ) ) {
            fputs( ((UsrSh == AWK) ? "ARGV\n" : "set -- "), stdout );
         }
         else {
            if ( ModArr )   { /* use fake array syntax */
               i = strlen( val->name );
               varname = (char *)ckalloc( (i + 4) * sizeof(char) );
               for ( i = 0 ; i < val->value.Vector.count ; i++ ) {
                  sprintf( varname, "%s%d", val->name, i+1 );
                  printf( Shell[ UsrSh ].setcmd, varname );
                  printf( Shell[ UsrSh ].prefix );
                  put_arg( stdout, ad, val, i );
                  printf( "%s", Shell[ UsrSh ].suffix );
               }
               sprintf( varname, "%s_count", val->name );
               printf( Shell[ UsrSh ].setcmd, varname );
               printf( Shell[ UsrSh ].prefix );
               printf( "%d", val->value.Vector.count );
               printf( "%s", Shell[ UsrSh ].suffix );
               free( varname );
               if ( val->value.Vector.array ) {
                  free( val->value.Vector.array );
                  val->value.Vector.array = NULL;
               }
               return;
            }/*if ModArr*/
            else {
               is_array = FALSE;
               printf( Shell[ UsrSh ].setcmd, val->name );
               printf( Shell[ UsrSh ].prefix );
            }
         }/*else !positional-parms*/
         break;

   }/*switch*/

   for ( i = 0 ; i < val->value.Vector.count ; i++ ) {
      if ( is_array )  printf( Shell[ UsrSh ].prefix );
      put_arg( stdout, ad, val, i );
      if ( is_array )  printf( Shell[ UsrSh ].prefix );
      if ( i != (val->value.Vector.count - 1) ) {
         fputs( ((UsrSh == PERL) ? ", " : FieldSep), stdout );
      }
   }/* end-for */

   if ( val->value.Vector.array ) {
      free( val->value.Vector.array );
      val->value.Vector.array = NULL;
   }

   if ( UsrSh == CSH  ||  UsrSh == TCSH  || UsrSh == RC  ||  UsrSh == PERL ) {
     fputc( ')', stdout );
   }

   fputs( ((! is_array) ? Shell[ UsrSh ].suffix : ";\n"), stdout );
}


/***************************************************************************
** ^FUNCTION: print_args - print the shell variable settings for the usr args
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static VOID print_args( vals, argd )
/*
** ^PARAMETERS:
*/
   cmdarg_t *vals;
/*    -- the table of argument values.
*/
   ARGDESC *argd;
/*    -- the table of argument-descriptors.
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Print_args prints the actual shell variable assignment statement(s) for
**    each argument found on the command line. If a command-line argument was
**    specified withch may take an optional value, then regargdless of whether
**    or not the optional value was supplied, the variable <name>_flag is set 
**    to the value indicated by StrTrue.
**
** ^REQUIREMENTS:
**    The argument values have already been set due to the fact that parseargs
**    should already have been invoked to parse the command-line
**
** ^SIDE-EFECTS:
**    Variable assignment statements are printed on standard output.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - for each argument in the argument table
**      - if this argument was supplied on the command-line
**        - if the argument takes an optional value
**            then set argname_flag = TRUE
**        - if the argument is a vector
**          - call print_argvector to print the vector elements
**        - else
**          - print the beginning of the variable assignment statement for 
**            the shell indicated by UsrSh
**          - print the argument value using put_arg
**          - print the end of the variable assignment statement for the shell
**            indicated by UsrSh
**        end-if vector
**      end-if supplied
**    end-for
***^^**********************************************************************/
#ifdef __ANSI_C__
   static void print_args( const cmdarg_t *vals, const ARGDESC *argd )
#endif
{
   register CONST  ARGDESC *ad;
   register  int i;
   argName_t  buf;

      /* print values for all options given */
   for ( ad = ARG_FIRST(argd), i = 0 ; !ARG_isEND(ad) ; ARG_ADVANCE(ad), i++ ) {
      if ( ARG_isGIVEN(ad) ) {
         /******************************************************************
         ** ^SECTION: ARGVALOPT
         **    Options that may take an optional argument need special
         **    consideration.  The shell programmer needs to know whether
         **    or not the option was given, and (if given) if it was
         **    accompanied by an argument. In order to accommodate this
         **    need, parseargs will set an additional shell variable for
         **    each argument that is given the ARGVALOPT flag if it is
         **    supplied on the command line regardless of whether or not
         **    it was accompanied by its optional argument.  If the user
         **    has defined an option which may optionally take an argument 
         **    and the option appears on the command line with or without
         **    its associated argument, then the shell variable <name>_flag
         **    will be assigned the value "TRUE" (or the value supplied with
         **    the -T option to parseargs) where <name> is the name of the
         **    shell variable associated with the option in the argument
         **    description string.
         ***^^*************************************************************/
         if ( ARG_isVALOPTIONAL(ad) ) {
            sprintf(buf, "%s_flag", vals[i].name);
            printf( Shell[ UsrSh ].setcmd, buf);
            printf( Shell[ UsrSh ].prefix );
            printf( "%s%s", StrTrue, Shell[ UsrSh ].suffix );

            if ( !ARG_isVALGIVEN(ad) )  continue;
         }/*if OPTARG*/

            /* vectors are special */
         if ( ARG_isVEC(ad) ) {
            print_argvector( ad, (vals + i) );
            continue;
         }

            /* print shell-specific variable prefix and name */
         printf( Shell[ UsrSh ].setcmd, vals[i].name );
         printf( Shell[ UsrSh ].prefix );

            /* print shell-variable value */
         put_arg( stdout, ad, (vals + i), 0 );

            /* print the shell-specific suffix */
         printf( "%s", Shell[ UsrSh ].suffix );
      }/*if ARGGIVEN*/
   }/* end-for */
}


/***************************************************************************
** ^FUNCTION: unset_positional_parameters - unset shell parameters
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static void unset_positional_parameters()
#endif
/*
** ^PARAMETERS:
**    None.
**
** ^DESCRIPTION:
**    Unset_positional_parameters will print (on standard output) the
**    shell commands to unset the positional parameters of the invoking
**    shell_script.
**
** ^REQUIREMENTS:
**    The currenty shell-type has already been determined.
**
** ^SIDE-EFECTS:
**    Prints on stdout.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - Use the syntax of the current shell to unset the positional parameters
***^^**********************************************************************/
#ifdef __ANSI_C__
   static void unset_positional_parameters( void )
#endif
{
   switch( UsrSh ) {
      case TCSH: case CSH:
         printf( "set argv=();\n" );
         break;

      case PERL:
         printf( "@ARGV = ();\n" );
         break;

      case KSH:
         printf( "set --;\n" );
         break;

      case BASH: case SH:
         printf( "shift $#;\n" );
         break;

      case RC:
         printf( "*=();\n" );
         break;

      case AWK:
         printf( "%s%s", Shell[ UsrSh ].varname, Shell[ UsrSh ].suffix );
         break;

      default:
         break;
   }/*switch*/
}


/***************************************************************************
** ^FUNCTION: ck_cmd_errs - check for command syntax errors
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static int ck_cmd_errs()
#endif
/*
** ^PARAMETERS:
**    None.
**
** ^DESCRIPTION:
**    Ck_cmd_errs will check for the improper specification of arguments
**    from the command-line.
**
** ^REQUIREMENTS:
**    The command-line should already have been parsed by parseargs(3)
**
** ^SIDE-EFECTS:
**    - Exits the program if an error is encountered.
**    - Assigns any needed defaults for StrTrue and StrFalse.
**    - Gets the argd-string from an environment variable if needed
**      (or sets UseStdin if it is to be read from standard input)
**    - Determines the shell specified by the user (default=Bourne)
**
** ^RETURN-VALUE:
**    e_SUCCESS if everything checks out all right
**    Exits with one of the following exit-codes upon failure:
**
**      e_SYNTAX : command-line sytntax error
**
**      e_NOENV : AN environment variable was "purported" to contain the
**                description string that describes all the arguments but
**                upon examination, the variable was unset or empty.
**
** ^ALGORITHM:
**    - make sure only one of '-a', '-e', and '-f' was given
**    - turn OFF UseStdin if any of the above were given
**    - make sure only one of '-l' and '-o' was given
**    - if '-e' was specified, read the environment-variable and
**      make sure it is non-NULL and non-empty
**    - assign default values for StrTrue and StrFalse if they were not
**      supplied on the command-line
**    - determine the type of the user's shell
**    - return
***^^**********************************************************************/
#ifdef __ANSI_C__
   static int ck_cmd_errs( void )
#endif
{
      /* make sure certain arg-combos were NOT used */
   if ( (ArgdString && (ArgdFname || ArgdEnv)) || (ArgdFname && ArgdEnv) )  {
      eprintf( "%s: only one of `-a', `-e', or `-f' may be given.\n\n",
               Cmd_Name );
      usage( Args );
      exit( e_SYNTAX );
   }

      /* make sure at least ONE of KeyWords and Options are enabled */
   if ( OptsOnly  &&  KwdsOnly ) {
      eprintf( "%s: only one of `-o' or `l' may be given.\n\n",
               Cmd_Name );
      exit( e_SYNTAX );
   }

      /* turn OFF UseStdin if `-a', `-e', or `-f' was given */
   if (ArgdString || ArgdEnv || ArgdFname)
      UseStdin = FALSE;

      /* get the argd-string from an environment variable if so specified */
   if ( ArgdEnv ) {
      ArgdString = getenv( ArgdEnv );
      if ( !ArgdString   ||   !*ArgdString ) {
         eprintf( "%s: variable \"%s\" is NULL or does not exist\n",
                  Cmd_Name, ArgdEnv);
         exit( e_NOENV );
      }
   }

      /* set up default boolean value strings if needed */
   if ( !StrTrue ) {
      StrTrue  = (char *)Default_StrTrue;
   }
   if ( !StrFalse ) {
      StrFalse = (char *)Default_StrFalse;
   }

      /* see if we need to "default" the shell name */
   if ( !PrUsage   &&   !PrManual ) {
      if ( !ShellName ) {
         UsrSh = SH;   /* default to Bourne Shell */
      }
      else {
         UsrSh = get_shell_type( basename( ShellName ) );
      }
   }

      /* everything is a-ok */
   return   e_SUCCESS;
} /* ck_cmd_errs */


/***************************************************************************
** ^FUNCTION: main
**
** ^SYNOPSIS:
**    main( argc, argv )
**
** ^PARAMETERS:
**    int argc;
**    -- the number of argumenst on the command-line
**
**    char *argv[];
**    -- the NULL terminated vector of arguments from the command-line
**       (the first of which is the name of the paraseargs(1) command).
**
** ^DESCRIPTION:
**    This is the main program for parseargs(1). It parses the user's command-
**    line and outputs the approriate variable assignment statements (or prints
**    a usage message or manual template).
**
** ^REQUIREMENTS:
**    All the static local variables that are used to define the argument
**    table and the values it points to (for parseargs(1) not for the user's
**    command) should be properly initialized).
**
** ^SIDE-EFFECTS:
**    Shell variable assignment statements, A usage message, or a manual
**    page template is printed on standard output. Any diagnostic messages
**    are printed on standard error.
**
** ^RETURN-VALUE:
**    Execution is terminated and the corresponding exit-code is returned
**    to the calling progarm (see the RETURN-CODES section).
**
** ^ALGORITHM:
**    - save the name of this program
**    - parse the command-line
**    - read the argument descriptor string into memory (if needed)
**    - build the argument and value tables
**    - set ProgName to the name of the user's program
**    - if need to print usage, then do so and exit with status e_USAGE
**    - if need to print manual template, then do so, exit-status=e_USAGE
**    - modify parsing-behavior as specified on the command-line
**    - parse the user's command-line arguments
**    - unset the positional parameters if required
**    - print thye resulting shell variable assignments
**    - deallocate any remaining storage
**    - exit, status=e_SUCCESS
***^^**********************************************************************/
MAIN( argc, argv )
{
   int  rc;
   argMask_t   flags = pa_ARGV0;

   Cmd_Name = *argv = basename( *argv );

      /* parse command-line */
   parseargs( argv, Args );

      /* see if there is any reason to exit now */
   (VOID) ck_cmd_errs();

      /* set desired option-syntax */
   if ( KwdsOnly )   BSET(flags, pa_KWDSONLY);
   if ( OptsOnly )   BSET(flags, pa_OPTSONLY);

      /* if needed - allocate and read in the argument descriptor string */
   if ( !ArgdString ) {
      ArgdString = get_argtable_string();
      if ( ArgdString ) {
         strrtrim( ArgdString, WhiteSpace );
         if ( !*ArgdString )   ArgdString = CHARNULL;
      }
   }

      /* fill in the argument tables from the environment variable */
   if ( ArgdString )   UsrArgc = build_tables( ArgdString );

      /* if no arguments or options taken, use NULL */
   if ( !UsrArgc )   UsrArgd = ARGDESCNULL;

   ProgName = UsrName;      /* set up program name */

   if ( PrUsage ) {   /* just print usage and exit */
      usage( UsrArgd );
   }
   else if ( PrManual ) { /* print man pages and exit */
      manpage( UsrArgd );
   }
   else {   /* parse callers command-line & print variable settings */
      if ( Prompt )  BSET(flags, pa_PROMPT);
      if ( Ignore )  BSET(flags, pa_IGNORE);
      if ( flags )  (VOID) parsecntl( UsrArgd, pc_PARSEFLAGS, pc_WRITE, flags );

      if ( (rc = parseargs( UsrArgv.array, UsrArgd )) != 0 ) {
         if ( rc > 0 ) {
            rc = e_SYNTAX;
         }
         else {  /* (rc < 0) means a system error */
            cleanup();
            if ( errno )  perror( UsrName );
            exit( e_SYSTEM );
         }
      }/*if*/

      if ( Unset )  unset_positional_parameters();

      print_args( UsrVals, UsrArgd );
   }

   cleanup();
   exit( rc );
} /* main */
