[comp.sources.misc] v02i088: argproc

dan@srs.UUCP (04/05/88)

Submitted-By: "A. Nonymous" <dan@srs.UUCP>

Archive-Name: argproc


comp.sources.misc: Volume 2, Issue 88
Submitted-By: "A. Nonymous" <dan@srs.UUCP>
Archive-Name: argproc

#!/bin/sh
#
# Recently, there's been discussion on comp.lang.c and comp.unix.wizards
# about what a convenient command line option parser should be like.
# By popular demand, here is our contribution to the cause: argproc().
# It has saved us a lot of programming time; perhaps it will help others.
# It isn't getopt compatible, but you can't have everything.
# Perhaps somebody would like to make it conform to getopt()?  Any takers?
#
# Dan Kegel   "... earn it anew if thou wouldst possess it."  - Goethe: Faust
#  srs!dan@cs.rochester.edu  rochester!srs!dan dan%srs.uucp@harvard.harvard.edu
#
# shar archiver, delete everything above the #!/bin/sh line
# and run through sh (not csh)
#
echo 'shar: extracting "argproc.doc" (5442 characters)'
# 'argproc.doc' has a checksum of 29484 on BSD and 54780 on System V.
sed 's/^X//' > argproc.doc << 'XXX_EOF_XXX'
Xargproc - process command line arguments
X
XSYNOPSIS
X     #include "argproc.h"
X
X     long argproc(argc, argv, format [ , pointer ] . . .  )
X     int argc;
X     char *argv[], *format;
X
X     Format string contents:
X	-  introduces one or more switches with single-letter names
X	=  is same as -, but sets a boolean telling whether each switch 
X	   actually appeared on the command line
X	{} surround switches with long names
X	%s, %d, %f, %hd, %ld, %lf, etc. may appear alone to indicate 
X	   positional parameters, or after a switch name to indicate 
X	   arguments to that switch.  These are processed by sscanf().
X	,  separates arguments of a single switch
X	[] surround optional arguments
X
XNOTICE
X     Argproc is a routine written by Steve Colwell at S.R. Systems.
X     It is being posted to Usenet to stimulate discussion about
X     command line argument parsers, and may be freely used and copied
X     for any purpose.  This routine has been in daily use for over a year
X     now; however, the version being posted was modified to use vsprintf
X     instead of _doprnt, and has been tested only lightly.
X
XDESCRIPTION
X     This routine provides an easy  way  to  parse  command  line
X     arguments.   The  main  routine passes its argc, argv, and a
X     scanf-type format string to argproc, which parses  the  com-
X     mand  line  for  parameters and switches as described below,
X     returning the various values in the pointed-to variables.
X     It does not alter argv[].
X
X     Each entry in the format string has  one  of  the  following
X     forms:
X
X     -XYZ X, Y, and Z are the one-char names of boolean flags.
X
X     -X%s %s is a scanf-type format specification, which is  used
X          only if -X precedes it.
X
X     -X[%s] %s is a optional scanf-type format specification, which 
X	 may or may not be supplied after the -X.
X         -X[%d,%s] is allowed, but -X%d[,%s] is not supported.
X
X     =X   X is a boolean flag, a boolean value is put in the associated
X	  variable.  The '=' may be used in place of the '-' in any
X	  of the above formats.
X
X     {=XYZ}
X	  XYZ is a many-char name for a single boolean flag.
X     {-outfile%s}
X	  'outfile' is a many-char name for a flag with a string argument.
X
X     %s   the next argument that doesn't start with - is taken as a %s 
X          type.
X
X     Generally, anywhere a "%d" or "%s" is listed above, ANY scanf-style
X     format specifier may appear.
X
X     The only way to have a switch with a greater than  one  char
X     name  is  by  using the braces.  For example, "{-longname}".
X     All other flag names are only one character long.  The flags
X     are  detected anywhere in the argument list, no matter where
X     they appear in the format string.
X
X     A single argument may contain more than one format  command.
X     For  example,  "%d,%d"  gets  two  integers,  seperated by a
X     comma, from one argument.  This form  implies  two  relevant
X     bits in the returned bit map as well as two variables in the
X     variable list.
X
X     A format may use the scanf "%*c" form to throw away an argu-
X     ment  or  a  part  of  an  argument.   In this case a bit is
X     assigned, but no variable is used as there is  no  value  to
X     receive.   This may be used to get two ints separated by any
X     char (ie. "%d%*c%d" to read 5,6 5-6 etc).
X
X     The order of switches in the format string doesn't imply
X     that the switches must be given in any order on the commandline.
X
XRETURN VALUE
X     The return value is -1 for error.
X
X     If there was no error, one bit is set for each unit of the 
X     format string which was successfully parsed.
X     This turns out to be difficult to use in practice, especially when
X     adding new options; I suggest using the = notation instead.
X     (A unit is a flag or argument format command in the format string.
X      For example, "-moyx"  has  four units and "-X%d" has two units.
X      The leftmost unit  is  assigned bit  0,  the  second unit is bit 1, 
X      etc.  If there are more than 32 "units" in the format string,
X      only the first 32 have bits in the return value.
X      This is one reason why the equals sign "=x" notation was added.)
X
XEXAMPLES
X     Always call lose_title(argv[0]) first thing in main().
X
X     boolean x, y, z;
X     argproc(argc,argv,"=xy =z", &x, &y, &z);
X
X     short a = 5;
X     argproc(argc,argv,"-a%hd", &a);
X
X     char infile[100];
X     static char logfile[100] = "";
X     argproc(argc,argv,"{-logfile%s} %s", logfile, infile);
X
X     char ofile[100], pfile[100];
X     boolean pGiven;
X     argproc(argc,argv,"-o%s =p[%s]", ofile, &pGiven, pfile);
X
X     See also demo.c.
X
XDIAGNOSTICS
X     If the user enters an unknown option, argproc usually prints a message to
X     stderr and exits with a status of 1.
X     However, if the first character of the format string is 'W',
X     argproc prints an error message and returns with a value of -1.
X     If the first character of the format string is 'N',
X     argproc places an error message in the global ErrString, and returns 
X     with a value of -1.
X     See 'lose.doc'.
X
XBUGS
X     Longer switch names must occur before shorter ones to override them;
X     that is, the format string "-f {-fc}" will never allow the flag
X     -fc to be found, it will just give the error "no flag -c".
X
X     Does not support the "--" convention.
X
X     Does not allow spaces between an option and its argument
X     (e.g. "-o foo" does NOT match the format "-o%s"; "-ofoo" does).
XXX_EOF_XXX
if test 5442 -ne "`wc -c < argproc.doc`"
then
    echo 'shar: transmission error on "argproc.doc"'
