[comp.sources.misc] v06i062: A library for command argument parsing for C

allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) (03/08/89)

Posting-number: Volume 6, Issue 62
Submitted-by: Nigel Perry <np@doc.ic.ac.uk>
Archive-name: copt

[ Getopt is here to stay, but for those who don't like it...
  Manpages would help.  --r$  ]

[This is silly.  One person thinks to overcome the trend toward standardized
option processing?  Easier to convert the Ayatollah to Catholicism.  And has
the author given any thought to how one would use a wildcard in his string
options?  (hint:  "dd if=ar*xy of=/dev/rmt8" fails.)  ++bsa]

	This is a library which contains routines to perform nice
parsing of command lines for C programs, no more "-letter" options
for everything now you can have +option, -option, option=value etc.

					Nigel

--- cut here ---
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	README
#	abbrev.c
#	cset.c
#	cset.d
#	cset.h
#	d_optable.c
#	demo.c
#	makefile
#	not_kwd.c
#	numarg.c
# This archive created: Fri Mar  3 12:57:09 1989
export PATH; PATH=/bin:$PATH
if test -f 'README'
then
	echo shar: will not over-write existing file "'README'"
else
cat << \SHAR_EOF > 'README'
Description
===========

A radical departure fro Unix - proper command line parsing!

