296 lines
6.6 KiB
C
296 lines
6.6 KiB
C
/* antigetopt -- a getopt replacement
|
|
* Copyright(C) 2001 Salvatore Sanfilippo <antirez@invece.org>
|
|
* This software is released under the GPL license
|
|
* see the COPYING file for more information */
|
|
|
|
/* TODO:
|
|
* argument list sanity check */
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "antigetopt.h"
|
|
|
|
/* global vars */
|
|
char *ago_optarg = NULL;
|
|
char *ago_optname = NULL;
|
|
char ago_optchar = '\0';
|
|
|
|
/* static vars */
|
|
static struct ago_exception {
|
|
int (*tester)(void);
|
|
char *msg;
|
|
} ago_exceptions[3] = {
|
|
{ NULL, NULL },
|
|
{ NULL, NULL },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
static int ago_exception_bits[] = { AGO_EXCEPT0, AGO_EXCEPT1, AGO_EXCEPT2 };
|
|
|
|
/* static functions */
|
|
static struct ago_optlist
|
|
*ago_lookup(struct ago_optlist *list, char *arg, int *islong, int *amb);
|
|
static int strinitcmp(char *a, char *b);
|
|
|
|
/*----------------------------- implementation ------------------------------ */
|
|
|
|
int antigetopt(int argc, char **argv, struct ago_optlist *list)
|
|
{
|
|
static char **save_argv = NULL;
|
|
static char *chain = NULL;
|
|
static int endoptions = 0;
|
|
struct ago_optlist *opt;
|
|
int islong;
|
|
|
|
/* Reset */
|
|
if (argv == NULL) {
|
|
save_argv = NULL;
|
|
chain = NULL;
|
|
endoptions = 0;
|
|
return AGO_RESET;
|
|
} else {
|
|
if (save_argv == NULL) {
|
|
save_argv = argv+1; /* skips the argv[0] */
|
|
/* XXX: argument list sanity check */
|
|
}
|
|
}
|
|
|
|
chain_start:
|
|
if (chain) {
|
|
if (*chain == '\0')
|
|
chain = NULL;
|
|
else {
|
|
if ((opt = ago_lookup(list, chain, &islong, NULL))
|
|
== NULL)
|
|
return AGO_UNKNOWN;
|
|
if (!(opt->ao_flags & AGO_NOARG)) {
|
|
/* the if expression maybe false if the
|
|
* argument is optional */
|
|
if (chain[1] == '\0' && *save_argv)
|
|
ago_optarg = *save_argv++;
|
|
/* while it is mandatory for the NEEDARG type */
|
|
else if (opt->ao_flags & AGO_NEEDARG)
|
|
return AGO_REQARG;
|
|
}
|
|
chain++;
|
|
return opt->ao_id;
|
|
}
|
|
}
|
|
|
|
argv = save_argv;
|
|
|
|
/* handle the "--" special option */
|
|
if (*argv && strcmp(*argv, "--") == 0) {
|
|
endoptions = 1;
|
|
argv++;
|
|
save_argv++;
|
|
}
|
|
|
|
while(*argv) {
|
|
/* The option must start with '-' */
|
|
if (!endoptions && argv[0][0] == '-' && argv[0][1] != '\0') {
|
|
int amb;
|
|
|
|
/* note: ago_lookup also sets ago_optname */
|
|
if ((opt = ago_lookup(list, argv[0], &islong, &amb))
|
|
== NULL)
|
|
return amb ? AGO_AMBIG : AGO_UNKNOWN;
|
|
|
|
/* handle the collapsed short options */
|
|
if (!islong && argv[0][2] != '\0') {
|
|
chain = argv[0]+1;
|
|
save_argv++;
|
|
goto chain_start;
|
|
}
|
|
|
|
/* if the option require or may have an argument */
|
|
ago_optarg = NULL;
|
|
/* If the argument is needed we get the next argv[]
|
|
* element without care about what it contains */
|
|
if (opt->ao_flags & AGO_NEEDARG) {
|
|
if (argv[1] == NULL)
|
|
return AGO_REQARG;
|
|
ago_optarg = argv[1];
|
|
argv++;
|
|
}
|
|
/* If the argument is optional we only recognize it
|
|
* as argument if it does not starts with '-' */
|
|
else if (opt->ao_flags & AGO_OPTARG) {
|
|
if (argv[1] && argv[1][0] != '-') {
|
|
ago_optarg = argv[1];
|
|
argv++;
|
|
}
|
|
}
|
|
save_argv = argv+1;
|
|
return opt->ao_id;
|
|
} else {
|
|
save_argv = argv+1;
|
|
ago_optarg = argv[0];
|
|
ago_optchar = '\0';
|
|
ago_optname = NULL;
|
|
return AGO_ALONE;
|
|
}
|
|
}
|
|
return AGO_EOF;
|
|
}
|
|
|
|
#define UNK_SHORT_ERRSTRING "invalid option -- %c\n"
|
|
#define UNK_LONG_ERRSTRING "unrecognized option `--%s'\n"
|
|
#define ARG_SHORT_ERRSTRING "option requires an argument -- %c\n"
|
|
#define ARG_LONG_ERRSTRING "option `--%s' requires an argument\n"
|
|
#define AMB_ERRSTRING "option `--%s' is ambiguous\n"
|
|
#define IERR_ERRSTRING "internal error. ago_gnu_error() called with " \
|
|
"a bad error code (%d)\n"
|
|
void ago_gnu_error(char *pname, int error)
|
|
{
|
|
if (pname)
|
|
fprintf(stderr, "%s: ", pname);
|
|
switch(error) {
|
|
case AGO_UNKNOWN:
|
|
if (ago_optname)
|
|
fprintf(stderr, UNK_LONG_ERRSTRING,
|
|
ago_optname);
|
|
else
|
|
fprintf(stderr, UNK_SHORT_ERRSTRING,
|
|
ago_optchar);
|
|
break;
|
|
case AGO_REQARG:
|
|
if (ago_optname)
|
|
fprintf(stderr, ARG_LONG_ERRSTRING,
|
|
ago_optname);
|
|
else
|
|
fprintf(stderr, ARG_SHORT_ERRSTRING,
|
|
ago_optchar);
|
|
break;
|
|
case AGO_AMBIG:
|
|
fprintf(stderr, AMB_ERRSTRING, ago_optname);
|
|
break;
|
|
default:
|
|
fprintf(stderr, IERR_ERRSTRING, error);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int ago_set_exception(int except_nr, int (*tester)(void), char *msg)
|
|
{
|
|
if (tester == NULL || msg == NULL || except_nr < 0 || except_nr >= 3)
|
|
return -1;
|
|
ago_exceptions[except_nr].tester = tester;
|
|
ago_exceptions[except_nr].msg = msg;
|
|
return 0;
|
|
}
|
|
|
|
/*-------------------------- static functions ------------------------------- */
|
|
|
|
struct ago_optlist
|
|
*ago_lookup(struct ago_optlist *list, char *arg, int *islong, int *amb)
|
|
{
|
|
int i;
|
|
|
|
/* ago_lookup can be receive as `arg' a pointer to a
|
|
* long argument, like --option, a pointer to a short
|
|
* argument like -O, or just a pointer to a char sequence
|
|
* in the case of collapsed short arguments like -abcde. */
|
|
|
|
/* Clear the 'ambiguos' flag, used to report the caller
|
|
* an ambiguos option abbreviation error */
|
|
if (amb) *amb = 0;
|
|
|
|
if (*arg == '-') /* skips the first - if any */
|
|
arg++;
|
|
|
|
switch(*arg) {
|
|
case '\0':
|
|
return NULL;
|
|
case '-':
|
|
*islong = 1;
|
|
arg++; /* skip the last - */
|
|
break;
|
|
default:
|
|
*islong = 0;
|
|
break;
|
|
}
|
|
|
|
/* search the argument in the list */
|
|
if (*islong) {
|
|
int retval;
|
|
struct ago_optlist *last = NULL;
|
|
|
|
while(!(list->ao_flags & AGO_ENDOFLIST)) {
|
|
ago_optname = arg;
|
|
ago_optchar = '\0';
|
|
if ((retval = strinitcmp(arg, list->ao_long)) != 0) {
|
|
switch(retval) {
|
|
case 1:
|
|
if (last) {
|
|
if (amb) *amb = 1;
|
|
return NULL;
|
|
}
|
|
last = list;
|
|
break;
|
|
case 2:
|
|
goto ok;
|
|
}
|
|
}
|
|
list++;
|
|
}
|
|
if (last) {
|
|
ago_optname = last->ao_long;
|
|
list = last;
|
|
goto ok;
|
|
}
|
|
} else {
|
|
ago_optchar = *arg;
|
|
ago_optname = NULL;
|
|
while(!(list->ao_flags & AGO_ENDOFLIST)) {
|
|
if (*arg == list->ao_short)
|
|
goto ok;
|
|
list++;
|
|
}
|
|
}
|
|
return NULL;
|
|
ok:
|
|
/* handle the exceptions if any */
|
|
for (i = 0; i < 3; i++) {
|
|
if ((list->ao_flags & ago_exception_bits[i]) &&
|
|
ago_exceptions[i].tester)
|
|
{
|
|
if (ago_exceptions[i].tester()) {
|
|
if (ago_optname) {
|
|
fprintf(stderr, "%s `--%s'\n",
|
|
ago_exceptions[i].msg,
|
|
ago_optname);
|
|
} else {
|
|
fprintf(stderr, "%s `-%c'\n",
|
|
ago_exceptions[i].msg,
|
|
ago_optchar);
|
|
}
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
/* Given two strings this function returns:
|
|
* 1, if the strings are the same for the len of the first string (abc, abcde)
|
|
* 2, if the strings are exactly the same: (abcd, abcd)
|
|
* otherwise zero is returned (abcde, abcd) ... (djf, 293492) */
|
|
int strinitcmp(char *a, char *b)
|
|
{
|
|
if (!a || !b)
|
|
return 0;
|
|
while (*a && *b) {
|
|
if (*a != *b)
|
|
return 0;
|
|
a++; b++;
|
|
}
|
|
if (*a)
|
|
return 0;
|
|
if (*a == *b)
|
|
return 2;
|
|
return 1;
|
|
}
|