minetest-mapper-cpp/Minetestmapper/parg.c

355 lines
7.1 KiB
C

/*
* parg - parse argv
*
* Written in 2015-2016 by Joergen Ibsen
*
* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to the
* public domain worldwide. This software is distributed without any
* warranty. <http://creativecommons.org/publicdomain/zero/1.0/>
*/
#include "parg.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
/*
* Check if state is at end of argv.
*/
static int
is_argv_end(const struct parg_state *ps, int argc, char *const argv[])
{
return ps->optind >= argc || argv[ps->optind] == NULL;
}
/*
* Match nextchar against optstring.
*/
static int
match_short(struct parg_state *ps, int argc, char *const argv[],
const char *optstring)
{
const char *p = strchr(optstring, *ps->nextchar);
if (p == NULL) {
ps->optopt = *ps->nextchar++;
return '?';
}
/* If no option argument, return option */
if (p[1] != ':') {
return *ps->nextchar++;
}
/* If more characters, return as option argument */
if (ps->nextchar[1] != '\0') {
ps->optarg = &ps->nextchar[1];
ps->nextchar = NULL;
return *p;
}
/* If option argument is optional, return option */
if (p[2] == ':') {
return *ps->nextchar++;
}
/* Option argument required, so return next argv element */
if (is_argv_end(ps, argc, argv)) {
ps->optopt = *ps->nextchar++;
return optstring[0] == ':' ? ':' : '?';
}
ps->optarg = argv[ps->optind++];
ps->nextchar = NULL;
return *p;
}
/*
* Match string at nextchar against longopts.
*/
static int
match_long(struct parg_state *ps, int argc, char *const argv[],
const char *optstring,
const struct parg_option *longopts, int *longindex)
{
size_t len;
int num_match = 0;
int match = -1;
int i;
len = strcspn(ps->nextchar, "=");
for (i = 0; longopts[i].name; ++i) {
if (strncmp(ps->nextchar, longopts[i].name, len) == 0) {
match = i;
num_match++;
/* Take if exact match */
if (longopts[i].name[len] == '\0') {
num_match = 1;
break;
}
}
}
/* Return '?' on no or ambiguous match */
if (num_match != 1) {
ps->optopt = 0;
ps->nextchar = NULL;
return '?';
}
assert(match != -1);
if (longindex) {
*longindex = match;
}
if (ps->nextchar[len] == '=') {
/* Option argument present, check if extraneous */
if (longopts[match].has_arg == PARG_NOARG) {
ps->optopt = longopts[match].flag ? 0 : longopts[match].val;
ps->nextchar = NULL;
return optstring[0] == ':' ? ':' : '?';
}
else {
ps->optarg = &ps->nextchar[len + 1];
}
}
else if (longopts[match].has_arg == PARG_REQARG) {
/* Option argument required, so return next argv element */
if (is_argv_end(ps, argc, argv)) {
ps->optopt = longopts[match].flag ? 0 : longopts[match].val;
ps->nextchar = NULL;
return optstring[0] == ':' ? ':' : '?';
}
ps->optarg = argv[ps->optind++];
}
ps->nextchar = NULL;
if (longopts[match].flag != NULL) {
*longopts[match].flag = longopts[match].val;
return 0;
}
return longopts[match].val;
}
void
parg_init(struct parg_state *ps)
{
ps->optarg = NULL;
ps->optind = 1;
ps->optopt = '?';
ps->nextchar = NULL;
}
int
parg_getopt(struct parg_state *ps, int argc, char *const argv[],
const char *optstring)
{
return parg_getopt_long(ps, argc, argv, optstring, NULL, NULL);
}
int
parg_getopt_long(struct parg_state *ps, int argc, char *const argv[],
const char *optstring,
const struct parg_option *longopts, int *longindex)
{
assert(ps != NULL);
assert(argv != NULL);
assert(optstring != NULL);
ps->optarg = NULL;
if (argc < 2) {
return -1;
}
/* Advance to next element if needed */
if (ps->nextchar == NULL || *ps->nextchar == '\0') {
if (is_argv_end(ps, argc, argv)) {
return -1;
}
ps->nextchar = argv[ps->optind++];
/* Check for nonoption element (including '-') */
if (ps->nextchar[0] != '-' || ps->nextchar[1] == '\0') {
ps->optarg = ps->nextchar;
ps->nextchar = NULL;
return 1;
}
/* Check for '--' */
if (ps->nextchar[1] == '-') {
if (ps->nextchar[2] == '\0') {
ps->nextchar = NULL;
return -1;
}
if (longopts != NULL) {
ps->nextchar += 2;
return match_long(ps, argc, argv, optstring,
longopts, longindex);
}
}
ps->nextchar++;
}
/* Match nextchar */
return match_short(ps, argc, argv, optstring);
}
/*
* Reverse elements of `v` from `i` to `j`.
*/
static void
reverse(char *v[], int i, int j)
{
while (j - i > 1) {
char *tmp = v[i];
v[i] = v[j - 1];
v[j - 1] = tmp;
++i;
--j;
}
}
/*
* Reorder elements of `argv` with no special cases.
*
* This function assumes there is no `--` element, and the last element
* is not an option missing a required argument.
*
* The algorithm is described here:
* http://hardtoc.com/2016/11/07/reordering-arguments.html
*/
static int
parg_reorder_simple(int argc, char *argv[],
const char *optstring,
const struct parg_option *longopts)
{
struct parg_state ps;
int change;
int l = 0;
int m = 0;
int r = 0;
if (argc < 2) {
return argc;
}
do {
int nextind;
int c;
parg_init(&ps);
nextind = ps.optind;
/* Parse until end of argument */
do {
c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL);
} while (ps.nextchar != NULL && *ps.nextchar != '\0');
change = 0;
do {
/* Find next non-option */
for (l = nextind; c != 1 && c != -1;) {
l = ps.optind;
do {
c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL);
} while (ps.nextchar != NULL && *ps.nextchar != '\0');
}
/* Find next option */
for (m = l; c == 1;) {
m = ps.optind;
do {
c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL);
} while (ps.nextchar != NULL && *ps.nextchar != '\0');
}
/* Find next non-option */
for (r = m; c != 1 && c != -1;) {
r = ps.optind;
do {
c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL);
} while (ps.nextchar != NULL && *ps.nextchar != '\0');
}
/* Find next option */
for (nextind = r; c == 1;) {
nextind = ps.optind;
do {
c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL);
} while (ps.nextchar != NULL && *ps.nextchar != '\0');
}
if (m < r) {
change = 1;
reverse(argv, l, m);
reverse(argv, m, r);
reverse(argv, l, r);
}
} while (c != -1);
} while (change != 0);
return l + (r - m);
}
int
parg_reorder(int argc, char *argv[],
const char *optstring,
const struct parg_option *longopts)
{
struct parg_state ps;
int lastind;
int optend;
int c;
assert(argv != NULL);
assert(optstring != NULL);
if (argc < 2) {
return argc;
}
parg_init(&ps);
/* Find end of normal arguments */
do {
lastind = ps.optind;
c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL);
/* Check for trailing option with error */
if ((c == '?' || c == ':') && is_argv_end(&ps, argc, argv)) {
lastind = ps.optind - 1;
break;
}
} while (c != -1);
optend = parg_reorder_simple(lastind, argv, optstring, longopts);
/* Rotate `--` or trailing option with error into position */
if (lastind < argc) {
reverse(argv, optend, lastind);
reverse(argv, optend, lastind + 1);
++optend;
}
return optend;
}