1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-11-10 21:38:50 +01:00
gnupg/util/argparse.c
1998-07-21 12:53:38 +00:00

826 lines
22 KiB
C

/* [argparse.c wk 17.06.97] Argument Parser for option handling
* Copyright (C) 1998 Free Software Foundation, Inc.
* This file is part of WkLib.
*
* WkLib is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* WkLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*
*
* Note: This is an independent version of the one in WkLib
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "util.h"
#include "i18n.h"
/*********************************
* @Summary arg_parse
* #include <wk/lib.h>
*
* typedef struct {
* char *argc; pointer to argc (value subject to change)
* char ***argv; pointer to argv (value subject to change)
* unsigned flags; Global flags (DO NOT CHANGE)
* int err; print error about last option
* 1 = warning, 2 = abort
* int r_opt; return option
* int r_type; type of return value (0 = no argument found)
* union {
* int ret_int;
* long ret_long
* ulong ret_ulong;
* char *ret_str;
* } r; Return values
* struct {
* int index;
* const char *last;
* } internal; DO NOT CHANGE
* } ARGPARSE_ARGS;
*
* typedef struct {
* int short_opt;
* const char *long_opt;
* unsigned flags;
* } ARGPARSE_OPTS;
*
* int arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts );
*
* @Description
* This is my replacement for getopt(). See the example for a typical usage.
* Global flags are:
* Bit 0 : Do not remove options form argv
* Bit 1 : Do not stop at last option but return other args
* with r_opt set to -1.
* Bit 2 : Assume options and real args are mixed.
* Bit 3 : Do not use -- to stop option processing.
* Bit 4 : Do not skip the first arg.
* Bit 5 : allow usage of long option with only one dash
* Bit 6 : ignore --version
* all other bits must be set to zero, this value is modified by the function
* so assume this is write only.
* Local flags (for each option):
* Bit 2-0 : 0 = does not take an argument
* 1 = takes int argument
* 2 = takes string argument
* 3 = takes long argument
* 4 = takes ulong argument
* Bit 3 : argument is optional (r_type will the be set to 0)
* Bit 4 : allow 0x etc. prefixed values.
* Bit 7 : this is an command and not an option
* If can stop the option processing by setting opts to NULL, the function will
* then return 0.
* @Return Value
* Returns the args.r_opt or 0 if ready
* r_opt may be -2/-7 to indicate an unknown option/command.
* @See Also
* ArgExpand
* @Notes
* You do not need to process the options 'h', '--help' or '--version'
* because this function includes standard help processing; but if you
* specify '-h', '--help' or '--version' you have to do it yourself.
* The option '--' stops argument processing; if bit 1 is set the function
* continues to return normal arguments.
* To process float args or unsigned args you must use a string args and do
* the conversion yourself.
* @Example
*
* ARGPARSE_OPTS opts[] = {
* { 'v', "verbose", 0 },
* { 'd', "debug", 0 },
* { 'o', "output", 2 },
* { 'c', "cross-ref", 2|8 },
* { 'm', "my-option", 1|8 },
* { 500, "have-no-short-option-for-this-long-option", 0 },
* {0} };
* ARGPARSE_ARGS pargs = { &argc, &argv, 0 }
*
* while( ArgParse( &pargs, &opts) ) {
* switch( pargs.r_opt ) {
* case 'v': opt.verbose++; break;
* case 'd': opt.debug++; break;
* case 'o': opt.outfile = pargs.r.ret_str; break;
* case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break;
* case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break;
* case 500: opt.a_long_one++; break
* default : pargs.err = 1; break; -- force warning output --
* }
* }
* if( argc > 1 )
* log_fatal( "Too many args");
*
*/
static int set_opt_arg(ARGPARSE_ARGS *arg, unsigned flags, char *s);
static void show_help(ARGPARSE_OPTS *opts, unsigned flags);
static void show_version(void);
static void
initialize( ARGPARSE_ARGS *arg, const char *filename, unsigned *lineno )
{
if( !(arg->flags & (1<<15)) ) { /* initialize this instance */
arg->internal.index = 0;
arg->internal.last = NULL;
arg->internal.inarg = 0;
arg->internal.stopped= 0;
arg->err = 0;
arg->flags |= 1<<15; /* mark initialized */
if( *arg->argc < 0 )
log_bug("Invalid argument for ArgParse\n");
}
if( arg->err ) { /* last option was erroneous */
const char *s;
if( filename ) {
if( arg->r_opt == -6 )
s = "%s:%u: argument not expected\n";
else if( arg->r_opt == -5 )
s = "%s:%u: read error\n";
else if( arg->r_opt == -4 )
s = "%s:%u: keyword too long\n";
else if( arg->r_opt == -3 )
s = "%s:%u: missing argument\n";
else if( arg->r_opt == -7 )
s = "%s:%u: invalid command\n";
else
s = "%s:%u: invalid option\n";
log_error(s, filename, *lineno );
}
else {
if( arg->r_opt == -3 )
s = "Missing argument for option \"%.50s\"\n";
else if( arg->r_opt == -7 )
s = "Invalid command \"%.50s\"\n";
else
s = "Invalid option \"%.50s\"\n";
log_error(s, arg->internal.last? arg->internal.last:"[??]" );
}
if( arg->err != 1 )
exit(2);
arg->err = 0;
}
}
/****************
* Get options from a file.
* Lines starting with '#' are comment lines.
* Syntax is simply a keyword and the argument.
* Valid keywords are all keywords from the long_opt list without
* the leading dashes. The special keywords help, warranty and version
* are not valid here.
* Caller must free returned strings.
* If called with FP set to NULL command line args are parse instead.
*/
int
optfile_parse( FILE *fp, const char *filename, unsigned *lineno,
ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts)
{
int state, i, c;
int index=0;
char keyword[100];
char *buffer = NULL;
size_t buflen = 0;
int inverse=0;
if( !fp ) /* same as arg_parse() in this case */
return arg_parse( arg, opts );
initialize( arg, filename, lineno );
/* find the next keyword */
state = i = 0;
for(;;) {
c=getc(fp);
if( c == '\n' || c== EOF ) {
if( c != EOF )
++*lineno;
if( state == -1 )
break;
else if( state == 2 ) {
keyword[i] = 0;
for(i=0; opts[i].short_opt; i++ )
if( opts[i].long_opt && !strcmp( opts[i].long_opt, keyword) )
break;
index = i;
arg->r_opt = opts[index].short_opt;
if( inverse )
arg->r_opt = -arg->r_opt;
if( !opts[index].short_opt ) /* unknown command/option */
arg->r_opt = (opts[index].flags & 256)? -7:-2;
else if( (opts[index].flags & 8) ) /* no argument */
arg->r_opt = -3; /* error */
else /* no or optiona argument */
arg->r_type = 0; /* okay */
break;
}
else if( state == 3 ) { /* no argument found */
if( !(opts[index].flags & 7) ) /* does not take an argument */
arg->r_type = 0; /* okay */
else if( (opts[index].flags & 8) ) /* no optional argument */
arg->r_type = 0; /* okay */
else /* no required argument */
arg->r_opt = -3; /* error */
break;
}
else if( state == 4 ) { /* have an argument */
if( !(opts[index].flags & 7) ) /* does not take an argument */
arg->r_opt = -6; /* error */
else {
if( !buffer ) {
keyword[i] = 0;
buffer = m_strdup(keyword);
}
else
buffer[i] = 0;
if( !set_opt_arg(arg, opts[index].flags, buffer) )
m_free(buffer);
}
break;
}
else if( c == EOF ) {
if( ferror(fp) )
arg->r_opt = -5; /* read error */
else
arg->r_opt = 0; /* eof */
break;
}
state = 0;
i = 0;
}
else if( state == -1 )
; /* skip */
else if( !state && isspace(c) )
; /* skip leading white space */
else if( !state && c == '#' )
state = 1; /* start of a comment */
else if( state == 1 )
; /* skip comments */
else if( state == 2 && isspace(c) ) {
keyword[i] = 0;
for(i=0; opts[i].short_opt; i++ )
if( opts[i].long_opt && !strcmp( opts[i].long_opt, keyword) )
break;
index = i;
arg->r_opt = opts[index].short_opt;
if( !opts[index].short_opt ) {
arg->r_opt = (opts[index].flags & 256)? -7:-2;
state = -1; /* skip rest of line and leave */
}
else
state = 3;
}
else if( state == 3 ) { /* skip leading spaces of the argument */
if( !isspace(c) ) {
i = 0;
keyword[i++] = c;
state = 4;
}
}
else if( state == 4 ) { /* collect the argument */
if( buffer ) {
if( i < buflen-1 )
buffer[i++] = c;
else {
buflen += 50;
buffer = m_realloc(buffer, buflen);
buffer[i++] = c;
}
}
else if( i < DIM(keyword)-1 )
keyword[i++] = c;
else {
buflen = DIM(keyword)+50;
buffer = m_alloc(buflen);
memcpy(buffer, keyword, i);
buffer[i++] = c;
}
}
else if( i >= DIM(keyword)-1 ) {
arg->r_opt = -4; /* keyword to long */
state = -1; /* skip rest of line and leave */
}
else {
keyword[i++] = c;
state = 2;
}
}
return arg->r_opt;
}
int
arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts)
{
int index;
int argc;
char **argv;
char *s, *s2;
int i;
initialize( arg, NULL, NULL );
argc = *arg->argc;
argv = *arg->argv;
index = arg->internal.index;
if( !index && argc && !(arg->flags & (1<<4)) ) { /* skip the first entry */
argc--; argv++; index++;
}
next_one:
if( !argc ) { /* no more args */
arg->r_opt = 0;
goto leave; /* ready */
}
s = *argv;
arg->internal.last = s;
if( arg->internal.stopped && (arg->flags & (1<<1)) ) {
arg->r_opt = -1; /* not an option but a argument */
arg->r_type = 2;
arg->r.ret_str = s;
argc--; argv++; index++; /* set to next one */
}
else if( arg->internal.stopped ) { /* ready */
arg->r_opt = 0;
goto leave;
}
else if( *s == '-' && s[1] == '-' ) { /* long option */
arg->internal.inarg = 0;
if( !s[2] && !(arg->flags & (1<<3)) ) { /* stop option processing */
arg->internal.stopped = 1;
argc--; argv++; index++;
goto next_one;
}
for(i=0; opts[i].short_opt; i++ )
if( opts[i].long_opt && !strcmp( opts[i].long_opt, s+2) )
break;
if( !opts[i].short_opt && !strcmp( "help", s+2) )
show_help(opts, arg->flags);
else if( !opts[i].short_opt && !strcmp( "version", s+2) ) {
if( !(arg->flags & (1<<6)) ) {
show_version();
exit(0);
}
}
else if( !opts[i].short_opt && !strcmp( "warranty", s+2) ) {
puts( strusage(16) );
exit(0);
}
arg->r_opt = opts[i].short_opt;
if( !opts[i].short_opt ) {
arg->r_opt = (opts[i].flags & 256)? -7:-2;
arg->r.ret_str = s+2;
}
else if( (opts[i].flags & 7) ) {
s2 = argv[1];
if( !s2 && (opts[i].flags & 8) ) { /* no argument but it is okay*/
arg->r_type = 0; /* because it is optional */
}
else if( !s2 ) {
arg->r_opt = -3; /* missing argument */
}
else if( *s2 == '-' && (opts[i].flags & 8) ) {
/* the argument is optional and the next seems to be
* an option. We do not check this possible option
* but assume no argument */
arg->r_type = 0;
}
else {
set_opt_arg(arg, opts[i].flags, s2);
argc--; argv++; index++; /* skip one */
}
}
else { /* does not take an argument */
arg->r_type = 0;
}
argc--; argv++; index++; /* set to next one */
}
else if( (*s == '-' && s[1]) || arg->internal.inarg ) { /* short option */
int dash_kludge = 0;
i = 0;
if( !arg->internal.inarg ) {
arg->internal.inarg++;
if( arg->flags & (1<<5) ) {
for(i=0; opts[i].short_opt; i++ )
if( opts[i].long_opt && !strcmp( opts[i].long_opt, s+1)) {
dash_kludge=1;
break;
}
}
}
s += arg->internal.inarg;
if( !dash_kludge ) {
for(i=0; opts[i].short_opt; i++ )
if( opts[i].short_opt == *s )
break;
}
if( !opts[i].short_opt && ( *s == 'h' || *s == '?' ) )
show_help(opts, arg->flags);
arg->r_opt = opts[i].short_opt;
if( !opts[i].short_opt ) {
arg->r_opt = (opts[i].flags & 256)? -7:-2;
arg->internal.inarg++; /* point to the next arg */
arg->r.ret_str = s;
}
else if( (opts[i].flags & 7) ) {
if( s[1] && !dash_kludge ) {
s2 = s+1;
set_opt_arg(arg, opts[i].flags, s2);
}
else {
s2 = argv[1];
if( !s2 && (opts[i].flags & 8) ) { /* no argument but it is okay*/
arg->r_type = 0; /* because it is optional */
}
else if( !s2 ) {
arg->r_opt = -3; /* missing argument */
}
else if( *s2 == '-' && s2[1] && (opts[i].flags & 8) ) {
/* the argument is optional and the next seems to be
* an option. We do not check this possible option
* but assume no argument */
arg->r_type = 0;
}
else {
set_opt_arg(arg, opts[i].flags, s2);
argc--; argv++; index++; /* skip one */
}
}
s = "x"; /* so that !s[1] yields false */
}
else { /* does not take an argument */
arg->r_type = 0;
arg->internal.inarg++; /* point to the next arg */
}
if( !s[1] || dash_kludge ) { /* no more concatenated short options */
arg->internal.inarg = 0;
argc--; argv++; index++;
}
}
else if( arg->flags & (1<<2) ) {
arg->r_opt = -1; /* not an option but a argument */
arg->r_type = 2;
arg->r.ret_str = s;
argc--; argv++; index++; /* set to next one */
}
else {
arg->internal.stopped = 1; /* stop option processing */
goto next_one;
}
leave:
*arg->argc = argc;
*arg->argv = argv;
arg->internal.index = index;
return arg->r_opt;
}
static int
set_opt_arg(ARGPARSE_ARGS *arg, unsigned flags, char *s)
{
int base = (flags & 16)? 0 : 10;
switch( arg->r_type = (flags & 7) ) {
case 1: /* takes int argument */
arg->r.ret_int = (int)strtol(s,NULL,base);
return 0;
case 3: /* takes long argument */
arg->r.ret_long= strtol(s,NULL,base);
return 0;
case 4: /* takes ulong argument */
arg->r.ret_ulong= strtoul(s,NULL,base);
return 0;
case 2: /* takes string argument */
default:
arg->r.ret_str = s;
return 1;
}
}
static size_t
long_opt_strlen( ARGPARSE_OPTS *o )
{
size_t n = strlen(o->long_opt);
if( o->description && *o->description == '|' ) {
const char *s;
s=o->description+1;
if( *s != '=' )
n++;
for(; *s && *s != '|'; s++ )
n++;
}
return n;
}
/****************
* Print formatted help. The description string has some special
* meanings:
* - A description string which is "@" suppresses help output for
* this option
* - a description,ine which starts with a '@' and is followed by
* any other characters is printed as is; this may be used for examples
* ans such.
* - A description which starts with a '|' outputs the string between this
* bar and the next one as arguments of the long option.
*/
static void
show_help( ARGPARSE_OPTS *opts, unsigned flags )
{
const char *s;
show_version();
putchar('\n');
s = strusage(41);
puts(s);
if( opts[0].description ) { /* auto format the option description */
int i,j, indent;
/* get max. length of long options */
for(i=indent=0; opts[i].short_opt; i++ ) {
if( opts[i].long_opt )
if( !opts[i].description || *opts[i].description != '@' )
if( (j=long_opt_strlen(opts+i)) > indent && j < 35 )
indent = j;
}
/* example: " -v, --verbose Viele Sachen ausgeben" */
indent += 10;
if( *opts[0].description != '@' )
puts("Options:");
for(i=0; opts[i].short_opt; i++ ) {
s = _( opts[i].description );
if( s && *s== '@' && !s[1] ) /* hide this line */
continue;
if( s && *s == '@' ) { /* unindented comment only line */
for(s++; *s; s++ ) {
if( *s == '\n' ) {
if( s[1] )
putchar('\n');
}
else
putchar(*s);
}
putchar('\n');
continue;
}
j = 3;
if( opts[i].short_opt < 256 ) {
printf(" -%c", opts[i].short_opt );
if( !opts[i].long_opt ) {
if(s && *s == '|' ) {
putchar(' '); j++;
for(s++ ; *s && *s != '|'; s++, j++ )
putchar(*s);
if( *s )
s++;
}
}
}
else
fputs(" ", stdout);
if( opts[i].long_opt ) {
j += printf("%c --%s", opts[i].short_opt < 256?',':' ',
opts[i].long_opt );
if(s && *s == '|' ) {
if( *++s != '=' ) {
putchar(' ');
j++;
}
for( ; *s && *s != '|'; s++, j++ )
putchar(*s);
if( *s )
s++;
}
fputs(" ", stdout);
j += 3;
}
for(;j < indent; j++ )
putchar(' ');
if( s ) {
if( *s && j > indent ) {
putchar('\n');
for(j=0;j < indent; j++ )
putchar(' ');
}
for(; *s; s++ ) {
if( *s == '\n' ) {
if( s[1] ) {
putchar('\n');
for(j=0;j < indent; j++ )
putchar(' ');
}
}
else
putchar(*s);
}
}
putchar('\n');
}
if( flags & 32 )
puts("\n(A single dash may be used instead of the double ones)");
}
if( (s=strusage(19)) ) { /* bug reports to ... */
putchar('\n');
fputs(s, stdout);
}
fflush(stdout);
exit(0);
}
static void
show_version()
{
const char *s;
int i;
/* version line */
fputs(strusage(11), stdout);
if( (s=strusage(12)) )
printf(" (%s)", s );
printf(" %s\n", strusage(13) );
/* additional version lines */
for(i=20; i < 30; i++ )
if( (s=strusage(i)) )
printf("%s\n", s );
/* copyright string */
if( (s=strusage(14)) )
printf("%s\n", s );
/* copying conditions */
if( (s=strusage(15)) )
fputs(s, stdout);
/* thanks */
if( (s=strusage(18)) )
fputs(s, stdout);
/* additional program info */
for(i=30; i < 40; i++ )
if( (s=strusage(i)) )
fputs(s, stdout);
fflush(stdout);
}
void
usage( int level )
{
if( !level ) {
fprintf(stderr,"%s %s; %s\n", strusage(11), strusage(13),
strusage(14) );
fflush(stderr);
}
else if( level == 1 ) {
fputs(strusage(40),stderr);
exit(2);
}
else if( level == 2 ) {
puts(strusage(41));
exit(0);
}
}
/* Level
* 0: Copyright String auf stderr ausgeben
* 1: Kurzusage auf stderr ausgeben und beenden
* 2: Langusage auf stdout ausgeben und beenden
* 11: name of program
* 12: optional name of package which includes this program.
* 13: version string
* 14: copyright string
* 15: Short copying conditions (with LFs)
* 16: Long copying conditions (with LFs)
* 17: Optional printable OS name
* 18: Optional thanks list (with LFs)
* 19: Bug report info
*20..29: Additional lib version strings.
*30..39: Additional program info (with LFs)
* 40: short usage note (with LF)
* 41: long usage note (with LF)
*/
const char *
default_strusage( int level )
{
const char *p = NULL;
switch( level ) {
case 11: p = "foo"; break;
case 13: p = "0.0"; break;
case 14: p = "Copyright (C) 1998 Free Software Foundation, Inc."; break;
case 15: p =
"This program comes with ABSOLUTELY NO WARRANTY.\n"
"This is free software, and you are welcome to redistribute it\n"
"under certain conditions. See the file COPYING for details.\n"; break;
case 16: p =
"This is free software; you can redistribute it and/or modify\n"
"it under the terms of the GNU General Public License as published by\n"
"the Free Software Foundation; either version 2 of the License, or\n"
"(at your option) any later version.\n\n"
"It is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
"GNU General Public License for more details.\n\n"
"You should have received a copy of the GNU General Public License\n"
"along with this program; if not, write to the Free Software\n"
"Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\n";
break;
case 40: /* short and long usage */
case 41: p = ""; break;
}
return p;
}
#ifdef TEST
static struct {
int verbose;
int debug;
char *outfile;
char *crf;
int myopt;
int echo;
int a_long_one;
}opt;
int
main(int argc, char **argv)
{
ARGPARSE_OPTS opts[] = {
{ 'v', "verbose", 0 , "Laut sein"},
{ 'e', "echo" , 0 , "Zeile ausgeben, damit wir sehen, was wir einegegeben haben"},
{ 'd', "debug", 0 , "Debug\nfalls mal etasws\nSchief geht"},
{ 'o', "output", 2 },
{ 'c', "cross-ref", 2|8, "cross-reference erzeugen\n" },
{ 'm', "my-option", 1|8 },
{ 500, "a-long-option", 0 },
{0} };
ARGPARSE_ARGS pargs = { &argc, &argv, 2|4|32 };
int i;
while( ArgParse( &pargs, opts) ) {
switch( pargs.r_opt ) {
case -1 : printf( "arg='%s'\n", pargs.r.ret_str); break;
case 'v': opt.verbose++; break;
case 'e': opt.echo++; break;
case 'd': opt.debug++; break;
case 'o': opt.outfile = pargs.r.ret_str; break;
case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break;
case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break;
case 500: opt.a_long_one++; break;
default : pargs.err = 1; break; /* force warning output */
}
}
for(i=0; i < argc; i++ )
printf("%3d -> (%s)\n", i, argv[i] );
puts("Options:");
if( opt.verbose )
printf(" verbose=%d\n", opt.verbose );
if( opt.debug )
printf(" debug=%d\n", opt.debug );
if( opt.outfile )
printf(" outfile='%s'\n", opt.outfile );
if( opt.crf )
printf(" crffile='%s'\n", opt.crf );
if( opt.myopt )
printf(" myopt=%d\n", opt.myopt );
if( opt.a_long_one )
printf(" a-long-one=%d\n", opt.a_long_one );
if( opt.echo )
printf(" echo=%d\n", opt.echo );
return 0;
}
#endif
/**** bottom of file ****/