You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
8.1 KiB
206 lines
8.1 KiB
3 years ago
|
/*
|
||
|
* C Reusables
|
||
|
* <http://github.com/mity/c-reusables>
|
||
|
*
|
||
|
* Copyright (c) 2017-2020 Martin Mitas
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||
|
* copy of this software and associated documentation files (the "Software"),
|
||
|
* to deal in the Software without restriction, including without limitation
|
||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||
|
* Software is furnished to do so, subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be included in
|
||
|
* all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* 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 OR COPYRIGHT HOLDERS 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.
|
||
|
*/
|
||
|
|
||
|
#include "cmdline.h"
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
|
||
|
#ifdef _WIN32
|
||
|
#define snprintf _snprintf
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#define CMDLINE_AUXBUF_SIZE 32
|
||
|
|
||
|
|
||
|
|
||
|
static int
|
||
|
cmdline_handle_short_opt_group(const CMDLINE_OPTION* options, const char* arggroup,
|
||
|
int (*callback)(int /*optval*/, const char* /*arg*/, void* /*userdata*/),
|
||
|
void* userdata)
|
||
|
{
|
||
|
const CMDLINE_OPTION* opt;
|
||
|
int i;
|
||
|
int ret = 0;
|
||
|
|
||
|
for(i = 0; arggroup[i] != '\0'; i++) {
|
||
|
for(opt = options; opt->id != 0; opt++) {
|
||
|
if(arggroup[i] == opt->shortname)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if(opt->id != 0 && !(opt->flags & CMDLINE_OPTFLAG_REQUIREDARG)) {
|
||
|
ret = callback(opt->id, NULL, userdata);
|
||
|
} else {
|
||
|
/* Unknown option. */
|
||
|
char badoptname[3];
|
||
|
badoptname[0] = '-';
|
||
|
badoptname[1] = arggroup[i];
|
||
|
badoptname[2] = '\0';
|
||
|
ret = callback((opt->id != 0 ? CMDLINE_OPTID_MISSINGARG : CMDLINE_OPTID_UNKNOWN),
|
||
|
badoptname, userdata);
|
||
|
}
|
||
|
|
||
|
if(ret != 0)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
cmdline_read(const CMDLINE_OPTION* options, int argc, char** argv,
|
||
|
int (*callback)(int /*optval*/, const char* /*arg*/, void* /*userdata*/),
|
||
|
void* userdata)
|
||
|
{
|
||
|
const CMDLINE_OPTION* opt;
|
||
|
char auxbuf[CMDLINE_AUXBUF_SIZE+1];
|
||
|
int fast_optarg_decision = 1;
|
||
|
int after_doubledash = 0;
|
||
|
int i = 1;
|
||
|
int ret = 0;
|
||
|
|
||
|
auxbuf[CMDLINE_AUXBUF_SIZE] = '\0';
|
||
|
|
||
|
/* Check whether there is any CMDLINE_OPTFLAG_COMPILERLIKE option with
|
||
|
* a name not starting with '-'. That would imply we can to check for
|
||
|
* non-option arguments only after refusing all such options. */
|
||
|
for(opt = options; opt->id != 0; opt++) {
|
||
|
if((opt->flags & CMDLINE_OPTFLAG_COMPILERLIKE) && opt->longname[0] != '-')
|
||
|
fast_optarg_decision = 0;
|
||
|
}
|
||
|
|
||
|
while(i < argc) {
|
||
|
if(after_doubledash || strcmp(argv[i], "-") == 0) {
|
||
|
/* Non-option argument.
|
||
|
* Standalone "-" usually means "read from stdin" or "write to
|
||
|
* stdout" so treat it always as a non-option. */
|
||
|
ret = callback(CMDLINE_OPTID_NONE, argv[i], userdata);
|
||
|
} else if(strcmp(argv[i], "--") == 0) {
|
||
|
/* End of options. All the remaining tokens are non-options
|
||
|
* even if they start with a dash. */
|
||
|
after_doubledash = 1;
|
||
|
} else if(fast_optarg_decision && argv[i][0] != '-') {
|
||
|
/* Non-option argument. */
|
||
|
ret = callback(CMDLINE_OPTID_NONE, argv[i], userdata);
|
||
|
} else {
|
||
|
for(opt = options; opt->id != 0; opt++) {
|
||
|
if(opt->flags & CMDLINE_OPTFLAG_COMPILERLIKE) {
|
||
|
size_t len = strlen(opt->longname);
|
||
|
if(strncmp(argv[i], opt->longname, len) == 0) {
|
||
|
/* Compiler-like option. */
|
||
|
if(argv[i][len] != '\0')
|
||
|
ret = callback(opt->id, argv[i] + len, userdata);
|
||
|
else if(i+1 < argc)
|
||
|
ret = callback(opt->id, argv[++i], userdata);
|
||
|
else
|
||
|
ret = callback(CMDLINE_OPTID_MISSINGARG, opt->longname, userdata);
|
||
|
break;
|
||
|
}
|
||
|
} else if(opt->longname != NULL && strncmp(argv[i], "--", 2) == 0) {
|
||
|
size_t len = strlen(opt->longname);
|
||
|
if(strncmp(argv[i]+2, opt->longname, len) == 0) {
|
||
|
/* Regular long option. */
|
||
|
if(argv[i][2+len] == '\0') {
|
||
|
/* with no argument provided. */
|
||
|
if(!(opt->flags & CMDLINE_OPTFLAG_REQUIREDARG))
|
||
|
ret = callback(opt->id, NULL, userdata);
|
||
|
else
|
||
|
ret = callback(CMDLINE_OPTID_MISSINGARG, argv[i], userdata);
|
||
|
break;
|
||
|
} else if(argv[i][2+len] == '=') {
|
||
|
/* with an argument provided. */
|
||
|
if(opt->flags & (CMDLINE_OPTFLAG_OPTIONALARG | CMDLINE_OPTFLAG_REQUIREDARG)) {
|
||
|
ret = callback(opt->id, argv[i]+2+len+1, userdata);
|
||
|
} else {
|
||
|
snprintf(auxbuf, CMDLINE_AUXBUF_SIZE, "--%s", opt->longname);
|
||
|
ret = callback(CMDLINE_OPTID_BOGUSARG, auxbuf, userdata);
|
||
|
}
|
||
|
break;
|
||
|
} else {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
} else if(opt->shortname != '\0' && argv[i][0] == '-') {
|
||
|
if(argv[i][1] == opt->shortname) {
|
||
|
/* Regular short option. */
|
||
|
if(opt->flags & CMDLINE_OPTFLAG_REQUIREDARG) {
|
||
|
if(argv[i][2] != '\0')
|
||
|
ret = callback(opt->id, argv[i]+2, userdata);
|
||
|
else if(i+1 < argc)
|
||
|
ret = callback(opt->id, argv[++i], userdata);
|
||
|
else
|
||
|
ret = callback(CMDLINE_OPTID_MISSINGARG, argv[i], userdata);
|
||
|
break;
|
||
|
} else {
|
||
|
ret = callback(opt->id, NULL, userdata);
|
||
|
|
||
|
/* There might be more (argument-less) short options
|
||
|
* grouped together. */
|
||
|
if(ret == 0 && argv[i][2] != '\0')
|
||
|
ret = cmdline_handle_short_opt_group(options, argv[i]+2, callback, userdata);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(opt->id == 0) { /* still not handled? */
|
||
|
if(argv[i][0] != '-') {
|
||
|
/* Non-option argument. */
|
||
|
ret = callback(CMDLINE_OPTID_NONE, argv[i], userdata);
|
||
|
} else {
|
||
|
/* Unknown option. */
|
||
|
char* badoptname = argv[i];
|
||
|
|
||
|
if(strncmp(badoptname, "--", 2) == 0) {
|
||
|
/* Strip any argument from the long option. */
|
||
|
char* assignment = strchr(badoptname, '=');
|
||
|
if(assignment != NULL) {
|
||
|
size_t len = assignment - badoptname;
|
||
|
if(len > CMDLINE_AUXBUF_SIZE)
|
||
|
len = CMDLINE_AUXBUF_SIZE;
|
||
|
strncpy(auxbuf, badoptname, len);
|
||
|
auxbuf[len] = '\0';
|
||
|
badoptname = auxbuf;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ret = callback(CMDLINE_OPTID_UNKNOWN, badoptname, userdata);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(ret != 0)
|
||
|
return ret;
|
||
|
i++;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|