fi
chk=`sum argproc.doc | awk '{print $1}'`
if test 29484 -ne $chk -a 54780 -ne $chk
then
    echo 'shar: checksum error on "argproc.doc"'
fi
echo 'shar: extracting "lose.doc" (1861 characters)'
# 'lose.doc' has a checksum of 29331 on BSD and 17779 on System V.
sed 's/^X//' > lose.doc << 'XXX_EOF_XXX'
Xlose, warn, lose_title - easy routines for printing error messages
X
XSYNOPSIS
X     extern char *ErrStr, *ProgTitle;
X
X     lose_title(title)
X     char *title;
X
X     lose(format [ , pointer ] . . .  )
X     char *format;
X
X     warn(format [ , pointer ] . . .  )
X     char *format;
X
X     #include "lose.h"		/* for values of errMode */
X
X     PrintErr(errMode, format [	, pointer ] . .	.  )
X     int errMode;		/* ERR_FATAL, ERR_WARN, or ERR_NOTE */
X     char *format;
X
XDESCRIPTION
X     These routines provide an easy to use error printing system.
X
X     lose_title	is called at the beginning  of	the  user's  main
X     with  argv[0] as title. This sets ProgTitle with the name of
X     the program.  Errors of various severity levels can  now  be
X     processed with calls to lose, warn or PrintErr.
X
X     lose prints an error message (described below) and	exits the
X     program with value	1.
X
X     warn prints an error message, sets	errno to 0, and	returns.
X
X     PrintErr performs one of the above	depending on the value of
X     errMode;  ERR_FATAL selects lose, ERR_WARN	selects	warn, and
X     ERR_NOTE just places the error message in ErrStr.
X
XERROR MESSAGE CONSTRUCTION
X     The error message is built by calling vsprintf with the given
X     parameters; it is then prefixed with the program name.
X
X     Then, if errno (see man errno) is non-zero, the  appropriate
X     error message (see	sys_nerr in perror(3)) is appended.  Once
X     the error message has been	constructed, errno is reset to 0.
X
XEXAMPLE
X     lose_title(argv[0]);
X     if (argc != 2)
X	 Epitaph("Usage: %s filename", argv[0]);
X     ...
X     if ((fp=fopen(argv[1], "r")) == NULL)
X	 lose("Can't open source file %s", argv[1]);
X     ...
X     if (line is bad)
X	 Epitaph("Badly formatted line %s at line %d of file %s",
X	    line, lineNumber, argv[1]);
X
XSEE ALSO
X     perror, argproc, vsprintf, varargs
XXX_EOF_XXX
if test 1861 -ne "`wc -c < lose.doc`"
then
    echo 'shar: transmission error on "lose.doc"'
fi
chk=`sum lose.doc | awk '{print $1}'`
if test 29331 -ne $chk -a 17779 -ne $chk
then
    echo 'shar: checksum error on "lose.doc"'
