#include #include #include #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; }