1628 lines
45 KiB
C
1628 lines
45 KiB
C
|
/** dropt.c
|
||
|
*
|
||
|
* A deliberately rudimentary command-line option parser.
|
||
|
*
|
||
|
* Copyright (C) 2006-2018 James D. Lin <jamesdlin@berkeley.edu>
|
||
|
*
|
||
|
* The latest version of this file can be downloaded from:
|
||
|
* <http://www.taenarum.com/software/dropt/>
|
||
|
*
|
||
|
* This software is provided 'as-is', without any express or implied
|
||
|
* warranty. In no event will the authors be held liable for any damages
|
||
|
* arising from the use of this software.
|
||
|
*
|
||
|
* Permission is granted to anyone to use this software for any purpose,
|
||
|
* including commercial applications, and to alter it and redistribute it
|
||
|
* freely, subject to the following restrictions:
|
||
|
*
|
||
|
* 1. The origin of this software must not be misrepresented; you must not
|
||
|
* claim that you wrote the original software. If you use this software
|
||
|
* in a product, an acknowledgment in the product documentation would be
|
||
|
* appreciated but is not required.
|
||
|
*
|
||
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
||
|
* misrepresented as being the original software.
|
||
|
*
|
||
|
* 3. This notice may not be removed or altered from any source distribution.
|
||
|
*/
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <ctype.h>
|
||
|
#include <wctype.h>
|
||
|
#include <assert.h>
|
||
|
|
||
|
#include "dropt.h"
|
||
|
#include "dropt_string.h"
|
||
|
|
||
|
#if __STDC_VERSION__ >= 199901L
|
||
|
#include <stdint.h>
|
||
|
#include <stdbool.h>
|
||
|
#else
|
||
|
/* Compatibility junk for things that don't yet support ISO C99. */
|
||
|
#ifndef SIZE_MAX
|
||
|
#define SIZE_MAX ((size_t) -1)
|
||
|
#endif
|
||
|
|
||
|
typedef enum { false, true } bool;
|
||
|
#endif
|
||
|
|
||
|
#ifndef MIN
|
||
|
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
|
||
|
#endif
|
||
|
|
||
|
#ifndef ARRAY_LENGTH
|
||
|
#define ARRAY_LENGTH(array) (sizeof (array) / sizeof (array)[0])
|
||
|
#endif
|
||
|
|
||
|
#define IMPLIES(p, q) (!(p) || q)
|
||
|
|
||
|
#define OPTION_TAKES_ARG(option) ((option)->arg_description != NULL)
|
||
|
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
default_help_indent = 2,
|
||
|
default_description_start_column = 6,
|
||
|
};
|
||
|
|
||
|
|
||
|
/** A string that might not be `NUL`-terminated. */
|
||
|
typedef struct
|
||
|
{
|
||
|
const dropt_char* s;
|
||
|
|
||
|
/* The length of s, excluding any `NUL` terminator. */
|
||
|
size_t len;
|
||
|
} char_array;
|
||
|
|
||
|
|
||
|
/** A proxy for a `dropt_option` used for `qsort` and `bsearch`. Instead of
|
||
|
* sorting the `dropt_option` table directly, we sort arrays of `option_proxy`
|
||
|
* structures. This allows us to have separate arrays sorted by different
|
||
|
* keys and allows passing along additional data.
|
||
|
*/
|
||
|
typedef struct
|
||
|
{
|
||
|
const dropt_option* option;
|
||
|
|
||
|
/* The `qsort` and `bsearch` comparison callbacks don't pass along any
|
||
|
* client-supplied contextual data, so we have to embed it alongside the
|
||
|
* regular data.
|
||
|
*/
|
||
|
const dropt_context* context;
|
||
|
} option_proxy;
|
||
|
|
||
|
|
||
|
struct dropt_context
|
||
|
{
|
||
|
const dropt_option* options;
|
||
|
size_t numOptions;
|
||
|
|
||
|
/* These may be NULL. */
|
||
|
option_proxy* sortedByLong;
|
||
|
option_proxy* sortedByShort;
|
||
|
|
||
|
bool allowConcatenatedArgs;
|
||
|
|
||
|
dropt_error_handler_func errorHandler;
|
||
|
void* errorHandlerData;
|
||
|
|
||
|
struct
|
||
|
{
|
||
|
dropt_error err;
|
||
|
dropt_char* optionName;
|
||
|
dropt_char* optionArgument;
|
||
|
dropt_char* message;
|
||
|
} errorDetails;
|
||
|
|
||
|
/* This isn't named strncmp because platforms might provide a macro
|
||
|
* version of strncmp, and we want to avoid a potential naming
|
||
|
* conflict.
|
||
|
*/
|
||
|
dropt_strncmp_func ncmpstr;
|
||
|
};
|
||
|
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
const dropt_option* option;
|
||
|
const dropt_char* optionArgument;
|
||
|
dropt_char** argNext;
|
||
|
int argsLeft;
|
||
|
} parse_state;
|
||
|
|
||
|
|
||
|
/** make_char_array
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN s : A string. Might not be NUL-terminated.
|
||
|
* May be `NULL`.
|
||
|
* len : The length of `s`, excluding any `NUL` terminator.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* The constructed char_array structure.
|
||
|
*/
|
||
|
static char_array
|
||
|
make_char_array(const dropt_char* s, size_t len)
|
||
|
{
|
||
|
char_array a;
|
||
|
|
||
|
assert(IMPLIES(s == NULL, len == 0));
|
||
|
|
||
|
a.s = s;
|
||
|
a.len = len;
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** cmp_key_option_proxy_long
|
||
|
*
|
||
|
* Comparison callback for `bsearch`. Compares a `char_array` structure
|
||
|
* against an `option_proxy` structure based on long option names.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN key : A pointer to the `char_array` structure to search for.
|
||
|
* IN item : A pointer to the `option_proxy` structure being searched
|
||
|
* against.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* 0 if `key` and `item` are equivalent,
|
||
|
* < 0 if `key` should precede `item`,
|
||
|
* > 0 if `key` should follow `item`.
|
||
|
*/
|
||
|
static int
|
||
|
cmp_key_option_proxy_long(const void* key, const void* item)
|
||
|
{
|
||
|
const char_array* longName = key;
|
||
|
const option_proxy* op = item;
|
||
|
|
||
|
size_t optionLen;
|
||
|
int ret;
|
||
|
|
||
|
assert(longName != NULL);
|
||
|
assert(op != NULL);
|
||
|
assert(op->option != NULL);
|
||
|
assert(op->context != NULL);
|
||
|
assert(op->context->ncmpstr != NULL);
|
||
|
|
||
|
if (longName->s == op->option->long_name)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
else if (longName->s == NULL)
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
else if (op->option->long_name == NULL)
|
||
|
{
|
||
|
return +1;
|
||
|
}
|
||
|
|
||
|
/* Although the `longName` key might not be `NUL`-terminated, the
|
||
|
* `option_proxy` item we're searching against must be.
|
||
|
*/
|
||
|
optionLen = dropt_strlen(op->option->long_name);
|
||
|
ret = op->context->ncmpstr(longName->s,
|
||
|
op->option->long_name,
|
||
|
MIN(longName->len, optionLen));
|
||
|
if (ret != 0)
|
||
|
{
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
if (longName->len < optionLen)
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
else if (longName->len > optionLen)
|
||
|
{
|
||
|
return +1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** cmp_option_proxies_long
|
||
|
*
|
||
|
* Comparison callback for `qsort`. Compares two `option_proxy`
|
||
|
* structures based on long option names.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN p1, p2 : Pointers to the `option_proxy` structures to compare.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* 0 if `p1` and `p2` are equivalent,
|
||
|
* < 0 if `p1` should precede `p2`,
|
||
|
* > 0 if `p1` should follow `p2`.
|
||
|
*/
|
||
|
static int
|
||
|
cmp_option_proxies_long(const void* p1, const void* p2)
|
||
|
{
|
||
|
const option_proxy* o1 = p1;
|
||
|
const option_proxy* o2 = p2;
|
||
|
|
||
|
char_array ca1;
|
||
|
|
||
|
assert(o1 != NULL);
|
||
|
assert(o2 != NULL);
|
||
|
assert(o1->option != NULL);
|
||
|
assert(o1->context == o2->context);
|
||
|
|
||
|
ca1 = make_char_array(o1->option->long_name,
|
||
|
(o1->option->long_name == NULL)
|
||
|
? 0
|
||
|
: dropt_strlen(o1->option->long_name));
|
||
|
return cmp_key_option_proxy_long(&ca1, o2);
|
||
|
}
|
||
|
|
||
|
|
||
|
/** cmp_key_option_proxy_short
|
||
|
*
|
||
|
* Comparison callback for `bsearch`. Compares a `dropt_char` against an
|
||
|
* `option_proxy` structure based on short option names.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN key : A pointer to the `dropt_char` to search for.
|
||
|
* IN item : A pointer to the `option_proxy` structure being searched
|
||
|
* against.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* 0 if `key` and `item` are equivalent,
|
||
|
* < 0 if `key` should precede `item`,
|
||
|
* > 0 if `key` should follow `item`.
|
||
|
*/
|
||
|
static int
|
||
|
cmp_key_option_proxy_short(const void* key, const void* item)
|
||
|
{
|
||
|
const dropt_char* shortName = key;
|
||
|
const option_proxy* op = item;
|
||
|
|
||
|
assert(shortName != NULL);
|
||
|
assert(op != NULL);
|
||
|
assert(op->option != NULL);
|
||
|
assert(op->context != NULL);
|
||
|
assert(op->context->ncmpstr != NULL);
|
||
|
|
||
|
return op->context->ncmpstr(shortName,
|
||
|
&op->option->short_name,
|
||
|
1);
|
||
|
}
|
||
|
|
||
|
|
||
|
/** cmp_option_proxies_short
|
||
|
*
|
||
|
* Comparison callback for `qsort`. Compares two `option_proxy`
|
||
|
* structures based on short option names.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN p1, p2 : Pointers to the `option_proxy` structures to compare.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* 0 if `p1` and `p2` are equivalent,
|
||
|
* < 0 if `p1` should precede `p2`,
|
||
|
* > 0 if `p1` should follow `p2`.
|
||
|
*/
|
||
|
static int
|
||
|
cmp_option_proxies_short(const void* p1, const void* p2)
|
||
|
{
|
||
|
const option_proxy* o1 = p1;
|
||
|
const option_proxy* o2 = p2;
|
||
|
|
||
|
assert(o1 != NULL);
|
||
|
assert(o2 != NULL);
|
||
|
assert(o1->option != NULL);
|
||
|
assert(o1->context == o2->context);
|
||
|
|
||
|
return cmp_key_option_proxy_short(&o1->option->short_name, o2);
|
||
|
}
|
||
|
|
||
|
|
||
|
/** init_lookup_tables
|
||
|
*
|
||
|
* Initializes the sorted lookup tables in a dropt context if not already
|
||
|
* initialized.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT context : The dropt context.
|
||
|
* Must not be `NULL`.
|
||
|
*/
|
||
|
static void
|
||
|
init_lookup_tables(dropt_context* context)
|
||
|
{
|
||
|
const dropt_option* options;
|
||
|
size_t n;
|
||
|
|
||
|
assert(context != NULL);
|
||
|
|
||
|
options = context->options;
|
||
|
n = context->numOptions;
|
||
|
|
||
|
if (context->sortedByLong == NULL)
|
||
|
{
|
||
|
context->sortedByLong
|
||
|
= dropt_safe_malloc(n, sizeof *(context->sortedByLong));
|
||
|
if (context->sortedByLong != NULL)
|
||
|
{
|
||
|
size_t i;
|
||
|
for (i = 0; i < n; i++)
|
||
|
{
|
||
|
context->sortedByLong[i].option = &options[i];
|
||
|
context->sortedByLong[i].context = context;
|
||
|
}
|
||
|
|
||
|
qsort(context->sortedByLong,
|
||
|
n, sizeof *(context->sortedByLong),
|
||
|
cmp_option_proxies_long);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (context->sortedByShort == NULL)
|
||
|
{
|
||
|
context->sortedByShort
|
||
|
= dropt_safe_malloc(n, sizeof *(context->sortedByShort));
|
||
|
if (context->sortedByShort != NULL)
|
||
|
{
|
||
|
size_t i;
|
||
|
for (i = 0; i < n; i++)
|
||
|
{
|
||
|
context->sortedByShort[i].option = &options[i];
|
||
|
context->sortedByShort[i].context = context;
|
||
|
}
|
||
|
|
||
|
qsort(context->sortedByShort,
|
||
|
n, sizeof *(context->sortedByShort),
|
||
|
cmp_option_proxies_short);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/** free_lookup_tables
|
||
|
*
|
||
|
* Frees the sorted lookup tables in a dropt context.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT context : The dropt context.
|
||
|
* May be `NULL`.
|
||
|
*/
|
||
|
static void
|
||
|
free_lookup_tables(dropt_context* context)
|
||
|
{
|
||
|
if (context != NULL)
|
||
|
{
|
||
|
free(context->sortedByLong);
|
||
|
context->sortedByLong = NULL;
|
||
|
|
||
|
free(context->sortedByShort);
|
||
|
context->sortedByShort = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/** is_valid_option
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN option : Specification for an individual option.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* true if the specified option is valid, false if it's a sentinel value.
|
||
|
*/
|
||
|
static bool
|
||
|
is_valid_option(const dropt_option* option)
|
||
|
{
|
||
|
return option != NULL
|
||
|
&& !( option->long_name == NULL
|
||
|
&& option->short_name == DROPT_TEXT_LITERAL('\0')
|
||
|
&& option->description == NULL
|
||
|
&& option->arg_description == NULL
|
||
|
&& option->handler == NULL
|
||
|
&& option->dest == NULL
|
||
|
&& option->attr == 0
|
||
|
&& option->extra_data == 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
/** find_option_long
|
||
|
*
|
||
|
* Finds the option specification for a long option name (i.e., an option
|
||
|
* of the form "--option").
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN context : The dropt context.
|
||
|
* IN longName : The long option name to search for (excluding leading
|
||
|
* dashes).
|
||
|
* `longName.s` must not be `NULL`.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* A pointer to the corresponding option specification or `NULL` if not
|
||
|
* found.
|
||
|
*/
|
||
|
static const dropt_option*
|
||
|
find_option_long(const dropt_context* context,
|
||
|
char_array longName)
|
||
|
{
|
||
|
assert(context != NULL);
|
||
|
assert(longName.s != NULL);
|
||
|
|
||
|
if (context->sortedByLong != NULL)
|
||
|
{
|
||
|
option_proxy* found = bsearch(&longName, context->sortedByLong,
|
||
|
context->numOptions,
|
||
|
sizeof *(context->sortedByLong),
|
||
|
cmp_key_option_proxy_long);
|
||
|
return (found == NULL) ? NULL : found->option;
|
||
|
}
|
||
|
|
||
|
/* Fall back to a linear search. */
|
||
|
{
|
||
|
option_proxy item = { 0 };
|
||
|
item.context = context;
|
||
|
for (item.option = context->options;
|
||
|
is_valid_option(item.option);
|
||
|
item.option++)
|
||
|
{
|
||
|
if (cmp_key_option_proxy_long(&longName, &item) == 0)
|
||
|
{
|
||
|
return item.option;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** find_option_short
|
||
|
*
|
||
|
* Finds the option specification for a short option name (i.e., an
|
||
|
* option of the form "-o").
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN context : The dropt context.
|
||
|
* IN shortName : The short option name to search for.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* A pointer to the corresponding option specification or `NULL` if not
|
||
|
* found.
|
||
|
*/
|
||
|
static const dropt_option*
|
||
|
find_option_short(const dropt_context* context, dropt_char shortName)
|
||
|
{
|
||
|
assert(context != NULL);
|
||
|
assert(shortName != DROPT_TEXT_LITERAL('\0'));
|
||
|
assert(context->ncmpstr != NULL);
|
||
|
|
||
|
if (context->sortedByShort != NULL)
|
||
|
{
|
||
|
option_proxy* found = bsearch(&shortName, context->sortedByShort,
|
||
|
context->numOptions,
|
||
|
sizeof *(context->sortedByShort),
|
||
|
cmp_key_option_proxy_short);
|
||
|
return (found == NULL) ? NULL : found->option;
|
||
|
}
|
||
|
|
||
|
/* Fall back to a linear search. */
|
||
|
{
|
||
|
const dropt_option* option;
|
||
|
for (option = context->options; is_valid_option(option); option++)
|
||
|
{
|
||
|
if (context->ncmpstr(&shortName, &option->short_name, 1) == 0)
|
||
|
{
|
||
|
return option;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** set_error_details
|
||
|
*
|
||
|
* Generates error details in the dropt context.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT context : The dropt context.
|
||
|
* Must not be `NULL`.
|
||
|
* IN err : The error code.
|
||
|
* IN optionName : The name of the option we failed on.
|
||
|
* `optionName.s` must not be `NULL`.
|
||
|
* IN optionArgument : The value of the option we failed on.
|
||
|
* Pass `NULL` if unwanted.
|
||
|
*/
|
||
|
static void
|
||
|
set_error_details(dropt_context* context, dropt_error err,
|
||
|
char_array optionName,
|
||
|
const dropt_char* optionArgument)
|
||
|
{
|
||
|
assert(context != NULL);
|
||
|
assert(optionName.s != NULL);
|
||
|
|
||
|
context->errorDetails.err = err;
|
||
|
|
||
|
free(context->errorDetails.optionName);
|
||
|
free(context->errorDetails.optionArgument);
|
||
|
|
||
|
context->errorDetails.optionName = dropt_strndup(optionName.s,
|
||
|
optionName.len);
|
||
|
context->errorDetails.optionArgument = (optionArgument == NULL)
|
||
|
? NULL
|
||
|
: dropt_strdup(optionArgument);
|
||
|
|
||
|
/* The message will be generated lazily on retrieval. */
|
||
|
free(context->errorDetails.message);
|
||
|
context->errorDetails.message = NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** set_short_option_error_details
|
||
|
*
|
||
|
* Generates error details in the dropt context.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT context : The dropt context.
|
||
|
* IN err : The error code.
|
||
|
* IN shortName : the "short" name of the option we failed on.
|
||
|
* IN optionArgument : The value of the option we failed on.
|
||
|
* Pass `NULL` if unwanted.
|
||
|
*/
|
||
|
static void
|
||
|
set_short_option_error_details(dropt_context* context, dropt_error err,
|
||
|
dropt_char shortName,
|
||
|
const dropt_char* optionArgument)
|
||
|
{
|
||
|
/* "-?" is just a placeholder. */
|
||
|
dropt_char shortNameBuf[] = DROPT_TEXT_LITERAL("-?");
|
||
|
|
||
|
assert(context != NULL);
|
||
|
assert(shortName != DROPT_TEXT_LITERAL('\0'));
|
||
|
|
||
|
shortNameBuf[1] = shortName;
|
||
|
|
||
|
set_error_details(context, err,
|
||
|
make_char_array(shortNameBuf,
|
||
|
ARRAY_LENGTH(shortNameBuf) - 1),
|
||
|
optionArgument);
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_get_error
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN context : The dropt context.
|
||
|
* Must not be NULL.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* The current error code waiting in the dropt context.
|
||
|
*/
|
||
|
dropt_error
|
||
|
dropt_get_error(const dropt_context* context)
|
||
|
{
|
||
|
if (context == NULL)
|
||
|
{
|
||
|
DROPT_MISUSE("No dropt context specified.");
|
||
|
return dropt_error_bad_configuration;
|
||
|
}
|
||
|
return context->errorDetails.err;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_get_error_details
|
||
|
*
|
||
|
* Retrieves details about the current error.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN context : The dropt context.
|
||
|
* OUT optionName : On output, the name of the option we failed
|
||
|
* on. Do not free this string.
|
||
|
* Pass `NULL` if unwanted.
|
||
|
* OUT optionArgument : On output, the value (possibly `NULL`) of the
|
||
|
* option we failed on. Do not free this
|
||
|
* string.
|
||
|
* Pass `NULL` if unwanted.
|
||
|
*/
|
||
|
void
|
||
|
dropt_get_error_details(const dropt_context* context,
|
||
|
dropt_char** optionName, dropt_char** optionArgument)
|
||
|
{
|
||
|
if (optionName != NULL)
|
||
|
{
|
||
|
*optionName = context->errorDetails.optionName;
|
||
|
}
|
||
|
|
||
|
if (optionArgument != NULL)
|
||
|
{
|
||
|
*optionArgument = context->errorDetails.optionArgument;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_get_error_message
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN context : The dropt context.
|
||
|
* Must not be `NULL`.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* The current error message waiting in the dropt context or the empty
|
||
|
* string if there are no errors. Note that calling any dropt
|
||
|
* function other than `dropt_get_error`, `dropt_get_error_details`, and
|
||
|
* `dropt_get_error_message` may invalidate a previously-returned
|
||
|
* string.
|
||
|
*/
|
||
|
const dropt_char*
|
||
|
dropt_get_error_message(dropt_context* context)
|
||
|
{
|
||
|
if (context == NULL)
|
||
|
{
|
||
|
DROPT_MISUSE("No dropt context specified.");
|
||
|
return DROPT_TEXT_LITERAL("");
|
||
|
}
|
||
|
|
||
|
if (context->errorDetails.err == dropt_error_none)
|
||
|
{
|
||
|
return DROPT_TEXT_LITERAL("");
|
||
|
}
|
||
|
|
||
|
if (context->errorDetails.message == NULL)
|
||
|
{
|
||
|
if (context->errorHandler != NULL)
|
||
|
{
|
||
|
context->errorDetails.message
|
||
|
= context->errorHandler(context->errorDetails.err,
|
||
|
context->errorDetails.optionName,
|
||
|
context->errorDetails.optionArgument,
|
||
|
context->errorHandlerData);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
#ifndef DROPT_NO_STRING_BUFFERS
|
||
|
context->errorDetails.message
|
||
|
= dropt_default_error_handler(context->errorDetails.err,
|
||
|
context->errorDetails.optionName,
|
||
|
context->errorDetails.optionArgument);
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return (context->errorDetails.message == NULL)
|
||
|
? DROPT_TEXT_LITERAL("Unknown error")
|
||
|
: context->errorDetails.message;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_clear_error
|
||
|
*
|
||
|
* Clears the error waiting in the dropt context.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT context : The dropt context to free.
|
||
|
* May be `NULL`.
|
||
|
*/
|
||
|
void
|
||
|
dropt_clear_error(dropt_context* context)
|
||
|
{
|
||
|
if (context != NULL)
|
||
|
{
|
||
|
context->errorDetails.err = dropt_error_none;
|
||
|
|
||
|
free(context->errorDetails.optionName);
|
||
|
context->errorDetails.optionName = NULL;
|
||
|
|
||
|
free(context->errorDetails.optionArgument);
|
||
|
context->errorDetails.optionArgument = NULL;
|
||
|
|
||
|
free(context->errorDetails.message);
|
||
|
context->errorDetails.message = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
#ifndef DROPT_NO_STRING_BUFFERS
|
||
|
/** dropt_default_error_handler
|
||
|
*
|
||
|
* Default error handler.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN error : The error code.
|
||
|
* IN optionName : The name of the option we failed on.
|
||
|
* IN optionArgument : The value of the option we failed on.
|
||
|
* Pass `NULL` if unwanted.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* An allocated string for the given error. The caller is responsible for
|
||
|
* calling `free()` on it when no longer needed.
|
||
|
* May return `NULL`.
|
||
|
*/
|
||
|
dropt_char*
|
||
|
dropt_default_error_handler(dropt_error error,
|
||
|
const dropt_char* optionName,
|
||
|
const dropt_char* optionArgument)
|
||
|
{
|
||
|
dropt_char* s = NULL;
|
||
|
|
||
|
const dropt_char* separator = DROPT_TEXT_LITERAL(": ");
|
||
|
|
||
|
if (optionArgument == NULL)
|
||
|
{
|
||
|
separator = optionArgument = DROPT_TEXT_LITERAL("");
|
||
|
}
|
||
|
|
||
|
switch (error)
|
||
|
{
|
||
|
case dropt_error_none:
|
||
|
/* This shouldn't happen (unless client code invokes this directly
|
||
|
* with `dropt_error_none`), but it's here for completeness.
|
||
|
*/
|
||
|
break;
|
||
|
|
||
|
case dropt_error_bad_configuration:
|
||
|
s = dropt_strdup(DROPT_TEXT_LITERAL("Invalid option configuration"));
|
||
|
break;
|
||
|
|
||
|
case dropt_error_invalid_option:
|
||
|
s = dropt_asprintf(DROPT_TEXT_LITERAL("Invalid option: %s"),
|
||
|
optionName);
|
||
|
break;
|
||
|
case dropt_error_insufficient_arguments:
|
||
|
s = dropt_asprintf(DROPT_TEXT_LITERAL("Value required after option %s"),
|
||
|
optionName);
|
||
|
break;
|
||
|
case dropt_error_mismatch:
|
||
|
s = dropt_asprintf(DROPT_TEXT_LITERAL("Invalid value for option %s%s%s"),
|
||
|
optionName, separator, optionArgument);
|
||
|
break;
|
||
|
case dropt_error_overflow:
|
||
|
s = dropt_asprintf(DROPT_TEXT_LITERAL("Value too large for option %s%s%s"),
|
||
|
optionName, separator, optionArgument);
|
||
|
break;
|
||
|
case dropt_error_underflow:
|
||
|
s = dropt_asprintf(DROPT_TEXT_LITERAL("Value too small for option %s%s%s"),
|
||
|
optionName, separator, optionArgument);
|
||
|
break;
|
||
|
case dropt_error_insufficient_memory:
|
||
|
s = dropt_strdup(DROPT_TEXT_LITERAL("Insufficient memory"));
|
||
|
break;
|
||
|
case dropt_error_unknown:
|
||
|
default:
|
||
|
s = dropt_asprintf(DROPT_TEXT_LITERAL("Unknown error handling option %s"),
|
||
|
optionName);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_get_help
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN context : The dropt context.
|
||
|
* Must not be `NULL`.
|
||
|
* IN helpParams : The help parameters.
|
||
|
* Pass `NULL` to use the default help parameters.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* An allocated help string for the available options. The caller is
|
||
|
* responsible for calling `free()` on it when no longer needed.
|
||
|
* Returns `NULL` on error.
|
||
|
*/
|
||
|
dropt_char*
|
||
|
dropt_get_help(const dropt_context* context,
|
||
|
const dropt_help_params* helpParams)
|
||
|
{
|
||
|
dropt_char* helpText = NULL;
|
||
|
dropt_stringstream* ss = dropt_ssopen();
|
||
|
|
||
|
if (context == NULL)
|
||
|
{
|
||
|
DROPT_MISUSE("No dropt context specified.");
|
||
|
}
|
||
|
else if (ss != NULL)
|
||
|
{
|
||
|
const dropt_option* option;
|
||
|
dropt_help_params hp;
|
||
|
|
||
|
if (helpParams == NULL)
|
||
|
{
|
||
|
dropt_init_help_params(&hp);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hp = *helpParams;
|
||
|
}
|
||
|
|
||
|
for (option = context->options; is_valid_option(option); option++)
|
||
|
{
|
||
|
bool hasLongName = option->long_name != NULL
|
||
|
&& option->long_name[0] != DROPT_TEXT_LITERAL('\0');
|
||
|
bool hasShortName = option->short_name != DROPT_TEXT_LITERAL('\0');
|
||
|
|
||
|
/* The number of characters printed on the current line so far. */
|
||
|
int n;
|
||
|
|
||
|
if ( option->description == NULL
|
||
|
|| (option->attr & dropt_attr_hidden))
|
||
|
{
|
||
|
/* Undocumented option. Ignore it and move on. */
|
||
|
continue;
|
||
|
}
|
||
|
else if (hasLongName && hasShortName)
|
||
|
{
|
||
|
n = dropt_ssprintf(ss, DROPT_TEXT_LITERAL("%*s-%c, --%s"),
|
||
|
hp.indent, DROPT_TEXT_LITERAL(""),
|
||
|
option->short_name, option->long_name);
|
||
|
}
|
||
|
else if (hasLongName)
|
||
|
{
|
||
|
n = dropt_ssprintf(ss, DROPT_TEXT_LITERAL("%*s--%s"),
|
||
|
hp.indent, DROPT_TEXT_LITERAL(""),
|
||
|
option->long_name);
|
||
|
}
|
||
|
else if (hasShortName)
|
||
|
{
|
||
|
n = dropt_ssprintf(ss, DROPT_TEXT_LITERAL("%*s-%c"),
|
||
|
hp.indent, DROPT_TEXT_LITERAL(""),
|
||
|
option->short_name);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Comment text. Don't bother with indentation. */
|
||
|
assert(option->description != NULL);
|
||
|
dropt_ssprintf(ss, DROPT_TEXT_LITERAL("%s\n"), option->description);
|
||
|
goto next;
|
||
|
}
|
||
|
|
||
|
if (n < 0) { n = 0; }
|
||
|
|
||
|
if (option->arg_description != NULL)
|
||
|
{
|
||
|
int m = dropt_ssprintf(ss,
|
||
|
(option->attr & dropt_attr_optional_val)
|
||
|
? DROPT_TEXT_LITERAL("[=%s]")
|
||
|
: DROPT_TEXT_LITERAL("=%s"),
|
||
|
option->arg_description);
|
||
|
if (m > 0) { n += m; }
|
||
|
}
|
||
|
|
||
|
/* Check for equality to make sure that there's at least one
|
||
|
* space between the option name and its description.
|
||
|
*/
|
||
|
if ((unsigned int) n >= hp.description_start_column)
|
||
|
{
|
||
|
dropt_ssprintf(ss, DROPT_TEXT_LITERAL("\n"));
|
||
|
n = 0;
|
||
|
}
|
||
|
|
||
|
{
|
||
|
const dropt_char* line = option->description;
|
||
|
while (line != NULL)
|
||
|
{
|
||
|
int lineLen;
|
||
|
const dropt_char* nextLine;
|
||
|
const dropt_char* newline = dropt_strchr(line, DROPT_TEXT_LITERAL('\n'));
|
||
|
|
||
|
if (newline == NULL)
|
||
|
{
|
||
|
lineLen = dropt_strlen(line);
|
||
|
nextLine = NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
lineLen = newline - line;
|
||
|
nextLine = newline + 1;
|
||
|
}
|
||
|
|
||
|
dropt_ssprintf(ss, DROPT_TEXT_LITERAL("%*s%.*s\n"),
|
||
|
hp.description_start_column - n, DROPT_TEXT_LITERAL(""),
|
||
|
lineLen, line);
|
||
|
n = 0;
|
||
|
|
||
|
line = nextLine;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
next:
|
||
|
if (hp.blank_lines_between_options)
|
||
|
{
|
||
|
dropt_ssprintf(ss, DROPT_TEXT_LITERAL("\n"));
|
||
|
}
|
||
|
}
|
||
|
helpText = dropt_ssfinalize(ss);
|
||
|
}
|
||
|
|
||
|
return helpText;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_print_help
|
||
|
*
|
||
|
* Prints help for the available options.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT f : The file stream to print to.
|
||
|
* IN context : The dropt context.
|
||
|
* Must not be `NULL`.
|
||
|
* IN helpParams : The help parameters.
|
||
|
* Pass `NULL` to use the default help parameters.
|
||
|
*/
|
||
|
void
|
||
|
dropt_print_help(FILE* f, const dropt_context* context,
|
||
|
const dropt_help_params* helpParams)
|
||
|
{
|
||
|
dropt_char* helpText = dropt_get_help(context, helpParams);
|
||
|
if (helpText != NULL)
|
||
|
{
|
||
|
dropt_fputs(helpText, f);
|
||
|
free(helpText);
|
||
|
}
|
||
|
}
|
||
|
#endif /* DROPT_NO_STRING_BUFFERS */
|
||
|
|
||
|
|
||
|
/** set_option_value
|
||
|
*
|
||
|
* Sets the value for a specified option by invoking the option's
|
||
|
* handler callback.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT context : The dropt context.
|
||
|
* IN option : The option.
|
||
|
* IN optionArgument : The option's value. May be `NULL`.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* An error code.
|
||
|
*/
|
||
|
static dropt_error
|
||
|
set_option_value(dropt_context* context,
|
||
|
const dropt_option* option, const dropt_char* optionArgument)
|
||
|
{
|
||
|
assert(option != NULL);
|
||
|
|
||
|
if (option->handler == NULL)
|
||
|
{
|
||
|
DROPT_MISUSE("No option handler specified.");
|
||
|
return dropt_error_bad_configuration;
|
||
|
}
|
||
|
|
||
|
return option->handler(context, option, optionArgument,
|
||
|
option->dest);
|
||
|
}
|
||
|
|
||
|
|
||
|
/** parse_option_arg
|
||
|
*
|
||
|
* Helper function to `parse_long_option` and `parse_short_option` to
|
||
|
* deal with consuming possibly optional arguments.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT context : The dropt context.
|
||
|
* IN/OUT ps : The current parse state.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* An error code.
|
||
|
*/
|
||
|
static dropt_error
|
||
|
parse_option_arg(dropt_context* context, parse_state* ps)
|
||
|
{
|
||
|
dropt_error err;
|
||
|
|
||
|
bool consumeNextArg = false;
|
||
|
|
||
|
if (OPTION_TAKES_ARG(ps->option) && ps->optionArgument == NULL)
|
||
|
{
|
||
|
/* The option expects an argument, but none was specified with '='.
|
||
|
* Try using the next item from the command-line.
|
||
|
*/
|
||
|
if (ps->argsLeft > 0 && *(ps->argNext) != NULL)
|
||
|
{
|
||
|
consumeNextArg = true;
|
||
|
ps->optionArgument = *(ps->argNext);
|
||
|
}
|
||
|
else if (!(ps->option->attr & dropt_attr_optional_val))
|
||
|
{
|
||
|
err = dropt_error_insufficient_arguments;
|
||
|
goto exit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Even for options that don't ask for arguments, always parse and
|
||
|
* consume an argument that was specified with '='.
|
||
|
*/
|
||
|
err = set_option_value(context, ps->option, ps->optionArgument);
|
||
|
|
||
|
if ( err != dropt_error_none
|
||
|
&& (ps->option->attr & dropt_attr_optional_val)
|
||
|
&& consumeNextArg
|
||
|
&& ps->optionArgument != NULL)
|
||
|
{
|
||
|
/* The option's handler didn't like the argument we fed it. If the
|
||
|
* argument was optional, try again without it.
|
||
|
*/
|
||
|
consumeNextArg = false;
|
||
|
ps->optionArgument = NULL;
|
||
|
err = set_option_value(context, ps->option, NULL);
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
if (err == dropt_error_none && consumeNextArg)
|
||
|
{
|
||
|
ps->argNext++;
|
||
|
ps->argsLeft--;
|
||
|
}
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** parse_long_option
|
||
|
*
|
||
|
* Helper function to `dropt_parse` to deal with consuming a long option.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT context : The dropt context.
|
||
|
* IN/OUT ps : The current parse state.
|
||
|
* IN arg : The command-line argument with the long option.
|
||
|
* (Examples: "--longName", "--longName=ARGUMENT")
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* true if parsing should continue, false if it should halt.
|
||
|
*/
|
||
|
static bool
|
||
|
parse_long_option(dropt_context* context, parse_state* ps,
|
||
|
const dropt_char* arg)
|
||
|
{
|
||
|
bool continueParsing = false;
|
||
|
|
||
|
dropt_error err = dropt_error_none;
|
||
|
|
||
|
const dropt_char* longName = arg + 2;
|
||
|
const dropt_char* longNameEnd;
|
||
|
if (longName[0] == DROPT_TEXT_LITERAL('\0'))
|
||
|
{
|
||
|
/* -- */
|
||
|
|
||
|
/* This is used to mark the end of the option processing
|
||
|
* to prevent some arguments with leading '-' characters
|
||
|
* from being treated as options.
|
||
|
*
|
||
|
* Don't pass this back to the caller.
|
||
|
*/
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
/* --longName */
|
||
|
longNameEnd = dropt_strchr(longName, DROPT_TEXT_LITERAL('='));
|
||
|
if (longNameEnd == longName)
|
||
|
{
|
||
|
/* Deal with the pathological case of a user supplying
|
||
|
* "--=".
|
||
|
*/
|
||
|
err = dropt_error_invalid_option;
|
||
|
set_error_details(context, err,
|
||
|
make_char_array(arg, dropt_strlen(arg)),
|
||
|
NULL);
|
||
|
goto exit;
|
||
|
}
|
||
|
else if (longNameEnd == NULL)
|
||
|
{
|
||
|
longNameEnd = longName + dropt_strlen(longName);
|
||
|
assert(ps->optionArgument == NULL);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* --longName=ARGUMENT */
|
||
|
ps->optionArgument = longNameEnd + 1;
|
||
|
}
|
||
|
|
||
|
/* Pass the length of the option name so that we don't need
|
||
|
* to mutate the original string by inserting a
|
||
|
* `NUL`-terminator.
|
||
|
*/
|
||
|
ps->option = find_option_long(context,
|
||
|
make_char_array(longName,
|
||
|
longNameEnd - longName));
|
||
|
if (ps->option == NULL)
|
||
|
{
|
||
|
err = dropt_error_invalid_option;
|
||
|
set_error_details(context, err,
|
||
|
make_char_array(arg, longNameEnd - arg),
|
||
|
NULL);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
err = parse_option_arg(context, ps);
|
||
|
if (err != dropt_error_none)
|
||
|
{
|
||
|
set_error_details(context, err,
|
||
|
make_char_array(arg, longNameEnd - arg),
|
||
|
ps->optionArgument);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( err != dropt_error_none
|
||
|
|| (ps->option->attr & dropt_attr_halt))
|
||
|
{
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
continueParsing = true;
|
||
|
|
||
|
exit:
|
||
|
return continueParsing;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** parse_short_option
|
||
|
*
|
||
|
* Helper function to `dropt_parse` to deal with consuming one or more
|
||
|
* short options.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT context : The dropt context.
|
||
|
* IN/OUT ps : The current parse state.
|
||
|
* IN arg : The command-line argument with one or more short
|
||
|
* options.
|
||
|
* (Examples: "-a", "-abc", "-oARGUMENT", "-o=ARGUMENT")
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* true if parsing should continue, false if it should halt.
|
||
|
*/
|
||
|
static bool
|
||
|
parse_short_option(dropt_context* context, parse_state* ps,
|
||
|
const dropt_char* arg)
|
||
|
{
|
||
|
bool continueParsing = false;
|
||
|
|
||
|
dropt_error err = dropt_error_none;
|
||
|
|
||
|
size_t len;
|
||
|
size_t j;
|
||
|
|
||
|
const dropt_char* shortOptionGroup = arg + 1;
|
||
|
const dropt_char* shortOptionGroupEnd
|
||
|
= dropt_strchr(shortOptionGroup, DROPT_TEXT_LITERAL('='));
|
||
|
|
||
|
if (shortOptionGroupEnd == shortOptionGroup)
|
||
|
{
|
||
|
/* Deal with the pathological case of a user supplying
|
||
|
* "-=".
|
||
|
*/
|
||
|
err = dropt_error_invalid_option;
|
||
|
set_error_details(context, err,
|
||
|
make_char_array(arg, dropt_strlen(arg)),
|
||
|
NULL);
|
||
|
goto exit;
|
||
|
}
|
||
|
else if (shortOptionGroupEnd != NULL)
|
||
|
{
|
||
|
/* -x=ARGUMENT */
|
||
|
len = shortOptionGroupEnd - shortOptionGroup;
|
||
|
ps->optionArgument = shortOptionGroupEnd + 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
len = dropt_strlen(shortOptionGroup);
|
||
|
assert(ps->optionArgument == NULL);
|
||
|
}
|
||
|
|
||
|
for (j = 0; j < len; j++)
|
||
|
{
|
||
|
ps->option = find_option_short(context, shortOptionGroup[j]);
|
||
|
if (ps->option == NULL)
|
||
|
{
|
||
|
err = dropt_error_invalid_option;
|
||
|
set_short_option_error_details(context, err,
|
||
|
shortOptionGroup[j], NULL);
|
||
|
goto exit;
|
||
|
}
|
||
|
else if (j + 1 == len)
|
||
|
{
|
||
|
/* The last short option in a condensed list gets
|
||
|
* to use an argument.
|
||
|
*/
|
||
|
err = parse_option_arg(context, ps);
|
||
|
if (err != dropt_error_none)
|
||
|
{
|
||
|
set_short_option_error_details(context, err,
|
||
|
shortOptionGroup[j],
|
||
|
ps->optionArgument);
|
||
|
goto exit;
|
||
|
}
|
||
|
}
|
||
|
else if ( context->allowConcatenatedArgs
|
||
|
&& OPTION_TAKES_ARG(ps->option)
|
||
|
&& j == 0)
|
||
|
{
|
||
|
err = set_option_value(context, ps->option,
|
||
|
&shortOptionGroup[j + 1]);
|
||
|
|
||
|
if ( err != dropt_error_none
|
||
|
&& (ps->option->attr & dropt_attr_optional_val))
|
||
|
{
|
||
|
err = set_option_value(context, ps->option, NULL);
|
||
|
}
|
||
|
|
||
|
if (err != dropt_error_none)
|
||
|
{
|
||
|
set_short_option_error_details(context, err,
|
||
|
shortOptionGroup[j],
|
||
|
&shortOptionGroup[j + 1]);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
/* Skip to the next argument. */
|
||
|
break;
|
||
|
}
|
||
|
else if ( OPTION_TAKES_ARG(ps->option)
|
||
|
&& !(ps->option->attr & dropt_attr_optional_val))
|
||
|
{
|
||
|
/* Short options with required arguments can't be used
|
||
|
* in condensed lists except in the last position.
|
||
|
*
|
||
|
* e.g. -abcd ARGUMENT
|
||
|
* ^
|
||
|
*/
|
||
|
err = dropt_error_insufficient_arguments;
|
||
|
set_short_option_error_details(context, err,
|
||
|
shortOptionGroup[j], NULL);
|
||
|
goto exit;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
err = set_option_value(context, ps->option, NULL);
|
||
|
if (err != dropt_error_none)
|
||
|
{
|
||
|
set_short_option_error_details(context, err,
|
||
|
shortOptionGroup[j],
|
||
|
NULL);
|
||
|
goto exit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (ps->option->attr & dropt_attr_halt) { goto exit; }
|
||
|
}
|
||
|
|
||
|
assert(err == dropt_error_none);
|
||
|
continueParsing = true;
|
||
|
|
||
|
exit:
|
||
|
return continueParsing;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_parse
|
||
|
*
|
||
|
* Parses command-line options.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT context : The dropt context.
|
||
|
* Must not be `NULL`.
|
||
|
* IN argc : The maximum number of arguments to parse from argv.
|
||
|
* Pass -1 to parse all arguments up to a `NULL` sentinel
|
||
|
* value.
|
||
|
* IN argv : The list of command-line arguments, not including the
|
||
|
* initial program name.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* A pointer to the first unprocessed element in `argv`.
|
||
|
*/
|
||
|
dropt_char**
|
||
|
dropt_parse(dropt_context* context,
|
||
|
int argc, dropt_char** argv)
|
||
|
{
|
||
|
dropt_char* arg;
|
||
|
parse_state ps;
|
||
|
|
||
|
ps.option = NULL;
|
||
|
ps.optionArgument = NULL;
|
||
|
ps.argNext = argv;
|
||
|
|
||
|
if (argv == NULL)
|
||
|
{
|
||
|
/* Nothing to do. */
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
if (context == NULL)
|
||
|
{
|
||
|
DROPT_MISUSE("No dropt context specified.");
|
||
|
set_error_details(context, dropt_error_bad_configuration,
|
||
|
make_char_array(DROPT_TEXT_LITERAL(""), 0),
|
||
|
NULL);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
#ifdef DROPT_NO_STRING_BUFFERS
|
||
|
if (context->errorHandler == NULL)
|
||
|
{
|
||
|
DROPT_MISUSE("No error handler specified.");
|
||
|
set_error_details(context, dropt_error_bad_configuration,
|
||
|
make_char_array(DROPT_TEXT_LITERAL(""), 0),
|
||
|
NULL);
|
||
|
goto exit;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if (argc == -1)
|
||
|
{
|
||
|
argc = 0;
|
||
|
while (argv[argc] != NULL) { argc++; }
|
||
|
}
|
||
|
|
||
|
if (argc == 0)
|
||
|
{
|
||
|
/* Nothing to do. */
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
init_lookup_tables(context);
|
||
|
|
||
|
ps.argsLeft = argc;
|
||
|
|
||
|
while ( ps.argsLeft-- > 0
|
||
|
&& (arg = *ps.argNext) != NULL
|
||
|
&& arg[0] == DROPT_TEXT_LITERAL('-'))
|
||
|
{
|
||
|
if (arg[1] == DROPT_TEXT_LITERAL('\0'))
|
||
|
{
|
||
|
/* - */
|
||
|
|
||
|
/* This intentionally leaves "-" unprocessed for the caller to
|
||
|
* deal with. This allows construction of programs that treat
|
||
|
* "-" to mean `stdin`.
|
||
|
*/
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
ps.argNext++;
|
||
|
|
||
|
if (arg[1] == DROPT_TEXT_LITERAL('-'))
|
||
|
{
|
||
|
if (!parse_long_option(context, &ps, arg)) { goto exit; }
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Short name. (-x) */
|
||
|
if (!parse_short_option(context, &ps, arg)) { goto exit; }
|
||
|
}
|
||
|
|
||
|
ps.option = NULL;
|
||
|
ps.optionArgument = NULL;
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
return ps.argNext;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_new_context
|
||
|
*
|
||
|
* Creates a new dropt context.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN options : The list of option specifications.
|
||
|
* Must not be `NULL`.
|
||
|
* The list is *not* copied and must outlive the dropt
|
||
|
* context.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* An allocated dropt context. The caller is responsible for freeing
|
||
|
* it with `dropt_free_context` when no longer needed.
|
||
|
* Returns `NULL` on error.
|
||
|
*/
|
||
|
dropt_context*
|
||
|
dropt_new_context(const dropt_option* options)
|
||
|
{
|
||
|
dropt_context* context = NULL;
|
||
|
size_t n;
|
||
|
|
||
|
if (options == NULL)
|
||
|
{
|
||
|
DROPT_MISUSE("No option list specified.");
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
/* Sanity-check the options. */
|
||
|
for (n = 0; is_valid_option(&options[n]); n++)
|
||
|
{
|
||
|
if ( options[n].short_name == DROPT_TEXT_LITERAL('=')
|
||
|
|| ( options[n].long_name != NULL
|
||
|
&& dropt_strchr(options[n].long_name, DROPT_TEXT_LITERAL('=')) != NULL))
|
||
|
{
|
||
|
DROPT_MISUSE("Invalid option list. '=' may not be used in an option name.");
|
||
|
goto exit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
context = malloc(sizeof *context);
|
||
|
if (context == NULL)
|
||
|
{
|
||
|
goto exit;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
dropt_context emptyContext = { 0 };
|
||
|
*context = emptyContext;
|
||
|
|
||
|
context->options = options;
|
||
|
context->numOptions = n;
|
||
|
dropt_set_strncmp(context, NULL);
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
return context;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_free_context
|
||
|
*
|
||
|
* Frees a dropt context.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT context : The dropt context to free.
|
||
|
* May be `NULL`.
|
||
|
*/
|
||
|
void
|
||
|
dropt_free_context(dropt_context* context)
|
||
|
{
|
||
|
dropt_clear_error(context);
|
||
|
free_lookup_tables(context);
|
||
|
free(context);
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_get_options
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN context : The dropt context.
|
||
|
* Must not be `NULL`.
|
||
|
*
|
||
|
* RETURNS:
|
||
|
* The context's list of option specifications.
|
||
|
*/
|
||
|
const dropt_option*
|
||
|
dropt_get_options(const dropt_context* context)
|
||
|
{
|
||
|
if (context == NULL)
|
||
|
{
|
||
|
DROPT_MISUSE("No dropt context specified.");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return context->options;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_init_help_params
|
||
|
*
|
||
|
* Initializes a `dropt_help_params` structure with the default values.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* OUT helpParams : On output, set to the default help parameters.
|
||
|
* Must not be `NULL`.
|
||
|
*/
|
||
|
void
|
||
|
dropt_init_help_params(dropt_help_params* helpParams)
|
||
|
{
|
||
|
if (helpParams == NULL)
|
||
|
{
|
||
|
DROPT_MISUSE("No dropt help parameters specified.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
helpParams->indent = default_help_indent;
|
||
|
helpParams->description_start_column = default_description_start_column;
|
||
|
helpParams->blank_lines_between_options = true;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_set_error_handler
|
||
|
*
|
||
|
* Sets the callback function used to generate error strings from error
|
||
|
* codes.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT context : The dropt context.
|
||
|
* Must not be `NULL`.
|
||
|
* IN handler : The error handler callback.
|
||
|
* Pass `NULL` to use the default error handler.
|
||
|
* IN handlerData : Caller-defined callback data.
|
||
|
*/
|
||
|
void
|
||
|
dropt_set_error_handler(dropt_context* context,
|
||
|
dropt_error_handler_func handler,
|
||
|
void* handlerData)
|
||
|
{
|
||
|
if (context == NULL)
|
||
|
{
|
||
|
DROPT_MISUSE("No dropt context specified.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
context->errorHandler = handler;
|
||
|
context->errorHandlerData = handlerData;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_set_strncmp
|
||
|
*
|
||
|
* Sets the callback function used to compare strings.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT context : The dropt context.
|
||
|
* Must not be `NULL`.
|
||
|
* IN cmp : The string comparison function.
|
||
|
* Pass `NULL` to use the default string comparison
|
||
|
* function.
|
||
|
*/
|
||
|
void
|
||
|
dropt_set_strncmp(dropt_context* context, dropt_strncmp_func cmp)
|
||
|
{
|
||
|
if (context == NULL)
|
||
|
{
|
||
|
DROPT_MISUSE("No dropt context specified.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (cmp == NULL) { cmp = dropt_strncmp; }
|
||
|
context->ncmpstr = cmp;
|
||
|
|
||
|
/* Changing the sort method invalidates our existing lookup tables. */
|
||
|
free_lookup_tables(context);
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_allow_concatenated_arguments
|
||
|
*
|
||
|
* Specifies whether "short" options are allowed to have concatenated
|
||
|
* arguments (i.e. without space or "=" separators, such as "-oARGUMENT").
|
||
|
*
|
||
|
* (Concatenated arguments are disallowed by default.)
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN/OUT context : The dropt context.
|
||
|
* IN allow : Pass 1 if concatenated arguments should be allowed,
|
||
|
* 0 otherwise.
|
||
|
*/
|
||
|
void
|
||
|
dropt_allow_concatenated_arguments(dropt_context* context, dropt_bool allow)
|
||
|
{
|
||
|
if (context == NULL)
|
||
|
{
|
||
|
DROPT_MISUSE("No dropt context specified.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
context->allowConcatenatedArgs = (allow != 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
/** dropt_misuse
|
||
|
*
|
||
|
* Prints a diagnostic for logical errors caused by external clients
|
||
|
* calling into dropt improperly.
|
||
|
*
|
||
|
* In debug builds, terminates the program and prints the filename and
|
||
|
* line number of the failure.
|
||
|
*
|
||
|
* For logical errors entirely internal to dropt, use `assert()`
|
||
|
* instead.
|
||
|
*
|
||
|
* PARAMETERS:
|
||
|
* IN message : The error message.
|
||
|
* Must not be `NULL`.
|
||
|
* IN filename : The name of the file where the logical error occurred.
|
||
|
* Must not be `NULL`.
|
||
|
* IN line : The line number where the logical error occurred.
|
||
|
*/
|
||
|
void
|
||
|
dropt_misuse(const char* message, const char* filename, int line)
|
||
|
{
|
||
|
#ifdef NDEBUG
|
||
|
fprintf(stderr, "dropt: %s\n", message);
|
||
|
#else
|
||
|
fprintf(stderr, "dropt: %s (%s: %d)\n", message, filename, line);
|
||
|
abort();
|
||
|
#endif
|
||
|
}
|