fi
echo 'shar: extracting "demo.c" (4163 characters)'
# 'demo.c' has a checksum of 42031 on BSD and 5936 on System V.
sed 's/^X//' > demo.c << 'XXX_EOF_XXX'
X/*--------------------------------------------------------------------------
X Demo program for argproc().
X Demonstrates boolean, string, integer, and float argument-getting.
X
X Just about every program written here follows the scheme shown below:
X    #define default values
X    main() {
X	declare all command-line-settable variables and initialize
X	call lose_title() to register the program name for error messages
X	call argproc() to parse the command line
X	if any mistakes, print usage message and abort
X	else loop over argv[] and do the real work.
X    }
X--------------------------------------------------------------------------*/
X
X#include <stdio.h>
X#include <string.h>
X#include "argproc.h"
X
X/* Default values for variables set by command line options */
X#define DEF_X 32
X#define DEF_PI 3.1445
X#define DEF_S "this is a test"
X
Xmain(argc, argv)
X    int argc;
X    char **argv;
X{
X    /* These variables are set according to the command line */
X    boolean b_bool, c_bool, help_bool;
X    static char s_string[256] = DEF_S;
X    boolean s_string_given;	/* TRUE if user gave value for s_string */
X    int     x = DEF_X;
X    double  pi = DEF_PI;
X    char    arg_string[256];
X
X    /* Variables */
X    int i;
X
X    /* Register program name for use in error messages generated by lose() */
X    lose_title(argv[0]);
X
X    /* Parse command-line options.
X     * - and = introduce switches with single-letter names.
X     * {-name...} and {=name...} introduce switches with longer names.
X     * Switches introduced with = set or clear a corresponding boolean 
X     * so you can tell if the user gave them; - is only useful for switches
X     * that take arguments.
X     *
X     * Switch names followed by scanf-style format specifiers separated by 
X     * commas, e.g. "-m%d" or "-m%d,%f" etc., indicate required arguments.
X     * If such a switch appears in argv[i], its arguments must also appear
X     * in argv[i], separated by commas.
X     *
X     * Format specifiers surrounded by square brackets, e.g. "=m[%d]", indicate 
X     * optional arguments; both "... -m " and "... -m546 " are accepted.
X     * To tell if the optional argument was given, you must either
X     * check to see if its value changed during the call to argproc(), or
X     * (yech!) check the bitmask returned by argproc.  Checking the bitmask
X     * is rather difficult; nowadays, I just ignore argproc's return value.
X     */
X    argproc(argc, argv, "=bc {=help} =s%s -x%d {-pi%lf} %s",
X		&b_bool, &c_bool, &help_bool, &s_string_given, s_string, 
X		&x, &pi, arg_string);
X
X    /* If user didn't give a value for arg_string, or if she gave the
X     * -help switch, print a terse usage message.
X     * In a real program, this usage message is very helpful for the user
X     * who just needs a little reminder about which options do what.
X     */
X    if (!arg_string[0] || help_bool)
X	lose("Usage: %s file ...\n\
XDemonstration program for argproc().  Note that no spaces are allowed\n\
Xbetween a switch and its arguments (i.e. -x3 is okay, -x 3 is not allowed).\n\
XOptions:\n\
X    -help    Print this message\n\
X    -b       Set b_bool TRUE\n\
X    -c       Set c_bool TRUE\n\
X    -sSTRING Set string s  [default: %s]\n\
X    -xINT    Set integer x [default: %d]\n\
X    -piFLOAT Set double pi [default: %f]",
X	argv[0], DEF_S, DEF_X, DEF_PI);
X
X    /* argproc() accepts options anywhere on the command line; filenames
X     * and switches may be freely intermixed.  (C'mon, haven't you wished you
X     * could type "ls *.c -l", and have it work?)
X     * Therefore, to handle an arbitrary number of filenames, you must
X     * step through argv[], and treat each string not beginning with a dash
X     * as a filename argument.
X     */
X    for (i=1; i<argc; i++) {
X	if (argv[i][0] != '-') {
X	    /* Do something with this argument. */
X	    do_file(argv[i], b_bool, c_bool, s_string_given, s_string, x, pi);
X	}
X    }
X
X    exit(0);
X}
X
X/* Dummy routine to do something with each file argument. */
X
Xdo_file(arg, b, c, sGiven, s, x, pi)
X    char *arg;
X    boolean b, c, sGiven;
X    char *s;
X    int x;
X    double pi;
X{
X    (void) printf("arg=%s, b=%d, c=%d, sGiven=%d, s=%s, x=%d, pi=%f\n",
X	arg, b, c, sGiven, s, x, pi);
X}
X
XXX_EOF_XXX
if test 4163 -ne "`wc -c < demo.c`"
then
    echo 'shar: transmission error on "demo.c"'
fi
chk=`sum demo.c | awk '{print $1}'`
if test 42031 -ne $chk -a 5936 -ne $chk
then
    echo 'shar: checksum error on "demo.c"'
fi
echo 'shar: extracting "boolean.h" (328 characters)'
# 'boolean.h' has a checksum of 19429 on BSD and 21422 on System V.
sed 's/^X//' > boolean.h << 'XXX_EOF_XXX'
X/*--------------------------------------------------------------------------
X Local definitions; peoples' preferences for these are hard to predict...
X--------------------------------------------------------------------------*/
X
Xtypedef short boolean;
X
X#ifndef TRUE
X#define TRUE	((boolean) 1)
X#define FALSE	((boolean) 0)
X#endif
XXX_EOF_XXX
if test 328 -ne "`wc -c < boolean.h`"
then
    echo 'shar: transmission error on "boolean.h"'
fi
chk=`sum boolean.h | awk '{print $1}'`
if test 19429 -ne $chk -a 21422 -ne $chk
then
    echo 'shar: checksum error on "boolean.h"'
fi
echo 'shar: extracting "Makefile" (119 characters)'
# 'Makefile' has a checksum of 61063 on BSD and 9549 on System V.
sed 's/^X//' > Makefile << 'XXX_EOF_XXX'
X# Makefile for argproc() demo
X
XCFLAGS = -g
Xdemo: demo.o argproc.o lose.o
X	cc $(CFLAGS) demo.o argproc.o lose.o -o demo
XXX_EOF_XXX
if test 119 -ne "`wc -c < Makefile`"
then
    echo 'shar: transmission error on "Makefile"'
fi
chk=`sum Makefile | awk '{print $1}'`
if test 61063 -ne $chk -a 9549 -ne $chk
then
    echo 'shar: checksum error on "Makefile"'