This is a library to replace getopt() or simple "-" option processing by a program.
It provies automatic scanning switch options, + to turn on, - to turn off; string
valued options, e.g. file=../fred, and numeric options - these may be in any base
between 2 and 36 or autobase, which follows the C convention for constants. Multiple
numeric value options are also parsed, e.g. tabs=4,7,23, and return as a vector the
first element being the number of values found. As well as parsing options the library
provides for displaying a table of options on request. Run the demo program - which is
the only "proper" documentation (sorry, but I've never got around to it).

Installation
============

Run make. Copy cset.a & cset.h to suitable directories.

History
=======

Many years ago I used a Honeywell GCOS/TSS system and the B language from the University of
Waterloo, Ontario. The B language came with a command line parser library, bset(). I extended
that library, adding options to print the argument table etc. and standardised all the commands
on the TSS system (plus some other major hacking, but enough said...). Later I rewrote the whole
library from scratch in C for Unix.

Acknowlegements
===============

To the people at Waterloo for the original idea.
To by boss at the time - which is the reason for the detailed copyright notice
[We had an offical agreement, anything I wrote and distributed could either be
sold at a price set by him with me getting a percentage, or if I disagreed with
his proposed price I could overrule and distribe it free...]

If anybody writes some proper manual pages, please send me a copy!

Nigel Perry
Dept of Computing
Imperial College
London SW7
England

np@uk.ac.ic.doc
SHAR_EOF
fi # end of overwriting check
if test -f 'abbrev.c'
then
	echo shar: will not over-write existing file "'abbrev.c'"
else
cat << \SHAR_EOF > 'abbrev.c'
static char rcsid[] = "$Header";
/* $Log:	abbrev.c,v $
 * Revision 1.1  89/03/03  12:53:21  np
 * Initial revision
 * 
 */

/*
 * Copyright (C) 1985-1989 Nigel Perry
 *
 * This program is distributed without any warranty.
 * The author and and distributors accept no resposibility
 * to anyone for the consequence of using this program or
 * whether it serves any particular purpose or works at all,
 * unless they say so in writing.
 *
 * Everyone is granted permission to copy, modify and
 * redistribute this program, but only provided that:
 *
 * 1) They do so without financial or material gain.
 *
 * 2) The copyright notice and this notice are preserved on
 *    all copies.
 *
 * 3) The original source is preserved in any redistribution.
 *
 * I hope you find this program useful!
 *
 */

#define repeat while(1)  /* try to make C look high level! */

#define REQUIRED 32
#define OPTIONAL 1

/* check if string is a valid abbreviation of pattern
 *
 * In a pattern:
 *   a thru z and _ are optional
 *   other characters must be present
 *
 * Return:
 *  -1 : no match
 *  n = REQUIRED * (number of required matches) + OPTIONAL * (number of optional characters matched)
 *
 * e.g
 *	Pattern		String		Return
 *	LineFeed	lf		2 * REQUIRED
 *	Longform	lf		1 * REQUIRED + 1 * OPTIONAL
 * => lf is a better match for LineFeed
 *
 * The higher the result the better the match.
 *
 */

int abbrev(pat, str) register char *pat, *str;
{  register char c, pc;
   register int score, sc;

   score = 0;
   c = *str++;
   if(c >= 'a' && c <= 'z') c += 'A' - 'a';
   repeat
      if( (pc = *pat++) == '\0' )		/* this SHOULD be a case... */
	 return( c == '\0' ? score : -1 );
      else if(pc == '_') /* optional */
      {  if(c != '_') continue;			/* try next pattern character */
	 if( (sc = abbrev(pat, str)) >= 0 ) return(score + OPTIONAL + sc);
	 /* rest of match failed, try next pattern char */
      }
      else if(pc >= 'a' && pc <= 'z')		/* = case 'a' :: 'z', B rules! */
      {  if( (c - 'A' + 'a') != pc ) continue;	/* no match, try next pat */
	 if( (sc = abbrev(pat, str)) >= 0 ) return(score + OPTIONAL + sc);
      }
      else /* must match */
      {  if(c != pc) return(-1);
	 score += REQUIRED;
	 c = *str++;				/* get next str char */
	 if(c >= 'a' && c <= 'z') c += 'A' - 'a';
      }
}
SHAR_EOF
fi # end of overwriting check
if test -f 'cset.c'
then
	echo shar: will not over-write existing file "'cset.c'"
else
cat << \SHAR_EOF > 'cset.c'
static char rcsid[] = "$Header: cset.c,v 1.1 89/03/03 12:53:22 np Exp $";
/* $Log:	cset.c,v $
 * Revision 1.1  89/03/03  12:53:22  np
 * Initial revision
 * 
 */

/*
 * Copyright (C) 1985-1989 Nigel Perry
 *
 * This program is distributed without any warranty.
 * The author and and distributors accept no resposibility
 * to anyone for the consequence of using this program or
 * whether it serves any particular purpose or works at all,
 * unless they say so in writing.
 *
 * Everyone is granted permission to copy, modify and
 * redistribute this program, but only provided that:
 *
 * 1) They do so without financial or material gain.
 *
 * 2) The copyright notice and this notice are preserved on
 *    all copies.
 *
 * 3) The original source is preserved in any redistribution.
 *
 * I hope you find this program useful!
 *
 */

#include <stdio.h>
#include "cset.h"
#include "cset.d"

#define strequ(a, b) (!strcmp(a,b))

extern char *malloc(), *realloc();

/* args:
 *    args     - array of char *'s to args (i.e. argv from main())
 *    helpexit - exit program iff help given
 * return:
 *    array of _opt_desc describing options
 */

_opt_desc *_cset(args, helpexit) char **args;
{  char *name, *value, *info, *offset, *left, *right;
   int i, type, maxopt, max, score, helpgiven, len;
   _opt_entry *help_entry, *cp, *p;
   int do_help_entry;
   int super;
   _opt_desc *result, *resvec;
   extern _opt_entry _optable[];
   extern char *index();

   helpgiven = do_help_entry = 0;
   super = getuid() == 0; /* we be a super user */

   /* find largest option number, last entry in _optable is help_entry */
   help_entry = _optable;
   maxopt = 1;
   while( help_entry->_opt_pat ) { help_entry++; maxopt++; }

   /* get a vector for the result */
   for(i = 0; args[i]; i++); /* required length */
   resvec = result = (_opt_desc *) malloc( sizeof(_opt_desc) * (i + 1) );
   result[i]._opt_num = -1; /* flag end */

   for(i = 0; info = args[i]; i++)
   {  if(*info == '+') /* +option */
      {  name = info + 1;
	 type = PLUS_KWD;
	 result->_opt_type = '+';
      }
      else if(*info == '-') /* -option */
      {  name = info + 1;
	 type = DASH_KWD;
	 result->_opt_type = '-';
      }
      else if(value = index(info, '='))
      {  if(value != info) /* option=value */
	 {  name = info;
	    *value++ = '\0'; /* add eos over =, bump ptr to value */
	    if( *value == '\0' && args[i+1] != 0 ) /* option= value */
	       value = args[++i];
	    type = (SVAL_KWD | NVAL_KWD);
	    result->_opt_type = '=';
	 }
	 else /* =<something> */
	 {  value++; /* ptr to <something> */
	    if( strequ(value, "=") ) /* display complete option table */
	    {  d_optable(0, (_opt_entry *)0, super);
	       helpgiven = 1;
	       continue; /* don't wan't to scan optable, go direct to next */
	    }
	    else if( strequ(value, "+") ) /* display long optable */
	    {  d_optable(1, (_opt_entry *)0, super);
	       helpgiven = 1;
	       continue; /* don't wan't to scan optable, go direct to next */
	    }
	    else if( strequ(value, "#") ) /* display help info */
	    {  if(help_entry->_opt_types == 0)
		  fprintf(stderr, "Sorry, no help available, see manual\n");
	       else
		  do_help_entry  = 1;
	       helpgiven = 1;
	       continue; /* don't wan't to scan optable, go direct to next */
	    }
	    else /* see if <something> is an option */
	    {  /* scan optable for match */
	       max = 0; /* best match so far */
	       cp = (_opt_entry *)0;
	       p = _optable;
	       while( p->_opt_pat )
	       {  if( super || !(p->_opt_types & PRIV_BIT) )
		  {  if( (score = abbrev(p->_opt_pat, value)) > max )
		     {  max = score;
			cp = p;
		     }
		  }
		  p++;
	       }
	       if(cp) /* got a match - print help message */
	       {  _opt_adorn(cp->_opt_types, &left, &right);
		  len = strlen(left) + strlen(cp->_opt_pat) + strlen(right);
		  fprintf(stderr, "%*s%s%s%s%*s%s\n", TAB1, "",
		       left, cp->_opt_pat, right,
		       TAB3 - (len + TAB1), "",
		       cp->_opt_help == (char *)0 ? "(see manual)" : cp->_opt_help);
		  helpgiven = 1;
		  continue; /* don't wan't to scan optable */
	       }
	       else /* treat as possible BLNK_KWD */
	       {  name = info;
		  type = BLNK_KWD | (i == 0 ? COMM_KWD : 0);
		  result->_opt_type = ' ';
	       }
	    }
	 }
      }
      else
      {  name = info;
	 type = BLNK_KWD | (i == 0 ? COMM_KWD : 0 );
	 result->_opt_type = ' ';
      }

      /* scan optable for match */
      max = 0; /* best match so far */
      cp = (_opt_entry *)0;
      p = _optable;
      while( p->_opt_pat )
      {  if( super || !(p->_opt_types & PRIV_BIT) ) /* check for PRIV_KWD */
	 {  if( p->_opt_types & type ) /* compatible type? */
	    {  if( (score = abbrev(p->_opt_pat, name)) > max )
	       {  /* better match */
		  max = score;
		  cp = p;
	       }
	    }
	 }
	 p++;
      }
      if(cp)
      {  result->_opt_num = (int)(cp - _optable) + 1;
	 switch( cp->_opt_types & type )
	 {  case SVAL_KWD:
	       result->_opt.sval = value;
	       break;
	    case NVAL_KWD:
	       if( read_num(value, cp->_opt_types, result) == 0 )
	       {  result->_opt_num = maxopt + 1; /* read_num failed */
		  result->_opt.sval = info; /* return 'looks like' */
	       }
	       break;
	 }
      }
      else if( type == BLNK_KWD )
      {  result->_opt_num = 0; /* flag as a simple string */
	 result->_opt.sval = info;
      }
      else
      {  result->_opt_num = maxopt + 1; /* flag as looks like an option */
	 result->_opt.sval = info;
      }
      result++; /* bump ptr */
   }
   result->_opt_num = -1; /* flag end */

   /* should we do help_entry */
   if( do_help_entry ) switch( help_entry->_opt_types )
   {  case OPT_LIST:
	 list_file(help_entry->_opt_help);
	 break;
      case OPT_EXEC:
	 system(help_entry->_opt_help);
	 break;
   }

   /* see if we should exit... */
   if( helpexit && helpgiven ) exit(0);
   return(resvec);
}

#define CHUNK (sizeof(int) * 10)

static read_num(str, type, result) char *str; _opt_desc *result;
{  int base, flag, offset, *mvals, maxnums, i;

   flag = 0; /* dont skip leading junk */
   offset = 0;
   base = type & BASE_MASK;
   if( !(type & MVAL_BIT) ) /* single number */
   {  result->_opt.nval = numarg(str, &offset, base, &flag);
      if( flag == 0 || str[offset] != '\0' ) /* bad num or terminator */
	 return(0); /* fail */
   }
   else /* multivalued */
   {  maxnums = CHUNK;
      mvals = (int *)malloc(CHUNK);
      i = 1; /* mvals[0] will be number of numbers */
      do
      {  if(i == maxnums) /* run out of space */
	 {  maxnums += CHUNK;
	    mvals = (int *)realloc(mvals, CHUNK); /* grow it */
	 }
	 flag = 0;
	 mvals[i++] = numarg(str, &offset, base, &flag);
	 if(flag == 0) /* bad input */
	 {  free(mvals); /* give back vector */
	    return(0);
	 }
      }  while( str[offset++] != '\0' );
      mvals[0] = i - 1; /* number of numbers found */
      result->_opt.mval = mvals;
   }
   return(1); /* ok */
}

static list_file(f) char *f;
{  FILE *in;
   char c;

   if( (in = fopen(f, "r")) == NULL ) return;
   while( (c = getc(in)) != EOF ) putc(c, stderr);
   fclose(in);
}
SHAR_EOF
fi # end of overwriting check
if test -f 'cset.d'
then
	echo shar: will not over-write existing file "'cset.d'"
else
cat << \SHAR_EOF > 'cset.d'
/* $Header: cset.d,v 1.1 89/03/03 12:53:22 np Exp $
 * $Log:	cset.d,v $
Revision 1.1  89/03/03  12:53:22  np
Initial revision

 */

/* defines for cset source files */

/*
 * Copyright (C) 1985-1989 Nigel Perry
 *
 * This program is distributed without any warranty.
 * The author and and distributors accept no resposibility
 * to anyone for the consequence of using this program or
 * whether it serves any particular purpose or works at all,
 * unless they say so in writing.
 *
 * Everyone is granted permission to copy, modify and
 * redistribute this program, but only provided that:
 *
 * 1) They do so without financial or material gain.
 *
 * 2) The copyright notice and this notice are preserved on
 *    all copies.
 *
 * 3) The original source is preserved in any redistribution.
 *
 * I hope you find this program useful!
 *
 */

#define TAB1 5 /* offset for first option in table */
#define TAB2 40 /* offset of 2nd option in table */
#define TAB3 25 /* offset to help text */

#define MVAL_BIT (MVAL_KWD ^ NVAL_KWD) /* =0x4000 */
#define PRIV_BIT (PRIV_KWD ^ HIDE_KWD) /* =0x20000 */
#define BASE_MASK 0xFF /* base of numeric keyword */
SHAR_EOF
fi # end of overwriting check
if test -f 'cset.h'
then
	echo shar: will not over-write existing file "'cset.h'"
else
cat << \SHAR_EOF > 'cset.h'
/* $Header: cset.h,v 1.1 89/03/03 12:53:23 np Exp $
 * $Log:	cset.h,v $
 * Revision 1.1  89/03/03  12:53:23  np
 * Initial revision
 * 
 */

/*
 * Copyright (C) 1985-1989 Nigel Perry
 *
 * This program is distributed without any warranty.
 * The author and and distributors accept no resposibility
 * to anyone for the consequence of using this program or
 * whether it serves any particular purpose or works at all,
 * unless they say so in writing.
 *
 * Everyone is granted permission to copy, modify and
 * redistribute this program, but only provided that:
 *
 * 1) They do so without financial or material gain.
 *
 * 2) The copyright notice and this notice are preserved on
 *    all copies.
 *
 * 3) The original source is preserved in any redistribution.
 *
 * I hope you find this program useful!
 *
 */

/* a type definition for the options table */
typedef struct { char *_opt_pat; int _opt_types; char *_opt_help } _opt_entry;
#define OPTIONS _opt_entry _optable[] =
/* the end of list marker for above defn */
#define OPT_END { (char *)0, 0, (char *)0 }
/* end of list marker including details of an info file */
#define OPT_HELP(act, str) { (char *)0, act, str }
/* empty help message constant */
#define NOHELP (char *)0
/* 'actions' for OPT_HELP, must be != 0 */
#define OPT_LIST 1
#define OPT_EXEC 2

/* the option types */
#define BLNK_KWD 0x00100 /*  keyword                                     */
#define PLUS_KWD 0x00200 /* +keyword                                     */
#define DASH_KWD 0x00400 /* -keyword                                     */
#define COMM_KWD 0x00800 /*  keyword - only recognised if command (first)*/
#define SVAL_KWD 0x01000 /*  keyword=string                              */
#define NVAL_KWD 0x02000 /*  keyword=number - 0ddd => oct, 0xddd => hex  */
#define MVAL_KWD 0x06000 /*  keyword=number,...,number                   */
#define DVAL_KWD 0x0200a /*  keyword=decimal                             */
#define MDVL_KWD 0x0600a /*  keyword=decimal,...,decimal                 */
#define OVAL_KWD 0x02008 /*  keyword=octal                               */
#define MOVL_KWD 0x06008 /*  keyword=octal,...,octal                     */
#define HVAL_KWD 0x02010 /*  keyword=hex                                 */
#define MHVL_KWD 0x06010 /*  keyword=hex,...,hex                         */
#define HIDE_KWD 0x10000 /*  Hidden keyword - not shown by d_optable()   */
#define PRIV_KWD 0x30000 /*  Priviledged keyword - only for su's         */

/* _cset() returns a pointer to an array of... */
typedef struct { int _opt_num;
		 char _opt_type;
		 union { int nval; /* numeric value */
			 int *mval; /* ptr to vec of values */
			 char *sval; /* string value */
		       } _opt;
	      } _opt_desc;

extern _opt_desc *_cset();

/* defn for environ var to control use of keyword arguments */
#define AS_ENVAR "ArgStyle"
#define AS_KWD   "keyword"    /* arg style is keyword */
#define AS_ORG   "original"   /* arg style as supplied */
SHAR_EOF
fi # end of overwriting check
if test -f 'd_optable.c'
then
	echo shar: will not over-write existing file "'d_optable.c'"
else
cat << \SHAR_EOF > 'd_optable.c'
static char rcsid[] = "$Header: d_optable.c,v 1.1 89/03/03 12:53:23 np Exp $";
/* $Log:	d_optable.c,v $
 * Revision 1.1  89/03/03  12:53:23  np
 * Initial revision
 * 
 */

/*
 * Copyright (C) 1985-1989 Nigel Perry
 *
 * This program is distributed without any warranty.
 * The author and and distributors accept no resposibility
 * to anyone for the consequence of using this program or
 * whether it serves any particular purpose or works at all,
 * unless they say so in writing.
 *
 * Everyone is granted permission to copy, modify and
 * redistribute this program, but only provided that:
 *
 * 1) They do so without financial or material gain.
 *
 * 2) The copyright notice and this notice are preserved on
 *    all copies.
 *
 * 3) The original source is preserved in any redistribution.
 *
 * I hope you find this program useful!
 *
 */

#include <stdio.h>
#include "cset.h"
#include "cset.d"

#define next continue

/* Pretty print an _optable - or try to, boring ol' stdio!

   Adapted from d.optab - written in the REAL language B Plus!

   args:
      withelp - true is want long form with short desc
      table   - the table to be printed, 0 => _optable
      hidden  - if true, the 'hidden' options are also printed
 */

d_optable(withelp, table, hidden) int withelp, hidden; _opt_entry *table;
{  extern _opt_entry _optable[];
   _opt_entry *p;
   int c, n, flip, column;
   char *left, *right;

   /* set default table */
   if( table == (_opt_entry *)0 ) table = _optable;

   p = table;
   flip = c = n = 0;
   /* count command and option names */
   while(p->_opt_pat) if((p++)->_opt_types == COMM_KWD) c++; else n++;
   if(c > 1) /* only list command names if > 1 */
   {  fputs("Command Names:", stderr);
      p = table;
      while(p->_opt_pat)
      {  if(p->_opt_types == COMM_KWD)
	 {  if(withelp)
	    {  fprintf(stderr, "\n%*s%s%*s%s", TAB1, "", p->_opt_pat,
		  TAB3 - (strlen(p->_opt_pat) + TAB1), "",
		  p->_opt_help == NOHELP ? "(see manual)" : p->_opt_help);
	    }
	    else
	   {  fprintf(stderr, "%sc%*s%s", flip ? (char *)0 : "\n",
		  flip ? (TAB2 - column) : TAB1, "", p->_opt_pat);
	       if(flip = !flip) column = TAB1 + strlen(p->_opt_pat);
	    }
	 }
	 p++;
      }
      fputc('\n', stderr);
      if(!n) return;
   }
   fputs("Options:", stderr);
   flip = 0;
   p = table; p--;
   while( (++p)->_opt_pat )
   {  if(p->_opt_types == COMM_KWD) next;
      /* if HIDE and not hidden don't print it! */
      if( (p->_opt_types & HIDE_KWD) && !hidden) next;
      _opt_adorn(p->_opt_types, &left, &right);
      if(withelp)
      {  fprintf( stderr, "\n%*s%s%s%s%*s%s", TAB1, "", left, p->_opt_pat,
		  right,
		  TAB3 - (TAB1+strlen(left)+strlen(p->_opt_pat)+strlen(right)),
		  "", p->_opt_help == NOHELP ? "(see manual)" : p->_opt_help);
      }
      else
      {  fprintf( stderr, "%s%*s", flip ? "" : "\n",
		  flip ? (TAB2 - column) : TAB1, "");
	 flip = !flip;
	 fprintf(stderr, "%s%s%s", left, p->_opt_pat, right);
	 if(flip)
	    column = TAB1 + strlen(left) + strlen(p->_opt_pat) + strlen(right);
      }
   }
   fputc('\n', stderr);
}

/* return strings to 'adorn' options */
_opt_adorn(p, left, right) char **left, **right;
{  static char *dpb[] = { (char *)0, "", "+", "[+]", "-", "[-]",
			  "(+|-)", "[+|-]" };
   static char *s_val = { "=X" };
   static char *m_val = { "=X,...,X" };
   int c;
   if(p == COMM_KWD)
      *left = *right = "";
   else if(c = p & (DASH_KWD | PLUS_KWD | BLNK_KWD))
   {  *left = dpb[c >> 8];
      *right = "";
   }
   else if(p  == SVAL_KWD)
   {  *left = "";
      s_val[1] = 's'; /* construct '=s' */
      *right = s_val;
   }
   else /* numeric */
   {  switch(p & 0xFF) /* find base */
      {  case 0: c = 'n'; break;
	 case 8: c = 'o'; break;
	 case 10: c = 'd'; break;
	 case 16: c = 'x'; break;
	 default: /* somebody is being ackward... */
	    c = p & 0xFF;
	    c += c < 10 ? '0' : ('A' - 10);
      }
      *left = "";
      if(p & MVAL_BIT)
      {  m_val[1] = m_val[7] = c;
	 *right = m_val;
      }
      else
      {  s_val[1] = c;
	 *right = s_val;
      }
   }
}
SHAR_EOF
fi # end of overwriting check
if test -f 'demo.c'
then
	echo shar: will not over-write existing file "'demo.c'"
else
cat << \SHAR_EOF > 'demo.c'
/* demonstration of _cset */

#include "cset.h"

OPTIONS
{  "DEMO",   COMM_KWD, "a command name",
   "Tesco",  COMM_KWD, "another command name",
   "String", SVAL_KWD, "an arbitrary string",
   "Number", NVAL_KWD, "a number, base 8, 10 or 16",
   "Tabs",   MVAL_KWD, "list of tab stops",
   "Wide",   PLUS_KWD, "turn an option on",
   "Multi",  PLUS_KWD | DASH_KWD | BLNK_KWD, "option may be +, - or plain string",
   "Hidden", HIDE_KWD | NVAL_KWD | 19, "hidden numeric base 19 argument!",
   OPT_HELP(OPT_EXEC, "more README")
};

#define STRING	0
#define	DEMO	1
#define	TESCO	(DEMO+1)
#define STR	(TESCO+1)
#define NUM	(STR+1)
#define TABS	(NUM+1)
#define WIDE	(TABS+1)
#define MULTI	(WIDE+1)
#define HID	(MULTI+1)

/* I'm sure I should be able to do this in one go..., but the compiler barfs */
char *d1[] = {"demo", "st=asda", (char *)0};
char *d2[] = {"demo", "tesco", "num=0xd", "l=57", "+g", (char *)0};
char *d3[] = {"tesco", "+w", "-mu", "+mt", "muti", (char *)0};
char *d4[] = {"demo", "h=beef t=09,9,0x9", (char *)0};
char *d5[] = {"demo", "==", (char *)0};
char *d6[] = {"demo", "=hi", "=+", "=#", (char *)0};

char **Demos[] =
{  d1, d2, d3, d4, d5, d6,
   (char **)0
};

execute(cmd, args)
char *cmd, **args;
{  if( fork() )
      wait(0);
   else
      execv(cmd, args);
};

main(argc, argv) char **argv;
{  _opt_desc *parsed;
   char ***dp, **cmd;
   int *p, num;

   if(argc <= 1)
   {  /* no arguments, do demos */
      dp = Demos;
      while(*dp != (char **)0)
      {  printf("\n************************\nExecuting:");
         cmd = *dp;
         while(*cmd != (char *)0) printf(" %s", *cmd++);
         printf("\nGives:\n\n");
	 execute("./demo", *dp++);
      }
      return;
   }

   parsed = _cset(argv, 1);

   while( parsed->_opt_num != -1 )
   {  switch( parsed->_opt_num ) 
      {  case STRING:
     	    printf("Arbitrary string option: %s\n", parsed->_opt.sval); break;
         case DEMO:
   	    break;
         case TESCO:
   	    printf("Called using alternative command name \"tesco\"\n"); break;
         case NUM:
   	    printf("Numeric value: 0%o, %d, 0x%x\n", parsed->_opt.nval, parsed->_opt.nval, parsed->_opt.nval); break;
         case STR:
   	    printf("String valued option: %s\n", parsed->_opt.sval); break;
         case HID:
   	    printf("Hidden keyword, number input in base 19 = %d in decimal\n", parsed->_opt.nval);
         case MULTI:
   	    printf("Option may be +, - or blank, this time it was: '%c'\n", parsed->_opt_type);
   	    break;
         case WIDE:
   	    printf("Wide option turned on\n"); break;
         case TABS:
   	    p = parsed->_opt.mval;
   	    printf("Muliple numeric valued keyword with %d values:", (num = *p++));
            while(num--) printf(" %d", *p++);
   	    printf("\n");
   	    break;
	 default:	/* out of range - looks like an option but didn't match */
   	    printf("Unknown option: %s\n", parsed->_opt.sval );

      }
      parsed++;
   }
}
   
SHAR_EOF
fi # end of overwriting check
if test -f 'makefile'
then
	echo shar: will not over-write existing file "'makefile'"
else
cat << \SHAR_EOF > 'makefile'
# $Header: makefile,v 1.1 89/03/03 12:53:24 np Exp $
all:	cset.a demo

demo:	demo.c cset.a
	cc -o demo demo.c cset.a
cset.a: cset.o d_optable.o numarg.o abbrev.o not_kwd.o
	ranlib cset.a
cset.o: cset.c cset.d
	cc -c cset.c
	ar rv cset.a cset.o
.c.o:;  cc -c $*.c
	ar rv cset.a $*.o

cleanup:
	rm *.o
SHAR_EOF
fi # end of overwriting check
if test -f 'not_kwd.c'
then
	echo shar: will not over-write existing file "'not_kwd.c'"
else
cat << \SHAR_EOF > 'not_kwd.c'
static char rcsid[] = "$Header: not_kwd.c,v 1.1 89/03/03 12:53:25 np Exp $";
/* $Log:	not_kwd.c,v $
 * Revision 1.1  89/03/03  12:53:25  np
 * Initial revision
 * 
 */

/*
 * Copyright (C) 1985-1989 Nigel Perry
 *
 * This program is distributed without any warranty.
 * The author and and distributors accept no resposibility
 * to anyone for the consequence of using this program or
 * whether it serves any particular purpose or works at all,
 * unless they say so in writing.
 *
 * Everyone is granted permission to copy, modify and
 * redistribute this program, but only provided that:
 *
 * 1) They do so without financial or material gain.
 *
 * 2) The copyright notice and this notice are preserved on
 *    all copies.
 *
 * 3) The original source is preserved in any redistribution.
 *
 * I hope you find this program useful!
 *
 */

/* do execv if AS_ENVAR not set to AS_KWD
 * enables programs to switch the option format according to the environment setting
 */

#include "cset.h"

not_kwd(object, args) register char *object, **args;
{  register char *val;
   extern char *getenv();

   if( (val = getenv(AS_ENVAR)) == (char *)0 || strcmp(val, AS_KWD) != 0 )
      execv(object, args);
}
SHAR_EOF
fi # end of overwriting check
if test -f 'numarg.c'
then
	echo shar: will not over-write existing file "'numarg.c'"
else
cat << \SHAR_EOF > 'numarg.c'
static char rcsid[] = "$Header: numarg.c,v 1.1 89/03/03 12:53:25 np Exp $";
/* $Log:	numarg.c,v $
 * Revision 1.1  89/03/03  12:53:25  np
 * Initial revision
 * 
 */

/*
 * Copyright (C) 1985-1989 Nigel Perry
 *
 * This program is distributed without any warranty.
 * The author and and distributors accept no resposibility
 * to anyone for the consequence of using this program or
 * whether it serves any particular purpose or works at all,
 * unless they say so in writing.
 *
 * Everyone is granted permission to copy, modify and
 * redistribute this program, but only provided that:
 *
 * 1) They do so without financial or material gain.
 *
 * 2) The copyright notice and this notice are preserved on
 *    all copies.
 *
 * 3) The original source is preserved in any redistribution.
 *
 * I hope you find this program useful!
 *
 */

#define repeat while(1)

/* convert string to number
 * args:
 *   str:	ptr to string to be parsed
 *   poffset:   ptr to offset into str at which to start, will be updated on exit
 *   base:	base of number to parse (2 -> 36), < 2 implies 0ddd -> oct, 0xddd -> hex, ddd -> dec
 *   pflag:	ptr to flag, on input *pflag = 1 => skip leading junk (blanks always skipped)
 *			     on exit  *pflag = 0 => failed to parse number
 * return:
 *   *pflag = 1 => number parsed
 *   *pflag = 0 => 0, failed to parse
 *
 */

int numarg(str, poffset, base, pflag) register char *str;
				      register int base, *poffset, *pflag;
{  int neg, autobase;
   register int val, dig;
   char *start;
   register char c;

   neg = autobase = val = 0;

   /* if base < 2, set oct/dec/hex */
   if(base < 2) { base = 10; autobase = 1; }

   start = str; /* save start */
   str += *poffset; /* add in starting offset */

   /* find first digit */
   repeat
   {  switch(c = *str++)
      {  case ' ': continue; /* skip leading blanks */
	 case '-': neg = 1; /* flag negative */
	 case '+': if( (dig = getdig(*str++, base)) < 0 )
		   {  *poffset = str - start;
		      return(*pflag = 0); /* error */
		   }
		   break;
	 default: if( (dig = getdig(c, base)) >= 0 )
		     break; /* good digit */
		  if( *pflag == 1 ) continue; /* skip leading junk */
		  *poffset = str - start;
		  return(*pflag = 0); /* junk in de way */
      }
      break; /* kill repeat */
   }

   /* if get here, dig is first digit & neg flag be set */
   if(dig == 0 && autobase) /* check for oct/hex */
   {  if( (c = *str) == 'x' || c == 'X' ) /* hex */
      {  str++; /* skip x */
	 base = 16;
      }
      else
	 base = 8;
   }
   else
      val = dig;

   /* read in till non-digit */
   while( (dig = getdig(*str++, base)) >= 0 ) val = val * base + dig;

   *poffset = str - start - 1;
   *pflag = 1; /* good return */
   return(neg ? -val : val);
}

static getdig(c, base) register int c, base;
{  if( c >= '0' && c <= '9' )  /* this SHOULD be a case... B rules! */
      return( (c -= '0') < base ? c : -1 );
   else if( c >= 'a' && c <= 'z' )
      return( (c += 10 - 'a') < base ? c : -1 );
   else if(c >= 'A' && c <= 'Z' )
      return( (c += 10 - 'A') < base ? c : -1);
   else return(-1);
}
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0
---
Nigel Perry                                  Department of Computing
                                             Imperial College
Janet: np@uk.ac.ic.doc                       London
DARPA: np%uk.ac.ic.doc@ucl-cs                SW7
Uucp:  np@icdoc.UUCP, ukc!icdoc!np           England