diff options
author | wlestes <wlestes> | 2001-10-17 14:29:52 +0000 |
---|---|---|
committer | wlestes <wlestes> | 2001-10-17 14:29:52 +0000 |
commit | 1808505646c1ef6817b02e16c366c2179964835a (patch) | |
tree | fdeb7c171900ea19c6b7228da8b1ba7fad1bb194 /scanopt.c | |
parent | 7812a5d7ee0166408403e0dc28e15503d5ef952a (diff) | |
download | flex-1808505646c1ef6817b02e16c366c2179964835a.tar.gz |
merge latest batch of millaway's changes
Diffstat (limited to 'scanopt.c')
-rw-r--r-- | scanopt.c | 803 |
1 files changed, 803 insertions, 0 deletions
diff --git a/scanopt.c b/scanopt.c new file mode 100644 index 0000000..0adf512 --- /dev/null +++ b/scanopt.c @@ -0,0 +1,803 @@ +/* + * Copyright (c) 2001, John W. Millaway <john43@astro.temple.edu> + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "scanopt.h" + + +/* Internal structures */ + +#ifdef HAVE_STRCASECMP +#define STRCASECMP(a,b) strcasecmp(a,b) +#else +static int STRCASECMP(a,b) + const char* a; + const char* b; +{ + while(tolower(*a++) == tolower(*b++)) + ; + return b-a; +} +#endif + +#define ARG_NONE 0x01 +#define ARG_REQ 0x02 +#define ARG_OPT 0x04 +#define IS_LONG 0x08 + +struct _aux { + int flags; /* The above hex flags. */ + int namelen; /* Length of the actual option word, e.g., "--file[=foo]" is 4 */ + int printlen; /* Length of entire string, e.g., "--file[=foo]" is 12 */ +}; + + +struct _scanopt_t +{ + const optspec_t * options; /* List of options. */ + struct _aux * aux; /* Auxiliary data about options. */ + int optc; /* Number of options. */ + int argc; /* Number of args. */ + char ** argv; /* Array of strings. */ + int index; /* Used as: argv[index][subscript]. */ + int subscript; + char no_err_msg; /* If true, do not print errors. */ + char has_long; + char has_short; +}; + +/* Accessor functions. These WOULD be one-liners, but portability calls. */ +static const char* NAME(s,i) + struct _scanopt_t *s; int i; +{ + return s->options[i].opt_fmt + ((s->aux[i].flags & IS_LONG)?2:1); +} + +static int PRINTLEN(s,i) + struct _scanopt_t *s; int i; +{ + return s->aux[i].printlen; +} + +static int RVAL(s,i) + struct _scanopt_t *s; int i; +{ + return s->options[i].r_val; +} + +static int FLAGS(s,i) + struct _scanopt_t *s; int i; +{ + return s->aux[i].flags; +} +static const char* DESC(s,i) + struct _scanopt_t *s; int i; +{ + return s->options[i].desc ? s->options[i].desc : ""; +} + +#ifndef NO_SCANOPT_USAGE +static int get_cols() +{ + char *env; + int cols = 80; /* default */ + +#ifdef HAVE_NCURSES_H + initscr(); + endwin(); + if ( COLS > 0 ) + return COLS; +#endif + + if((env = getenv("COLUMNS"))!=NULL) + cols=atoi(env); + + return cols; +} +#endif + +/* Macro to check for NULL before assigning a value. */ +#define SAFE_ASSIGN(ptr,val) \ + do{ \ + if((ptr)!=NULL) \ + *(ptr) = val; \ + }while(0) + +/* Macro to assure we reset subscript whenever we adjust s->index.*/ +#define INC_INDEX(s,n) \ + do{ \ + (s)->index += (n); \ + (s)->subscript= 0; \ + }while(0) + +scanopt_t * +scanopt_init ( options, argc, argv, flags) + const optspec_t* options; + int argc; + char** argv; + int flags; +{ + int i; + struct _scanopt_t * s; + s = (struct _scanopt_t*)malloc(sizeof(struct _scanopt_t)); + + s->options = options; + s->optc = 0; + s->argc = argc; + s->argv = (char**)argv; + s->index = 1; + s->subscript = 0; + s->no_err_msg = (flags & SCANOPT_NO_ERR_MSG); + s->has_long = 0; + s->has_short = 0; + + /* Determine option count. (Find entry with all zeros).*/ + s->optc = 0; + while ( options[s->optc].opt_fmt + || options[s->optc].r_val + || options[s->optc].desc ) + s->optc++; + + /* Build auxiliary data */ + s->aux = (struct _aux*)malloc(s->optc * sizeof(struct _aux)); + + for (i=0; i < s->optc; i++) { + const char * p, *pname; + const struct optspec_t* opt; + struct _aux * aux; + + opt = s->options + i; + aux = s->aux + i; + + aux->flags = ARG_NONE; + + if( opt->opt_fmt[0] == '-' && opt->opt_fmt[1] == '-') { + aux->flags |= IS_LONG; + pname = opt->opt_fmt + 2; + s->has_long = 1; + }else{ + pname = opt->opt_fmt + 1; + s->has_short = 1; + } + aux->printlen = strlen(opt->opt_fmt); + + aux->namelen = 0; + for (p=pname+1; *p; p++) { + /* detect required arg */ + if (*p == '=' || isspace(*p) || !(aux->flags & IS_LONG)) { + if (aux->namelen==0) + aux->namelen = p - pname; + aux->flags |= ARG_REQ; + aux->flags &= ~ARG_NONE; + } + /* detect optional arg. This overrides required arg. */ + if (*p == '[') { + if (aux->namelen==0) + aux->namelen = p - pname; + aux->flags &= ~(ARG_REQ|ARG_NONE); + aux->flags |= ARG_OPT; + break; + } + } + if (aux->namelen ==0) + aux->namelen = p - pname; + } + return (scanopt_t*)s; +} + +#ifndef NO_SCANOPT_USAGE +/* these structs are for scanopt_usage(). */ +struct usg_elem { + int idx; + struct usg_elem * next; + struct usg_elem * alias; +}; +typedef struct usg_elem usg_elem; + + +/* Prints a usage message based on contents of optlist. + * Parameters: + * scanner - The scanner, already initialized with scanopt_init(). + * fp - The file stream to write to. + * usage - Text to be prepended to option list. + * Return: Always returns 0 (zero). + * The output looks something like this: + +[indent][option, alias1, alias2...][indent][description line1 + description line2...] + */ +int scanopt_usage (scanner,fp,usage) + scanopt_t* scanner; + FILE* fp; + const char* usage; +{ + struct _scanopt_t * s; + int i,columns,indent=2; + usg_elem *byr_val=NULL; /* option indices sorted by r_val */ + usg_elem *store; /* array of preallocated elements. */ + int store_idx=0; + usg_elem *ue; + int maxlen[2] = {0,0}; + int desccol=0; + int print_run=0; + + + s = (struct _scanopt_t*)scanner; + + if (usage){ + fprintf(fp,"%s\n",usage); + }else{ + /* Find the basename of argv[0] */ + const char * p; + p = s->argv[0] + strlen(s->argv[0]); + while(p != s->argv[0] && *p != '/') + --p; + if (*p == '/') + p++; + + fprintf(fp,"Usage: %s [OPTIONS]...\n", p); + } + fprintf(fp,"\n"); + + /* Sort by r_val and string. Yes, this is O(n*n), but n is small. */ + store = (usg_elem*)malloc(s->optc*sizeof(usg_elem)); + for (i=0; i < s->optc; i++) { + + /* grab the next preallocate node. */ + ue = store + store_idx++; + ue->idx = i; + ue->next = ue->alias = NULL; + + /* insert into list.*/ + if( !byr_val ) + byr_val = ue; + else { + int found_alias=0; + usg_elem **ue_curr, **ptr_if_no_alias=NULL; + ue_curr = &byr_val; + while (*ue_curr) { + if( RVAL(s,(*ue_curr)->idx) == RVAL(s,ue->idx)) { + /* push onto the alias list. */ + ue_curr = &((*ue_curr)->alias); + found_alias=1; + break; + } + if( !ptr_if_no_alias + && STRCASECMP(NAME(s,(*ue_curr)->idx),NAME(s,ue->idx)) > 0){ + ptr_if_no_alias = ue_curr; + } + ue_curr = &((*ue_curr)->next); + } + if (!found_alias && ptr_if_no_alias) + ue_curr = ptr_if_no_alias; + ue->next = *ue_curr; + *ue_curr = ue; + } + } + +#if 0 + if(1){ + printf("ORIGINAL:\n"); + for(i=0; i < s->optc;i++) + printf("%2d: %s\n",i,NAME(s,i)); + printf("SORTED:\n"); + ue = byr_val; + while(ue) { + usg_elem *ue2; + printf("%2d: %s\n",ue->idx,NAME(s,ue->idx)); + for(ue2=ue->alias; ue2; ue2=ue2->next) + printf(" +---> %2d: %s\n", ue2->idx, NAME(s,ue2->idx)); + ue = ue->next; + } + } +#endif + + /* Now build each row of output. */ + + /* first pass calculate how much room we need. */ + for (ue=byr_val; ue; ue=ue->next) { + usg_elem *ap; + int len=0; + int nshort=0,nlong=0; + + +#define CALC_LEN(i) do {\ + if(FLAGS(s,i) & IS_LONG) \ + len += (nlong++||nshort) ? 2+PRINTLEN(s,i) : PRINTLEN(s,i);\ + else\ + len += (nshort++||nlong)? 2+PRINTLEN(s,i) : PRINTLEN(s,i);\ + }while(0) + + if(!(FLAGS(s,ue->idx) & IS_LONG)) + CALC_LEN(ue->idx); + + /* do short aliases first.*/ + for(ap=ue->alias; ap; ap=ap->next){ + if(FLAGS(s,ap->idx) & IS_LONG) + continue; + CALC_LEN(ap->idx); + } + + if(FLAGS(s,ue->idx) & IS_LONG) + CALC_LEN(ue->idx); + + /* repeat the above loop, this time for long aliases. */ + for(ap=ue->alias; ap; ap=ap->next){ + if( !(FLAGS(s,ap->idx) & IS_LONG)) + continue; + CALC_LEN(ap->idx); + } + + if(len > maxlen[0]) + maxlen[0] = len; + + /* It's much easier to calculate length for description column!*/ + len = strlen(DESC(s,ue->idx)); + if(len > maxlen[1]) + maxlen[1] = len; + } + + /* Determine how much room we have, and how much we will allocate to each col. + * Do not address pathological cases. Output will just be ugly. */ + columns = get_cols() - 1; + if(maxlen[0] + maxlen[1] + indent*2 > columns ) { + /* col 0 gets whatever it wants. we'll wrap the desc col. */ + maxlen[1] = columns - (maxlen[0] + indent*2); + if(maxlen[1]< 14) /* 14 is arbitrary lower limit on desc width.*/ + maxlen[1]= INT_MAX; + } + desccol = maxlen[0] + indent*2; + +#define PRINT_SPACES(fp,n)\ + do{\ + int _n;\ + _n=(n);\ + while(_n-- > 0)\ + fputc(' ',(fp));\ + }while(0) + + + /* Second pass (same as above loop), this time we print. */ + /* Sloppy hack: We iterate twice. The first time we print short and long options. + The second time we print those lines that have ONLY long options. */ + while(print_run++ < 2) { + for (ue=byr_val; ue; ue=ue->next) { + usg_elem *ap; + int nwords=0,nchars=0,has_short=0; + +/* TODO: get has_short schtick to work */ + has_short = !(FLAGS(s,ue->idx)&IS_LONG); + for(ap=ue->alias; ap; ap=ap->next){ + if(!(FLAGS(s,ap->idx) & IS_LONG)){ + has_short=1; + break; + } + } + if( (print_run == 1 && !has_short) || + (print_run == 2 && has_short)) + continue; + + PRINT_SPACES(fp,indent);nchars+=indent; + + /* Print, adding a ", " between aliases. */ + #define PRINT_IT(i) do{\ + if(nwords++)\ + nchars+=fprintf(fp,", ");\ + nchars+=fprintf(fp,"%s",s->options[i].opt_fmt);\ + }while(0) + + if(!(FLAGS(s,ue->idx) & IS_LONG)) + PRINT_IT(ue->idx); + + /* print short aliases first.*/ + for(ap=ue->alias; ap; ap=ap->next){ + if(!(FLAGS(s,ap->idx) & IS_LONG)) + PRINT_IT(ap->idx); + } + + + if(FLAGS(s,ue->idx) & IS_LONG) + PRINT_IT(ue->idx); + + /* repeat the above loop, this time for long aliases. */ + for(ap=ue->alias; ap; ap=ap->next){ + if( FLAGS(s,ap->idx) & IS_LONG) + PRINT_IT(ap->idx); + } + + /* pad to desccol */ + PRINT_SPACES(fp, desccol - nchars); + + /* Print description, wrapped to maxlen[1] columns.*/ + if(1){ + const char * pstart; + pstart = DESC(s,ue->idx); + while(1){ + int n=0; + const char * lastws=NULL,*p; + p=pstart; + + while(*p && n < maxlen[1] && *p != '\n'){ + if(isspace(*p) || *p=='-') + lastws = p; + n++; + p++; + } + + if(!*p){ /* hit end of desc. done. */ + fprintf(fp,"%s\n",pstart); + break; + } + else if(*p == '\n'){ /* print everything up to here then wrap.*/ + fprintf(fp,"%.*s\n",n,pstart); + PRINT_SPACES(fp,desccol); + pstart = p+1; + continue; + } + else{ /* we hit the edge of the screen. wrap at space if possible.*/ + if( lastws){ + fprintf(fp,"%.*s\n",lastws-pstart,pstart); + pstart = lastws+1; + }else{ + fprintf(fp,"%.*s\n",n,pstart); + pstart = p+1; + } + PRINT_SPACES(fp,desccol); + continue; + } + } + } + } + }/* end while */ + free(store); + return 0; +} +#endif /* no scanopt_usage */ + + +static int +scanopt_err(s,opt_offset,is_short,err) + struct _scanopt_t * s; + int opt_offset; + int is_short; + int err; +{ + const char *optname=""; + char optchar[2]; + const optspec_t * opt=NULL; + + if ( opt_offset >= 0) + opt = s->options + opt_offset; + + if ( !s->no_err_msg ) { + + if( s->index > 0 && s->index < s->argc){ + if (is_short ) { + optchar[0] = s->argv[s->index][s->subscript]; + optchar[1] = '\0'; + optname = optchar; + }else { + optname = s->argv[s->index]; + } + } + + fprintf(stderr,"%s: ", s->argv[0]); + switch (err) { + case SCANOPT_ERR_ARG_NOT_ALLOWED: + fprintf(stderr,"option `%s' doesn't allow an argument\n",optname); + break; + case SCANOPT_ERR_ARG_NOT_FOUND: + fprintf(stderr,"option `%s' requires an an argument\n",optname); + break; + case SCANOPT_ERR_OPT_AMBIGUOUS: + fprintf(stderr,"option `%s' is ambiguous\n",optname); + break; + case SCANOPT_ERR_OPT_UNRECOGNIZED: + fprintf(stderr,"Unrecognized option -- `%s'\n",optname); + break; + default: + fprintf(stderr,"Unknown error=(%d)\n",err); + break; + } + } + return err; +} + + +/* Internal. Match str against the regex ^--([^=]+)(=(.*))? + * return 1 if *looks* like a long option. + * 'str' is the only input argument, the rest of the arguments are output only. + * optname will point to str + 2 + * + */ +static int +matchlongopt(str, optname ,optlen, arg, arglen) + char* str; + char** optname; + int* optlen; + char** arg; + int* arglen; +{ + char * p; + + *optname = *arg = (char*)0; + *optlen = *arglen = 0; + + /* Match regex /--./ */ + p = str; + if( p[0]!='-' || p[1]!='-' || !p[2]) + return 0; + + p += 2; + *optname = (char*)p; + + /* find the end of optname */ + while(*p && *p != '=') + ++p; + + *optlen = p - *optname; + + if (!*p) + /* an option with no '=...' part. */ + return 1; + + + /* We saw an '=' char. The rest of p is the arg.*/ + p++; + *arg = p; + while(*p) + ++p; + *arglen = p - *arg; + + return 1; +} + + +/* Internal. Look up long or short option by name. + * Long options must match a non-ambiguous prefix, or exact match. + * Short options must be exact. + * Return boolean true if found and no error. + * Error stored in err_code or zero if no error. */ +static int +find_opt (s, lookup_long, optstart, len, err_code, opt_offset) + struct _scanopt_t * s; + int lookup_long; + char * optstart; + int len; + int *err_code; + int* opt_offset; +{ + int nmatch=0,lastr_val=0,i; + *err_code = 0; + *opt_offset = -1; + + if (!optstart) + return 0; + + for(i=0; i < s->optc; i++) { + char* optname; + optname = (char*)(s->options[i].opt_fmt + (lookup_long?2:1)); + + if (lookup_long && (s->aux[i].flags & IS_LONG)) { + if (len > s->aux[i].namelen) + continue; + + if (strncmp(optname, optstart, len) == 0) { + nmatch++; + *opt_offset = i; + + /* exact match overrides all.*/ + if(len == s->aux[i].namelen){ + nmatch=1; + break; + } + + /* ambiguity is ok between aliases. */ + if(lastr_val && lastr_val == s->options[i].r_val) + nmatch--; + lastr_val = s->options[i].r_val; + } + } + else if ( !lookup_long && !(s->aux[i].flags&IS_LONG)) { + if (optname[0] == optstart[0]){ + nmatch++; + *opt_offset = i; + } + } + } + + if ( nmatch == 0 ) { + *err_code = SCANOPT_ERR_OPT_UNRECOGNIZED; + *opt_offset = -1; + } + else if ( nmatch > 1) { + *err_code = SCANOPT_ERR_OPT_AMBIGUOUS; + *opt_offset = -1; + } + + return *err_code ? 0 : 1; +} + + +int +scanopt (svoid, arg, optindex) + scanopt_t * svoid; + char ** arg; + int * optindex; +{ + char * optname=NULL, * optarg=NULL, *pstart; + int namelen=0, arglen=0; + int errcode=0, has_next; + const optspec_t * optp; + struct _scanopt_t* s; + struct _aux * auxp; + int is_short; + int opt_offset=-1; + + s = (struct _scanopt_t*)svoid; + + /* Normalize return-parameters. */ + SAFE_ASSIGN(arg,NULL); + SAFE_ASSIGN(optindex , s->index); + + if ( s->index >= s->argc ) + return 0; + + /* pstart always points to the start of our current scan. */ + pstart = s->argv[s->index] + s->subscript; + if ( !pstart ) + return 0; + + if ( s->subscript == 0 ) { + + /* test for exact match of "--" */ + if ( pstart[0]=='-' && pstart[1]=='-' && !pstart[2]) { + SAFE_ASSIGN(optindex,s->index+1); + INC_INDEX(s,1); + return 0; + } + + /* Match an opt. */ + if(matchlongopt(pstart,&optname,&namelen,&optarg,&arglen)) { + + /* it LOOKS like an opt, but is it one?! */ + if( !find_opt(s, 1, optname, namelen, &errcode,&opt_offset)){ + scanopt_err(s,opt_offset,0,errcode); + return errcode; + } + /* We handle this below. */ + is_short=0; + + /* Check for short opt. */ + }else if ( pstart[0] == '-' && pstart[1]) { + /* Pass through to below. */ + is_short=1; + s->subscript++; + pstart++; + } + + else { + /* It's not an option. We're done. */ + return 0; + } + } + + /* We have to re-check the subscript status because it + * may have changed above. */ + + if(s->subscript != 0){ + + /* we are somewhere in a run of short opts, + * e.g., at the 'z' in `tar -xzf` */ + + optname = pstart; + namelen = 1; + + if(!find_opt(s, 0, pstart, namelen, &errcode,&opt_offset)) { + return scanopt_err(s,opt_offset,1,errcode); + } + + optarg = pstart+1; + arglen = 0; + while(optarg[arglen]) + arglen++; + + if (arglen==0) + optarg=NULL; + } + + /* At this point, we have a long or short option matched at opt_offset into + * the s->options array (and corresponding aux array). + * A trailing argument is in {optarg,arglen}, if any. + */ + + /* Look ahead in argv[] to see if there is something + * that we can use as an argument (if needed). */ + has_next = s->index+1 < s->argc + && strcmp("--",s->argv[s->index+1]) != 0; + + optp = s->options + opt_offset; + auxp = s->aux + opt_offset; + + /* case: no args allowed */ + if ( auxp->flags & ARG_NONE) { + if ( optarg){ + scanopt_err(s,opt_offset,is_short,errcode=SCANOPT_ERR_ARG_NOT_ALLOWED); + INC_INDEX(s,1); + return errcode; + } + INC_INDEX(s,1); + return optp->r_val; + } + + /* case: required */ + if (auxp->flags & ARG_REQ) { + if ( !optarg && !has_next) + return scanopt_err(s,opt_offset,is_short,SCANOPT_ERR_ARG_NOT_FOUND); + + if (!optarg) { + /* Let the next argv element become the argument. */ + SAFE_ASSIGN(arg,s->argv[s->index+1]); + INC_INDEX(s,2); + }else{ + SAFE_ASSIGN(arg,(char*)optarg); + INC_INDEX(s,1); + } + return optp->r_val; + } + + /* case: optional */ + if (auxp->flags & ARG_OPT){ + SAFE_ASSIGN(arg,optarg); + INC_INDEX(s,1); + return optp->r_val; + } + + + /* Should not reach here. */ + return 0; +} + + +int +scanopt_destroy(svoid) + scanopt_t* svoid; +{ + struct _scanopt_t* s; + s = (struct _scanopt_t*)svoid; + if ( s ) { + if (s->aux) + free (s->aux); + free(s); + } + return 0; +} + + +/* vim:set tabstop=8 softtabstop=4 shiftwidth=4: */ |