fi
echo 'shar: extracting "argproc.c" (16865 characters)'
# 'argproc.c' has a checksum of 18141 on BSD and 31035 on System V.
sed 's/^X//' > argproc.c << 'XXX_EOF_XXX'
X/*--------------------------------------------------------------------------
X This module defines argproc(), a simple-to-use command line option grabber.
X It was written by Steve Colwell @ srs.uucp.
X--------------------------------------------------------------------------*/
X#include <stdio.h>
X#include <ctype.h>
X#include <string.h>
X#include <varargs.h>
X#include "boolean.h"
X#include "lose.h"
X
X#define ERROR -1
X
X/*LINTLIBRARY*/
X#ifndef lint
Xstatic char Sccs_id[] = "@(#)argproc.c	from 1.19 4/15/87 (C) SRS";
X#endif
X
X/* Dummy function declaration; this is lint's idea of a function prototype.
X * If you don't use lint, you can safely omit this section.
X */
X#ifdef lint
X    /*VARARGS3*/
X    long argproc(argc, argv, format) 
X	int argc;
X	char **argv, *format; 
X    {
X	/* Dummy function body; make lint think all the args were used. */
X	*argv[0]= *format; *format = (char) argc; return 0L; 
X    }
X#else
X
X#define MAX_ARG_NAME_LEN 20
X#define MAX_ARG_FORM_LEN 50
X#define MAX_ARGS	 200
X
X#define SWITCH 8
X#define NORM_ARG 4
X#define BOOL_FLAG (SWITCH|1) 
X#define BOOL_VAR_FLAG (SWITCH|2)
X#define VALUE_FLAG (SWITCH|NORM_ARG)
X
X/*----------------------------------------------------------------------
X The format string entry structure.  Holds different information, depending
X on whether the entry is a boolean "-x", and boolean with variable "=x",
X a normal argument "%s", a switched argument "-x%s", or a combination "=x%s".
X----------------------------------------------------------------------*/
Xtypedef
X    struct argpType {
X	/* one of the above defines */
X	int type; 
X    /* the number of the bit in the returned long which this entry
X     * corresponds to */
X	int bitnum;
X
X    /* name of the switch for SWITCH types */
X	char name[MAX_ARG_NAME_LEN];
X
X    /* format reading parameters for NORM_ARG types */
X	/* the scanf format string */
X	char format[MAX_ARG_FORM_LEN];
X	/* the number of format commands in format, e.g. %d,%d gives 2 */
X	int fmt_count;
X	/* Are the values for the flag optional, or must they be present? */
X	boolean optional;
X
X    /* An index into the pointer array for the processed args recipient;
X     * used for NORM_ARG and BOOL_VAR_FLAG */
X	int pnt_index;
X    }
X	argp_t;
X
X
X/*--------------------------------------------------------------------------
X Return pointer to first char in s which matches a char from untilstr.
X--------------------------------------------------------------------------*/
Xchar * 
Xsindex(s, untilstr)
X    register char *s, *untilstr;
X{
X    register char c1, c2, *up;
X
X    while ((c1= *s++) != '\0') {
X	up = untilstr;
X	while ((c2= *up++) != '\0')
X	    if (c1 == c2)
X		return(s-1);
X    }
X
X    return(s-1);
X}
X
X/*--------------------------------------------------------------------------
X Copy chars from beg to end-1 into res, add null char.
X--------------------------------------------------------------------------*/
Xstatic void
Xbe_strcpy(res, beg, end)
X    register char *res, *beg, *end;
X{
X    while (beg != end)
X	*res++ = *beg++;
X    *res = '\0';
X}
X
X/*--------------------------------------------------------------------------
X Copy s2 to s1 until a char from untilstr occurs, then null 
X terminate s1 and return a pointer to the terminal char in s2.
X--------------------------------------------------------------------------*/
Xstatic char *
Xcopy_until(s1, s2, untilstr)
X    char *s1, *s2, *untilstr;
X{
X    char *olds2;
X
X    olds2 = s2;
X    s2 = sindex(s2, untilstr);
X    be_strcpy(s1, olds2, s2);
X
X    return(s2);
X}
X
X
X/*----------------------------------------------------------------------
X Count the number of format specifications.
X Ignore literal "%" and dummy field spec "*".
X----------------------------------------------------------------------*/
Xstatic int 
Xformat_count(str)
X    char *str;
X{
X    int count=0;
X
X    str = strchr(str, '%');
X    while (str++ != NULL) {
X	if (*str == '%') 
X	    str++;
X	else if (*str != '*')
X	    count++;
X
X	str = strchr(str, '%');
X    }
X
X    return count;
X}
X
X
X/*----------------------------------------------------------------------
X Process the format word which started with a '%'.  The whole word should
X look like '%s' or '%*d' or some other scanf format word starting with %.
X It may have multiple entries, for instance '%d%s'.
X-----------------------------------------------------------------------*/
Xstatic char *
XnormArgParse(form, argp, argpcountp, bitnump, pntcountp)
X    char *form;
X    argp_t *argp;
X    int *argpcountp, *bitnump, *pntcountp;
X{
X    int pos = (*argpcountp)++;
X    int fmt_count;
X
X/* copy everything, including the first '%' */
X    form = copy_until(argp[pos].format, form-1, " \t\n");
X
X    argp[pos].type = NORM_ARG;
X    argp[pos].optional = FALSE;
X    argp[pos].bitnum = *bitnump;
X    argp[pos].pnt_index = *pntcountp;
X    argp[pos].fmt_count = fmt_count = format_count(argp[pos].format);
X
X    *pntcountp += fmt_count;
X    *bitnump += fmt_count;
X
X    return form;
X}
X
X
X
X/*----------------------------------------------------------------------
X Make a new entry in the form entry table 'form' for a boolean switch.  It
X may be a boolean variable (according to isBoolVar), and should use the
X specified bit number and name.  If it is boolVar, a pointer to the user
X variables is used up too (pntcountp).
X-----------------------------------------------------------------------*/
Xstatic void
XnewBoolEntry(form, bitnum, name, nameLen, isBoolVar, pntcountp)
X    argp_t *form;
X    char *name;
X    int bitnum, nameLen, *pntcountp;
X    boolean isBoolVar;
X{
X    (void) strncpy(form->name, name, nameLen);
X    form->name[nameLen] = '\0';
X
X    form->bitnum = bitnum;
X
X    if (isBoolVar) {
X	form->type = BOOL_VAR_FLAG;
X	form->pnt_index = (*pntcountp)++;
X    } else
X	form->type = BOOL_FLAG;
X}
X
X
X/*----------------------------------------------------------------------
X Process the format word which started with a dash.  The whole word should
X look like '-xy%s' or '=xy%s' where x is a boolean switch, y is a value switch.
X Also handles the case where there are braces around the switch '{-long%d}'
X which means that the switch has a long name, and the case of optional values
X '-x[%d]', or both '{-longname[%d]}'.  The 'form' always points to the char
X after the '-' or '=', and 'wasBrace' indicates whether there was a left
X brace before that.
X-----------------------------------------------------------------------*/
Xstatic char *
XswitchParse(form, argp, argpcountp, bitnump, pntcountp, wasBrace)
X    char *form;
X    argp_t *argp;
X    int *argpcountp, *bitnump, *pntcountp;
X    boolean wasBrace;
X{
X    char *oldform = form;
X    int pos = *argpcountp;
X    boolean leftBracket, isBoolVar;
X
X/* if switch started with '=', will return result in a boolean variable */
X    isBoolVar = (form[-1] == '=');
X
X    form = sindex(form, "}%[ \t\n");
X    leftBracket = (*form == '[');
X
X    if (oldform == form) 
X	Epitaph("argproc: switch must include 1 char flag name(s)");
X
X    if (wasBrace) {
X    /* Use the entire chunk as one long name since this is in braces.
X     * It may have its type changed, to VALUE for instance if % is the
X     * next char */
X	newBoolEntry(&argp[pos++], (*bitnump)++, oldform, form - oldform,
X					isBoolVar, pntcountp);
X    } else {
X    /* Assign the one character switch names to individual array places.  
X     * The type of the last one may be changed, to VALUE for instance 
X     * if the next char is a %. */
X	while (oldform != form)
X	    newBoolEntry(&argp[pos++], (*bitnump)++, oldform++, 1,
X					    isBoolVar, pntcountp);
X    }
X
X/* skip the left bracket if there is one */
X    if (leftBracket)
X	++form;
X
X/* if there is a %, set up the last switch as a VALUE, not a BOOL. */
X    if (*form == '%') {
X	int fmt_count;
X
X	--pos;
X	argp[pos].optional = leftBracket;
X	argp[pos].type |= VALUE_FLAG;
X	form = copy_until(argp[pos].format, form, 
X				leftBracket ? "]" : "} \t\n");
X
X	/* figure out how many variables-to-be-filled this will require */
X	argp[pos].fmt_count = fmt_count = format_count(argp[pos].format);
X
X	/* show where the first variable-to-be-filled is unless this switch
X	 * also had a boolean variable-to-fill, in which case the position
X	 * of the variables is already set. */
X	if (!isBoolVar)
X	    argp[pos].pnt_index = *pntcountp;
X	*pntcountp += fmt_count;
X
X	*bitnump += fmt_count;
X
X	++pos;
X    }
X
X    if (leftBracket)
X	if (*form++ != ']')
X	    Epitaph("argproc: missing closing ']' in format string");
X
X    if (wasBrace)
X	if (*form++ != '}')
X	    Epitaph("argproc: missing closing '}' in format string");
X
X    *argpcountp = pos;
X    return form;
X}
X
X
X/*----------------------------------------------------------------------
X Analyse the contents of the argproc format string.  The result is an
X array, one element per switch in the format string.  The number of
X elements in the array is returned in *argpcountp.  The number of variables
X needed to receive the values from the string is the return value.  That is,
X if the string were "%f -x%d -q =y" the return value would be 3: 1 each for
X the %f, the %d, and the =y variables.
X-----------------------------------------------------------------------*/
Xstatic int
Xformat_parse(form, argp, argpcountp)
X    char *form;
X    argp_t *argp;
X    int *argpcountp;
X{
X    int pntcount, bitnum;
X
X    *argpcountp = 0;
X
X    /* The position in the argument list of variables-to-be-filled. */
X    pntcount = 0;
X
X    /* The output bit number to be affected by the current format entries */
X    bitnum = 0;
X
X/* skip white space, process the next word of the format string */
X    for (form += strspn(form, " \t\n"); *form; form += strspn(form, " \t\n")) {
X	char c;
X
X	switch(*form++) {
X	case '{': /* A multi-character name switch */
X	    if ((c = *form++) != '-' && c != '=')
X		Epitaph("argproc: brace must start with a switch");
X
X	    form = switchParse(form, argp, argpcountp, &bitnum, &pntcount, 
X									TRUE);
X	    break;
X	case '-': /* One or several concatenated switches */
X	case '=': /* same as '-' but returns boolean result variable */
X	    form = switchParse(form, argp, argpcountp, &bitnum, &pntcount,
X									FALSE);
X	    break;
X	case '%': /* A normal scanf type format string argument */
X	    form = normArgParse(form, argp, argpcountp, &bitnum, &pntcount);
X	    break;
X	default:
X	    Epitaph("argproc: invalid char (%c) in format string", *--form);
X	}
X    }
X
X    return pntcount;
X}
X
X
X/*----------------------------------------------------------------------
X Set the variable corresponding to any BOOL_VAR types in the format string
X to an initial value of FALSE; they will be reset to TRUE when the use
X of that switch is discovered in the user argument string.
X----------------------------------------------------------------------*/
Xstatic void
XinitBoolVar(vars, form, formCnt)
X    char *vars[];
X    argp_t *form;
X    int formCnt;
X{
X    int i;
X
X    for (i=0; i<formCnt; ++i)
X	if ((form[i].type & BOOL_VAR_FLAG) == BOOL_VAR_FLAG)
X	    *((boolean *)vars[ form[i].pnt_index ]) = FALSE;
X}
X
X
X/*----------------------------------------------------------------------
X Read in up to argp->fmt_count values from indata using sscanf,
X return with bits argp->bitnum+x set if the xth parameter was there.
X----------------------------------------------------------------------*/
Xstatic long 
Xargscanf(indata, argp, ps, errMode)
X    char *indata;
X    argp_t *argp;
X    char **ps;
X    int errMode;
X{
X    long bits;
X    int i, howmany, pos;
X    char *p1, *p2, *p3, *p4;
X
X/* look up the position of the user variable to put the data into; if the
X * format entry has a boolean variable too, skip that to get the position
X * for the scanf. */
X    pos = argp->pnt_index;
X    if ((argp->type & BOOL_VAR_FLAG) == BOOL_VAR_FLAG)
X	++pos;
X
X/* set up the parameters that are needed for the sscanf. */
X    switch (argp->fmt_count) {
X    case 4: p4 = ps[pos + 3];
X    case 3: p3 = ps[pos + 2];
X    case 2: p2 = ps[pos + 1];
X    case 1: p1 = ps[pos + 0];
X    case 0: break;
X    default:
X	Epitaph("argproc: can only have 4 variables per argument");
X    }
X    howmany = sscanf(indata, argp->format, p1, p2, p3, p4);
X
X/* set the bit in the result for each parameter that was there */
X    bits = 0;
X    for (i=0; i<howmany; i++)
X	bits |= 1 << (argp->bitnum+i);
X    
X    if (!argp->optional && howmany < 1 && argp->fmt_count > 0) {
X	/* This error is caused by the user, not by the programmer,
X	 * so let the programmer say whether to abort or not
X	 */
X	PrintErr(errMode, "argproc: bad or missing value for flag %s",
X	    argp->name);
X	return ERROR;
X    }
X    
X    return bits;
X}
X
X
X/*----------------------------------------------------------------------
X Assign values from the user's switch to the appropriate variables.
X 'str' is the contents of the switch, starting just after the '-'.
X----------------------------------------------------------------------*/
Xstatic long
XprocessSwitch(str, form, formCnt, vars, errMode)
X    char *str, *vars[];
X    argp_t *form;
X    int formCnt, errMode;
X{
X    int offset, j, ty;
X    long found = 0;
X
X/* go through each character of the string looking for multiple switches */
X    for (offset=0; str[offset] != '\0';) {
X	/* check each of the format string entries to see if any match */
X	for (j=0; j<formCnt; j++) {
X	    if ( (form[j].type & SWITCH) && strncmp(str+offset, form[j].name, 
X						    strlen(form[j].name))==0) {
X		/* skip over the name of the switch */
X		offset += strlen(form[j].name);
X
X		/* mark that this switch was found */
X		found |= 1 << form[j].bitnum;
X
X		ty = form[j].type;
X
X		if ((ty & BOOL_VAR_FLAG) == BOOL_VAR_FLAG)
X		    /* set the boolean variable to show the line had this
X			switch */
X		    *((boolean *)vars[ form[j].pnt_index ]) = TRUE;
X
X		if ((ty & VALUE_FLAG) == VALUE_FLAG)
X		    /* if VALUE, no more string to examine after argscanf,
X			so return. */
X		    return found | 
X			(argscanf(str+offset, &form[j], vars, errMode) << 1);
X
X		/* don't have to do anything for BOOL_FLAG, since the 'found' 
X		    bit was already set by the code before this switch. */
X
X		/* go on to any other switches in the string */
X		break;
X	    }
X	}
X
X	/* if didn't find switch in format list, it's an error */
X	if (j == formCnt) {
X	    PrintErr(errMode,"argproc: invalid flag -%s", str+offset);
X	    return ERROR;
X	}
X    }
X
X    return found;
X}
X
X
X/*----------------------------------------------------------------------
X Go through the argument list, assigning values to the user's variables
X as indicated by the format string breakdown.
X----------------------------------------------------------------------*/
Xstatic long
XprocessArgs(form, formCnt, vars, argv, argCnt, errMode)
X    argp_t *form;
X    char *vars[], *argv[];
X    int formCnt, argCnt, errMode;
X{
X    long found;
X    int i, normArgPos;
X
X    found = 0;
X
X    /* go through the normal arguments in the format string in order as they
X     * come off the command line. */
X    normArgPos = 0;
X
X    for (i=1; i<argCnt; i++)
X	/* if argument is a switch... */
X	if (argv[i][0] == '-' && argv[i][1] != '\0')
X	    found |= processSwitch(argv[i] + 1, form, formCnt, vars, errMode);
X        else
X	    /* argument is not a switch, must be a NORM_ARG */
X	    /* look for the next NORM_ARG from the format string */
X	    for(; normArgPos < formCnt; ++normArgPos)
X		if (form[normArgPos].type == NORM_ARG) {
X		    found |= argscanf(argv[i], &form[normArgPos++], 
X							vars, errMode);
X		    break;
X		}
X
X    return found;
X}
X
X
X/*----------------------------------------------------------------------
X The actual argument list is argproc(argc, argv, format, vars . . .).  The
X argc and argv are from the user's main(), the format is a switch describing
X string, and the vars are pointers to variables like those passed to scanf
X for receiving the values extracted from argv and arranged as indicated in
X the format string.
X----------------------------------------------------------------------*/
Xint argproc(va_alist)
X    va_dcl
X{
X    va_list ap;
X    char **argv, *form;
X    char *vars[MAX_ARGS];
X    int argpcount, varCnt, i, argc, errMode;
X    argp_t argp[MAX_ARGS];
X
X    va_start(ap);
X
X    argc = va_arg(ap, int);
X    argv = va_arg(ap, char **);
X    form = va_arg(ap, char *);
X
X    if (form == NULL)
X	return 0;
X
X    switch (*form++) {
X	case 'N': case 'n': errMode = ERR_NOTE;   break;
X	case 'W': case 'w': errMode = ERR_WARN;   break;
X	case 'F': case 'f': errMode = ERR_FATAL;  break;
X	default : --form;   errMode = ERR_FATAL;  break;
X    }
X
X/* setup argp with contents of format string, get how many arguments should
X * be following the format string */
X    if ((varCnt = format_parse(form, argp, &argpcount)) > MAX_ARGS)
X	Epitaph("too many args. (limit %d)", MAX_ARGS);
X    
X/* load in the pointers for the rest of the args */
X    for (i=0; i<varCnt; i++)
X	vars[i] = va_arg(ap, char *);
X
X    va_end(ap);
X
X/* initialize the boolean variables to FALSE */
X    initBoolVar(vars, argp, argpcount); 
X
X    return processArgs(argp, argpcount, vars, argv, argc, errMode);
X}
X
X#endif		/* "ifndef lint" around real guts of module */
XXX_EOF_XXX
if test 16865 -ne "`wc -c < argproc.c`"
then
    echo 'shar: transmission error on "argproc.c"'
