1574 lines
41 KiB
C
1574 lines
41 KiB
C
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "malunal/argparse.h"
|
|
|
|
#define ARGPARSE_NPOS ((malunal_size_t)-1)
|
|
#define ARGPARSE_HELP_SLOT 0
|
|
#define ARGPARSE_HELP_COLUMN 24
|
|
|
|
#define argparse_error(errcode) \
|
|
((error_t){ .domain = &ERROR_DOMAIN_ARGPARSE_T, .code = (errcode) })
|
|
|
|
|
|
static
|
|
malunal_cstr_t
|
|
describe(malunal_int32_t code) {
|
|
switch (code) {
|
|
case ARGPARSE_ERROR_NULL_INPUT:
|
|
return "a required input was null";
|
|
case ARGPARSE_ERROR_INVALID_DEFINITION:
|
|
return "argument was defined inconsistently";
|
|
case ARGPARSE_ERROR_DUPLICATE_DEFINITION:
|
|
return "argument name is already in use";
|
|
case ARGPARSE_ERROR_HELP_REQUESTED:
|
|
return "help was requested";
|
|
case ARGPARSE_ERROR_UNKNOWN_OPTION:
|
|
return "unrecognized arguments";
|
|
case ARGPARSE_ERROR_AMBIGUOUS_OPTION:
|
|
return "ambiguous option";
|
|
case ARGPARSE_ERROR_UNKNOWN_SUBCOMMAND:
|
|
return "invalid choice";
|
|
case ARGPARSE_ERROR_MISSING_VALUE:
|
|
return "expected one argument";
|
|
case ARGPARSE_ERROR_UNEXPECTED_VALUE:
|
|
return "ignored explicit argument";
|
|
case ARGPARSE_ERROR_INVALID_VALUE:
|
|
return "invalid value";
|
|
case ARGPARSE_ERROR_MISSING_REQUIRED_OPTION:
|
|
case ARGPARSE_ERROR_MISSING_REQUIRED_POSITIONAL:
|
|
return "the following arguments are required";
|
|
case ARGPARSE_ERROR_TOO_MANY_POSITIONALS:
|
|
return "unrecognized arguments";
|
|
case ARGPARSE_ERROR_UNKNOWN_NAME:
|
|
return "no argument is registered under that name";
|
|
case ARGPARSE_ERROR_TYPE_MISMATCH:
|
|
return "argument type does not match the accessor";
|
|
case ARGPARSE_ERROR_NOT_PRESENT:
|
|
return "argument has no value";
|
|
case ARGPARSE_ERROR_OUT_OF_RANGE:
|
|
return "value index is out of range";
|
|
}
|
|
|
|
return "Unknown argparse error";
|
|
}
|
|
|
|
const error_domain_t ERROR_DOMAIN_ARGPARSE_T = {
|
|
.describe = &describe,
|
|
.name = "malunal.argparse.error"
|
|
};
|
|
|
|
|
|
typedef struct {
|
|
malunal_cstr_t name;
|
|
malunal_cstr_t desc;
|
|
allocator_mptr_t allocator;
|
|
argparser_iptr_t parent;
|
|
vector_container_t options;
|
|
vector_container_t positionals;
|
|
vector_container_t subcommands;
|
|
} parser_impl_t;
|
|
|
|
typedef parser_impl_t* parser_impl_mptr_t;
|
|
typedef const parser_impl_t* parser_impl_iptr_t;
|
|
_Static_assert(
|
|
sizeof(argparser_t) == sizeof(parser_impl_t),
|
|
"Argparser must be the size of its implementation."
|
|
);
|
|
|
|
typedef union {
|
|
strview_t string;
|
|
malunal_int64_t number;
|
|
malunal_double_t floating;
|
|
malunal_bool_t boolean;
|
|
} argitem_t;
|
|
|
|
typedef struct {
|
|
malunal_size_t slot;
|
|
argitem_t item;
|
|
} argentry_t;
|
|
|
|
typedef struct {
|
|
argparser_iptr_t parser;
|
|
allocator_mptr_t allocator;
|
|
vector_container_t counts; /* malunal_size_t per slot. */
|
|
vector_container_t entries; /* argentry_t. */
|
|
argresult_mptr_t subresult;
|
|
argparser_iptr_t error_parser;
|
|
malunal_size_t error_slot;
|
|
strview_t error_arg;
|
|
} result_impl_t;
|
|
|
|
typedef result_impl_t* result_impl_mptr_t;
|
|
typedef const result_impl_t* result_impl_iptr_t;
|
|
_Static_assert(
|
|
sizeof(argresult_t) == sizeof(result_impl_t),
|
|
"Argresult must be the size of its implementation."
|
|
);
|
|
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Small helpers over vectors and argument definitions.
|
|
* ---------------------------------------------------------------------- */
|
|
|
|
static
|
|
malunal_size_t
|
|
vector_count(
|
|
vector_container_iptr_t vector
|
|
) {
|
|
malunal_size_t count = 0;
|
|
vector_container_count(vector, &count);
|
|
return count;
|
|
}
|
|
|
|
static
|
|
error_t
|
|
vector_release(
|
|
vector_container_mptr_t vector
|
|
) {
|
|
malunal_size_t stride = 0;
|
|
vector_container_stride(vector, &stride);
|
|
return stride != 0
|
|
? vector_container_free(vector)
|
|
: NO_ERROR;
|
|
}
|
|
|
|
static
|
|
malunal_size_t
|
|
option_keys(
|
|
argoption_iptr_t option,
|
|
strview_t keys[2]
|
|
) {
|
|
malunal_size_t count = 0;
|
|
if (option->isflag) {
|
|
if (option->short_name != '\0')
|
|
keys[count++] = strview(&option->short_name, 1);
|
|
if (option->long_name != null)
|
|
keys[count++] = strview_from_cstr(option->long_name);
|
|
} else if (option->long_name != null) {
|
|
keys[count++] = strview_from_cstr(option->long_name);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static
|
|
malunal_size_t
|
|
parser_slot_count(
|
|
parser_impl_iptr_t self
|
|
) {
|
|
return
|
|
vector_count(&self->options) +
|
|
vector_count(&self->positionals);
|
|
}
|
|
|
|
static
|
|
argoption_t
|
|
parser_slot(
|
|
parser_impl_iptr_t self,
|
|
malunal_size_t slot
|
|
) {
|
|
argoption_t option = { 0 };
|
|
malunal_size_t count = vector_count(&self->options);
|
|
if (slot < count)
|
|
vector_container_get(&self->options, slot, &option);
|
|
else
|
|
vector_container_get(&self->positionals, slot - count, &option);
|
|
return option;
|
|
}
|
|
|
|
static
|
|
malunal_bool_t
|
|
strview_iequals_cstr(
|
|
strview_iptr_t view,
|
|
malunal_cstr_t text
|
|
) {
|
|
malunal_size_t length = strlen(text);
|
|
if (strview_length(view) != length)
|
|
return false;
|
|
|
|
malunal_size_t index;
|
|
for (index = 0; index < length; index++)
|
|
if (tolower(view->beg[index]) != text[index])
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static
|
|
error_t
|
|
argitem_convert(
|
|
strview_iptr_t token,
|
|
argoption_type_t type,
|
|
argitem_t* out
|
|
) {
|
|
switch (type) {
|
|
case ARGOPTION_TYPE_STRING:
|
|
out->string = *token;
|
|
return NO_ERROR;
|
|
|
|
case ARGOPTION_TYPE_NUMBER:
|
|
return strview_to_int64(token, &out->number).domain != null
|
|
? argparse_error(ARGPARSE_ERROR_INVALID_VALUE)
|
|
: NO_ERROR;
|
|
|
|
case ARGOPTION_TYPE_FLOAT:
|
|
return strview_to_double(token, &out->floating).domain != null
|
|
? argparse_error(ARGPARSE_ERROR_INVALID_VALUE)
|
|
: NO_ERROR;
|
|
|
|
case ARGOPTION_TYPE_BOOLEAN:
|
|
if (
|
|
strview_iequals_cstr(token, "true") ||
|
|
strview_iequals_cstr(token, "yes") ||
|
|
strview_iequals_cstr(token, "on") ||
|
|
strview_iequals_cstr(token, "1")
|
|
) {
|
|
out->boolean = true;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
if (
|
|
strview_iequals_cstr(token, "false") ||
|
|
strview_iequals_cstr(token, "no") ||
|
|
strview_iequals_cstr(token, "off") ||
|
|
strview_iequals_cstr(token, "0")
|
|
) {
|
|
out->boolean = false;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_VALUE);
|
|
|
|
case ARGOPTION_TYPE_FLAG:
|
|
break;
|
|
}
|
|
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_VALUE);
|
|
}
|
|
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Parser construction.
|
|
* ---------------------------------------------------------------------- */
|
|
|
|
static
|
|
malunal_bool_t
|
|
parser_name_taken(
|
|
parser_impl_iptr_t self,
|
|
argoption_iptr_t option
|
|
) {
|
|
strview_t keys[2];
|
|
malunal_size_t nkeys = option_keys(option, keys);
|
|
malunal_size_t slots = parser_slot_count(self);
|
|
|
|
malunal_size_t slot;
|
|
for (slot = 0; slot < slots; slot++) {
|
|
argoption_t existing = parser_slot(self, slot);
|
|
strview_t others[2];
|
|
malunal_size_t nothers = option_keys(&existing, others);
|
|
|
|
malunal_size_t key, other;
|
|
for (key = 0; key < nkeys; key++)
|
|
for (other = 0; other < nothers; other++)
|
|
if (strview_equals(&keys[key], &others[other]))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static
|
|
error_t
|
|
parser_validate_option(
|
|
parser_impl_iptr_t self,
|
|
argoption_iptr_t option
|
|
) {
|
|
argoption_type_t type = option->value_type;
|
|
if (type > ARGOPTION_TYPE_BOOLEAN)
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_DEFINITION);
|
|
|
|
if (option->isflag) {
|
|
malunal_char_t short_name = option->short_name;
|
|
malunal_cstr_t long_name = option->long_name;
|
|
if (short_name == '\0' && long_name == null)
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_DEFINITION);
|
|
if (short_name == '-' || short_name == '=' || isspace((unsigned char)short_name))
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_DEFINITION);
|
|
if (long_name != null && (long_name[0] == '\0' || long_name[0] == '-' || strchr(long_name, '=') != null))
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_DEFINITION);
|
|
if (type == ARGOPTION_TYPE_FLAG && (option->required || option->default_value != null))
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_DEFINITION);
|
|
} else {
|
|
malunal_cstr_t value_name = option->long_name;
|
|
if (value_name == null || value_name[0] == '\0' || value_name[0] == '-')
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_DEFINITION);
|
|
if (type == ARGOPTION_TYPE_FLAG)
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_DEFINITION);
|
|
if (vector_count(&self->subcommands) > 0)
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_DEFINITION);
|
|
|
|
malunal_size_t npositionals = vector_count(&self->positionals);
|
|
if (npositionals > 0) {
|
|
argoption_t last;
|
|
vector_container_get(&self->positionals, npositionals - 1, &last);
|
|
if (last.multiple)
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_DEFINITION);
|
|
if (option->required && !last.required)
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_DEFINITION);
|
|
}
|
|
}
|
|
|
|
if (parser_name_taken(self, option))
|
|
return argparse_error(ARGPARSE_ERROR_DUPLICATE_DEFINITION);
|
|
|
|
malunal_cstr_t fallback = option->default_value;
|
|
if (fallback != null) {
|
|
argitem_t item = { 0 };
|
|
strview_t token = strview_from_cstr(fallback);
|
|
if (argitem_convert(&token, type, &item).domain != null)
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_DEFINITION);
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
error_t
|
|
argparser_init(
|
|
malunal_cstr_t name,
|
|
malunal_cstr_t desc,
|
|
allocator_mptr_t allocator,
|
|
argparser_mptr_t parser
|
|
) {
|
|
if (parser == null || name == null)
|
|
return argparse_error(ARGPARSE_ERROR_NULL_INPUT);
|
|
|
|
if (allocator == null)
|
|
allocator = libc_allocator();
|
|
|
|
parser_impl_mptr_t self = (parser_impl_mptr_t)parser;
|
|
memset(self, 0, sizeof(parser_impl_t));
|
|
self->name = name;
|
|
self->desc = desc;
|
|
self->allocator = allocator;
|
|
|
|
error_t result = vector_container_init(sizeof(argoption_t), allocator, &self->options);
|
|
if (result.domain == null)
|
|
result = vector_container_init(sizeof(argoption_t), allocator, &self->positionals);
|
|
if (result.domain == null)
|
|
result = vector_container_init(sizeof(argparser_mptr_t), allocator, &self->subcommands);
|
|
if (result.domain == null)
|
|
result = argparser_add_option(parser, &ARGOPTION_FLAG(
|
|
.short_name = 'h',
|
|
.long_name = "help",
|
|
.help_string = "show this help message and exit"
|
|
));
|
|
|
|
if (result.domain != null)
|
|
argparser_free(parser);
|
|
return result;
|
|
}
|
|
|
|
error_t
|
|
argparser_free(
|
|
argparser_mptr_t parser
|
|
) {
|
|
if (parser == null)
|
|
return argparse_error(ARGPARSE_ERROR_NULL_INPUT);
|
|
|
|
parser_impl_mptr_t self = (parser_impl_mptr_t)parser;
|
|
error_t result = NO_ERROR;
|
|
malunal_size_t count = vector_count(&self->subcommands);
|
|
|
|
malunal_size_t index;
|
|
for (index = 0; index < count; index++) {
|
|
argparser_mptr_t child = null;
|
|
vector_container_get(&self->subcommands, index, &child);
|
|
argparser_free(child);
|
|
|
|
error_t disposed = allocator_dispose(self->allocator, child, sizeof(argparser_t));
|
|
if (result.domain == null)
|
|
result = disposed;
|
|
}
|
|
|
|
error_t released[3] = {
|
|
vector_release(&self->subcommands),
|
|
vector_release(&self->positionals),
|
|
vector_release(&self->options)
|
|
};
|
|
|
|
for (index = 0; index < 3; index++)
|
|
if (result.domain == null)
|
|
result = released[index];
|
|
|
|
memset(self, 0, sizeof(parser_impl_t));
|
|
return result;
|
|
}
|
|
|
|
error_t
|
|
argparser_add_option(
|
|
argparser_mptr_t parser,
|
|
argoption_iptr_t option
|
|
) {
|
|
if (parser == null || option == null)
|
|
return argparse_error(ARGPARSE_ERROR_NULL_INPUT);
|
|
|
|
parser_impl_mptr_t self = (parser_impl_mptr_t)parser;
|
|
error_t result = parser_validate_option(self, option);
|
|
if (result.domain != null)
|
|
return result;
|
|
|
|
return vector_container_append(
|
|
option->isflag ? &self->options : &self->positionals,
|
|
option
|
|
);
|
|
}
|
|
|
|
error_t
|
|
argparser_add_subcommand(
|
|
argparser_mptr_t parser,
|
|
malunal_cstr_t name,
|
|
malunal_cstr_t desc,
|
|
argparser_mptr_t* out
|
|
) {
|
|
if (parser == null || name == null || out == null)
|
|
return argparse_error(ARGPARSE_ERROR_NULL_INPUT);
|
|
|
|
parser_impl_mptr_t self = (parser_impl_mptr_t)parser;
|
|
if (name[0] == '\0' || name[0] == '-')
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_DEFINITION);
|
|
if (vector_count(&self->positionals) > 0)
|
|
return argparse_error(ARGPARSE_ERROR_INVALID_DEFINITION);
|
|
|
|
malunal_size_t count = vector_count(&self->subcommands);
|
|
malunal_size_t index;
|
|
for (index = 0; index < count; index++) {
|
|
argparser_mptr_t sibling = null;
|
|
vector_container_get(&self->subcommands, index, &sibling);
|
|
if (strcmp(((parser_impl_iptr_t)sibling)->name, name) == 0)
|
|
return argparse_error(ARGPARSE_ERROR_DUPLICATE_DEFINITION);
|
|
}
|
|
|
|
malunal_mptr_t memory = null;
|
|
error_t result = allocator_acquire(self->allocator, sizeof(argparser_t), &memory);
|
|
if (result.domain != null)
|
|
return result;
|
|
|
|
argparser_mptr_t child = (argparser_mptr_t)memory;
|
|
result = argparser_init(name, desc, self->allocator, child);
|
|
if (result.domain != null) {
|
|
allocator_dispose(self->allocator, memory, sizeof(argparser_t));
|
|
return result;
|
|
}
|
|
|
|
((parser_impl_mptr_t)child)->parent = parser;
|
|
result = vector_container_append(&self->subcommands, &child);
|
|
if (result.domain != null) {
|
|
argparser_free(child);
|
|
allocator_dispose(self->allocator, memory, sizeof(argparser_t));
|
|
return result;
|
|
}
|
|
|
|
*out = child;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Result storage.
|
|
* ---------------------------------------------------------------------- */
|
|
|
|
static
|
|
error_t
|
|
result_init(
|
|
parser_impl_iptr_t parser,
|
|
result_impl_mptr_t self
|
|
) {
|
|
self->parser = (argparser_iptr_t)parser;
|
|
self->allocator = parser->allocator;
|
|
|
|
error_t result = vector_container_init(sizeof(malunal_size_t), parser->allocator, &self->counts);
|
|
if (result.domain == null)
|
|
result = vector_container_init(sizeof(argentry_t), parser->allocator, &self->entries);
|
|
if (result.domain == null)
|
|
result = vector_container_resize(&self->counts, parser_slot_count(parser));
|
|
|
|
if (result.domain != null) {
|
|
argresult_free((argresult_mptr_t)self);
|
|
return result;
|
|
}
|
|
|
|
malunal_size_t zero = 0;
|
|
malunal_size_t slots = parser_slot_count(parser);
|
|
malunal_size_t slot;
|
|
for (slot = 0; slot < slots; slot++)
|
|
vector_container_set(&self->counts, slot, &zero);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static
|
|
error_t
|
|
result_fail(
|
|
result_impl_mptr_t self,
|
|
parser_impl_iptr_t parser,
|
|
argparse_error_t code,
|
|
malunal_size_t slot,
|
|
strview_t arg
|
|
) {
|
|
self->error_parser = (argparser_iptr_t)parser;
|
|
self->error_slot = slot;
|
|
self->error_arg = arg;
|
|
return argparse_error(code);
|
|
}
|
|
|
|
static
|
|
malunal_size_t
|
|
result_slot_count(
|
|
result_impl_iptr_t self,
|
|
malunal_size_t slot
|
|
) {
|
|
malunal_size_t count = 0;
|
|
vector_container_get(&self->counts, slot, &count);
|
|
return count;
|
|
}
|
|
|
|
static
|
|
malunal_size_t
|
|
result_find_entry(
|
|
result_impl_iptr_t self,
|
|
malunal_size_t slot,
|
|
malunal_size_t nth,
|
|
malunal_size_t* total
|
|
) {
|
|
malunal_size_t found = 0;
|
|
malunal_size_t match = ARGPARSE_NPOS;
|
|
malunal_size_t count = vector_count(&self->entries);
|
|
|
|
malunal_size_t index;
|
|
for (index = 0; index < count; index++) {
|
|
argentry_t entry;
|
|
vector_container_get(&self->entries, index, &entry);
|
|
if (entry.slot != slot)
|
|
continue;
|
|
|
|
if (found == nth)
|
|
match = index;
|
|
found++;
|
|
|
|
if (total == null && match != ARGPARSE_NPOS)
|
|
break;
|
|
}
|
|
|
|
if (total != null)
|
|
*total = found;
|
|
return match;
|
|
}
|
|
|
|
static
|
|
error_t
|
|
result_store(
|
|
result_impl_mptr_t self,
|
|
malunal_size_t slot,
|
|
malunal_bool_t multiple,
|
|
argitem_t item
|
|
) {
|
|
argentry_t entry = { .slot = slot, .item = item };
|
|
if (!multiple) {
|
|
malunal_size_t index = result_find_entry(self, slot, 0, null);
|
|
if (index != ARGPARSE_NPOS)
|
|
return vector_container_set(&self->entries, index, &entry);
|
|
}
|
|
|
|
return vector_container_append(&self->entries, &entry);
|
|
}
|
|
|
|
static
|
|
malunal_void_t
|
|
result_bump(
|
|
result_impl_mptr_t self,
|
|
malunal_size_t slot
|
|
) {
|
|
malunal_size_t count = result_slot_count(self, slot) + 1;
|
|
vector_container_set(&self->counts, slot, &count);
|
|
}
|
|
|
|
static
|
|
error_t
|
|
result_store_token(
|
|
result_impl_mptr_t self,
|
|
parser_impl_iptr_t parser,
|
|
malunal_size_t slot,
|
|
argoption_iptr_t option,
|
|
strview_t token
|
|
) {
|
|
argitem_t item = { 0 };
|
|
error_t result = argitem_convert(&token, option->value_type, &item);
|
|
if (result.domain != null)
|
|
return result_fail(self, parser, ARGPARSE_ERROR_INVALID_VALUE, slot, token);
|
|
|
|
result = result_store(self, slot, option->multiple, item);
|
|
if (result.domain != null)
|
|
return result;
|
|
|
|
result_bump(self, slot);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Parsing.
|
|
* ---------------------------------------------------------------------- */
|
|
|
|
static
|
|
malunal_bool_t
|
|
token_is_negative_number(
|
|
strview_iptr_t token
|
|
) {
|
|
malunal_size_t length = strview_length(token);
|
|
if (length < 2 || token->beg[0] != '-')
|
|
return false;
|
|
|
|
malunal_bool_t digits = false;
|
|
malunal_bool_t dotted = false;
|
|
malunal_size_t index;
|
|
for (index = 1; index < length; index++) {
|
|
malunal_char_t current = token->beg[index];
|
|
if (isdigit((unsigned char)current)) {
|
|
digits = true;
|
|
} else if (current == '.' && !dotted) {
|
|
dotted = true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return digits && token->beg[length - 1] != '.';
|
|
}
|
|
|
|
static
|
|
malunal_bool_t
|
|
parser_has_numeric_option(
|
|
parser_impl_iptr_t self
|
|
) {
|
|
malunal_size_t count = vector_count(&self->options);
|
|
malunal_size_t index;
|
|
for (index = 0; index < count; index++) {
|
|
argoption_t option;
|
|
vector_container_get(&self->options, index, &option);
|
|
if (isdigit(option.short_name))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static
|
|
malunal_bool_t
|
|
parser_is_option_token(
|
|
parser_impl_iptr_t self,
|
|
strview_iptr_t token
|
|
) {
|
|
if (strview_length(token) < 2 || token->beg[0] != '-')
|
|
return false;
|
|
|
|
return
|
|
!token_is_negative_number(token) ||
|
|
parser_has_numeric_option(self);
|
|
}
|
|
|
|
static
|
|
malunal_bool_t
|
|
parser_can_take_value(
|
|
parser_impl_iptr_t self,
|
|
malunal_int32_t argc,
|
|
malunal_cstr_t* argv,
|
|
malunal_int32_t index
|
|
) {
|
|
if (index >= argc)
|
|
return false;
|
|
|
|
strview_t token = strview_from_cstr(argv[index]);
|
|
return !parser_is_option_token(self, &token);
|
|
}
|
|
|
|
static
|
|
malunal_size_t
|
|
parser_find_short(
|
|
parser_impl_iptr_t self,
|
|
malunal_char_t name
|
|
) {
|
|
malunal_size_t count = vector_count(&self->options);
|
|
malunal_size_t index;
|
|
for (index = 0; index < count; index++) {
|
|
argoption_t option;
|
|
vector_container_get(&self->options, index, &option);
|
|
if (option.short_name == name)
|
|
return index;
|
|
}
|
|
|
|
return ARGPARSE_NPOS;
|
|
}
|
|
|
|
static
|
|
error_t
|
|
parser_find_long(
|
|
parser_impl_iptr_t self,
|
|
strview_iptr_t name,
|
|
malunal_size_t* out
|
|
) {
|
|
malunal_size_t count = vector_count(&self->options);
|
|
malunal_size_t matches = 0;
|
|
malunal_size_t index;
|
|
|
|
*out = ARGPARSE_NPOS;
|
|
if (strview_length(name) == 0)
|
|
return argparse_error(ARGPARSE_ERROR_UNKNOWN_OPTION);
|
|
|
|
for (index = 0; index < count; index++) {
|
|
argoption_t option;
|
|
vector_container_get(&self->options, index, &option);
|
|
if (option.long_name == null)
|
|
continue;
|
|
|
|
strview_t candidate = strview_from_cstr(option.long_name);
|
|
if (strview_equals(&candidate, name)) {
|
|
*out = index;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
if (strview_starts_with(&candidate, name)) {
|
|
if (matches == 0)
|
|
*out = index;
|
|
matches++;
|
|
}
|
|
}
|
|
|
|
if (matches == 1)
|
|
return NO_ERROR;
|
|
|
|
*out = ARGPARSE_NPOS;
|
|
return argparse_error(matches > 1
|
|
? ARGPARSE_ERROR_AMBIGUOUS_OPTION
|
|
: ARGPARSE_ERROR_UNKNOWN_OPTION);
|
|
}
|
|
|
|
static
|
|
error_t
|
|
parser_parse_long(
|
|
parser_impl_iptr_t self,
|
|
result_impl_mptr_t out,
|
|
malunal_int32_t argc,
|
|
malunal_cstr_t* argv,
|
|
malunal_int32_t* index
|
|
) {
|
|
strview_t token = strview_from_cstr(argv[*index]);
|
|
strview_t name = token;
|
|
strview_t value = strview(null, 0);
|
|
malunal_bool_t has_inline = false;
|
|
strview_ltrim(&name, 2);
|
|
|
|
malunal_cstr_t equals = memchr(name.beg, '=', strview_length(&name));
|
|
if (equals != null) {
|
|
value = (strview_t){ equals + 1, name.end };
|
|
name.end = equals;
|
|
has_inline = true;
|
|
}
|
|
|
|
malunal_size_t slot = ARGPARSE_NPOS;
|
|
error_t lookup = parser_find_long(self, &name, &slot);
|
|
if (lookup.domain != null)
|
|
return result_fail(out, self, lookup.code, ARGPARSE_NPOS, (strview_t){ token.beg, name.end });
|
|
if (slot == ARGPARSE_HELP_SLOT)
|
|
return result_fail(out, self, ARGPARSE_ERROR_HELP_REQUESTED, ARGPARSE_NPOS, token);
|
|
|
|
argoption_t option;
|
|
vector_container_get(&self->options, slot, &option);
|
|
*index += 1;
|
|
|
|
if (option.value_type == ARGOPTION_TYPE_FLAG) {
|
|
if (has_inline)
|
|
return result_fail(out, self, ARGPARSE_ERROR_UNEXPECTED_VALUE, slot, value);
|
|
result_bump(out, slot);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
if (!has_inline) {
|
|
if (!parser_can_take_value(self, argc, argv, *index))
|
|
return result_fail(out, self, ARGPARSE_ERROR_MISSING_VALUE, slot, strview(null, 0));
|
|
value = strview_from_cstr(argv[*index]);
|
|
*index += 1;
|
|
}
|
|
|
|
return result_store_token(out, self, slot, &option, value);
|
|
}
|
|
|
|
static
|
|
error_t
|
|
parser_parse_short(
|
|
parser_impl_iptr_t self,
|
|
result_impl_mptr_t out,
|
|
malunal_int32_t argc,
|
|
malunal_cstr_t* argv,
|
|
malunal_int32_t* index
|
|
) {
|
|
strview_t token = strview_from_cstr(argv[*index]);
|
|
malunal_cstr_t cursor = token.beg + 1;
|
|
*index += 1;
|
|
|
|
while (cursor < token.end) {
|
|
malunal_size_t slot = parser_find_short(self, *cursor);
|
|
if (slot == ARGPARSE_NPOS)
|
|
return result_fail(out, self, ARGPARSE_ERROR_UNKNOWN_OPTION, ARGPARSE_NPOS, token);
|
|
if (slot == ARGPARSE_HELP_SLOT)
|
|
return result_fail(out, self, ARGPARSE_ERROR_HELP_REQUESTED, ARGPARSE_NPOS, token);
|
|
|
|
argoption_t option;
|
|
vector_container_get(&self->options, slot, &option);
|
|
cursor++;
|
|
|
|
if (option.value_type == ARGOPTION_TYPE_FLAG) {
|
|
result_bump(out, slot);
|
|
continue;
|
|
}
|
|
|
|
/* A value-bearing option consumes the rest of the token, or the next. */
|
|
strview_t value;
|
|
if (cursor < token.end) {
|
|
if (*cursor == '=')
|
|
cursor++;
|
|
value = (strview_t){ cursor, token.end };
|
|
} else {
|
|
if (!parser_can_take_value(self, argc, argv, *index))
|
|
return result_fail(out, self, ARGPARSE_ERROR_MISSING_VALUE, slot, strview(null, 0));
|
|
value = strview_from_cstr(argv[*index]);
|
|
*index += 1;
|
|
}
|
|
|
|
return result_store_token(out, self, slot, &option, value);
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static
|
|
error_t
|
|
parser_parse_subcommand(
|
|
parser_impl_iptr_t self,
|
|
result_impl_mptr_t out,
|
|
malunal_int32_t argc,
|
|
malunal_cstr_t* argv,
|
|
malunal_int32_t index
|
|
) {
|
|
strview_t token = strview_from_cstr(argv[index]);
|
|
argparser_mptr_t child = null;
|
|
malunal_size_t count = vector_count(&self->subcommands);
|
|
|
|
malunal_size_t current;
|
|
for (current = 0; current < count; current++) {
|
|
argparser_mptr_t candidate = null;
|
|
vector_container_get(&self->subcommands, current, &candidate);
|
|
|
|
strview_t name = strview_from_cstr(((parser_impl_iptr_t)candidate)->name);
|
|
if (strview_equals(&name, &token)) {
|
|
child = candidate;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (child == null)
|
|
return result_fail(out, self, ARGPARSE_ERROR_UNKNOWN_SUBCOMMAND, ARGPARSE_NPOS, token);
|
|
|
|
malunal_mptr_t memory = null;
|
|
error_t result = allocator_acquire(self->allocator, sizeof(argresult_t), &memory);
|
|
if (result.domain != null)
|
|
return result;
|
|
|
|
out->subresult = (argresult_mptr_t)memory;
|
|
result = argparser_parse(child, argc - index, argv + index, out->subresult);
|
|
if (result.domain != null) {
|
|
result_impl_iptr_t sub = (result_impl_iptr_t)out->subresult;
|
|
out->error_parser = sub->error_parser;
|
|
out->error_slot = sub->error_slot;
|
|
out->error_arg = sub->error_arg;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static
|
|
error_t
|
|
parser_finalize(
|
|
parser_impl_iptr_t self,
|
|
result_impl_mptr_t out
|
|
) {
|
|
malunal_size_t noptions = vector_count(&self->options);
|
|
malunal_size_t slots = parser_slot_count(self);
|
|
|
|
malunal_size_t slot;
|
|
for (slot = 0; slot < slots; slot++) {
|
|
if (result_slot_count(out, slot) > 0)
|
|
continue;
|
|
|
|
argoption_t option = parser_slot(self, slot);
|
|
if (option.required)
|
|
return result_fail(
|
|
out,
|
|
self,
|
|
slot < noptions
|
|
? ARGPARSE_ERROR_MISSING_REQUIRED_OPTION
|
|
: ARGPARSE_ERROR_MISSING_REQUIRED_POSITIONAL,
|
|
slot,
|
|
strview(null, 0)
|
|
);
|
|
|
|
malunal_cstr_t fallback = option.default_value;
|
|
if (fallback == null)
|
|
continue;
|
|
|
|
/* Defaults were validated when added, so conversion cannot fail. */
|
|
argitem_t item = { 0 };
|
|
strview_t token = strview_from_cstr(fallback);
|
|
argitem_convert(&token, option.value_type, &item);
|
|
|
|
error_t result = result_store(out, slot, false, item);
|
|
if (result.domain != null)
|
|
return result;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
error_t
|
|
argparser_parse(
|
|
argparser_iptr_t parser,
|
|
malunal_int32_t argc,
|
|
malunal_cstr_t* argv,
|
|
argresult_mptr_t result
|
|
) {
|
|
if (result == null)
|
|
return argparse_error(ARGPARSE_ERROR_NULL_INPUT);
|
|
|
|
result_impl_mptr_t out = (result_impl_mptr_t)result;
|
|
memset(out, 0, sizeof(result_impl_t));
|
|
out->error_slot = ARGPARSE_NPOS;
|
|
|
|
if (parser == null || argc < 0 || (argv == null && argc > 0))
|
|
return argparse_error(ARGPARSE_ERROR_NULL_INPUT);
|
|
|
|
parser_impl_iptr_t self = (parser_impl_iptr_t)parser;
|
|
error_t error = result_init(self, out);
|
|
if (error.domain != null)
|
|
return error;
|
|
|
|
malunal_bool_t options_ended = false;
|
|
malunal_bool_t dispatches = vector_count(&self->subcommands) > 0;
|
|
malunal_size_t noptions = vector_count(&self->options);
|
|
malunal_size_t npositionals = vector_count(&self->positionals);
|
|
malunal_size_t positional = 0;
|
|
malunal_int32_t index = 1;
|
|
|
|
while (index < argc) {
|
|
strview_t token = strview_from_cstr(argv[index]);
|
|
|
|
if (!options_ended && parser_is_option_token(self, &token)) {
|
|
if (strview_length(&token) == 2 && token.beg[1] == '-') {
|
|
options_ended = true;
|
|
index++;
|
|
continue;
|
|
}
|
|
|
|
error = token.beg[1] == '-'
|
|
? parser_parse_long(self, out, argc, argv, &index)
|
|
: parser_parse_short(self, out, argc, argv, &index);
|
|
if (error.domain != null)
|
|
return error;
|
|
continue;
|
|
}
|
|
|
|
if (dispatches) {
|
|
error = parser_parse_subcommand(self, out, argc, argv, index);
|
|
if (error.domain != null)
|
|
return error;
|
|
break;
|
|
}
|
|
|
|
if (positional >= npositionals)
|
|
return result_fail(out, self, ARGPARSE_ERROR_TOO_MANY_POSITIONALS, ARGPARSE_NPOS, token);
|
|
|
|
argoption_t option;
|
|
vector_container_get(&self->positionals, positional, &option);
|
|
error = result_store_token(out, self, noptions + positional, &option, token);
|
|
if (error.domain != null)
|
|
return error;
|
|
|
|
if (!option.multiple)
|
|
positional++;
|
|
index++;
|
|
}
|
|
|
|
return parser_finalize(self, out);
|
|
}
|
|
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Help and usage output.
|
|
* ---------------------------------------------------------------------- */
|
|
|
|
static
|
|
malunal_int32_t
|
|
print_prog(
|
|
FILE* stream,
|
|
parser_impl_iptr_t self
|
|
) {
|
|
malunal_int32_t written = 0;
|
|
if (self->parent != null)
|
|
written += print_prog(stream, (parser_impl_iptr_t)self->parent) + fprintf(stream, " ");
|
|
return written + fprintf(stream, "%s", self->name);
|
|
}
|
|
|
|
static
|
|
malunal_int32_t
|
|
print_metavar(
|
|
FILE* stream,
|
|
argoption_iptr_t option
|
|
) {
|
|
if (!option->isflag)
|
|
return fprintf(stream, "%s",
|
|
option->meta_name != null
|
|
? option->meta_name
|
|
: option->long_name);
|
|
|
|
if (option->meta_name != null)
|
|
return fprintf(stream, "%s", option->meta_name);
|
|
|
|
if (option->long_name == null)
|
|
return fprintf(stream, "%c", toupper((unsigned char)option->short_name));
|
|
|
|
malunal_cstr_t cursor = option->long_name;
|
|
for (; *cursor != '\0'; cursor++)
|
|
fputc(*cursor == '-' ? '_' : toupper((unsigned char)*cursor), stream);
|
|
return (malunal_int32_t)(cursor - option->long_name);
|
|
}
|
|
|
|
|
|
static
|
|
malunal_void_t
|
|
print_names(
|
|
FILE* stream,
|
|
argoption_iptr_t option
|
|
) {
|
|
if (!option->isflag) {
|
|
print_metavar(stream, option);
|
|
return;
|
|
}
|
|
|
|
if (option->short_name != '\0')
|
|
fprintf(stream, "-%c", option->short_name);
|
|
if (option->short_name != '\0' && option->long_name != null)
|
|
fputc('/', stream);
|
|
if (option->long_name != null)
|
|
fprintf(stream, "--%s", option->long_name);
|
|
}
|
|
|
|
static
|
|
malunal_void_t
|
|
print_option_usage(
|
|
FILE* stream,
|
|
argoption_iptr_t option
|
|
) {
|
|
if (!option->isflag) {
|
|
malunal_bool_t required = option->required;
|
|
if (option->multiple) {
|
|
if (required) {
|
|
print_metavar(stream, option);
|
|
fputc(' ', stream);
|
|
}
|
|
fputc('[', stream);
|
|
print_metavar(stream, option);
|
|
fputs(" ...]", stream);
|
|
} else {
|
|
if (!required) fputc('[', stream);
|
|
print_metavar(stream, option);
|
|
if (!required) fputc(']', stream);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!option->required)
|
|
fputc('[', stream);
|
|
|
|
if (option->short_name != '\0')
|
|
fprintf(stream, "-%c", option->short_name);
|
|
else
|
|
fprintf(stream, "--%s", option->long_name);
|
|
|
|
if (option->value_type != ARGOPTION_TYPE_FLAG) {
|
|
fputc(' ', stream);
|
|
print_metavar(stream, option);
|
|
}
|
|
|
|
if (!option->required)
|
|
fputc(']', stream);
|
|
}
|
|
|
|
static
|
|
malunal_void_t
|
|
print_subcommand_choices(
|
|
FILE* stream,
|
|
parser_impl_iptr_t self
|
|
) {
|
|
malunal_size_t count = vector_count(&self->subcommands);
|
|
malunal_size_t index;
|
|
|
|
fputc('{', stream);
|
|
for (index = 0; index < count; index++) {
|
|
argparser_mptr_t child = null;
|
|
vector_container_get(&self->subcommands, index, &child);
|
|
fprintf(stream, index > 0 ? ",%s" : "%s", ((parser_impl_iptr_t)child)->name);
|
|
}
|
|
fputc('}', stream);
|
|
}
|
|
|
|
static
|
|
malunal_void_t
|
|
print_help_text(
|
|
FILE* stream,
|
|
malunal_int32_t column,
|
|
malunal_cstr_t help
|
|
) {
|
|
if (help == null) {
|
|
fputc('\n', stream);
|
|
return;
|
|
}
|
|
|
|
if (column + 2 <= ARGPARSE_HELP_COLUMN)
|
|
fprintf(stream, "%*s%s\n", ARGPARSE_HELP_COLUMN - column, "", help);
|
|
else
|
|
fprintf(stream, "\n%*s%s\n", ARGPARSE_HELP_COLUMN, "", help);
|
|
}
|
|
|
|
error_t
|
|
argparser_print_usage(
|
|
argparser_iptr_t parser
|
|
) {
|
|
if (parser == null)
|
|
return argparse_error(ARGPARSE_ERROR_NULL_INPUT);
|
|
|
|
parser_impl_iptr_t self = (parser_impl_iptr_t)parser;
|
|
malunal_size_t slots = parser_slot_count(self);
|
|
|
|
fputs("usage: ", stdout);
|
|
print_prog(stdout, self);
|
|
|
|
malunal_size_t slot;
|
|
for (slot = 0; slot < slots; slot++) {
|
|
argoption_t option = parser_slot(self, slot);
|
|
fputc(' ', stdout);
|
|
print_option_usage(stdout, &option);
|
|
}
|
|
|
|
if (vector_count(&self->subcommands) > 0) {
|
|
fputc(' ', stdout);
|
|
print_subcommand_choices(stdout, self);
|
|
fputs(" ...", stdout);
|
|
}
|
|
|
|
fputc('\n', stdout);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
error_t
|
|
argparser_print_help(
|
|
argparser_iptr_t parser
|
|
) {
|
|
error_t result = argparser_print_usage(parser);
|
|
if (result.domain != null)
|
|
return result;
|
|
|
|
parser_impl_iptr_t self = (parser_impl_iptr_t)parser;
|
|
malunal_size_t noptions = vector_count(&self->options);
|
|
malunal_size_t npositionals = vector_count(&self->positionals);
|
|
malunal_size_t nsubcommands = vector_count(&self->subcommands);
|
|
malunal_size_t index;
|
|
|
|
if (self->desc != null)
|
|
fprintf(stdout, "\n%s\n", self->desc);
|
|
|
|
if (npositionals > 0 || nsubcommands > 0)
|
|
fputs("\npositional arguments:\n", stdout);
|
|
|
|
for (index = 0; index < npositionals; index++) {
|
|
argoption_t option;
|
|
vector_container_get(&self->positionals, index, &option);
|
|
malunal_int32_t column = fprintf(stdout, " ") + print_metavar(stdout, &option);
|
|
print_help_text(stdout, column, option.help_string);
|
|
}
|
|
|
|
if (nsubcommands > 0) {
|
|
fputs(" ", stdout);
|
|
print_subcommand_choices(stdout, self);
|
|
fputc('\n', stdout);
|
|
}
|
|
|
|
for (index = 0; index < nsubcommands; index++) {
|
|
argparser_mptr_t child = null;
|
|
vector_container_get(&self->subcommands, index, &child);
|
|
|
|
parser_impl_iptr_t sub = (parser_impl_iptr_t)child;
|
|
malunal_int32_t column = fprintf(stdout, " %s", sub->name);
|
|
print_help_text(stdout, column, sub->desc);
|
|
}
|
|
|
|
fputs("\noptions:\n", stdout);
|
|
for (index = 0; index < noptions; index++) {
|
|
argoption_t option;
|
|
vector_container_get(&self->options, index, &option);
|
|
|
|
malunal_int32_t column = fprintf(stdout, " ");
|
|
if (option.short_name != '\0')
|
|
column += fprintf(stdout, "-%c", option.short_name);
|
|
if (option.short_name != '\0' && option.long_name != null)
|
|
column += fprintf(stdout, ", ");
|
|
if (option.long_name != null)
|
|
column += fprintf(stdout, "--%s", option.long_name);
|
|
if (option.value_type != ARGOPTION_TYPE_FLAG)
|
|
column += fprintf(stdout, " ") + print_metavar(stdout, &option);
|
|
|
|
print_help_text(stdout, column, option.help_string);
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Reading results back out.
|
|
* ---------------------------------------------------------------------- */
|
|
|
|
error_t
|
|
argresult_free(
|
|
argresult_mptr_t result
|
|
) {
|
|
if (result == null)
|
|
return argparse_error(ARGPARSE_ERROR_NULL_INPUT);
|
|
|
|
result_impl_mptr_t self = (result_impl_mptr_t)result;
|
|
error_t status = NO_ERROR;
|
|
if (self->subresult != null) {
|
|
argresult_free(self->subresult);
|
|
status = allocator_dispose(self->allocator, self->subresult, sizeof(argresult_t));
|
|
}
|
|
|
|
error_t entries = vector_release(&self->entries);
|
|
error_t counts = vector_release(&self->counts);
|
|
if (status.domain == null)
|
|
status = entries;
|
|
if (status.domain == null)
|
|
status = counts;
|
|
|
|
memset(self, 0, sizeof(result_impl_t));
|
|
self->error_slot = ARGPARSE_NPOS;
|
|
return status;
|
|
}
|
|
|
|
error_t
|
|
argresult_print_error(
|
|
argresult_iptr_t result,
|
|
error_t error
|
|
) {
|
|
if (result == null)
|
|
return argparse_error(ARGPARSE_ERROR_NULL_INPUT);
|
|
if (error.domain == null)
|
|
return NO_ERROR;
|
|
|
|
result_impl_iptr_t self = (result_impl_iptr_t)result;
|
|
argparser_iptr_t parser = self->error_parser != null
|
|
? self->error_parser
|
|
: self->parser;
|
|
|
|
malunal_bool_t ours = error.domain == &ERROR_DOMAIN_ARGPARSE_T;
|
|
if (ours && error.code == ARGPARSE_ERROR_HELP_REQUESTED && parser != null)
|
|
return argparser_print_help(parser);
|
|
|
|
malunal_cstr_t message = error.domain->describe != null
|
|
? error.domain->describe(error.code)
|
|
: error.domain->name;
|
|
|
|
if (parser == null) {
|
|
fprintf(stderr, "error: %s\n", message);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
parser_impl_iptr_t impl = (parser_impl_iptr_t)parser;
|
|
argparser_print_usage(parser);
|
|
print_prog(stderr, impl);
|
|
fputs(": error: ", stderr);
|
|
|
|
malunal_bool_t has_slot = ours && self->error_slot != ARGPARSE_NPOS;
|
|
malunal_bool_t missing = ours && (
|
|
error.code == ARGPARSE_ERROR_MISSING_REQUIRED_OPTION ||
|
|
error.code == ARGPARSE_ERROR_MISSING_REQUIRED_POSITIONAL
|
|
);
|
|
|
|
argoption_t option = { 0 };
|
|
if (has_slot)
|
|
option = parser_slot(impl, self->error_slot);
|
|
|
|
if (missing && has_slot) {
|
|
fprintf(stderr, "%s: ", message);
|
|
print_names(stderr, &option);
|
|
} else {
|
|
if (has_slot) {
|
|
fputs("argument ", stderr);
|
|
print_names(stderr, &option);
|
|
fputs(": ", stderr);
|
|
}
|
|
fputs(message, stderr);
|
|
}
|
|
|
|
malunal_size_t length = strview_length(&self->error_arg);
|
|
if (ours && (length > 0 || error.code == ARGPARSE_ERROR_INVALID_VALUE))
|
|
fprintf(stderr, ": '%.*s'", (malunal_int32_t)length, self->error_arg.beg != null ? self->error_arg.beg : "");
|
|
|
|
fputc('\n', stderr);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static
|
|
error_t
|
|
result_lookup(
|
|
result_impl_iptr_t self,
|
|
malunal_cstr_t name,
|
|
malunal_size_t* outslot,
|
|
argoption_t* outoption
|
|
) {
|
|
if (self == null || self->parser == null || name == null)
|
|
return argparse_error(ARGPARSE_ERROR_NULL_INPUT);
|
|
|
|
strview_t key = strview_from_cstr(name);
|
|
malunal_size_t stripped = 0;
|
|
while (stripped < 2 && strview_length(&key) > 1 && key.beg[0] == '-') {
|
|
strview_ltrim(&key, 1);
|
|
stripped++;
|
|
}
|
|
|
|
parser_impl_iptr_t parser = (parser_impl_iptr_t)self->parser;
|
|
malunal_size_t slots = parser_slot_count(parser);
|
|
|
|
malunal_size_t slot;
|
|
for (slot = 0; slot < slots; slot++) {
|
|
argoption_t option = parser_slot(parser, slot);
|
|
strview_t keys[2];
|
|
malunal_size_t nkeys = option_keys(&option, keys);
|
|
|
|
malunal_size_t index;
|
|
for (index = 0; index < nkeys; index++) {
|
|
if (strview_equals(&keys[index], &key)) {
|
|
*outslot = slot;
|
|
*outoption = option;
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
return argparse_error(ARGPARSE_ERROR_UNKNOWN_NAME);
|
|
}
|
|
|
|
static
|
|
error_t
|
|
result_get_item(
|
|
argresult_iptr_t result,
|
|
malunal_cstr_t name,
|
|
argoption_type_t type,
|
|
malunal_size_t index,
|
|
argitem_t* out
|
|
) {
|
|
result_impl_iptr_t self = (result_impl_iptr_t)result;
|
|
malunal_size_t slot = ARGPARSE_NPOS;
|
|
argoption_t option = { 0 };
|
|
error_t error = result_lookup(self, name, &slot, &option);
|
|
if (error.domain != null)
|
|
return error;
|
|
|
|
argoption_type_t actual = option.value_type;
|
|
if (type == ARGOPTION_TYPE_BOOLEAN && actual == ARGOPTION_TYPE_FLAG) {
|
|
if (index != 0)
|
|
return argparse_error(ARGPARSE_ERROR_OUT_OF_RANGE);
|
|
out->boolean = result_slot_count(self, slot) > 0;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
if (actual != type)
|
|
return argparse_error(ARGPARSE_ERROR_TYPE_MISMATCH);
|
|
|
|
malunal_size_t total = 0;
|
|
malunal_size_t found = result_find_entry(self, slot, index, &total);
|
|
if (total == 0)
|
|
return argparse_error(ARGPARSE_ERROR_NOT_PRESENT);
|
|
if (found == ARGPARSE_NPOS)
|
|
return argparse_error(ARGPARSE_ERROR_OUT_OF_RANGE);
|
|
|
|
argentry_t entry;
|
|
vector_container_get(&self->entries, found, &entry);
|
|
*out = entry.item;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
malunal_bool_t
|
|
argresult_has(
|
|
argresult_iptr_t result,
|
|
malunal_cstr_t name
|
|
) {
|
|
result_impl_iptr_t self = (result_impl_iptr_t)result;
|
|
malunal_size_t slot = ARGPARSE_NPOS;
|
|
argoption_t option = { 0 };
|
|
return result_lookup(self, name, &slot, &option).domain == null
|
|
&& result_slot_count(self, slot) > 0;
|
|
}
|
|
|
|
malunal_size_t
|
|
argresult_count(
|
|
argresult_iptr_t result,
|
|
malunal_cstr_t name
|
|
) {
|
|
result_impl_iptr_t self = (result_impl_iptr_t)result;
|
|
malunal_size_t slot = ARGPARSE_NPOS;
|
|
argoption_t option = { 0 };
|
|
if (result_lookup(self, name, &slot, &option).domain != null)
|
|
return 0;
|
|
|
|
if (option.value_type == ARGOPTION_TYPE_FLAG)
|
|
return result_slot_count(self, slot);
|
|
|
|
malunal_size_t total = 0;
|
|
result_find_entry(self, slot, 0, &total);
|
|
return total;
|
|
}
|
|
|
|
error_t
|
|
argresult_get_string(
|
|
argresult_iptr_t result,
|
|
malunal_cstr_t name,
|
|
strview_mptr_t out
|
|
) {
|
|
return argresult_get_string_at(result, name, 0, out);
|
|
}
|
|
|
|
error_t
|
|
argresult_get_number(
|
|
argresult_iptr_t result,
|
|
malunal_cstr_t name,
|
|
malunal_int64_t* out
|
|
) {
|
|
return argresult_get_number_at(result, name, 0, out);
|
|
}
|
|
|
|
error_t
|
|
argresult_get_float(
|
|
argresult_iptr_t result,
|
|
malunal_cstr_t name,
|
|
malunal_double_t* out
|
|
) {
|
|
return argresult_get_float_at(result, name, 0, out);
|
|
}
|
|
|
|
error_t
|
|
argresult_get_boolean(
|
|
argresult_iptr_t result,
|
|
malunal_cstr_t name,
|
|
malunal_bool_t* out
|
|
) {
|
|
return argresult_get_boolean_at(result, name, 0, out);
|
|
}
|
|
|
|
error_t
|
|
argresult_get_string_at(
|
|
argresult_iptr_t result,
|
|
malunal_cstr_t name,
|
|
malunal_size_t index,
|
|
strview_mptr_t out
|
|
) {
|
|
if (out == null)
|
|
return argparse_error(ARGPARSE_ERROR_NULL_INPUT);
|
|
|
|
argitem_t item = { 0 };
|
|
error_t error = result_get_item(result, name, ARGOPTION_TYPE_STRING, index, &item);
|
|
if (error.domain == null)
|
|
*out = item.string;
|
|
return error;
|
|
}
|
|
|
|
error_t
|
|
argresult_get_number_at(
|
|
argresult_iptr_t result,
|
|
malunal_cstr_t name,
|
|
malunal_size_t index,
|
|
malunal_int64_t* out
|
|
) {
|
|
if (out == null)
|
|
return argparse_error(ARGPARSE_ERROR_NULL_INPUT);
|
|
|
|
argitem_t item = { 0 };
|
|
error_t error = result_get_item(result, name, ARGOPTION_TYPE_NUMBER, index, &item);
|
|
if (error.domain == null)
|
|
*out = item.number;
|
|
return error;
|
|
}
|
|
|
|
error_t
|
|
argresult_get_float_at(
|
|
argresult_iptr_t result,
|
|
malunal_cstr_t name,
|
|
malunal_size_t index,
|
|
malunal_double_t* out
|
|
) {
|
|
if (out == null)
|
|
return argparse_error(ARGPARSE_ERROR_NULL_INPUT);
|
|
|
|
argitem_t item = { 0 };
|
|
error_t error = result_get_item(result, name, ARGOPTION_TYPE_FLOAT, index, &item);
|
|
if (error.domain == null)
|
|
*out = item.floating;
|
|
return error;
|
|
}
|
|
|
|
error_t
|
|
argresult_get_boolean_at(
|
|
argresult_iptr_t result,
|
|
malunal_cstr_t name,
|
|
malunal_size_t index,
|
|
malunal_bool_t* out
|
|
) {
|
|
if (out == null)
|
|
return argparse_error(ARGPARSE_ERROR_NULL_INPUT);
|
|
|
|
argitem_t item = { 0 };
|
|
error_t error = result_get_item(result, name, ARGOPTION_TYPE_BOOLEAN, index, &item);
|
|
if (error.domain == null)
|
|
*out = item.boolean;
|
|
return error;
|
|
}
|
|
|
|
malunal_cstr_t
|
|
argresult_subcommand(
|
|
argresult_iptr_t result
|
|
) {
|
|
if (result == null)
|
|
return null;
|
|
|
|
result_impl_iptr_t self = (result_impl_iptr_t)result;
|
|
if (self->subresult == null)
|
|
return null;
|
|
|
|
result_impl_iptr_t sub = (result_impl_iptr_t)self->subresult;
|
|
return ((parser_impl_iptr_t)sub->parser)->name;
|
|
}
|
|
|
|
argresult_iptr_t
|
|
argresult_subresult(
|
|
argresult_iptr_t result
|
|
) {
|
|
return result != null
|
|
? ((result_impl_iptr_t)result)->subresult
|
|
: null;
|
|
}
|