/* antigetopt -- a getopt replacement * Copyright(C) 2001 Salvatore Sanfilippo * This software is released under the GPL license * see the COPYING file for more information */ /* TODO: * argument list sanity check */ #include #include #include #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; }