fi
chk=`sum argproc.c | awk '{print $1}'`
if test 18141 -ne $chk -a 31035 -ne $chk
then
    echo 'shar: checksum error on "argproc.c"'
fi
echo 'shar: extracting "argproc.h" (99 characters)'
# 'argproc.h' has a checksum of 55178 on BSD and 8130 on System V.
sed 's/^X//' > argproc.h << 'XXX_EOF_XXX'
X/* Include file for users of argproc(). */
X#include "boolean.h"
X#include "lose.h"
X
Xlong argproc();
XXX_EOF_XXX
if test 99 -ne "`wc -c < argproc.h`"
then
    echo 'shar: transmission error on "argproc.h"'
fi
chk=`sum argproc.h | awk '{print $1}'`
if test 55178 -ne $chk -a 8130 -ne $chk
then
    echo 'shar: checksum error on "argproc.h"'
fi
echo 'shar: extracting "lose.c" (4704 characters)'
# 'lose.c' has a checksum of 43151 on BSD and 15971 on System V.
sed 's/^X//' > lose.c << 'XXX_EOF_XXX'
X/*--------------------------------------------------------------------------
X Routines to build and print error messages.
X Includes:  ErrStr, ProgTitle, lose_title(), lose(), warn(), and PrintErr().
X--------------------------------------------------------------------------*/
X
X#include <stdio.h>
X#include <string.h>
X#include <varargs.h>
X#include <errno.h>
X#include "lose.h"
X
X/*LINTLIBRARY*/
X#ifndef lint
Xstatic char Sccs_id[] = "@(#)lose.c	from 1.16 5/31/87 (C) SRS";
X#endif
X
X#define MAX_STR_LEN 200
X#define MAX_ERR_STR_LEN 1000
X
X/* Private variables */
Xstatic char title[MAX_STR_LEN] = "program_name";
Xstatic char errorString[MAX_ERR_STR_LEN] = "";
X
X/* Public variables */
Xchar *ProgTitle = title;
Xchar *ErrStr = errorString;
X
X/*--------------------------------------------------------------------------
X Store the title of the program, initialize errno to zero.
X--------------------------------------------------------------------------*/
Xvoid
Xlose_title(name)
X    char *name;
X{
X    errno = 0;
X    ProgTitle[MAX_STR_LEN-1] = '\0';
X    (void) strncpy(ProgTitle, name, MAX_STR_LEN-1);
X}
X
X
X/*--------------------------------------------------------------------------
X Build an error message, then maybe print it to stderr and maybe exit,
X as indicated by errMode.
X--------------------------------------------------------------------------*/
X
Xstatic void
X_print_error(errMode, format, ap)
X    int errMode;
X    char *format;
X    va_list ap;
X{
X    extern int sys_nerr;
X    extern char *sys_errlist[];
X
X    /* Print the program name to the buffer */
X    (void) sprintf(ErrStr, "%s: ", ProgTitle);
X
X    /* Append the message to the buffer */
X    vsprintf(ErrStr + strlen(ErrStr), format, ap);
X
X    /* Append the system error message, if any */
X    if (errno > 0 && errno < sys_nerr) {
X	(void) strcat(ErrStr, "-- ");
X	(void) strcat(ErrStr, sys_errlist[errno]);
X    }
X
X    /* Print the message to the console and/or exit, as indicated by errMode */
X    switch(errMode) {
X    case ERR_FATAL: 
X	(void) fprintf(stderr, "%s\n", ErrStr);
X	exit(1);
X	break;
X    case ERR_WARN: 
X	(void) fprintf(stderr, "%s\n", ErrStr);
X	break;
X    case ERR_NOTE: 
X	break;
X    default:
X	(void) fprintf(stderr, "%s\n", ErrStr);
X	lose("illegal call to PrintErr with error mode %d\n", errMode);
X	break;
X    }
X}
X
X/*--------------------------------------------------------------------------
X Lint definitions to make lint shut up...
X you may safely omit this section if you don't use lint.
X--------------------------------------------------------------------------*/
X#ifdef lint
X    /*ARGSUSED*/
X    /*VARARGS1*/			/* Only check first argument */
X    void warn(message) char *message;	/* First arg must be char *  */
X    { message[0]=0; }			/* Dummy function body       */
X
X    /*ARGSUSED*/
X    /*VARARGS1*/			/* Only check first argument */
X    void lose(message) char *message;	/* First arg must be char *  */
X    { message[0]=0; }			/* Dummy function body       */
X
X    /*ARGSUSED*/
X    /*VARARGS1*/			/* Only check first argument */
X    void Epitaph(message) char *message;/* First arg must be char *  */
X    { message[0]=0; }			/* Dummy function body       */
X
X    /*ARGSUSED*/
X    /*VARARGS2*/			/* Check first 2 arguments   */
X    void PrintErr(errMode, message)
X	int errMode;			/* First arg must be int     */
X	char *message;			/* Second arg must be char * */
X    { message[0]=(char)errMode; }	/* Dummy function body       */
X#else
X
X/*--------------------------------------------------------------------------
X Various methods of calling _print_error():
X
X For nonfatal errors:
X    warn(format, ...)    -> _print_error(ERR_WARN,  format, ...)
X
X For fatal I/O errors (this one is the most useful):
X    lose(format, ...)    -> _print_error(ERR_FATAL, format, ...)
X
X For fatal non-I/O errors:
X    Epitaph(format, ...) -> errno=0, _print_error(ERR_FATAL, format, ...)
X
X For nonfatal errors when you don't want message printed to stderr:
X    PrintErr(errMode, format, ...) -> _print_error(errMode, format, ...)
X--------------------------------------------------------------------------*/
X/*VARARGS2*/
Xvoid
XPrintErr(errMode, format, va_alist)
X    int errMode;
X    char *format;
X    va_dcl
X{
X    va_list ap;
X    va_start(ap);
X    _print_error(errMode, format, ap);
X}
X
X/*VARARGS1*/
Xvoid
Xwarn(format, va_alist)
X    char *format;
X    va_dcl
X{
X    va_list ap;
X    va_start(ap);
X    _print_error(ERR_WARN, format, ap);
X}
X
X/*VARARGS1*/
Xvoid
Xlose(format, va_alist)
X    char *format;
X    va_dcl
X{
X    va_list ap;
X    va_start(ap);
X    _print_error(ERR_FATAL, format, ap);
X}
X
X/*VARARGS1*/
Xvoid
XEpitaph(format, va_alist)
X    char *format;
X    va_dcl
X{
X    va_list ap;
X    va_start(ap);
X    errno = 0;
X    _print_error(ERR_FATAL, format, ap);
X}
X
X#endif
XXX_EOF_XXX
if test 4704 -ne "`wc -c < lose.c`"
then
    echo 'shar: transmission error on "lose.c"'
