301 lines
9.1 KiB
C
301 lines
9.1 KiB
C
|
/*
|
||
|
* Copyright (C) 2019-2020 by Sukchan Lee <acetcom@gmail.com>
|
||
|
*
|
||
|
* The code is stolen from optparse
|
||
|
* https://github.com/skeeto/optparse
|
||
|
*
|
||
|
* This is free and unencumbered software released into the public domain.
|
||
|
*
|
||
|
* Anyone is free to copy, modify, publish, use, compile, sell, or
|
||
|
* distribute this software, either in source code form or as a compiled
|
||
|
* binary, for any purpose, commercial or non-commercial, and by any
|
||
|
* means.
|
||
|
*
|
||
|
* In jurisdictions that recognize copyright laws, the author or authors
|
||
|
* of this software dedicate any and all copyright interest in the
|
||
|
* software to the public domain. We make this dedication for the benefit
|
||
|
* of the public at large and to the detriment of our heirs and
|
||
|
* successors. We intend this dedication to be an overt act of
|
||
|
* relinquishment in perpetuity of all present and future rights to this
|
||
|
* software under copyright law.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||
|
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||
|
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||
|
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||
|
*
|
||
|
* For more information, please refer to <http://unlicense.org/>
|
||
|
*/
|
||
|
|
||
|
#include "ogs-getopt.h"
|
||
|
|
||
|
#define OGS_GETOPT_MSG_INVALID "invalid option"
|
||
|
#define OGS_GETOPT_MSG_MISSING "option requires an argument"
|
||
|
#define OGS_GETOPT_MSG_TOOMANY "option takes no arguments"
|
||
|
|
||
|
static int ogs_getopt_error(
|
||
|
ogs_getopt_t *options, const char *msg, const char *data)
|
||
|
{
|
||
|
unsigned p = 0;
|
||
|
const char *sep = " -- '";
|
||
|
while (*msg)
|
||
|
options->errmsg[p++] = *msg++;
|
||
|
while (*sep)
|
||
|
options->errmsg[p++] = *sep++;
|
||
|
while (p < sizeof(options->errmsg) - 2 && *data)
|
||
|
options->errmsg[p++] = *data++;
|
||
|
options->errmsg[p++] = '\'';
|
||
|
options->errmsg[p++] = '\0';
|
||
|
return '?';
|
||
|
}
|
||
|
|
||
|
void ogs_getopt_init(ogs_getopt_t *options, char **argv)
|
||
|
{
|
||
|
options->argv = argv;
|
||
|
options->permute = 1;
|
||
|
options->optind = 1;
|
||
|
options->subopt = 0;
|
||
|
options->optarg = 0;
|
||
|
options->errmsg[0] = '\0';
|
||
|
}
|
||
|
|
||
|
static int ogs_getopt_is_dashdash(const char *arg)
|
||
|
{
|
||
|
return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
|
||
|
}
|
||
|
|
||
|
static int ogs_getopt_is_shortopt(const char *arg)
|
||
|
{
|
||
|
return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
ogs_getopt_is_longopt(const char *arg)
|
||
|
{
|
||
|
return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
|
||
|
}
|
||
|
|
||
|
static void ogs_getopt_permute(ogs_getopt_t *options, int index)
|
||
|
{
|
||
|
char *nonoption = options->argv[index];
|
||
|
int i;
|
||
|
for (i = index; i < options->optind - 1; i++)
|
||
|
options->argv[i] = options->argv[i + 1];
|
||
|
options->argv[options->optind - 1] = nonoption;
|
||
|
}
|
||
|
|
||
|
static int ogs_getopt_argtype(const char *optstring, char c)
|
||
|
{
|
||
|
int count = OGS_GETOPT_NONE;
|
||
|
if (c == ':')
|
||
|
return -1;
|
||
|
for (; *optstring && c != *optstring; optstring++);
|
||
|
if (!*optstring)
|
||
|
return -1;
|
||
|
if (optstring[1] == ':')
|
||
|
count += optstring[2] == ':' ? 2 : 1;
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
int ogs_getopt(ogs_getopt_t *options, const char *optstring)
|
||
|
{
|
||
|
int type;
|
||
|
char *next;
|
||
|
char *option = options->argv[options->optind];
|
||
|
options->errmsg[0] = '\0';
|
||
|
options->optopt = 0;
|
||
|
options->optarg = 0;
|
||
|
if (option == 0) {
|
||
|
return -1;
|
||
|
} else if (ogs_getopt_is_dashdash(option)) {
|
||
|
options->optind++; /* consume "--" */
|
||
|
return -1;
|
||
|
} else if (!ogs_getopt_is_shortopt(option)) {
|
||
|
if (options->permute) {
|
||
|
int index = options->optind++;
|
||
|
int r = ogs_getopt(options, optstring);
|
||
|
ogs_getopt_permute(options, index);
|
||
|
options->optind--;
|
||
|
return r;
|
||
|
} else {
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
option += options->subopt + 1;
|
||
|
options->optopt = option[0];
|
||
|
type = ogs_getopt_argtype(optstring, option[0]);
|
||
|
next = options->argv[options->optind + 1];
|
||
|
switch (type) {
|
||
|
case -1: {
|
||
|
char str[2] = {0, 0};
|
||
|
str[0] = option[0];
|
||
|
options->optind++;
|
||
|
return ogs_getopt_error(options, OGS_GETOPT_MSG_INVALID, str);
|
||
|
}
|
||
|
case OGS_GETOPT_NONE:
|
||
|
if (option[1]) {
|
||
|
options->subopt++;
|
||
|
} else {
|
||
|
options->subopt = 0;
|
||
|
options->optind++;
|
||
|
}
|
||
|
return option[0];
|
||
|
case OGS_GETOPT_REQUIRED:
|
||
|
options->subopt = 0;
|
||
|
options->optind++;
|
||
|
if (option[1]) {
|
||
|
options->optarg = option + 1;
|
||
|
} else if (next != 0) {
|
||
|
options->optarg = next;
|
||
|
options->optind++;
|
||
|
} else {
|
||
|
char str[2] = {0, 0};
|
||
|
str[0] = option[0];
|
||
|
options->optarg = 0;
|
||
|
return ogs_getopt_error(options, OGS_GETOPT_MSG_MISSING, str);
|
||
|
}
|
||
|
return option[0];
|
||
|
case OGS_GETOPT_OPTIONAL:
|
||
|
options->subopt = 0;
|
||
|
options->optind++;
|
||
|
if (option[1])
|
||
|
options->optarg = option + 1;
|
||
|
else
|
||
|
options->optarg = 0;
|
||
|
return option[0];
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
char *ogs_getopt_arg(ogs_getopt_t *options)
|
||
|
{
|
||
|
char *option = options->argv[options->optind];
|
||
|
options->subopt = 0;
|
||
|
if (option != 0)
|
||
|
options->optind++;
|
||
|
return option;
|
||
|
}
|
||
|
|
||
|
static int ogs_getopt_longopts_end(const ogs_getopt_long_t *longopts, int i)
|
||
|
{
|
||
|
return !longopts[i].longname && !longopts[i].shortname;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ogs_getopt_from_long(const ogs_getopt_long_t *longopts, char *optstring)
|
||
|
{
|
||
|
char *p = optstring;
|
||
|
int i;
|
||
|
for (i = 0; !ogs_getopt_longopts_end(longopts, i); i++) {
|
||
|
if (longopts[i].shortname) {
|
||
|
int a;
|
||
|
*p++ = longopts[i].shortname;
|
||
|
for (a = 0; a < (int)longopts[i].argtype; a++)
|
||
|
*p++ = ':';
|
||
|
}
|
||
|
}
|
||
|
*p = '\0';
|
||
|
}
|
||
|
|
||
|
/* Unlike strcmp(), handles options containing "=". */
|
||
|
static int ogs_getopt_longopts_match(const char *longname, const char *option)
|
||
|
{
|
||
|
const char *a = option, *n = longname;
|
||
|
if (longname == 0)
|
||
|
return 0;
|
||
|
for (; *a && *n && *a != '='; a++, n++)
|
||
|
if (*a != *n)
|
||
|
return 0;
|
||
|
return *n == '\0' && (*a == '\0' || *a == '=');
|
||
|
}
|
||
|
|
||
|
/* Return the part after "=", or NULL. */
|
||
|
static char *ogs_getopt_longopts_arg(char *option)
|
||
|
{
|
||
|
for (; *option && *option != '='; option++);
|
||
|
if (*option == '=')
|
||
|
return option + 1;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ogs_getopt_long_fallback(ogs_getopt_t *options,
|
||
|
const ogs_getopt_long_t *longopts,
|
||
|
int *longindex)
|
||
|
{
|
||
|
int result;
|
||
|
char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
|
||
|
ogs_getopt_from_long(longopts, optstring);
|
||
|
result = ogs_getopt(options, optstring);
|
||
|
if (longindex != 0) {
|
||
|
*longindex = -1;
|
||
|
if (result != -1) {
|
||
|
int i;
|
||
|
for (i = 0; !ogs_getopt_longopts_end(longopts, i); i++)
|
||
|
if (longopts[i].shortname == options->optopt)
|
||
|
*longindex = i;
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
int ogs_getopt_long(ogs_getopt_t *options,
|
||
|
const ogs_getopt_long_t *longopts,
|
||
|
int *longindex)
|
||
|
{
|
||
|
int i;
|
||
|
char *option = options->argv[options->optind];
|
||
|
if (option == 0) {
|
||
|
return -1;
|
||
|
} else if (ogs_getopt_is_dashdash(option)) {
|
||
|
options->optind++; /* consume "--" */
|
||
|
return -1;
|
||
|
} else if (ogs_getopt_is_shortopt(option)) {
|
||
|
return ogs_getopt_long_fallback(options, longopts, longindex);
|
||
|
} else if (!ogs_getopt_is_longopt(option)) {
|
||
|
if (options->permute) {
|
||
|
int index = options->optind++;
|
||
|
int r = ogs_getopt_long(options, longopts, longindex);
|
||
|
ogs_getopt_permute(options, index);
|
||
|
options->optind--;
|
||
|
return r;
|
||
|
} else {
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Parse as long option. */
|
||
|
options->errmsg[0] = '\0';
|
||
|
options->optopt = 0;
|
||
|
options->optarg = 0;
|
||
|
option += 2; /* skip "--" */
|
||
|
options->optind++;
|
||
|
for (i = 0; !ogs_getopt_longopts_end(longopts, i); i++) {
|
||
|
const char *name = longopts[i].longname;
|
||
|
if (ogs_getopt_longopts_match(name, option)) {
|
||
|
char *arg;
|
||
|
if (longindex)
|
||
|
*longindex = i;
|
||
|
options->optopt = longopts[i].shortname;
|
||
|
arg = ogs_getopt_longopts_arg(option);
|
||
|
if (longopts[i].argtype == OGS_GETOPT_NONE && arg != 0) {
|
||
|
return ogs_getopt_error(options, OGS_GETOPT_MSG_TOOMANY, name);
|
||
|
} if (arg != 0) {
|
||
|
options->optarg = arg;
|
||
|
} else if (longopts[i].argtype == OGS_GETOPT_REQUIRED) {
|
||
|
options->optarg = options->argv[options->optind];
|
||
|
if (options->optarg == 0)
|
||
|
return ogs_getopt_error(
|
||
|
options, OGS_GETOPT_MSG_MISSING, name);
|
||
|
else
|
||
|
options->optind++;
|
||
|
}
|
||
|
return options->optopt;
|
||
|
}
|
||
|
}
|
||
|
return ogs_getopt_error(options, OGS_GETOPT_MSG_INVALID, option);
|
||
|
}
|