mirror of
git://git.gnupg.org/gnupg.git
synced 2024-10-31 20:08:43 +01:00
1206 lines
34 KiB
C
1206 lines
34 KiB
C
/* [argparse.c wk 17.06.97] Argument Parser for option handling
|
|
* Copyright (C) 1998, 1999, 2000, 2001, 2006
|
|
* 2007, 2008 Free Software Foundation, Inc.
|
|
*
|
|
* This file is part of JNLIB.
|
|
*
|
|
* JNLIB is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as
|
|
* published by the Free Software Foundation; either version 2.1 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* JNLIB 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
|
|
#include "libjnlib-config.h"
|
|
#include "mischelp.h"
|
|
#include "stringhelp.h"
|
|
#include "logging.h"
|
|
#ifdef JNLIB_NEED_UTF8CONV
|
|
#include "utf8conv.h"
|
|
#endif
|
|
#include "argparse.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 idx;
|
|
* const char *last;
|
|
* void *aliases;
|
|
* } 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 a command and not an option
|
|
* You 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");
|
|
*
|
|
*/
|
|
|
|
typedef struct alias_def_s *ALIAS_DEF;
|
|
struct alias_def_s {
|
|
ALIAS_DEF next;
|
|
char *name; /* malloced buffer with name, \0, value */
|
|
const char *value; /* ptr into name */
|
|
};
|
|
|
|
static const char *(*strusage_handler)( int ) = NULL;
|
|
|
|
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.idx = 0;
|
|
arg->internal.last = NULL;
|
|
arg->internal.inarg = 0;
|
|
arg->internal.stopped = 0;
|
|
arg->internal.aliases = NULL;
|
|
arg->internal.cur_alias = NULL;
|
|
arg->err = 0;
|
|
arg->flags |= 1<<15; /* Mark as initialized. */
|
|
if ( *arg->argc < 0 )
|
|
jnlib_log_bug ("invalid argument for arg_parsee\n");
|
|
}
|
|
|
|
|
|
if (arg->err)
|
|
{
|
|
/* Last option was erroneous. */
|
|
const char *s;
|
|
|
|
if (filename)
|
|
{
|
|
if ( arg->r_opt == ARGPARSE_UNEXPECTED_ARG )
|
|
s = _("argument not expected");
|
|
else if ( arg->r_opt == ARGPARSE_READ_ERROR )
|
|
s = _("read error");
|
|
else if ( arg->r_opt == ARGPARSE_KEYWORD_TOO_LONG )
|
|
s = _("keyword too long");
|
|
else if ( arg->r_opt == ARGPARSE_MISSING_ARG )
|
|
s = _("missing argument");
|
|
else if ( arg->r_opt == ARGPARSE_INVALID_COMMAND )
|
|
s = _("invalid command");
|
|
else if ( arg->r_opt == ARGPARSE_INVALID_ALIAS )
|
|
s = _("invalid alias definition");
|
|
else if ( arg->r_opt == ARGPARSE_OUT_OF_CORE )
|
|
s = _("out of core");
|
|
else
|
|
s = _("invalid option");
|
|
jnlib_log_error ("%s:%u: %s\n", filename, *lineno, s);
|
|
}
|
|
else
|
|
{
|
|
s = arg->internal.last? arg->internal.last:"[??]";
|
|
|
|
if ( arg->r_opt == ARGPARSE_MISSING_ARG )
|
|
jnlib_log_error (_("missing argument for option \"%.50s\"\n"), s);
|
|
else if ( arg->r_opt == ARGPARSE_UNEXPECTED_ARG )
|
|
jnlib_log_error (_("option \"%.50s\" does not expect an "
|
|
"argument\n"), s );
|
|
else if ( arg->r_opt == ARGPARSE_INVALID_COMMAND )
|
|
jnlib_log_error (_("invalid command \"%.50s\"\n"), s);
|
|
else if ( arg->r_opt == ARGPARSE_AMBIGUOUS_OPTION )
|
|
jnlib_log_error (_("option \"%.50s\" is ambiguous\n"), s);
|
|
else if ( arg->r_opt == ARGPARSE_AMBIGUOUS_OPTION )
|
|
jnlib_log_error (_("command \"%.50s\" is ambiguous\n"),s );
|
|
else if ( arg->r_opt == ARGPARSE_OUT_OF_CORE )
|
|
jnlib_log_error ("%s\n", _("out of core\n"));
|
|
else
|
|
jnlib_log_error (_("invalid option \"%.50s\"\n"), s);
|
|
}
|
|
if ( arg->err != 1 )
|
|
exit (2);
|
|
arg->err = 0;
|
|
}
|
|
|
|
/* Zero out the return value union. */
|
|
arg->r.ret_str = NULL;
|
|
arg->r.ret_long = 0;
|
|
}
|
|
|
|
|
|
static void
|
|
store_alias( ARGPARSE_ARGS *arg, char *name, char *value )
|
|
{
|
|
/* TODO: replace this dummy function with a rea one
|
|
* and fix the probelms IRIX has with (ALIAS_DEV)arg..
|
|
* used as lvalue
|
|
*/
|
|
(void)arg;
|
|
(void)name;
|
|
(void)value;
|
|
#if 0
|
|
ALIAS_DEF a = jnlib_xmalloc( sizeof *a );
|
|
a->name = name;
|
|
a->value = value;
|
|
a->next = (ALIAS_DEF)arg->internal.aliases;
|
|
(ALIAS_DEF)arg->internal.aliases = a;
|
|
#endif
|
|
}
|
|
|
|
/****************
|
|
* 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.
|
|
* The special keyword "alias" may be used to store alias definitions,
|
|
* which are later expanded like long options.
|
|
* Caller must free returned strings.
|
|
* If called with FP set to NULL command line args are parse instead.
|
|
*
|
|
* Q: Should we allow the syntax
|
|
* keyword = value
|
|
* and accept for boolean options a value of 1/0, yes/no or true/false?
|
|
* Note: Abbreviation of options is here not allowed.
|
|
*/
|
|
int
|
|
optfile_parse (FILE *fp, const char *filename, unsigned *lineno,
|
|
ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts)
|
|
{
|
|
int state, i, c;
|
|
int idx=0;
|
|
char keyword[100];
|
|
char *buffer = NULL;
|
|
size_t buflen = 0;
|
|
int in_alias=0;
|
|
|
|
if (!fp) /* Divert to to 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;
|
|
}
|
|
idx = i;
|
|
arg->r_opt = opts[idx].short_opt;
|
|
if (!opts[idx].short_opt )
|
|
arg->r_opt = ((opts[idx].flags & ARGPARSE_OPT_COMMAND)
|
|
? ARGPARSE_INVALID_COMMAND
|
|
: ARGPARSE_INVALID_OPTION);
|
|
else if (!(opts[idx].flags & 7))
|
|
arg->r_type = 0; /* Does not take an arg. */
|
|
else if ((opts[idx].flags & 8) )
|
|
arg->r_type = 0; /* Arg is optional. */
|
|
else
|
|
arg->r_opt = ARGPARSE_MISSING_ARG;
|
|
|
|
break;
|
|
}
|
|
else if (state == 3)
|
|
{
|
|
/* No argument found. */
|
|
if (in_alias)
|
|
arg->r_opt = ARGPARSE_MISSING_ARG;
|
|
else if (!(opts[idx].flags & 7))
|
|
arg->r_type = 0; /* Does not take an arg. */
|
|
else if ((opts[idx].flags & 8))
|
|
arg->r_type = 0; /* No optional argument. */
|
|
else
|
|
arg->r_opt = ARGPARSE_MISSING_ARG;
|
|
|
|
break;
|
|
}
|
|
else if (state == 4)
|
|
{
|
|
/* Has an argument. */
|
|
if (in_alias)
|
|
{
|
|
if (!buffer)
|
|
arg->r_opt = ARGPARSE_UNEXPECTED_ARG;
|
|
else
|
|
{
|
|
char *p;
|
|
|
|
buffer[i] = 0;
|
|
p = strpbrk (buffer, " \t");
|
|
if (p)
|
|
{
|
|
*p++ = 0;
|
|
trim_spaces (p);
|
|
}
|
|
if (!p || !*p)
|
|
{
|
|
jnlib_free (buffer);
|
|
arg->r_opt = ARGPARSE_INVALID_ALIAS;
|
|
}
|
|
else
|
|
{
|
|
store_alias (arg, buffer, p);
|
|
}
|
|
}
|
|
}
|
|
else if (!(opts[idx].flags & 7))
|
|
arg->r_opt = ARGPARSE_UNEXPECTED_ARG;
|
|
else
|
|
{
|
|
char *p;
|
|
|
|
if (!buffer)
|
|
{
|
|
keyword[i] = 0;
|
|
buffer = jnlib_strdup (keyword);
|
|
if (!buffer)
|
|
arg->r_opt = ARGPARSE_OUT_OF_CORE;
|
|
}
|
|
else
|
|
buffer[i] = 0;
|
|
|
|
if (buffer)
|
|
{
|
|
trim_spaces (buffer);
|
|
p = buffer;
|
|
if (*p == '"')
|
|
{
|
|
/* Remove quotes. */
|
|
p++;
|
|
if (*p && p[strlen(p)-1] == '\"' )
|
|
p[strlen(p)-1] = 0;
|
|
}
|
|
if (!set_opt_arg (arg, opts[idx].flags, p))
|
|
jnlib_free(buffer);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
else if (c == EOF)
|
|
{
|
|
if (ferror (fp))
|
|
arg->r_opt = ARGPARSE_READ_ERROR;
|
|
else
|
|
arg->r_opt = 0; /* EOF. */
|
|
break;
|
|
}
|
|
state = 0;
|
|
i = 0;
|
|
}
|
|
else if (state == -1)
|
|
; /* Skip. */
|
|
else if (state == 0 && isascii (c) && isspace(c))
|
|
; /* Skip leading white space. */
|
|
else if (state == 0 && c == '#' )
|
|
state = 1; /* Start of a comment. */
|
|
else if (state == 1)
|
|
; /* Skip comments. */
|
|
else if (state == 2 && isascii (c) && isspace(c))
|
|
{
|
|
/* Check keyword. */
|
|
keyword[i] = 0;
|
|
for (i=0; opts[i].short_opt; i++ )
|
|
if (opts[i].long_opt && !strcmp (opts[i].long_opt, keyword))
|
|
break;
|
|
idx = i;
|
|
arg->r_opt = opts[idx].short_opt;
|
|
if (!opts[idx].short_opt)
|
|
{
|
|
if (!strcmp (keyword, "alias"))
|
|
{
|
|
in_alias = 1;
|
|
state = 3;
|
|
}
|
|
else
|
|
{
|
|
arg->r_opt = ((opts[idx].flags & ARGPARSE_OPT_COMMAND)
|
|
? ARGPARSE_INVALID_COMMAND
|
|
: ARGPARSE_INVALID_OPTION);
|
|
state = -1; /* Skip rest of line and leave. */
|
|
}
|
|
}
|
|
else
|
|
state = 3;
|
|
}
|
|
else if (state == 3)
|
|
{
|
|
/* Skip leading spaces of the argument. */
|
|
if (!isascii (c) || !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
|
|
{
|
|
char *tmp;
|
|
size_t tmplen = buflen + 50;
|
|
|
|
tmp = jnlib_realloc (buffer, tmplen);
|
|
if (tmp)
|
|
{
|
|
buflen = tmplen;
|
|
buffer = tmp;
|
|
buffer[i++] = c;
|
|
}
|
|
else
|
|
{
|
|
jnlib_free (buffer);
|
|
arg->r_opt = ARGPARSE_OUT_OF_CORE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (i < DIM(keyword)-1)
|
|
keyword[i++] = c;
|
|
else
|
|
{
|
|
size_t tmplen = DIM(keyword) + 50;
|
|
buffer = jnlib_malloc (tmplen);
|
|
if (buffer)
|
|
{
|
|
buflen = tmplen;
|
|
memcpy(buffer, keyword, i);
|
|
buffer[i++] = c;
|
|
}
|
|
else
|
|
{
|
|
arg->r_opt = ARGPARSE_OUT_OF_CORE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (i >= DIM(keyword)-1)
|
|
{
|
|
arg->r_opt = ARGPARSE_KEYWORD_TOO_LONG;
|
|
state = -1; /* Skip rest of line and leave. */
|
|
}
|
|
else
|
|
{
|
|
keyword[i++] = c;
|
|
state = 2;
|
|
}
|
|
}
|
|
|
|
return arg->r_opt;
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
find_long_option( ARGPARSE_ARGS *arg,
|
|
ARGPARSE_OPTS *opts, const char *keyword )
|
|
{
|
|
int i;
|
|
size_t n;
|
|
|
|
(void)arg;
|
|
|
|
/* Would be better if we can do a binary search, but it is not
|
|
possible to reorder our option table because we would mess
|
|
up our help strings - What we can do is: Build a nice option
|
|
lookup table wehn this function is first invoked */
|
|
if( !*keyword )
|
|
return -1;
|
|
for(i=0; opts[i].short_opt; i++ )
|
|
if( opts[i].long_opt && !strcmp( opts[i].long_opt, keyword) )
|
|
return i;
|
|
#if 0
|
|
{
|
|
ALIAS_DEF a;
|
|
/* see whether it is an alias */
|
|
for( a = args->internal.aliases; a; a = a->next ) {
|
|
if( !strcmp( a->name, keyword) ) {
|
|
/* todo: must parse the alias here */
|
|
args->internal.cur_alias = a;
|
|
return -3; /* alias available */
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
/* not found, see whether it is an abbreviation */
|
|
/* aliases may not be abbreviated */
|
|
n = strlen( keyword );
|
|
for(i=0; opts[i].short_opt; i++ ) {
|
|
if( opts[i].long_opt && !strncmp( opts[i].long_opt, keyword, n ) ) {
|
|
int j;
|
|
for(j=i+1; opts[j].short_opt; j++ ) {
|
|
if( opts[j].long_opt
|
|
&& !strncmp( opts[j].long_opt, keyword, n ) )
|
|
return -2; /* abbreviation is ambiguous */
|
|
}
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts)
|
|
{
|
|
int idx;
|
|
int argc;
|
|
char **argv;
|
|
char *s, *s2;
|
|
int i;
|
|
|
|
initialize( arg, NULL, NULL );
|
|
argc = *arg->argc;
|
|
argv = *arg->argv;
|
|
idx = arg->internal.idx;
|
|
|
|
if (!idx && argc && !(arg->flags & ARGPARSE_FLAG_ARG0))
|
|
{
|
|
/* Skip the first argument. */
|
|
argc--; argv++; idx++;
|
|
}
|
|
|
|
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 & ARGPARSE_FLAG_ALL))
|
|
{
|
|
arg->r_opt = ARGPARSE_IS_ARG; /* Not an option but an argument. */
|
|
arg->r_type = 2;
|
|
arg->r.ret_str = s;
|
|
argc--; argv++; idx++; /* set to next one */
|
|
}
|
|
else if( arg->internal.stopped )
|
|
{
|
|
arg->r_opt = 0;
|
|
goto leave; /* Ready. */
|
|
}
|
|
else if ( *s == '-' && s[1] == '-' )
|
|
{
|
|
/* Long option. */
|
|
char *argpos;
|
|
|
|
arg->internal.inarg = 0;
|
|
if (!s[2] && !(arg->flags & ARGPARSE_FLAG_NOSTOP))
|
|
{
|
|
/* Stop option processing. */
|
|
arg->internal.stopped = 1;
|
|
argc--; argv++; idx++;
|
|
goto next_one;
|
|
}
|
|
|
|
argpos = strchr( s+2, '=' );
|
|
if ( argpos )
|
|
*argpos = 0;
|
|
i = find_long_option ( arg, opts, s+2 );
|
|
if ( argpos )
|
|
*argpos = '=';
|
|
|
|
if ( i < 0 && !strcmp ( "help", s+2) )
|
|
show_help (opts, arg->flags);
|
|
else if ( i < 0 && !strcmp ( "version", s+2) )
|
|
{
|
|
if (!(arg->flags & ARGPARSE_FLAG_NOVERSION))
|
|
{
|
|
show_version ();
|
|
exit(0);
|
|
}
|
|
}
|
|
else if ( i < 0 && !strcmp( "warranty", s+2))
|
|
{
|
|
puts ( strusage (16) );
|
|
exit (0);
|
|
}
|
|
else if ( i < 0 && !strcmp( "dump-options", s+2) )
|
|
{
|
|
for (i=0; opts[i].short_opt; i++ )
|
|
{
|
|
if ( opts[i].long_opt )
|
|
printf ("--%s\n", opts[i].long_opt);
|
|
}
|
|
fputs ("--dump-options\n--help\n--version\n--warranty\n", stdout);
|
|
exit (0);
|
|
}
|
|
|
|
if ( i == -2 )
|
|
arg->r_opt = ARGPARSE_AMBIGUOUS_OPTION;
|
|
else if ( i == -1 )
|
|
{
|
|
arg->r_opt = ARGPARSE_INVALID_OPTION;
|
|
arg->r.ret_str = s+2;
|
|
}
|
|
else
|
|
arg->r_opt = opts[i].short_opt;
|
|
if ( i < 0 )
|
|
;
|
|
else if ( (opts[i].flags & 0x07) )
|
|
{
|
|
if ( argpos )
|
|
{
|
|
s2 = argpos+1;
|
|
if ( !*s2 )
|
|
s2 = NULL;
|
|
}
|
|
else
|
|
s2 = argv[1];
|
|
if ( !s2 && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) )
|
|
{
|
|
arg->r_type = ARGPARSE_TYPE_NONE; /* Argument is optional. */
|
|
}
|
|
else if ( !s2 )
|
|
{
|
|
arg->r_opt = ARGPARSE_MISSING_ARG;
|
|
}
|
|
else if ( !argpos && *s2 == '-'
|
|
&& (opts[i].flags & ARGPARSE_OPT_OPTIONAL) )
|
|
{
|
|
/* 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 = ARGPARSE_TYPE_NONE;
|
|
}
|
|
else
|
|
{
|
|
set_opt_arg (arg, opts[i].flags, s2);
|
|
if ( !argpos )
|
|
{
|
|
argc--; argv++; idx++; /* Skip one. */
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Does not take an argument. */
|
|
if ( argpos )
|
|
arg->r_type = ARGPARSE_UNEXPECTED_ARG;
|
|
else
|
|
arg->r_type = 0;
|
|
}
|
|
argc--; argv++; idx++; /* 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 & ARGPARSE_FLAG_ONEDASH) )
|
|
{
|
|
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 & ARGPARSE_OPT_COMMAND)?
|
|
ARGPARSE_INVALID_COMMAND:ARGPARSE_INVALID_OPTION;
|
|
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 & ARGPARSE_OPT_OPTIONAL) )
|
|
{
|
|
arg->r_type = ARGPARSE_TYPE_NONE;
|
|
}
|
|
else if ( !s2 )
|
|
{
|
|
arg->r_opt = ARGPARSE_MISSING_ARG;
|
|
}
|
|
else if ( *s2 == '-' && s2[1]
|
|
&& (opts[i].flags & ARGPARSE_OPT_OPTIONAL) )
|
|
{
|
|
/* 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 = ARGPARSE_TYPE_NONE;
|
|
}
|
|
else
|
|
{
|
|
set_opt_arg (arg, opts[i].flags, s2);
|
|
argc--; argv++; idx++; /* Skip one. */
|
|
}
|
|
}
|
|
s = "x"; /* This is so that !s[1] yields false. */
|
|
}
|
|
else
|
|
{
|
|
/* Does not take an argument. */
|
|
arg->r_type = ARGPARSE_TYPE_NONE;
|
|
arg->internal.inarg++; /* Point to the next arg. */
|
|
}
|
|
if ( !s[1] || dash_kludge )
|
|
{
|
|
/* No more concatenated short options. */
|
|
arg->internal.inarg = 0;
|
|
argc--; argv++; idx++;
|
|
}
|
|
}
|
|
else if ( arg->flags & ARGPARSE_FLAG_MIXED )
|
|
{
|
|
arg->r_opt = ARGPARSE_IS_ARG;
|
|
arg->r_type = 2;
|
|
arg->r.ret_str = s;
|
|
argc--; argv++; idx++; /* Set to next one. */
|
|
}
|
|
else
|
|
{
|
|
arg->internal.stopped = 1; /* Stop option processing. */
|
|
goto next_one;
|
|
}
|
|
|
|
leave:
|
|
*arg->argc = argc;
|
|
*arg->argv = argv;
|
|
arg->internal.idx = idx;
|
|
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 ARGPARSE_TYPE_INT:
|
|
arg->r.ret_int = (int)strtol(s,NULL,base);
|
|
return 0;
|
|
case ARGPARSE_TYPE_LONG:
|
|
arg->r.ret_long= strtol(s,NULL,base);
|
|
return 0;
|
|
case ARGPARSE_TYPE_ULONG:
|
|
arg->r.ret_ulong= strtoul(s,NULL,base);
|
|
return 0;
|
|
case ARGPARSE_TYPE_STRING:
|
|
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;
|
|
#ifdef JNLIB_NEED_UTF8CONV
|
|
int is_utf8 = is_native_utf8 ();
|
|
#endif
|
|
|
|
s=o->description+1;
|
|
if ( *s != '=' )
|
|
n++;
|
|
/* For a (mostly) correct length calculation we exclude
|
|
continuation bytes (10xxxxxx) if we are on a native utf8
|
|
terminal. */
|
|
for (; *s && *s != '|'; s++ )
|
|
#ifdef JNLIB_NEED_UTF8CONV
|
|
if ( is_utf8 && (*s&0xc0) != 0x80 )
|
|
#endif
|
|
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 int 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 & ARGPARSE_FLAG_ONEDASH) )
|
|
puts ("\n(A single dash may be used instead of the double ones)");
|
|
}
|
|
if ( (s=strusage(19)) )
|
|
{
|
|
/* bug reports to ... */
|
|
char *s2;
|
|
|
|
putchar('\n');
|
|
s2 = strstr (s, "@EMAIL@");
|
|
if (s2)
|
|
{
|
|
if (s2-s)
|
|
fwrite (s, s2-s, 1, stdout);
|
|
#ifdef PACKAGE_BUGREPORT
|
|
fputs (PACKAGE_BUGREPORT, stdout);
|
|
#else
|
|
fputs ("bug@example.org", stdout);
|
|
#endif
|
|
s2 += 7;
|
|
if (*s2)
|
|
fputs (s2, stdout);
|
|
}
|
|
else
|
|
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 );
|
|
/* Licence string. */
|
|
if( (s=strusage (10)) )
|
|
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)
|
|
{
|
|
const char *p;
|
|
|
|
if (!level)
|
|
{
|
|
fprintf(stderr,"%s %s; %s\n", strusage(11), strusage(13), strusage (14));
|
|
fflush (stderr);
|
|
}
|
|
else if (level == 1)
|
|
{
|
|
p = strusage (40);
|
|
fputs (p, stderr);
|
|
if (*p && p[strlen(p)] != '\n')
|
|
putc ('\n', stderr);
|
|
exit (2);
|
|
}
|
|
else if (level == 2)
|
|
{
|
|
puts (strusage(41));
|
|
exit (0);
|
|
}
|
|
}
|
|
|
|
/* Level
|
|
* 0: Print copyright string to stderr
|
|
* 1: Print a short usage hint to stderr and terminate
|
|
* 2: Print a long usage hint to stdout and terminate
|
|
* 10: Return license info string
|
|
* 11: Return the name of the program
|
|
* 12: Return 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 *
|
|
strusage( int level )
|
|
{
|
|
const char *p = strusage_handler? strusage_handler(level) : NULL;
|
|
|
|
if ( p )
|
|
return p;
|
|
|
|
switch ( level )
|
|
{
|
|
case 10: p = ("License GPLv3+: GNU GPL version 3 or later "
|
|
"<http://gnu.org/licenses/gpl.html>");
|
|
break;
|
|
case 11: p = "foo"; break;
|
|
case 13: p = "0.0"; break;
|
|
case 14: p = "Copyright (C) 2009 Free Software Foundation, Inc."; break;
|
|
case 15: p =
|
|
"This is free software: you are free to change and redistribute it.\n"
|
|
"There is NO WARRANTY, to the extent permitted by law.\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 3 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 software. If not, see <http://www.gnu.org/licenses/>.\n";
|
|
break;
|
|
case 40: /* short and long usage */
|
|
case 41: p = ""; break;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
void
|
|
set_strusage ( const char *(*f)( int ) )
|
|
{
|
|
strusage_handler = f;
|
|
}
|
|
|
|
|
|
#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[] = {
|
|
ARGPARSE_x('v', "verbose", NONE, 0, "Laut sein"),
|
|
ARGPARSE_s_n('e', "echo" , ("Zeile ausgeben, damit wir sehen, "
|
|
"was wir ein gegeben haben")),
|
|
ARGPARSE_s_n('d', "debug", "Debug\nfalls mal etwas\nschief geht"),
|
|
ARGPARSE_s_s('o', "output", 0 ),
|
|
ARGPARSE_o_s('c', "cross-ref", "cross-reference erzeugen\n" ),
|
|
/* Note that on a non-utf8 terminal the ß might garble the output. */
|
|
ARGPARSE_s_n('s', "street","|Straße|set the name of the street to Straße"),
|
|
ARGPARSE_o_i('m', "my-option", 0),
|
|
ARGPARSE_s_n(500, "a-long-option", 0 ),
|
|
ARGPARSE_end
|
|
};
|
|
ARGPARSE_ARGS pargs = { &argc, &argv, 2|4|32 };
|
|
int i;
|
|
|
|
while( arg_parse ( &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 = ARGPARSE_PRINT_WARNING; break;
|
|
}
|
|
}
|
|
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 ****/
|