fi
chk=`sum lose.c | awk '{print $1}'`
if test 43151 -ne $chk -a 15971 -ne $chk
then
    echo 'shar: checksum error on "lose.c"'
fi
echo 'shar: extracting "lose.h" (123 characters)'
# 'lose.h' has a checksum of 51399 on BSD and 9611 on System V.
sed 's/^X//' > lose.h << 'XXX_EOF_XXX'
X#define ERR_FATAL 1
X#define ERR_WARN  2
X#define ERR_NOTE  3
X
Xvoid lose_title();
Xvoid lose();
Xvoid warn();
Xvoid PrintErr();
XXX_EOF_XXX
if test 123 -ne "`wc -c < lose.h`"
then
    echo 'shar: transmission error on "lose.h"'
fi
chk=`sum lose.h | awk '{print $1}'`
if test 51399 -ne $chk -a 9611 -ne $chk
then
    echo 'shar: checksum error on "lose.h"'
fi
echo 'shar: extracting "makeargv.doc" (965 characters)'
# 'makeargv.doc' has a checksum of 52222 on BSD and 13347 on System V.
sed 's/^X//' > makeargv.doc << 'XXX_EOF_XXX'
Xmakeargv - build an argv-style array from a string
X
XSYNOPSIS
X     int makeargv(str, argvp)
X     char *str;
X     char ***argvp;
X
XDESCRIPTION
X    makeargv breaks up an ordinary string into a count and array like
X    those passed to main() as argc & argv.
X    Argv is MALLOC'd in makeargv; to free it, call FREE(argv).
X
X    The first arg is placed in argv[0].
X    The original string is peppered with null chars to mark the ends of
X    the args, and argv[i] is filled with pointers into the original string.
X    An extra entry of NULL is included in argv, at argv[argc], for routines 
X    that use a final NULL for loop termination.
X
XEXAMPLE
X    #include "argproc.h"
X    char **argv;
X    int argc;
X    char cmdline[256], arg[256];
X    boolean xflag;
X
X    gets(cmdline);
X    argc = makeargv(cmdline, &argv);
X    argproc(argc+1, argv-1, "%s =x", arg, &xflag);
X
XBUGS
X    Perhaps this should set argv[0] to the empty string
X    to be more compatible with the real argv[].
XXX_EOF_XXX
if test 965 -ne "`wc -c < makeargv.doc`"
then
    echo 'shar: transmission error on "makeargv.doc"'
