blob: 468d0fec1137ed25e2b207dbabcfa48b707bb8bf [file] [log] [blame]
#include <stdio.h>
#include "cmdopt.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// Moves `optind' to the end and shifts other arguments.
static void cmdopt_shift(cmdopt_t *h) {
int i;
char *tmp;
tmp = h->argv[h->optind];
for (i = h->optind; i < h->argc - 1; i++) {
h->argv[i] = h->argv[i + 1];
}
h->argv[i] = tmp;
h->nextchar = NULL;
h->optnum--;
}
// Moves to the next argument.
static void cmdopt_next(cmdopt_t *h) {
h->optind++;
h->nextchar = NULL;
}
// Checks if the current argument is an option or not.
static int cmdopt_check(cmdopt_t *h) {
int ret = 1;
const char *arg = h->argv[h->optind];
if (*arg++ != '-') {
return 0;
}
if (*arg == '-') {
arg++;
ret++;
}
return ret - (*arg == '\0');
}
// Gets an argument of the current option.
static void cmdopt_getopt(cmdopt_t *h) {
// Moves to the next argument if the current argument has no more characters.
if (*h->nextchar == '\0') {
cmdopt_next(h);
h->nextchar = h->argv[h->optind];
}
// Checks whether the current option has an argument or not.
if (h->optind < h->optnum) {
h->optarg = h->nextchar;
cmdopt_next(h);
} else {
h->optarg = NULL;
}
}
// Searches an option.
static int cmdopt_search(cmdopt_t *h) {
const char *ptr;
// Updates an option character.
h->optopt = *h->nextchar++;
for (ptr = h->optstring; *ptr != '\0'; ptr++) {
if (*ptr == h->optopt) {
// Gets an option argument if required.
if (ptr[1] == ':') {
cmdopt_getopt(h);
// Returns ':' if there is no argument.
if (h->optarg == NULL && ptr[2] != ':') {
return ':';
}
}
return h->optopt;
}
}
if (h->optopt == '-') {
cmdopt_next(h);
while (h->optind < h->optnum) {
cmdopt_shift(h);
}
return -1;
}
// Returns '?' if the option character is undefined.
return '?';
}
// Compares a long option with an argument and returns the length of the
// matched prefix.
static int cmdopt_match_len(const char *opt, const char *arg) {
int len = 0;
// Returns 0 if there is a mismatch.
while ((*arg != '\0') && (*arg != '=')) {
if (*arg++ != *opt++) {
return 0;
}
len++;
}
// Returns a negative value in case of a perfect match.
if ((*arg == '\0') || (*arg == '=')) {
return -len;
}
return len;
}
// Checks long options.
static int cmdopt_match(cmdopt_t *h) {
int i, len;
int max = 0, max_optind = -1;
// Returns -1 if there are no long options.
if (h->longopts == NULL) {
return max_optind;
}
for (i = 0; h->longopts[i].name != NULL; i++) {
len = cmdopt_match_len(h->longopts[i].name, h->nextchar);
if (len < 0) {
// In case of a perfect match.
h->nextchar -= len;
return i;
} else if (len > max) {
// In case of a prefix match.
max = len;
max_optind = i;
} else if (len == max) {
// There are other candidates.
max_optind = -1;
}
}
// If there is no perfect match, adopts the longest one.
h->nextchar += max;
return max_optind;
}
// Gets an argument of a long option.
static void cmdopt_getopt_long(cmdopt_t *h) {
if (*h->nextchar == '=') {
h->optarg = h->nextchar + 1;
cmdopt_next(h);
} else {
cmdopt_next(h);
// Checks whether there are more options or not.
if (h->optind < h->optnum) {
h->optarg = h->argv[h->optind];
cmdopt_next(h);
} else {
h->optarg = NULL;
}
}
}
// Searches long options.
static int cmdopt_search_long(cmdopt_t *h) {
const cmdopt_option *option;
// Keeps the long option.
h->optlong = h->argv[h->optind];
// Gets the next option.
h->longindex = cmdopt_match(h);
if (h->longindex < 0) {
cmdopt_next(h);
return '?';
}
// Gets an argument if required.
option = h->longopts + h->longindex;
if (option->has_arg) {
cmdopt_getopt_long(h);
// Return ':' if there are no more arguments.
if (h->optarg == NULL) {
return ':';
}
} else if (*h->nextchar == '=') {
// Returns '?' for an extra option argument.
cmdopt_getopt_long(h);
return '?';
}
// Overwrites a variable if specified in settings.
if (option->flag != NULL) {
*option->flag = option->val;
return 0;
}
return option->val;
}
// Analyze command line option.
static int cmdopt_main(cmdopt_t *h) {
int type;
// Initializes the internal state.
h->optopt = 0;
h->optlong = NULL;
h->optarg = NULL;
h->longindex = 0;
while (h->optind < h->optnum) {
if (h->nextchar == NULL) {
// Checks whether the next argument is an option or not.
type = cmdopt_check(h);
if (type == 0) {
cmdopt_shift(h);
} else {
h->nextchar = h->argv[h->optind] + type;
if (type == 2) {
return cmdopt_search_long(h);
}
}
} else {
if (*h->nextchar == '\0') {
cmdopt_next(h);
continue;
}
// Searches an option string.
return cmdopt_search(h);
}
}
return -1;
}
// cmdopt_init() initializes a cmdopt_t for successive cmdopt_get()s.
void cmdopt_init(cmdopt_t *h, int argc, char **argv,
const char *optstring, const cmdopt_option *longopts) {
static const char empty_optstring[] = "";
h->argc = argc;
h->argv = argv;
h->optnum = h->argc;
h->longopts = longopts;
h->optstring = (optstring != NULL) ? optstring : empty_optstring;
h->optind = 1;
h->nextchar = NULL;
h->optarg = NULL;
h->optopt = 0;
h->optlong = NULL;
h->opterr = 1;
h->longindex = 0;
}
// cmdopt_get() analyzes command line arguments and gets the next option.
int cmdopt_get(cmdopt_t *h) {
int value = cmdopt_main(h);
// Prints a warning to the standard error stream if enabled.
if (h->opterr) {
if (value == ':') {
// Warning for a lack of an option argument.
if (h->optlong == NULL) {
fprintf(stderr, "option requires an argument -- %c\n", h->optopt);
} else {
fprintf(stderr, "option `--%s' requires an argument\n",
h->longopts[h->longindex].name);
}
} else if (value == '?') {
// Warning for an invalid option.
if (h->optlong == NULL) {
fprintf(stderr, "invalid option -- %c\n", h->optopt);
} else {
fprintf(stderr, "unrecognized option `%s'\n", h->optlong);
}
} else if ((value != -1) && (h->opterr == 2)) {
// Actually this is not for warning, but for debugging.
if (h->optlong == NULL) {
fprintf(stderr, "option with `%s' -- %c\n", h->optarg, h->optopt);
} else {
fprintf(stderr, "option `--%s' with `%s'\n",
h->longopts[h->longindex].name, h->optarg);
}
}
}
return value;
}
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus