/* -*- indent-tabs-mode: nil -*- | |
* | |
* ya_getopt - Yet another getopt | |
* https://github.com/kubo/ya_getopt | |
* | |
* Copyright 2015 Kubo Takehiro <kubo@jiubao.org> | |
* | |
* Redistribution and use in source and binary forms, with or without modification, are | |
* permitted provided that the following conditions are met: | |
* | |
* 1. Redistributions of source code must retain the above copyright notice, this list of | |
* conditions and the following disclaimer. | |
* | |
* 2. 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 AUTHORS ''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 <COPYRIGHT HOLDER> 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. | |
* | |
* The views and conclusions contained in the software and documentation are those of the | |
* authors and should not be interpreted as representing official policies, either expressed | |
* or implied, of the authors. | |
* | |
*/ | |
#include <stdio.h> | |
#include <stdarg.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include "ya_getopt.h" | |
char *ya_optarg = NULL; | |
int ya_optind = 1; | |
int ya_opterr = 1; | |
int ya_optopt = '?'; | |
static char *ya_optnext = NULL; | |
static int posixly_correct = -1; | |
static int handle_nonopt_argv = 0; | |
static void ya_getopt_error(const char *optstring, const char *format, ...); | |
static void check_gnu_extension(const char *optstring); | |
static int ya_getopt_internal(int argc, char *const argv[], const char *optstring, const struct option *longopts, | |
int *longindex, int long_only); | |
static int ya_getopt_shortopts(int argc, char *const argv[], const char *optstring, int long_only); | |
static int ya_getopt_longopts(int argc, char *const argv[], char *arg, const char *optstring, | |
const struct option *longopts, int *longindex, int *long_only_flag); | |
static void ya_getopt_error(const char *optstring, const char *format, ...) | |
{ | |
#if 0 | |
if (ya_opterr && optstring[0] != ':') | |
{ | |
va_list ap; | |
va_start(ap, format); | |
vfprintf(stderr, format, ap); | |
va_end(ap); | |
} | |
#endif | |
} | |
static void check_gnu_extension(const char *optstring) | |
{ | |
#if 1 | |
if (optstring[0] == '+' || getenv("POSIXLY_CORRECT") != NULL) | |
{ | |
#else | |
if (optstring[0] == '+') | |
{ | |
#endif | |
posixly_correct = 0; | |
} | |
else | |
{ | |
posixly_correct = 1; | |
} | |
if (optstring[0] == '-') | |
{ | |
handle_nonopt_argv = 1; | |
} | |
else | |
{ | |
handle_nonopt_argv = 0; | |
} | |
} | |
int ya_getopt(int argc, char *const argv[], const char *optstring) | |
{ | |
return ya_getopt_internal(argc, argv, optstring, NULL, NULL, 0); | |
} | |
int ya_getopt_long(int argc, char *const argv[], const char *optstring, const struct option *longopts, int *longindex) | |
{ | |
return ya_getopt_internal(argc, argv, optstring, longopts, longindex, 0); | |
} | |
int ya_getopt_long_only(int argc, char *const argv[], const char *optstring, const struct option *longopts, | |
int *longindex) | |
{ | |
return ya_getopt_internal(argc, argv, optstring, longopts, longindex, 1); | |
} | |
static int ya_getopt_internal(int argc, char *const argv[], const char *optstring, const struct option *longopts, | |
int *longindex, int long_only) | |
{ | |
static int start, end; | |
if (ya_optopt == '?') | |
{ | |
ya_optopt = 0; | |
} | |
if (posixly_correct == -1) | |
{ | |
check_gnu_extension(optstring); | |
} | |
if (ya_optind == 0) | |
{ | |
check_gnu_extension(optstring); | |
ya_optind = 1; | |
ya_optnext = NULL; | |
} | |
switch (optstring[0]) | |
{ | |
case '+': | |
case '-': | |
optstring++; | |
} | |
if (ya_optnext == NULL && start != 0) | |
{ | |
int last_pos = ya_optind - 1; | |
ya_optind -= end - start; | |
if (ya_optind <= 0) | |
{ | |
ya_optind = 1; | |
} | |
while (start < end--) | |
{ | |
int i; | |
char *arg = argv[end]; | |
for (i = end; i < last_pos; i++) | |
{ | |
((char **)argv)[i] = argv[i + 1]; | |
} | |
((char const **)argv)[i] = arg; | |
last_pos--; | |
} | |
start = 0; | |
} | |
if (ya_optind >= argc) | |
{ | |
ya_optarg = NULL; | |
return -1; | |
} | |
if (ya_optnext == NULL) | |
{ | |
const char *arg = argv[ya_optind]; | |
if (*arg != '-') | |
{ | |
if (handle_nonopt_argv) | |
{ | |
ya_optarg = argv[optind++]; | |
start = 0; | |
return 1; | |
} | |
else if (posixly_correct) | |
{ | |
ya_optarg = NULL; | |
return -1; | |
} | |
else | |
{ | |
int i; | |
start = ya_optind; | |
for (i = ya_optind + 1; i < argc; i++) | |
{ | |
if (argv[i][0] == '-') | |
{ | |
end = i; | |
break; | |
} | |
} | |
if (i == argc) | |
{ | |
ya_optarg = NULL; | |
return -1; | |
} | |
ya_optind = i; | |
arg = argv[ya_optind]; | |
} | |
} | |
if (strcmp(arg, "--") == 0) | |
{ | |
ya_optind++; | |
return -1; | |
} | |
if (longopts != NULL && arg[1] == '-') | |
{ | |
return ya_getopt_longopts(argc, argv, argv[ya_optind] + 2, optstring, longopts, longindex, NULL); | |
} | |
} | |
if (ya_optnext == NULL) | |
{ | |
ya_optnext = argv[ya_optind] + 1; | |
} | |
if (long_only && longopts != NULL) | |
{ | |
int long_only_flag = 0; | |
int rv = ya_getopt_longopts(argc, argv, ya_optnext, optstring, longopts, longindex, &long_only_flag); | |
if (!long_only_flag) | |
{ | |
ya_optnext = NULL; | |
return rv; | |
} | |
} | |
return ya_getopt_shortopts(argc, argv, optstring, long_only); | |
} | |
static int ya_getopt_shortopts(int argc, char *const argv[], const char *optstring, int long_only) | |
{ | |
int opt = *ya_optnext; | |
const char *os = strchr(optstring, opt); | |
if (os == NULL) | |
{ | |
ya_optarg = NULL; | |
if (long_only) | |
{ | |
ya_getopt_error(optstring, "%s: unrecognized option '-%s'\n", argv[0], ya_optnext); | |
ya_optind++; | |
ya_optnext = NULL; | |
} | |
else | |
{ | |
ya_optopt = opt; | |
ya_getopt_error(optstring, "%s: invalid option -- '%c'\n", argv[0], opt); | |
if (*(++ya_optnext) == 0) | |
{ | |
ya_optind++; | |
ya_optnext = NULL; | |
} | |
} | |
return '?'; | |
} | |
if (os[1] == ':') | |
{ | |
if (ya_optnext[1] == 0) | |
{ | |
ya_optind++; | |
if (os[2] == ':') | |
{ | |
/* optional argument */ | |
ya_optarg = NULL; | |
} | |
else | |
{ | |
if (ya_optind == argc) | |
{ | |
ya_optarg = NULL; | |
ya_optopt = opt; | |
ya_getopt_error(optstring, "%s: option requires an argument -- '%c'\n", argv[0], opt); | |
if (optstring[0] == ':') | |
{ | |
return ':'; | |
} | |
else | |
{ | |
return '?'; | |
} | |
} | |
ya_optarg = argv[ya_optind]; | |
ya_optind++; | |
} | |
} | |
else | |
{ | |
ya_optarg = ya_optnext + 1; | |
ya_optind++; | |
} | |
ya_optnext = NULL; | |
} | |
else | |
{ | |
ya_optarg = NULL; | |
if (ya_optnext[1] == 0) | |
{ | |
ya_optnext = NULL; | |
ya_optind++; | |
} | |
else | |
{ | |
ya_optnext++; | |
} | |
} | |
return opt; | |
} | |
static int ya_getopt_longopts(int argc, char *const argv[], char *arg, const char *optstring, | |
const struct option *longopts, int *longindex, int *long_only_flag) | |
{ | |
char *val = NULL; | |
const struct option *opt; | |
size_t namelen; | |
int idx; | |
for (idx = 0; longopts[idx].name != NULL; idx++) | |
{ | |
opt = &longopts[idx]; | |
namelen = strlen(opt->name); | |
if (strncmp(arg, opt->name, namelen) == 0) | |
{ | |
switch (arg[namelen]) | |
{ | |
case '\0': | |
switch (opt->has_arg) | |
{ | |
case ya_required_argument: | |
ya_optind++; | |
if (ya_optind == argc) | |
{ | |
ya_optarg = NULL; | |
ya_optopt = opt->val; | |
ya_getopt_error(optstring, "%s: option '--%s' requires an argument\n", argv[0], opt->name); | |
if (optstring[0] == ':') | |
{ | |
return ':'; | |
} | |
else | |
{ | |
return '?'; | |
} | |
} | |
val = argv[ya_optind]; | |
break; | |
} | |
goto found; | |
case '=': | |
if (opt->has_arg == ya_no_argument) | |
{ | |
const char *hyphens = (argv[ya_optind][1] == '-') ? "--" : "-"; | |
ya_optind++; | |
ya_optarg = NULL; | |
ya_optopt = opt->val; | |
ya_getopt_error(optstring, "%s: option '%s%s' doesn't allow an argument\n", argv[0], hyphens, opt->name); | |
return '?'; | |
} | |
val = arg + namelen + 1; | |
goto found; | |
} | |
} | |
} | |
if (long_only_flag) | |
{ | |
*long_only_flag = 1; | |
} | |
else | |
{ | |
ya_getopt_error(optstring, "%s: unrecognized option '%s'\n", argv[0], argv[ya_optind]); | |
ya_optind++; | |
} | |
return '?'; | |
found: | |
ya_optarg = val; | |
ya_optind++; | |
if (opt->flag) | |
{ | |
*opt->flag = opt->val; | |
} | |
if (longindex) | |
{ | |
*longindex = idx; | |
} | |
return opt->flag ? 0 : opt->val; | |
} |