fi
chk=`sum makeargv.doc | awk '{print $1}'`
if test 52222 -ne $chk -a 13347 -ne $chk
then
    echo 'shar: checksum error on "makeargv.doc"'
fi
echo 'shar: extracting "makeargv.c" (1193 characters)'
# 'makeargv.c' has a checksum of 37514 on BSD and 18775 on System V.
sed 's/^X//' > makeargv.c << 'XXX_EOF_XXX'
X#include <string.h>
X#include <ctype.h>
X#include "boolean.h"
X
X#ifndef NULL
X#define NULL 0L
X#endif
X
X#define MAX_ARGS 1000
X
X/*----------------------------------------------------------------------
X Given a string, extracts the arguments (separated by white-space) 
X and creates an argv type array full of pointers to the input string. 
X Modifies the input string.
X Returns the count of arguments.
X----------------------------------------------------------------------*/
Xint
Xmakeargv(s, argvp)
X    char *s, ***argvp;
X{
X    int cnt=0;
X    char *vals[MAX_ARGS];	/* temporary argv until we know argc */
X
X    while (TRUE) {
X	s += strspn(s, " \t\n");
X	if (*s == '\0')
X	    break;
X
X	if (cnt >= MAX_ARGS)
X	    lose("%s: only %d args allowed to makeargv", MAX_ARGS);
X
X	if (*s == '"') {
X	/* if the quote is unmatched, just stop processing */
X	    vals[cnt] = s;
X	    if ((s = strchr(s, '"')) == NULL)
X		break;
X
X	    ++cnt;
X	    *s++ = '\0';
X	} else {
X	    vals[cnt++] = s;
X	    s += strcspn(s, " \n\t");
X	    if (*s == '\0')
X		break;
X	    else
X		*s++ = '\0';
X	}
X    }
X
X    *argvp = malloc(sizeof(char *) * (cnt+1));
X
X    vals[cnt] = 0;
X    bcopy(vals, *argvp, (cnt+1) * sizeof(char *));
X
X    return cnt;
X}
XXX_EOF_XXX
if test 1193 -ne "`wc -c < makeargv.c`"
then
    echo 'shar: transmission error on "makeargv.c"'
fi
chk=`sum makeargv.c | awk '{print $1}'`
if test 37514 -ne $chk -a 18775 -ne $chk
then
    echo 'shar: checksum error on "makeargv.c"'
fi