parseargs.c
/* Notice of Copyright, License and Warranty
**
** This software is Copyright 1998, 1999, 2000 Jeffrey S. Dutky
** This software is licensed for use under the terms of the GNU General
** Public License (also called the GPL), a copy of which must be included
** with any distribution of this software. You may also find a copy of the
** GPL at the Free Software Foundation's web site at http://www.fsf.org/
** or http://www.gnu.org.
**
** This software is provided "as is" and without any express or implied
** warranties, including, without limitation, the implied waranties of
** merchantability and fitness for a particular purpose.
*/
/*
** Author: Jeffrey S. Dutky
** Date: April 1999
**
** parseargs is a replacement for the standard getopts library call avialable on many unix
** platforms. While getopts requires a fair amount of work on the programmers part, parseargs
** is relatively simple to use. getopts needs to be called repeatedly in a loop with code written
** to examine and respond to the results of each call. parseargs, however, is called just once
** and all values are filled in to user supplied string variables.
**
** the parseerror routine will analyze an error code returned by parseargs and return a string
** that describes error code in plain english.
**
** the parseusage routine will produce a usage string given the program name, print width,
** and the argument format string.
*/
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include <limits.h>
#include "parseargs.h"
static int getarg(int i, int args, char *arg[], char *value){
if(strlen(arg[i])>2){ /* value seems to be part of the option flag */
strcpy(value,arg[i]+2);
return i;
}
if(i<args-1){ /* value might be in the next argument */
if(arg[i+1][0]!='-'){ /* if the next argument is NOT an option flag */
strcpy(value,arg[i+1]);
return i+1;
}
}
return -i;
}
static int minargs(char *fmt){
int i,n;
for(i=n=0;fmt[i];i++)
if(fmt[i]=='+') n++;
return n+1; /* the +1 allows for the command name */
}
static int maxargs(char *fmt){
int i,n;
n=strlen(fmt);
if(fmt[n-1]=='?')
return INT_MAX;
return n+2; /* the +2 allows for the command name and a "--" argument */
}
static int flagindices(char *fmt, char flag, int *pindex, int *vindex){
int i,p,v;
for(i=p=v=0;fmt[i];i++){
if(strchr(":#=+",fmt[i])){
if(fmt[i]!=':')
p++;
v++;
}
if(fmt[i]==flag)
return 0;
}
return -1;
}
static char *nil="";
static char **makevarlist(va_list ap, char *fmt){
int i, args;
char **arg;
for(i=args=0;fmt[i];i++){
if(fmt[i]==':' || fmt[i]=='#' || fmt[i]=='=' || fmt[i]=='+')
args++;
}
arg=(char**)malloc(sizeof(char*)*args);
if(arg!=NULL){
for(i=0;i<args;i++){
arg[i]=va_arg(ap,char*);
if(arg[i]==NULL)
arg[i]=nil;
}
}
return arg;
}
struct param{
char flag;
int vindex;
int pindex;
int seen;
};
static struct param *makeparams(char *fmt, int nparams){
int i,v,p,err;
struct param *params;
if(nparams==0)
return NULL;
params=(struct param*)malloc(sizeof(struct param)*nparams);
if(params==NULL)
return NULL;
for(i=p=v=0;fmt[i];i++){
if(strchr(":#=+",fmt[i])==NULL)
continue;
if(fmt[i]!=':'){
if(fmt[i]=='=')
params[p].flag=fmt[i-1];
else
params[p].flag='\0';
params[p].vindex=v;
params[p].pindex=p;
params[p].seen=0;
p++;
}
v++;
}
return params;
}
#define ERR_NUMPARAMS (-1000)
#define ERR_VARSALLOC (-2000)
#define ERR_MAKEPARAMS (-3000)
#define ERR_INVALIDOPT (-4000)
#define ERR_MAPSALLOC (-5000)
static void cleanup(char **var, struct param *params, int *map){
if(var)
free(var);
if(params)
free(params);
if(map)
free(map);
return;
}
int parseargs(int args, char *arg[], char *fmt, char *opt, ...){
va_list ap;
char **var=NULL;
struct param *params=NULL;
int i, j, n, v, p, paramseen=0, paramcount;
char *tmp, c;
int *vmap=NULL, *pmap=NULL, *fmap=NULL;
int flagsdone=0;
va_start(ap,opt); /* make an array of the variable strings */
var=makevarlist(ap,fmt);
va_end(ap);
if(var==NULL){
cleanup(var,params,vmap);
return ERR_VARSALLOC;
}
vmap=(int*)malloc(sizeof(int)*768);
if(vmap==NULL){
cleanup(var,params,vmap);
return ERR_MAPSALLOC;
}
pmap=vmap+256;
fmap=vmap+512;
for(i=0; i<256; i++){
vmap[i]=-1; /* initialize flag char to variable mapping */
pmap[i]=-1; /* initialize flag char to parameter mapping */
fmap[i]=0; /* initialize flag validity mapping */
}
for(i=p=v=0; fmt[i]; i++){
switch(fmt[i]){
case '=': /* parameter with optional flag */
pmap[fmt[i-1]]=p++;
case ':': /* option flag wtih value */
vmap[fmt[i-1]]=v++;
break;
case '#':case '+': /* optional and required parameters */
v++, p++;
break;
default: /* any flag character */
fmap[fmt[i]]=1;
}
}
paramcount=p;
params=makeparams(fmt, paramcount); /* make an array of parameter structs */
if(params==NULL && paramcount>0){
cleanup(var,params,vmap);
return ERR_MAKEPARAMS;
}
if(args<minargs(fmt) || args>maxargs(fmt)){
cleanup(var,params,vmap);
return ERR_NUMPARAMS;
}
for(i=1,n=0;i<args;i++){ /* for each argument in the arg list */
if(arg[i][0]=='-' && !flagsdone && arg[i][1]!='\0'){ /* if the argument is a flag, */
if(arg[i][1]=='-'){ /* check for the special "--" argument */
flagsdone=1;
continue;
}
for(j=1;j>0 && (c=arg[i][j])!='\0';j++){
if(fmap[c]==0){ /* is this a valid option flag? */
cleanup(var,params,vmap);
return ERR_INVALIDOPT-i;
}
opt[n++]=c;
opt[n]='\0';
v=vmap[c];
if(v>=0){ /* does this flag have an argument? */
j=-1; /* quit examining this group of flags */
v=vmap[c];
p=pmap[c];
if(p>=0){
if(params[p].seen==0)
paramseen++;
params[p].seen=1;
}
i=getarg(i,args,arg,var[v]);
}
}
}else{ /* otherwise, the argument is a parameter */
if(paramseen<paramcount){ /* if we have any parameters left to fill */
for(p=0; p<paramcount; p++)
if(params[p].seen==0) break;
v=params[p].vindex;
if(var[v]!=nil)
strcpy(var[v],arg[i]);
if(params[p].seen==0)
paramseen++;
params[p].seen=1;
}else{
i--;
break; /* there are no parameters left */
}
}
}
cleanup(var,params,vmap);
return i;
}
static const char *errorstring[]={
"no error",
"too few or too many arguments on command line",
"failed to allocate variable string array",
"failed to create parameter list",
"encountered an undefined option flag",
"failed to allocate var and param map array",
NULL
};
const char *parseerror(int error){
int maxerror;
for(maxerror=0;errorstring[maxerror];maxerror++);
error=abs(error/1000);
if(error>maxerror)
return "unknown error code";
return errorstring[error];
}
int badusage(int retval){
retval=abs(retval/1000);
if(retval==abs(ERR_NUMPARAMS/1000) || retval==abs(ERR_INVALIDOPT/1000))
return 1;
return 0;
}
/* length of the last line in a string buffer */
static int lastlen(const char *str){
int i,n;
for(i=n=0; str[i]; i++){
if(str[i]=='\n') n=0;
else n++;
}
return n;
}
const char *parseusage(char *prog, int width, char *fmt, ...){
static char *usagebuffer=NULL;
int bufsize=0,flags=0,args=0,fparams=0,params=0,oparams=0,more=0,nlen=0;
int names=0, i, n=0;
char **name, flag[256], flg[2], buf[256];
va_list ap;
for(i=0; fmt[i]; i++){
switch(fmt[i]){
case ':':
args++;
names++;
if(n>0) n--;
break;
case '=':
fparams++;
names++;
if(n>0) n--;
break;
case '#':
oparams++;
names++;
break;
case '+':
params++;
names++;
break;
default:
flag[n++]=fmt[i];
flags++;
}
}
if(flag[n-1]=='?')
n--;
flag[n]='\0';
if(fmt[i-1]=='?')
more=1;
name=(char**)malloc(sizeof(char*)*names);
if(name==NULL)
return NULL;
va_start(ap,fmt);
for(i=0; i<names; i++){
name[i]=va_arg(ap,char*);
if(name[i]==NULL) return NULL;
nlen+=strlen(name[i]);
}
va_end(ap);
bufsize=strlen(prog)+((flags)?flags+4:0)+args*6+fparams*8;
bufsize+=params+oparams*3+more*4+nlen+1;
bufsize+=(bufsize/width)*2;
if(usagebuffer!=NULL)
free(usagebuffer);
usagebuffer=(char*)malloc(sizeof(char)*bufsize);
if(usagebuffer==NULL)
return NULL;
strcpy(usagebuffer, prog); /* put the program name in the usage buffer */
if(flags){ /* put the simple option flags in the buffer: [-fghi] */
strcat(usagebuffer, " [-");
strcat(usagebuffer, flag);
strcat(usagebuffer, "]");
}
for(i=1, n=0; fmt[i]; i++){ /* put in the argumented flags and parameters */
flg[0]=fmt[i-1];
flg[1]='\0';
buf[0]='\0';
switch(fmt[i]){
case ':': /* a flag with an argument: [-f arg] */
strcpy(buf, "[-");
strcat(buf, flg);
strcat(buf, " ");
strcat(buf, name[n++]);
strcat(buf, "]");
break;
case '=': /* a parameter with an optional flag: [[-f] param] */
strcpy(buf, "[[-");
strcat(buf, flg);
strcat(buf, "] ");
strcat(buf, name[n++]);
strcat(buf, "]");
break;
case '#': /* an optional parameter: [param] */
strcpy(buf, "[");
strcat(buf, name[n++]);
strcat(buf, "]");
break;
case '+': /* a required parameter */
strcpy(buf, name[n++]);
break;
default:
continue;
}
if(lastlen(usagebuffer)+strlen(buf)+1>=width)
strcat(usagebuffer,"\n\t");
else
strcat(usagebuffer," ");
strcat(usagebuffer,buf);
}
if(more){ /* put in the elipses for argument llists of indeterminate length */
if(lastlen(usagebuffer)+4>=width)
strcat(usagebuffer,"\n\t...");
else
strcat(usagebuffer, " ...");
}
return usagebuffer;
}