v17i057: parseargs - functions to parse command line arguments, Part12/12

Brad Appleton brad at hcx1.ssd.csd.harris.com
Tue Mar 19 01:58:17 AEST 1991


Submitted-by: Brad Appleton <brad at hcx1.ssd.csd.harris.com>
Posting-number: Volume 17, Issue 57
Archive-name: parseargs/part12

This is part 12 of parseargs

#!/bin/sh
# this is Part.12 (part 12 of a multipart archive)
# do not concatenate these parts, unpack them in order with /bin/sh
# file parseargs/xparse.c continued
#
if test ! -r _shar_seq_.tmp; then
	echo 'Please unpack part 1 first!'
	exit 1
fi
(read Scheck
 if test "$Scheck" != 12; then
	echo Please unpack part "$Scheck" next!
	exit 1
 else
	exit 0
 fi
) < _shar_seq_.tmp || exit 1
if test ! -f _shar_wnt_.tmp; then
	echo 'x - still skipping parseargs/xparse.c'
else
echo 'x - continuing file parseargs/xparse.c'
sed 's/^X//' << 'SHAR_EOF' >> 'parseargs/xparse.c' &&
**
** ^SIDE-EFECTS:
**    None.
**
** ^RETURN-VALUE:
**    TRUE if fd is associated with a terminal, FALSE otherwise.
**
** ^ALGORITHM:
**    Trivial - just use isatty and restore errno if necessary
***^^**********************************************************************/
#ifdef __ANSI_C__
X   static BOOL is_interactive( int fd )
#endif
{
#ifdef unix
X   EXTERN int isatty  ARGS((int));
X   extern int errno;
X   int saverr = errno;  /* save errno */
X
X   if ( isatty(fd) )
X      return  TRUE;
X   else {
X      errno = saverr;
X      return  FALSE;
X   }
#else
#ifdef vms
X   EXTERN int isatty  ARGS((int));
X   int ret = isatty( fd );
X
X   if ( ret == -1 ) /* error with fd */
X      syserr( "error on file descriptor #%d", fd );
X   else if ( ret )
X      return  TRUE;
X   else
X      return  FALSE;
#else
X   return  TRUE;
#endif
#endif
}
X
X
/***************************************************************************
** ^FUNCTION: get_description - get the description portion of a string
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   static char *get_description( str )
/*
** ^PARAMETERS:
*/
X   char *str;
/*    -- the string to parse for a description
*/
#endif  /* !__ANSI_C__ */
X
/* ^DESCRIPTION:
**    Get_description null terminates the first portion of the string and
**    returns a pointer to the second portion.
**
**    Two "portions" must be either separated by whitespace or the second
**    portion may be within "(),{},[], or <>" delimiters. The second
**    portion is assumed to begin with the first alphabetic following
**    separator.
**
** ^REQUIREMENTS:
**    str should be non-null and non-empty
**
** ^SIDE-EFECTS:
**    The characters which separated the two portions of <str> are
**    replaced with a single '\0'.
**
** ^RETURN-VALUE:
**    Address of the description (or NULL if the string has no description).
**
** ^ALGORITHM:
**    - locate the end of the first portion by scanning for whitespace or
**      balanced delimiters.
**    - locate the beginning of the second portion by scanning for the first
**      alpha-numeric following the end of the first portion.
**    - return the address of the description.
***^^**********************************************************************/
#ifdef __ANSI_C__
X   static char *get_description( char *str )
#endif
{
X   register char  *description = CHARNULL;
X   BOOL  is_end = FALSE, is_balanced = FALSE;
X   char  *p;
X   static CONST char  whitespace[]  = " \t\n\r\f\v";
X   static CONST char  beg_portion[] = "(<{[";
X   static CONST char  end_portion[] = ")>}]";
X
X   description = strpbrk( str, whitespace );
X   if ( description ) {
X      is_end = TRUE;
X      *description++ = '\0';  /* null terminate the 1st portion */
X      while ( isspace(*description) )  ++description;  /* trim leading ' ' */
X   }
X
X   if ( !is_end ) {
X       p = strpbrk( str, beg_portion );
X       if ( p )  description = p;
X   }
X
X   if ( description ) {
X      if ( !is_end ) {  /* null terminate and skip leading '(' */
X         is_end = is_balanced = TRUE;
X         *description++ = '\0';
X      }
X      else if ( strchr(beg_portion, *description) ) {
X            is_balanced = TRUE;
X            ++description;
X      }
X      if ( is_balanced ) {  /* remove trailing ')' */
X         p = description + (strlen( description ) - 1);
X         if ( strchr(end_portion, *p) )  *p = '\0';
X      }
X   }/*end-if*/
X
X   if ( description  &&  !is_balanced ) {
X      while ( !isalnum(*description) )  ++description;
X   }
X
X   return  description;
}
X
X
/***************************************************************************
** ^FUNCTION: init_args - Initialize the command object
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   VOID init_args( argd )
/*
** ^PARAMETERS:
*/
X   ARGDESC argd[];
/*    -- the array of command-arguments to initialize.
*/
#endif  /* !__ANSI_C__ */
X
/* ^DESCRIPTION:
**    Init_args performs all the actions that are required to prepare an
**    argdesc-array for use by any of the parseargs functions. Storrage
**    is allocated and initialized and argument descriptions are compiled.
**
** ^REQUIREMENTS:
**    <argd> must point to an array that has been declared using the CMD_XXXX
**    macros, or using the ENDOFARGS (and optionally STARTOFARGS) macro.
**
** ^SIDE-EFECTS:
**    The argd is initialized for use.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - compile the command name, purpose, and description if they were given
**    - if ENDOFARGS was used without STARTOFARGS, then shift each item in
**      the array down by one position.
**    - initialize the parse-flags to the default settings
**    - initialize the default argument search-list
**    - allocate and initialize the command-context
**    - for each command-line argument in argd
**      - compile the ad_prompt field and set the default initial ARGXXX flags
**      end-for
***^^**********************************************************************/
#ifdef __ANSI_C__
X   void init_args( ARGDESC argd[] )
#endif
{
X   register ARGDESC *ad, *anchor;
X   int  ad_count = 0;
X   BOOL old_style = FALSE;
X   char *description = (char *)NULL, *purpose = (char *)NULL;
X
X   if ( !argd )  return;
X
X      /* dont initialize if its already been done */
X   if ( CMD_isINIT(argd) )  return;
X
X   if ( !ARG_isEND(argd) )  old_style = TRUE;
X
X      /* determine the argument count and preprocess each ad-entry */
X   anchor = ( old_style ) ? argd : ARG_FIRST(argd);
X   for ( ad = anchor ; !ARG_isEND(ad) ; ARG_ADVANCE(ad) )  {
X      ad_count++;
X
X         /* set-up any positional args that we know of */
X      if ( ARG_isPOSONLY(ad) )  BSET( arg_flags(ad), ARGPOS );
X
X         /* set-up any default ARGNOVALs that we know of */
X      if (ARG_isBOOLEAN(ad) || ARG_isPSEUDOARG(ad))
X         BSET( arg_flags(ad), ARGNOVAL );
X
X      description = get_description( (char *)arg_sname(ad) );
X      if ( description )  {
X         BSET(arg_flags(ad), ARGDESCRIBED);
X         strcpy( (char *)arg_sdesc(ad), description );
X      }
X   }
X
X      /* shift all the entries down one to make room for a new 1st-entry
X      ** It would've been nice to just swap the 1st and last entries but
X      ** I have to preserve the order that all positional parameters are
X      ** given in.
X      */
X   if ( old_style  &&  ad_count > 0 ) {
X      anchor = ad + 1;  /* save this position */
X      for ( ; ad > argd ; ARG_RETREAT(ad) ) {
X         memcpy( (ARBPTR)ad, (ARBPTR)(ad - 1), sizeof(ARGDESC) );
X      }/*for*/
X      memcpy( (ARBPTR)argd, (ARBPTR)anchor, sizeof(ARGDESC) );
X   }
X   else  anchor = ad;
X
X     /* set default parse-flags */
X   cmd_flags(argd) = pa_DEFAULTS;
X
X     /* if new-style, get the purpose from the command name */
X   if ( !old_style  &&  cmd_name(argd) ) {
X      purpose = get_description( (char *)cmd_name(argd) );
X   }
X
X      /* set the program name */
X   if ( ProgName  &&  *ProgName  &&  !cmd_name(argd) ) {
X      cmd_name(argd) = ProgName;
X   }
X
X      /* set context */
X   if ( !cmd_context(argd) )  {
X      cmd_context(argd)  = (CTXDESC *) anchor;
X      cmd_state(argd)    = ( old_style ) ? ps_OLDSTYLE : (ps_flags_t) 0;
X      cmd_argv0(argd)    = cmd_name(argd);
X      cmd_purpose(argd)  = purpose;
X      cmd_ptrs(argd) = (ARGDPTRS *)malloc( sizeof(ARGDPTRS) );
X      if ( !cmd_ptrs(argd) ) {
X         syserr( "malloc failed in init_args()" );
X      }
X      if ( argd == Default_ArgDesc ) {
X         cmd_defargs(argd)  = ARGDESCNULL;
X      }
X      else {
X         if ( !CMD_isINIT(Default_ArgDesc) )  init_args( Default_ArgDesc );
X         cmd_defargs(argd)  = Default_ArgDesc;
X      }
X      cmd_list(argd) = ARGDESCNULL;
#ifdef amiga_style
X      cmd_prev(argd) = ARGDESCNULL;
#endif
X   }
}
X
X
/***************************************************************************
** ^FUNCTION: reset_args - (re)set a command for parsing
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   static VOID reset_args( argd )
/*
** ^PARAMETERS:
*/
X   ARGDESC argd[];
/*    -- array or command-line arguments to be reset
*/
#endif  /* !__ANSI_C__ */
X
/* ^DESCRIPTION:
**    Reset_args will prepare a command for parsing. The command-state is
**    initialized and each argument is reset to "unmatched".
**
** ^REQUIREMENTS:
**    <argd> must point to an array that has been declared using the CMD_XXXX
**    macros, or using the ENDOFARGS (and optionally STARTOFARGS) macro.
**
** ^SIDE-EFECTS:
**    resets the ARG flags of each argument and the command-state of argd.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - reset the command-context to be (as of yet) unparsed
**    - reset the ARG flags of the programmer & default argument descriptors
***^^**********************************************************************/
X
X   /* arg-flags to be reset before parsing a new command-line */
#define  ARGDEFAULTS  ( ARGGIVEN | ARGVALGIVEN | ARGKEYWORD | ARGVALSEP )
X
#ifdef __ANSI_C__
X   static void reset_args( ARGDESC argd[] )
#endif
{
X   register ARGDESC *args, *ad;
X
X   if ( !CMD_isINIT(argd) )  init_args(argd);
X
X      /* reset the command context */
X   BCLEAR( cmd_state(argd), (ps_NOFLAGS|ps_NOCMDENV|ps_NOPARSECNTL) );
X   cmd_list(argd) = ARGDESCNULL;
#ifdef amiga_style
X   cmd_prev(argd) = ARGDESCNULL; /* No argument requested */
#endif
X
X      /* clear out any cruft in the argument descriptors */
X   for ( args = argd ; args ; args = cmd_defargs(args) ) {
X      for ( ad = ARG_FIRST(args) ; !ARG_isEND(ad) ; ARG_ADVANCE(ad) ) {
X         BCLEAR( arg_flags(ad), ARGDEFAULTS );
X
#if ( defined(vms_style)  ||  defined(amiga_style) )
X            /* vms and amiga use keywords only */
X         BSET( arg_flags(ad), ARGKEYWORD );
#endif
X      }/*for*/
X   }/*for*/
}
X
#undef ARGDEFAULTS
X
X
/***************************************************************************
** ^FUNCTION: prompt_user - prompt the user for a missing argument
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   static BOOL prompt_user( ad, argname )
/*
** ^PARAMETERS:
*/
X   ARGDESC *ad;
/*    -- pointer to the argument to be supplied by the user
*/
X   char *argname;
/*    -- name of the argument to be supplied by the user
*/
#endif  /* !__ANSI_C__ */
X
/* ^DESCRIPTION:
**    Prompt_user will attempt to prompt the user to supply (on standard input)
**    the value for the given argument. If the argument is a list, then each
**    item of the list should be given on a separate line (with a blank line
**    terminating the list).
**
**    No special "escaping" or translation is performed on the resulting user
**    input, hence any whitespace ro quotes will be considered as part of the
**    argument value.
**
**    If stdin is NOT associated with a terminal then no prompting is performed
**    and FALSE is returned.  If the user enters in invalid value for the
**    argument then the value is discarded and FALSE is returned (so you
**    better get it right the first time).
**
** ^REQUIREMENTS:
**    Only the first 255 characters of user input is used.
**
** ^SIDE-EFECTS:
**    Modifies <ad> accordingly.
**
** ^RETURN-VALUE:
**    FALSE if there is an error, TRUE otherwise.
**
** ^ALGORITHM:
**    - if stdin is not connected to a terminal return FALSE
**    - if argument is not a list then
**      - get string from user
**      - attempt to convert the string
**      - return TRUE upon success and FALSE upon failure
**    - else (the argument is a list)
**      - while (user has not entered a blank line) do
**        - get string from user
**        - attempt to convert the string
**        - return FALSE upon failure (otherwise continue to loop)
**        end-while
**      end-if
***^^**********************************************************************/
#ifdef __ANSI_C__
X   static BOOL prompt_user( ARGDESC *ad, const char *argname )
#endif
{
X   BOOL  error = FALSE, first = TRUE;
X   int   end;
X   char  buf[ MAXLINE ];
X
X   if ( !is_interactive(STDIN) )  return  FALSE;
X
X   if ( ARG_isMULTIVAL(ad) ) {
X      fprintf(stderr, "Enter one %s per-line ", argname);
X      fprintf(stderr, "(enter a blank line to stop).\n");
X   }
X
X   do {  /* need repeated prompting for an ARGLIST or an ARGVEC */
X      /* get argument from user */
X      *buf='\0';
#ifdef vms_style
X      fprintf(stderr, "\r%s> ", argname);
#else
X      fprintf(stderr, "\rEnter %s: ", argname);
#endif
X      fflush( stderr );
X      if ( !fgets(buf, MAXLINE, stdin) )  *buf = '\0';
X
X      /* discard the newline */
X      end = strlen(buf) - 1;
X      if ( end >= 0  &&  buf[end] == '\n' )
X         buf[ end ] = '\0';
X
X      /* make sure we read something! */
X      if ( *buf == '\0' ) {
X         if ( first ) {
X            usrerr( "error - no %s given", argname );
X            error = TRUE;
X         }
X         continue;
X      }
X
X      /* try to convert what we read (remember - buf is transient) */
X      if (  HANDLE( ad, buf, TRUE ) )
X         BSET(arg_flags(ad), ARGGIVEN | ARGVALGIVEN);
X      else
X         error = TRUE;
X
X      first = FALSE;
X   } while ( !error &&  ARG_isMULTIVAL(ad)  &&  *buf );
X
X   return  (error) ? FALSE : TRUE;
}
X
X
/***************************************************************************
** ^FUNCTION: verify_argreqs - check for any missing required arguments
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   static BOOL verify_argreqs( cmd, parse_status )
/*
** ^PARAMETERS:
*/
X   ARGDESC *cmd;
/*    -- the argdesc-array of command-line arguments.
*/
X   int *parse_status;
/*    -- address of current parse_status
*/
#endif  /* !__ANSI_C__ */
X
/* ^DESCRIPTION:
**    Verify_argreqs will reset parse_status to a success-value if it 
**    previously indicated a syntax error but pa_IGNORE is set to ignore
**    syntax error.  Verify_argreqs will then verify that any required
**    command-line arguments have been supplied (if they were not and
**    pa_PROMPT is set, then the missing values will be prompted for).
**
** ^REQUIREMENTS:
**    <cmd> must point to an array that has been declared using the CMD_XXXX
**    macros, or using the ENDOFARGS (and optionally STARTOFARGS) macro.
**
**    parse_status should be a pointer to the result of a previous call to
**    {f,l,s,v,}parseargs.
**
** ^SIDE-EFECTS:
**    The arg-descs for missing arguments may be modified by prompt_user.
**    parse_status will be modified to indicate whether or not a syntax
**    error really has occurred (it will be pe_SUCCESS if all is hunky-dory).
**
** ^RETURN-VALUE:
**    FALSE if there is an error, TRUE otherwise.
**
** ^ALGORITHM:
**    - if parse_status is a syntax error but pa_IGNORE is set
**      - ignore the error by resetting the status to pe_SUCCESS
**      end-if
**    - if pa_NOCHECK is not set then check for any missing required
**      arguments (using prompt_user to get the values if pa_PROMPT is set).
***^^**********************************************************************/
#ifdef __ANSI_C__
X   static BOOL verify_argreqs( ARGDESC *cmd, int *parse_status )
#endif
{
X   register ARGDESC *ad;
X   BOOL error;
X   argName_t  s;
X
X   if ( !CMD_isINIT(cmd) )  init_args(cmd);
X
X   if ( *parse_status == pe_SYNTAX  &&  BTEST(cmd_flags(cmd), pa_IGNORE) ) {
X      *parse_status = pe_SUCCESS;
X   }
X   error = ( *parse_status != pe_SUCCESS ) ? TRUE : FALSE;
X
X   if ( !BTEST(cmd_flags(cmd), pa_NOCHECK) ) {
X      for (ad = ARG_FIRST(cmd) ; !ARG_isEND(ad) ; ARG_ADVANCE(ad)) {
X         if (arg_type(ad) == argDummy)  continue;
X
X         if ( ARG_isREQUIRED(ad)  &&  !ARG_isGIVEN(ad) ) {
X               /* still didn't get a value... sigh */
X            if ( ARG_isPOSITIONAL(ad) ) {
X               (VOID) get_name( arg_sname(ad), s );
X               usrerr("%s required", s);
X            }
X            else {
#ifdef amiga_style
X               (VOID) get_keyword( arg_sname(ad), s );
X               usrerr("argument required for %s keyword", s);
#endif
#ifdef ibm_style
X               (VOID) get_name( arg_sname(ad), s );
X               {
X                  char c, *pfx = getenv( "SWITCHAR" );
X                  c = ( pfx &&  *pfx ) ? *pfx : '/';
X                  usrerr("%s required for %c%c switch", s, c, arg_cname(ad));
X               }
#endif
#ifdef unix_style
X               (VOID) get_name( arg_sname(ad), s );
X               usrerr("%s required for %c%c flag", s, c_OPT_PFX, arg_cname(ad));
#endif
#ifdef vms_style
X               (VOID) get_keyword( arg_sname(ad), s );
X               usrerr("value required for %c%s qualifier", *s_KWD_PFX, s);
#endif
X            }
X
X            if ( !error  &&  BTEST(cmd_flags(cmd), pa_PROMPT) ) {
X               if ( !prompt_user(ad, s) )  error = TRUE;
X            }
X            else if ( !error ) {
X               error = TRUE;
X            }
X         }/*if*/
X      }/*for*/
X   }/*if*/
X
X   return  (error) ? FALSE : TRUE;
}
X
X
/***************************************************************************
** ^FUNCTION: read_flags - read bitmask-flags in a string
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   static argMask_t read_flags( str, c_tbl, m_tbl )
/*
** ^PARAMETERS:
*/
X   char *str;
/*    -- the string to parse
*/
X   char c_tbl[];
/*    -- a (NUL-terminated) array of alpha-numeric characters Each character
**    is the first letter/number of a token.  If the character is lowercase,
**    then the corresponding mask is set, if it is uppercase, the corresponding
**    mask is cleared.
*/
X   argMask_t m_tbl[];
/*    -- a table of bitmasks for the given character-table. The mask to use
**       for c_tbl[i] is m_tbl[i].
*/
#endif  /* !__ANSI_C__ */
X
/* ^DESCRIPTION:
**    Read_flags will parse the given string for any flags present in c_tbl[].
**    When a flag is matched, its corresponding entry in m_tbl[] is set (or
**    cleared) in the currently matched set of flags.
**
**    When the given string has been completely scanned, the resulting
**    combination of masks that were matched is returned.
**
** ^REQUIREMENTS:
**    A '\0' or a ';' indicates the end of parsing. A token/mask may be negated
**    by preceding it with one of '!', '^', or '~'. Tokens must be separated by
**    one or more non-alphanumerics (other than '!', '~', and '^').
**
** ^SIDE-EFECTS:
**    None.
**
** ^RETURN-VALUE:
**    The combination of bitmasks indicated by the given string.
**
** ^ALGORITHM:
**    - set masks = 0
**    - for each "token" in str
**      - if token doesnt match any entries in c_tbl then continue
**      - let i = index of matched token in c_tbl
**      - if c_tbl[i] is lowercase then OR masks with m_tbl[i]
**      - if c_tbl[i] is uppercase then AND masks with NOT(m_tbl[i])
**      end-for
**    - return masks
***^^**********************************************************************/
X
X      /* macros for parsing flags */
#define  IS_NEGCHAR(c)  ( c == '!' || c == '^' || c == '~' )
#define  SKIP_TOK(s)    while ( isalnum(*s) || *s == '_' || *s == '-' ) s++
#define  SKIP_SEP(s)    while ( *s && !IS_NEGCHAR(*s) && !isalnum(*s) ) s++
#define  NEXT_TOK(s)    SKIP_TOK(s) ; SKIP_SEP(s)
#define  TOGGLE(x)      x = (x) ? FALSE : TRUE
X
#ifdef __ANSI_C__
X   static argMask_t read_flags(
X      const char *str, const char c_tbl[], const argMask_t m_tbl[]
X   )
#endif
{
X   char *pos = CHARNULL;
X   BOOL  negated = FALSE;
X   argMask_t  mask = 0, flags = 0;
X
X   while ( *str  &&  *str != ';' ) {
X      mask = 0;
X      negated = FALSE;
X
X      while ( IS_NEGCHAR(*str) ) {
X         TOGGLE(negated);
X         ++str;
X         SKIP_SEP(str);
X      }
X
X      if ( (pos = strchr( c_tbl, TOLOWER(*str) )) != CHARNULL ) {
X         mask = m_tbl[ (int)(pos - (char *)c_tbl) ];
X      }
X      else if ( (pos = strchr( c_tbl, TOUPPER(*str) )) != CHARNULL ) {
X         TOGGLE(negated);
X         mask = m_tbl[ (int)(pos - (char *)c_tbl) ];
X      }
X      else {
X         negated = FALSE;
X      }
X
X      NEXT_TOK(str);
X
X      if ( mask || negated ) {
X         if   (   negated  )  BCLEAR(flags, mask);
X         else /* !negated */  BSET(flags, mask);
X      }/*if*/
X   }/*while*/
X
X   return  flags;
}
X
#undef  IS_NEGCHAR
#undef  SKIP_TOK
#undef  SKIP_SEP
#undef  NEXT_TOK
#undef  TOGGLE
X
X
/***************************************************************************
** ^FUNCTION: get_usage_flags - determine user-defined usage-message format
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   static argMask_t get_usage_flags( cmd )
/*
** ^PARAMETERS:
*/
X   ARGDESC *cmd;
/*    -- command-structure to determine usage-flags for
*/
#endif  /* !__ANSI_C__ */
X
/* ^DESCRIPTION:
**    Through the use of an environment variable (or a VMS symbol), the user
**    may control the syntax and the verbosity of the command-usage messages
**    that are printed by parseargs. The desired level of verbosity may be
**    set by defining the environment variable "USAGECNTL" to be a com-
**    bination of strings (case insensitive). The value of each string con-
**    trols one of three different "modes" of behavior in the displaying
**    of usage messages:  The first "mode" is "verbose" mode, which con-
**    trols whether or not a detailed description of each argument should
**    accompany the usual command-line sysnopsis. If verbose mode is
**    "off", then only a command-line synopsis is printed (this is also
**    refferred to as "terse" mode). The other two "modes" control the
**    displaying of option syntax and long-option syntax. A mode may be
**    explicitly disabled by preceding its corresponding string with the `!'
**    character. The "modes" which correspond to the possible values of the
**    "USAGECNTL" environment variable are given by the following table.
**
**         "Quiet"        No usage message of any kind is displayed.
**
**         "Silent"       Same as Quiet.
**
**         "Paged"        The usage message is piped to a pager. The pager
**                        used is named by the "USAGE_PAGER" environment
**                        variable. If this variable is unset or empty (or
**                        is not the name of an executable program), the
**                        pager named by the "PAGER" environment variable
**                        us used.  If this variable is unset or empty (or
**                        is not the name of an executable program) then
**                        the program "/usr/ucb/more" is used.
**         "Description"  The command description is printed.
**
**         "Terse"        Terse mode, just print command-line synopsis.
**
**         "Verbose"      Verbose mode, print descriptions for each argument
**
**         "Options"      Option syntax is displayed.
**
**         "LongOpts"     Long-option syntax is displayed.
**
**         "KeyWords"     Same as "LongOpts".
**
**    If the environment variable "USAGECNTL" is empty or undefined, then
**    the default usage level (which is presently "Verbose + Options")
**    will be used.
**
** ^REQUIREMENTS:
**    <cmd> must point to an array that has been declared using the CMD_XXXX
**    macros, or using the ENDOFARGS (and optionally STARTOFARGS) macro.
**
** ^SIDE-EFECTS:
**    None.
**
** ^RETURN-VALUE:
**    The usage-flags corresponding to the value of $USAGECNTL
**
** ^ALGORITHM:
**    - If $USAGECNTL is NULL or empty, return the default flags
**    - get the value of $USAGECNTL and call read_flags to parse it.
**    - return the resulting flags.
***^^**********************************************************************/
#ifdef __ANSI_C__
X   static argMask_t get_usage_flags( const ARGDESC *cmd )
#endif
{
X   register  char  *usage_env;
X   argMask_t  usg_ctl = 0;
X   static CONST char  usage_tokens[] = {
X      'n', 'q', 's',
X      'T', 'v',
X      'o',
X      'l', 'k',
X      'd',
X      'p',
X      '\0'
X   };
X   static CONST argMask_t  usage_masks[] = {
X      usg_NONE,     usg_NONE,     usg_NONE,
X      usg_VERBOSE,  usg_VERBOSE,
X      usg_OPTS,
X      usg_LONGOPTS, usg_LONGOPTS,
X      usg_DESCRIPTION,
X      usg_PAGED
X   };
X
X   usage_env = getenv("USAGECNTL");
X   if ( !usage_env ) {
X      usg_ctl = usg_VERBOSE;
X   }
X   else {
X      usg_ctl = read_flags( usage_env, usage_tokens, usage_masks );
X   }
X
X   envfree( usage_env );
X
#ifdef unix_style
X      /* make sure we print at least one of options/keywords */
X   if ( !BTEST(usg_ctl, usg_OPTS|usg_LONGOPTS) ) {
X      if ( BTEST(cmd_flags(cmd), pa_KWDSONLY) ) {
X         BSET(usg_ctl, usg_LONGOPTS);
X      }
X      else {
X         BSET(usg_ctl, usg_OPTS);
X      }
X   }
#endif /* unix_style */
X
X   return  usg_ctl;
}
X
X
/***************************************************************************
** ^FUNCTION: get_parse_flags - determine user-defined parsing behavior
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   static VOID get_parse_flags( cmd )
/*
** ^PARAMETERS:
*/
X   ARGDESC *cmd;
/*    -- command-structure to determine parse-flags for
*/
#endif  /* !__ANSI_C__ */
X
/* ^DESCRIPTION:
**    The programmer may control parsing behavior through the use of
**    parsecntl(3). The user may set this behavior through the use of the
**    PARSECNTL environment variable.  By indicating any number of flags
**    (possibly negated) the user will directly modify the behavior of the
**    parseargs library. Flags may be combined by placing a `+' or `|'
**    character in between flags. A switch is negated by immediately preceding
**    it with a `!' or `-' character.  The possible "flags" are given by
**    the following table. Flags are case-insensitive.
**
**         "Prompt"      Prompt the user for any missing arguments that are
**                       required on the command-line. No special escaping
**                       or quoting is performed on the user input. Required
**                       arguments that expect a list of values will be
**                       repeatedly prompted for (one item per line) until a
**                       blank line (followed by a carriage return) is
**                       entered.
**
**         "Ignore"      Ignore any unrecognized or improperly specified
**                       command-line arguments and continue execution of
**                       the program. Normally, if an argument is unmatched
**                       (or is improperly specified), a usage message is
**                       printed program execution is terminated.
**
**         "OptsOnly"    Under UNIX, setting this flag will disable the
**                       parsing of long-option syntax. This will cause all
**                       arguments starting with '+' to always be treated as
**                       a positional parameter (instead of a long-option).
**
**         "KwdsOnly"    Under UNIX, setting this flag disables the parsing
**                       of single-character options.  This will cause all
**                       arguments starting with '-' to always be treated as
**                       a positional parameter (instead of an option).
**
**         "LoptsOnly"   Same as KwdsOnly.
**
**         "Flags1st"    Setting this flag causes the parseargs library to
**                       force any and all non-positional arguments to be
**                       specified before any positional ones.  As an exam-
**                       ple, under UNIX, if this flag is SET then parseargs
**                       will consider the command line "cmd -x arg" to con-
**                       sist of one option and one positional argument;
**                       however the command line "cmd arg -x" would be con-
**                       sidered to consist of two positional arguments (the
**                       -x option will be unmatched).
**
**                       If this flag is UNSET, then both of the previous
**                       examples are considered to consist of one option
**                       and one positional argument.
**
**         "CaseIgnore"  Setting this flag cause character-case to be
**                       ignored when attempting to match single-character
**                       argument names (i.e. causes "-i" and "-I" will be
**                       considered equivalent).
**
**    If the environment variable "PARSECNTL" is empty or undefined, then
**    parsing behavior set by the programmer is used.  If the programmer has
**    not explicitly used parsecntl(3) to modify the parsing behavior will
**    be "!Prompt + !Ignore" for Unix and AmigaDOS systems, "Prompt" for
**    VMS systems, and "CaseIgnore" for MS-DOS and OS/2 systems.
**
** ^REQUIREMENTS:
**    <cmd> must point to an array that has been declared using the CMD_XXXX
**    macros, or using the ENDOFARGS (and optionally STARTOFARGS) macro.
**
** ^SIDE-EFECTS:
**    Modifies the parse-flags of <cmd> is $PARSECNTL is non-null & non-empty.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - If $PARSECNTL is NULL or empty, return
**    - Else turn off all flags that may be set by $PARSECNTL
**    - get the value of $PARSECNTL and call read_flags to parse it.
**    - set the returned flags in the cmd-structure.
**    - return the resulting flags.
***^^**********************************************************************/
X
X   /* the combination of parse-flags that may be set via $PARSECNTL */
#define  pa_USRFLAGS \
X	(pa_PROMPT|pa_IGNORE|pa_ANYCASE|pa_OPTSONLY|pa_KWDSONLY|pa_FLAGS1ST)
X
#ifdef __ANSI_C__
X   static void get_parse_flags( ARGDESC *cmd )
#endif
{
X   register argMask_t  flags = 0;
X   char *parse_env;
X   static CONST char  parse_tokens[] = {
X      'c',
X      'd',
X      'f',
X      'i',
X      'p',
X      'o',
X      'l','k',
X      '\0'
X   };
X   static CONST argMask_t  parse_masks[] = {
X      pa_ANYCASE,
X      pa_DEFAULTS,
X      pa_FLAGS1ST,
X      pa_IGNORE,
X      pa_PROMPT,
X      pa_OPTSONLY,
X      pa_KWDSONLY, pa_KWDSONLY
X   };
X
X   if ( !CMD_isINIT(cmd) )  init_args( cmd );
X
X   if ( (parse_env = getenv("PARSECNTL"))  &&  *parse_env ) {
X      flags = read_flags( parse_env, parse_tokens, parse_masks );
X
X         /*
X         ** mask off all the flags that may be set by $PARSECNTL
X         ** (including any default flags).
X         */
X      BCLEAR( cmd_flags(cmd), pa_USRFLAGS );
X
X      cmd_flags(cmd) |= flags;
X   }/*if*/
X
X   envfree( parse_env );
}
X
#undef  pa_USRFLAGS
X
X
/***************************************************************************
** ^FUNCTION: parse_user_defaults - parse user-supplied default arguments
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   static VOID parse_user_defaults( cmd )
/*
** ^PARAMETERS:
*/
X   ARGDESC *cmd;
/*    -- the command-line object to parse
*/
#endif  /* !__ANSI_C__ */
X
/* ^DESCRIPTION:
**    Programs that use parseargs may be given default arguments under
**    Unix and PCs through the use of environment variables (symbols
**    are used for VMS systems). If a  C-program or shell-script uses
**    parseargs to implement a command named "cmd" then the environment
**    variable CMD_ARGS will be parsed for any "default" arguments before
**    the command-line is parsed.  The command-line will over-ride any
**    options that are specified in this environment variable (except that
**    ARGLISTs and ARGVECs set in "CMD_ARGS" will be appended from the
**    command-line if they are selected).
**
**    It is important to note that the contents of the CMD_ARGS environ-
**    ment variable are NOT expanded by the shell and hence any special
**    characters (such as quotes or back-slashes) will NOT be escaped or
**    removed by parseargs. Furthermore, it will not be possible to try and
**    use a tab, space, or newline character in the environment variable as
**    anything other than an argument separator.
**
**    Lastly, parts of an option specification in CMD_ARGS may NOT be
**    continued on the command-line. As an example, if -f requires an
**    argument and CMD_ARGS="-f", then the command-line "cmd  bah" will
**    NOT assign "bah" as the argument to -f but will instead complain
**    about a missing argument for -f. Similarly, if -l takes a list of
**    arguments and CMD_ARGS="-l item1 item2", then the command-line
**    "cmd  bah", will NOT assign "bah" to the end of the list containing
**    "item1" and "item2" but will instead treat "bah" as the first
**    positional parameter on the command-line.
**
** ^REQUIREMENTS:
**    <cmd> should be an array of ARGDESCs declared using the CMD_XXXX macros
**    or with the ENDOFARGS (and possible STARTOFARGS) macros.
**
** ^SIDE-EFECTS:
**    Any matched arguments have their ARGDESCs modified accordingly.
**    Also, the command-state is changed to reflect the fact that the
**    environment variable has been parsed. Also, after parsing the
**    variable, the command is partially parsed. Hence the pa_CONTINUE
**    is set to prevent arguments from being reset when the actual command
**    line is parsed.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - Get the value of the environment variable
**    - if the variable is null or empty then return.
**    - turn OFF the VARIABLE-NOT-YET-PARSED flag
**    - turn on the NOCHECK flag so we dont check for missing required
**      arguments in the variable (there may be more on the command-line).
**    - parse the string and convert any arguments matched
**    - set the NOCHECK flag back to its previous setting
**    - set the CONTINUE flag since the command-line is now partially parsed.
**    - return
***^^**********************************************************************/
X
#define  ENV_SUFFIX  "_ARGS"  /* suffix for env-variable with default args */
X
#ifdef __ANSI_C__
X   static VOID parse_user_defaults( ARGDESC *cmd )
#endif
{
X   int  rc;
X   char      *env_args;
X   argName_t  env_name;
X   VOID usage();
X
X   if ( !CMD_isINIT(cmd) )  init_args( cmd );
X
X   if ( BTEST(cmd_state(cmd), ps_NOCMDENV) )  return;
X
X   strucpy( env_name, ProgName );
X   strcat( env_name, ENV_SUFFIX );
X
X   env_args = getenv(env_name);
X   if ( env_args ) {
X      char **argv = (char **)NULL;
X      char *env_val = strdup( env_args );
X      argMask_t  saveflags = cmd_flags(cmd);
X
X      BSET( cmd_flags(cmd), pa_NOCHECK | pa_ARGV0 | pa_COPYF );
X      BSET( cmd_state(cmd), ps_NOCMDENV );
X
X          /* split line up into whitespace separated tokens */
X      if ( !strsplit( &argv, env_val, (char *)NULL ) ) {
X         free( argv );
X         return;
X      }
X
X      rc = parse_argv_style( argv, cmd );
X      free( argv );
X      cmd_list(cmd) = ARGDESCNULL;  /* dont allow lists to continue on */
#ifdef amiga_style
X      cmd_prev(cmd) = ARGDESCNULL;
#endif
X
X      if ( rc  &&  !BTEST(cmd_flags(cmd), pa_IGNORE) ) {
X         eprintf( "%s: syntax-error in %s \"%s\".\n",
X                  ProgName, USER_VARIABLE, env_name );
X         eprintf( "\t%s = \"%s\"\n\n", env_name, env_args );
X         free( env_val );
X         usage( cmd );
X         exit( 2 );
X      }
X
X      free( env_val );
X      cmd_flags(cmd) = (saveflags | pa_CONTINUE);
X   }
X
X   envfree( env_args );
}
X
#undef ENV_SUFFIX
X
X
/***************************************************************************
** ^FUNCTION: parse_init -- initialize an ARGDESC for parsing
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   static ARGDESC *parse_init( argdp )
/*
** ^PARAMETERS:
*/
X   ARGDESC *argdp[];
/*    -- pointer to the argument descriptor array.
*/
#endif  /* !__ANSI_C__ */
X
/* ^DESCRIPTION:
**    Parse_init will perform the usual pre-parsing actions such as checking
**    $PARSECNTL, parsing $<NAME>_ARGS, and resetting the command-line.
**
** ^REQUIREMENTS:
**    <argdp> should point to an array of ARGDESCs declared using the CMD_XXXX
**    macros or with the ENDOFARGS (and possible STARTOFARGS) macros.
**
** ^SIDE-EFECTS:
**	  Initialize argd and parses any default arguments.
**
** ^RETURN-VALUE:
**    pointer to the arg-descriptors
**
** ^ALGORITHM:
**    - check for a NULL argdesc-array
**    - initialize the command-object if necessary
**    - set ProgName
**    - read $PARSECNTL
**    - reset the command-line if necessary (and parse $<NAME>_ARGS)
**    - return the command-object
***^^**********************************************************************/
#ifdef __ANSI_C__
X   static ARGDESC *parse_init( ARGDESC *argdp[] )
#endif
{
X   register ARGDESC *cmd;
X   BOOL  unnamed = FALSE;
X
X      /* allow null argument descriptor */
X   if ( !(*argdp) )  *argdp = Empty_ArgDesc;
X
X      /* initialize command-structure */
X   if ( !CMD_isINIT(*argdp) )  init_args( *argdp );
X   cmd = *argdp;
X
X      /* save the name of this program (for error messages) */
X   if ( cmd_argv0(cmd)  &&  *(cmd_argv0(cmd)) ) {
X      ProgName = cmd_argv0(cmd);
X   }
X   else if ( cmd_name(cmd)  &&  *(cmd_name(cmd)) ) {
X      ProgName = cmd_name(cmd);
X   }
X   else {
X      unnamed = TRUE;
X   }
X
X      /* read PARSECNTL environment variable */
X   if ( !BTEST(cmd_state(cmd), ps_NOPARSECNTL) ) {
X      get_parse_flags(*argdp);
X   }
X
X      /* reset argd if necessary */
X   if ( !BTEST(cmd_flags(cmd), pa_CONTINUE) )  {
X      reset_args( *argdp );
X
X         /* get any options from <CMDNAME>_ARGS environment variable */
X      if ( !BTEST(cmd_flags(cmd), pa_NOCMDENV)  &&  !unnamed )  {
X         parse_user_defaults( *argdp );
X      }
X   }
X
X   return  *argdp;
}
X
X
/***************************************************************************
** ^FUNCTION: usage -- print a usage message
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   VOID usage( argd )
/*
** ^PARAMETERS:
*/
X   ARGDESC argd[];
/*    -- the description of expected arguments.
*/
#endif  /* !__ANSI_C__ */
X
/* ^DESCRIPTION:
**    Given an argdesc array, usage will print the usage for the given
**    command in the format specified by the user's USAGECNTL environment
**    variable.
**
** ^REQUIREMENTS:
**    <argd> should be an array of ARGDESCs declared using the CMD_XXXX macros
**    or with the ENDOFARGS (and possible STARTOFARGS) macros.
**
** ^SIDE-EFECTS:
**    Prints on stderr.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - initialize the command-object
**    - set ProgName
**    - read $USAGECNTL
**    - call <os>_usage()
***^^**********************************************************************/
#ifdef __ANSI_C__
X   void usage( const ARGDESC argd[] )
#endif
{
X   register CONST ARGDESC *cmd;
X   argMask_t  usg_ctl = 0;
X
X      /* allow null argument descriptor */
X   if ( !argd )  argd = Empty_ArgDesc;
X
X   if ( !CMD_isINIT(argd) )   init_args( (ARGDESC *)argd );
X   cmd = argd;
X
X   if ( cmd_argv0(cmd) && *(cmd_argv0(cmd)) ) {
X      ProgName = cmd_argv0(cmd);
X   }
X   else if ( cmd_name(cmd) && *(cmd_name(cmd)) ) {
X      ProgName = cmd_name(cmd);
X   }
X
X   usg_ctl = get_usage_flags(cmd);
X   if ( !BTEST(usg_ctl, usg_NONE) )  print_usage_style( argd, usg_ctl );
}
X
X
/***************************************************************************
** ^FUNCTION: parsecntl - control various aspects of command-line parsing
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   int  parsecntl( argd, cntl, mode, va_alist )
/*
** ^PARAMETERS:
*/
X   ARGDESC *argd;
/*    --  The command-object to operate upon
*/
X   parsecntl_t cntl;
/*    --  The control-code corresponding to the desired operation
*/
X   parsemode_t mode;
/*    --  The mode of the operation (read, write, or both)
*/
X   va_dcl
/*    -- any further args followed by the item to be read/written
*/
#endif  /* !__ANSI_C__ */
X  
/* ^DESCRIPTION:
**    Parsecntl will read and/or write the desired attributes of the given
**    command-object. The attributes to be operated upon are specified by
**    the second parameter to parsecntl. The desired mode (read, write, or
**    both) are specified by the third parameter to parsecntl. If the
**    operation to be performed is pc_ARGFLAGS, then the fourth argument to
**    parsecntl should be the keyword name of the argument whose flags are to
**    be retrieved.  The last parameter to parsecntl is always the object to
**    contain the attribute(s) to be read/written.  If the attribute(s) are
**    to be read (regardless of whether or not they are also being changed)
**    then the last argument should be a pointer to an object, otherwise the
**    last argument should be the object itself.
**
**    If mode is pc_READ, then the desired attributes are copied into the
**    object pointed to by the last parameter. If the mode is pc_WRITE, then
**    the attributes from the last parameter are copied to the command-
**    object. If mode is pc_RDWR, then the attributes pointed to by the last
**    parameter are copied to the command-object, and then the previous
**    value of these attributes (before they were overwritten) is copied
**    into the object pointed to by the last parameter.
**
**    If cntl is pc_ARGFLAGS, then the only valid mode pc_READ. All other
**    attributes may be written or read by parsecntl.
**
** ^REQUIREMENTS:
**    If mode is READ or READ+WRITE then the last argument should be the
**    address of the desired object, otherwise it should be the object itself.
**
** ^SIDE-EFECTS:
**    None if the mode is READ, otherwise, the desired attibutes are (re)set
**
** ^RETURN-VALUE:
**    pe_SYSTEM
**       -- If a system error occurred
** 
**    pe_SUCCESS
**       -- success, no errors encountered.
** 
**    pe_DEFARGS
**       -- an attempt (using parsecntl()) was made to change the
**          default arg-search list of a command to point to an argdesc-array
**          which already has the given command on its default arg-search list
**          (which would cause an infinite loop when attempting to match an
**          unknown command-line argument).
** 
**    pe_NOMATCH
**       -- unable to match the named argument for the pc_ARGFLAGS cntl
** 
**    pe_BADMODE
**       -- bad mode for given command in parsecntl()
** 
**    pe_BADCNTL
**       -- bad command for parsecntl
**
** ^ALGORITHM:
**    - if cntl is pc_ARGFLAGS
**      - if mode is pc_WRITE or pc_RDWR then return pe_BADMODE
**      - get the argument name and try to match it.
**      - if no match, then return pe_NOMATCH
**      - copy the flags for the matched argument
**      - return pe_SUCCESS
**    - else if cntl is pc_PARSEFLAGS
**      - save the current parseflags.
**      - if requested, (re)set the current flags
**      - if requested, copy the old flags
**      - return pe_SUCCESS
**    - else if cntl is pc_DEFARGS
**      - save the current default-args
**      - make sure the given default-args does not already contain argd
**      - if the above assertion failed, return pe_DEFARGS
**      - if requested, (re)set the default-args
**      - if requested, copy the old default-args
**      - return pe_SUCCESS
**    - else ( cntl must be in {pc_NAME, pc_PURPOSE, pc_DESCRIPTION} )
**      - save the current setting of the attribute-string
**      - if requested, (re)set the current attribute-string
**      - if requested, copy the old attribute-string
**      - return pe_SUCCESS
**      endif
***^^**********************************************************************/
X
X   /*
X   ** define some convenience macros to determine if we are
X   ** reading/writing from/to the argdesc-array.
X   */
#define isREADING(pmode)  (pmode == pc_READ  || pmode == pc_RDWR)
#define isWRITING(pmode)  (pmode == pc_WRITE || pmode == pc_RDWR)
X
#ifdef __ANSI_C__
X   int parsecntl( ARGDESC *argd, parsecntl_t cntl, parsemode_t mode, ... )
#endif
{
X   register ARGDESC  *cmd;
X   register int  rc = pe_SUCCESS;
X   va_list   ap;
X
X   if ( !argd )   return   rc;
X   if ( !CMD_isINIT(argd) )   init_args(argd);
X   cmd = argd;
X   VA_START( ap, mode );   /* get argument to match */
X
X      /* now figure out what to do and go do it! */
X   switch( cntl ) {
X      case  pc_ARGFLAGS :
X         {
X            register ARGDESC *ad, *args;
X            char *name = VA_ARG( ap, char * );
X            int  *argflags;
X            BOOL  is_match = FALSE;
X
X            for (args = argd; !is_match  &&  args; args = cmd_defargs(argd) ) {
X               for (ad = ARG_FIRST(args); !ARG_isEND(ad); ARG_ADVANCE(ad) ) {
X                  if (arg_type(ad) == argDummy)   continue;
X                  if ( match(name, arg_sname(ad)) == 0 ) {
X                     is_match = TRUE;
X                     break;
X                  }
X               }/*foreach arg*/
X            }/*foreach argdesc*/
X
X            if ( !is_match ) {
X               VA_END(ap);
X               return  pe_NOMATCH;
X            }
X
X            if ( isREADING(mode) ) {
X              argflags = VA_ARG( ap, int * );
X              *argflags = (int) arg_flags(ad);
X            }
X            else {
X              rc = pe_BADMODE;  /* parsecntl() wont set ARGFLAGS */
X            }
X         }/*block*/
X         break;
X
X      case  pc_PARSEFLAGS :
X         {
X            int *pflags, flags;
X
X            if ( isREADING(mode) ) {
X              pflags = VA_ARG( ap, int * );
X              flags = (int) cmd_flags(cmd);
X            }
X            else {
X              flags = VA_ARG( ap, int );
X              pflags = &flags;
X            }
X
X            if ( isWRITING(mode) )  cmd_flags(cmd) = (argMask_t) *pflags;
X            if ( isREADING(mode) )  *pflags = flags;
X         }/*block*/
X         break;
X
X      case  pc_DEFARGS :
X         {
X            ARGDESC **pdefargd, *defargd;
X
X            if ( isREADING(mode) ) {
X               pdefargd = VA_ARG( ap, ARGDESC ** );
X               defargd = cmd_defargs(cmd);
X            }
X            else {
X               defargd = VA_ARG( ap, ARGDESC * );
X               pdefargd = &defargd;
X            }
X
X            if ( isWRITING(mode) ) {
X               ARGDESC *args;
X
X               if ( !CMD_isINIT(*pdefargd) )  init_args( *pdefargd );
X
X                  /* make sure we are not on the default-argdesc's
X                  ** default-argument hierarchy (or an infinite loop
X                  ** will result).
X                  */
X               for ( args = *pdefargd; rc && args; args = cmd_defargs(args) ) {
X                  if ( args == cmd )  rc = pe_DEFARGS;
X               }
X               if ( !rc )  cmd_defargs(cmd) = *pdefargd;  /* set new defaults */
X            }/*if*/
X
X            if ( isREADING(mode) )  *pdefargd = defargd;
X         }
X         break;
X
X      case  pc_NAME :
X      case  pc_PURPOSE :
X      case  pc_DESCRIPTION :
X         {
X            CONST char *str, **pstr;
X
X            if ( isREADING(mode) ) {
X               pstr = VA_ARG( ap, CONST char ** );
X               if      ( cntl == pc_NAME )        str = cmd_name(cmd);
X               else if ( cntl == pc_PURPOSE )     str = cmd_purpose(cmd);
X               else /* cntl == pc_DESCRIPTION */  str = cmd_description(cmd);
X            }
X            else {
X               str = VA_ARG( ap, CONST char * );
X               pstr = &str;
X            }
X
X            if ( isWRITING(mode) )  {
X               if      ( cntl == pc_NAME )        cmd_name(cmd) = *pstr;
X               else if ( cntl == pc_PURPOSE )     cmd_purpose(cmd) = *pstr;
X               else /* cntl == pc_DESCRIPTION */  cmd_description(cmd) = *pstr;
X            }
X            if ( isREADING(mode) )  *pstr = str;
X         }/*block*/
X         break;
X
X      default :
X         rc = pe_BADCNTL;
X         break;
X   }/*switch*/
X
X   VA_END( ap );
X   return   rc;
}
X
#undef  isREADING
#undef  isWRITING
X
X
/***************************************************************************
** ^FUNCTION: sparseargs - parse arguments in a string
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   int sparseargs( str, argd )
/*
** ^PARAMETERS:
*/
X   char    *str;
/*    -- string to parse
*/
X   ARGDESC *argd;
/*    -- pointer to argument descriptor table
*/
#endif  /* !__ANSI_C__ */
X  
/* ^DESCRIPTION:
**    Given a single string and an argdesc array, sparseargs will parse
**    arguments from a string in much the same manner as parseargs.
**    Sparseargs will split the given string up into a vector of whitespace
**    separated tokens and then attempt to parse the resultant vector as if
**    it were given as argv[] on the command-line.  NO special treatment is
**    given to characters such as single-quotes, double-quotes, or anything
**    else. Sparseargs will always assume that any whitespace characters are
**    intended as argument separators.
**
** ^REQUIREMENTS:
**    <str> should be non-NULL and non-empty
**
** ^SIDE-EFECTS:
**    <str> is modified by strsplit().
**    <argd> is modified accordingly as arguments are matched.
**
** ^RETURN-VALUE:
**    pe_SYSTEM
**       -- If a system error occurred
** 
**    pe_SUCCESS
**       -- success, no errors encountered.
** 
**    pe_SYNTAX
**       -- If a syntax error occurs in <str>
**
** ^ALGORITHM:
**    - save current parse-flags
**    - add pa_ARGV0 to current parse-flags
**    - split string up into a vector of tokens
**    - call parse init and then parse the arguments
**    - if syntax-error, print usage and exit
**    - restore parse-flags
**    - return the status from parsing the vector
***^^**********************************************************************/
#ifdef __ANSI_C__
X   int sparseargs( char *str, ARGDESC *argd )
#endif
{
X   argMask_t  saveflags;
X   char **argv;
X   int  rc = 0;
X
X   if ( !argd )  return  pe_SUCCESS;
X
X   if ( !CMD_isINIT(argd) )  init_args(argd);
X
X      /* save old flags & initialize set parse flags */
X   saveflags = cmd_flags(argd);
X   BSET(cmd_flags(argd), pa_ARGV0);
X
X      /* split line up into whitespace separated tokens */
X   if ( !strsplit( &argv, str, (char *)NULL ) ) {
X      free( argv );
X      return  rc;
X   }
X
X   rc = parse_argv_style( argv, parse_init( &argd ) );
X
X      /* reset previous parse flags */
X   cmd_flags(argd)  =  saveflags;
X
X      /* scan for missing required arguments */
X   if ( SYNTAX_ERROR(rc, argd) ) {
X      fputc( '\n', stderr );
X      usage( argd );
X      exit( 2 );
X   }
X
X   return  rc;
}
X
X
/***************************************************************************
** ^FUNCTION: fparseargs - parse arguments from a file
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   int fparseargs( fp, argd )
/*
** ^PARAMETERS:
*/
X   FILE    *fp;
/*    -- pointer to file to read (must already be open)
*/
X   ARGDESC *argd;
/*    -- pointer to argument descriptor table
*/
#endif  /* !__ANSI_C__ */
X
/* ^DESCRIPTION:
**    Given a readable input stream and an argdesc array, fparseargs will
**    parse arguments in a file in much the same manner as parseargs.  A
**    maximum-line length of 255 characters is imposed.  NO "escaping" of
**    any kind is performed. Comments of a limited form are permitted: if
**    the first non-whitespace character on a line is a '#' (or '!' for VMS)
**    then that entire line is considered a comment and is ignored.  If a
**    value is provided for an argument that is NOT a list or a vector, then
**    the value MUST be on the same line as the argument (in other words,
**    "-v val" is fine but "-v\nval" is a not).
**
** ^REQUIREMENTS:
**    <fp> should be non-NULL, already opened-for-reading, file-pointer
**
** ^SIDE-EFECTS:
**    <argd> is modified accordingly as arguments are matched.
**
** ^RETURN-VALUE:
**    pe_SYSTEM
**       -- If a system error occurred
** 
**    pe_SUCCESS
**       -- success, no errors encountered.
** 
**    pe_SYNTAX
**       -- if a syntax error occurs in the file
**
** ^ALGORITHM:
**    - save current parse-flags
**    - add pa_ARGV0 to current parse-flags
**    - add pa_NOCHECK to current parse-flags (dont check until eof)
**    - call parse init
**    - parse the first line of the file
**    - add pa_CONTINUE to current parse-flags
**    - parse the rest of the file
**    - restore parse-flags
**    - now check for missing args if required
**    - if syntax-error, print usage and exit
**    - return
***^^**********************************************************************/
X
#ifdef vms_style
#  define  c_COMMENT  '!'
#else
#  define  c_COMMENT  '#'
#endif
X
#ifdef __ANSI_C__
X   int fparseargs( FILE *fp, ARGDESC *argd )
#endif
{
X   int  rc;
X   char  text[ MAXLINE ];
X   argMask_t  saveflags;
X
X   if ( !argd )  return  0;
X
X   if ( !CMD_isINIT(argd) )  init_args(argd);
X
X      /* save old flags & initialize parse flags for first call */
X   saveflags = cmd_flags(argd);
X   BSET(cmd_flags(argd), pa_ARGV0 | pa_NOCHECK | pa_COPYF);
X
X   while ( !feof( fp ) ) {
X      if ( !fgets( text, MAXLINE, fp ) ) {
X         if ( ferror( fp ) )  {
X            cmd_flags(argd)  =  saveflags;
X            return  pe_SYSTEM;
X         }
X      }/*if*/
X
X         /* trim leading and trailing whitespace and check for comments */
X      (VOID) strtrim( text, (char *)NULL );
X      if ( !text  ||  !(*text)  ||  *text == c_COMMENT )  continue;
X
X      rc = sparseargs( text, argd );
X
X         /* set up parseflags for next call */
X      BSET(cmd_flags(argd), pa_CONTINUE);
X   }/*while !EOF*/
X
X      /* reset previous parse flags */
X   cmd_flags(argd)  =  saveflags;
X
X      /* scan for missing required args */
X   if ( SYNTAX_ERROR(rc, argd) ) {
X      fputc('\n', stderr);
X      usage( argd );
X      exit( 2 );
X   }
X
X   return  rc;
}
X
#undef  c_COMMENT
X
X
/***************************************************************************
** ^FUNCTION: lparseargs - parse arguments from a list
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   int lparseargs( argls, argd )
/*
** ^PARAMETERS:
*/
X   ArgList *argls;
/*    -- linked list of args to parse
*/
X   ARGDESC *argd;
/*    -- pointer to argument descriptor table
*/
#endif  /* !__ANSI_C__ */
X  
/* ^DESCRIPTION:
**    Given an ArgList and an argdesc array, lparseargs will parse arguments
**    in a file in much the same manner as parseargs.
**
** ^REQUIREMENTS:
**    <argls> should be an ArgList of strings
**
** ^SIDE-EFECTS:
**    <argd> is modified accordingly as arguments are matched.
**
** ^RETURN-VALUE:
**    pe_SYSTEM
**       -- If a system error occurred
** 
**    pe_SUCCESS
**       -- success, no errors encountered.
** 
**    pe_SYNTAX
**       -- if a syntax error occurs
**
** ^ALGORITHM:
**    - save current parse-flags
**    - add pa_ARGV0 to current parse-flags
**    - make a vector out of the ArgList
**    - call parse init
**    - restore parse-flags
**    - if syntax-error, print usage and exit
**    - return
***^^**********************************************************************/
#ifdef __ANSI_C__
X   int lparseargs( ArgList *argls, ARGDESC *argd )
#endif
{
X   int   i, argc = 0, rc = 0;
X   char **argv = (char **)NULL;
X   argMask_t   saveflags;
X   register    ArgList *ls;
X
X   if ( !argd )  return  0;
X   if ( !CMD_isINIT(argd) )   init_args(argd);
X
X      /* make 1st pass to count the args */
X   for ( ls = argls; ls ; ls = L_NEXT(ls) ) {
X      argc++;
X   }
X
X      /* allocate a NULL terminated arg-vector */
X   argv = (char **)malloc( (argc + 1) * sizeof(char *) );
X   if ( !argv )  return  pe_SYSTEM;
X   argv[ argc ] = (char *)NULL;
X
X      /* make 2nd pass to assign the elements of the vector */
X   for ( ls = argls, i = 0 ; ls ; ls = L_NEXT(ls), i++ ) {
X      argv[i] = L_STRING(ls);
X   }
X
X   saveflags = cmd_flags(argd);
X   BSET(cmd_flags(argd), pa_ARGV0);
X   rc = parse_argv_style( argv, parse_init( &argd ) );
X
X      /* reset previous parse-flags */
X   cmd_flags(argd) = saveflags;
X
X      /* scan for missing required arguments */
X   if ( SYNTAX_ERROR(rc, argd) ) {
X      fputc( '\n', stderr );
X      usage( argd );
X      exit( 2 );
X   }
X
X   return   rc;
}
X
X
/***************************************************************************
** ^FUNCTION: vparseargs - parse a variable-argument list
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   int vparseargs( argd, argc, va_alist )
/*
** ^PARAMETERS:
*/
X   ARGDESC  *argd;
/*    -- 
*/
X   int  argc;
/*    -- number of arguments to parse
*/
X   va_dcl
/*    -- the variable-list of arguments to parse
*/
#endif  /* !__ANSI_C__ */
X
/* ^DESCRIPTION:
**    Vparseargs takes an argdesc array, the number of arguments to parse,
**    and a (possibly NULL terminated) list of argument-strings and parses
**    them in much the same manner as parseargs.  Unlike sparseargs,
**    vparseargs assumes that all parameters are already split up into
**    tokens, hence any whitespace characters contained in any of the
**    string-parameters are used as is (and will be considered a part of
**    an argument name or value).
**
**
** ^REQUIREMENTS:
**    argc must contain the number of arguments to be parsed (NOT including
**    any terminating NULL pointer).  If a NULL pointer is given as one of
**    the arguments, and this NULL pointer appears before argc indicated
**    the last argument would appear, then the NULL pointer will end the
**    the list of arguments and argc is ignored.
**    
** ^SIDE-EFECTS:
**    <argd> is modified accordingly as arguments are matched.
**
** ^RETURN-VALUE:
**    pe_SYSTEM
**       -- If a system error occurred
** 
**    pe_SUCCESS
**       -- success, no errors encountered.
** 
**    pe_SYNTAX
**       -- if a syntax error occurs
**
** ^ALGORITHM:
**    - save current parse-flags
**    - add pa_ARGV0 to current parse-flags
**    - make a vector out of the variable list
**    - call parse init
**    - restore parse-flags
**    - if syntax-error, print usage and exit
**    - return
***^^**********************************************************************/
#ifdef __ANSI_C__
X   int vparseargs( ARGDESC *argd, int argc, ... )
#endif
{
X   register char   *arg;
X   int   i, rc = 0;
X   argMask_t   saveflags;
X   char **argv = (char **)NULL;
X   va_list   ap;
X
X   if ( !argd )   return   0;
X   if ( !CMD_isINIT(argd) )   init_args(argd);
X
X   saveflags = cmd_flags(argd);
X   BSET(cmd_flags(argd), pa_ARGV0 | pa_COPYF);
X
X      /* allocate a NULL terminated arg-vector */
X   argv = (char **) malloc( (argc + 1) * sizeof(char *) );
X   if ( !argv )  return  pe_SYSTEM;
X   argv[ argc ] = (char *)NULL;
X
X   VA_START(ap, argc);
X   for ( i = 0; i < argc  &&  (arg = VA_ARG(ap, char *)) ; i++ ) {
X      argv[i] = arg;
X   }
X   VA_END(ap);
X
X   rc = parse_argv_style( argv, parse_init( &argd ) );
X
X      /* reset previous parse-flags */
X   cmd_flags(argd) = saveflags;
X
X      /* scan for missing required arguments */
X   if ( SYNTAX_ERROR(rc, argd) ) {
X      fputc( '\n', stderr );
X      usage( argd );
X      exit( 2 );
X   }
X
X   return   rc;
}
X
X
/***************************************************************************
** ^FUNCTION: parseargs -- parse an argument vector
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
X   int parseargs( argv, argd )
/*
** ^PARAMETERS:
*/
X   char  *argv[];
/*    -- pointer to the argument vector as passed to main().
*/
X  ARGDESC argd[];
/*    -- the argument descriptor array.
*/
#endif  /* !__ANSI_C__ */
X
/* ^DESCRIPTION:
**    Given a vector of string-valued arguments such as that passed to main
**    and a vector 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.
**
** ^REQUIREMENTS:
**    <argv> must be non-NULL and have a NULL pointer as its last item.
**
** ^SIDE-EFECTS:
**    <argd> is modified accordingly as arguments are matched.
**
** ^RETURN-VALUE:
**    pe_SYSTEM
**       -- If a system error occurred
** 
**    pe_SUCCESS
**       -- success, no errors encountered.
** 
**    pe_SYNTAX
**       -- if a syntax error occurs
**
** ^ALGORITHM:
**    - call parse init
**    - if syntax-error, print usage and exit
**    - return
***^^**********************************************************************/
#ifdef __ANSI_C__
X   int parseargs( char *argv[], ARGDESC argd[] )
#endif
{
X   register ARGDESC *cmd;
X   register char **av = argv;
X   int rc = pe_SUCCESS;
X   argMask_t  saveflags;
X
X      /* allow null argument descriptor */
X   if ( !argd )  argd = Empty_ArgDesc;
X
X      /* initialize command-structure */
X   if ( !CMD_isINIT(argd) )  init_args( argd );
X   cmd = argd;
X   saveflags = cmd_flags(cmd);
X
X   if ( argv  &&  !BTEST(pa_ARGV0, cmd_flags(cmd)) ) {
X      cmd_argv0(cmd) = basename( *av++ );
X   }/*if*/
X
X   rc = parse_argv_style( av, parse_init( &argd ) );
X
X      /* reset previous parse-flags */
X   cmd_flags(cmd) = saveflags;
X
X      /* scan for missing required arguments */
X   if ( SYNTAX_ERROR(rc, argd) ) {
X      fputc( '\n', stderr );
X      usage( argd );
X      exit( 2 );
X   }
X
X   return  rc;
}
SHAR_EOF
echo 'File parseargs/xparse.c is complete' &&
chmod 0664 parseargs/xparse.c ||
echo 'restore of parseargs/xparse.c failed'
Wc_c="`wc -c < 'parseargs/xparse.c'`"
test 70930 -eq "$Wc_c" ||
	echo 'parseargs/xparse.c: original size 70930, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
rm -f _shar_seq_.tmp
echo You have unpacked the last part
exit 0
exit 0 # Just in case...
-- 
Kent Landfield                   INTERNET: kent at sparky.IMD.Sterling.COM
Sterling Software, IMD           UUCP:     uunet!sparky!kent
Phone:    (402) 291-8300         FAX:      (402) 291-4362
Please send comp.sources.misc-related mail to kent at uunet.uu.net.



More information about the Comp.sources.misc mailing list