diff options
Diffstat (limited to 'snag/libargs/args.c')
| -rw-r--r-- | snag/libargs/args.c | 1416 |
1 files changed, 0 insertions, 1416 deletions
diff --git a/snag/libargs/args.c b/snag/libargs/args.c deleted file mode 100644 index b004525..0000000 --- a/snag/libargs/args.c +++ /dev/null @@ -1,1416 +0,0 @@ -#include <stdbool.h> -#include <string.h> -#include <stdlib.h> -#include <stdio.h> -#include <limits.h> -#include <errno.h> -#include <ctype.h> -#include <stdarg.h> -#include <stdint.h> -#include <assert.h> -#include "args.h" - - -/* ------------------ */ -/* Utility functions. */ -/* ------------------ */ - - -// Prints a message to stderr and exits with a non-zero status code. -static void exit_with_error(const char* format_string, ...) { - fprintf(stderr, "error: "); - - va_list args; - va_start(args, format_string); - vfprintf(stderr, format_string, args); - va_end(args); - - fprintf(stderr, "\n"); - exit(1); -} - - -// Prints to an automatically-allocated string. Returns NULL if an encoding -// error occurs or if sufficient memory cannot be allocated. -static char* str(const char* format_string, ...) { - va_list args; - - va_start(args, format_string); - int len = vsnprintf(NULL, 0, format_string, args); - if (len < 0) { - return NULL; - } - va_end(args); - - char *string = malloc(len + 1); - if (string == NULL) { - return NULL; - } - - va_start(args, format_string); - vsnprintf(string, len + 1, format_string, args); - va_end(args); - - return string; -} - - -// Duplicates a string, automatically allocating memory for the copy. -// Returns NULL if memory cannot be allocated for the copy. -static char* str_dup(const char* string) { - size_t len = strlen(string) + 1; - char *copy = malloc(len); - return copy ? memcpy(copy, string, len) : NULL; -} - - -// Hashes a string using the FNV-1a algorithm. -static uint32_t str_hash(const char* string) { - uint32_t hash = 2166136261u; - size_t length = strlen(string); - for (size_t i = 0; i < length; i++) { - hash ^= (uint8_t)string[i]; - hash *= 16777619; - } - return hash; -} - - -// Attempts to parse a string as an integer value, exiting on failure. -static int try_str_to_int(const char* string) { - char *endptr; - errno = 0; - long result = strtol(string, &endptr, 0); - if (errno == ERANGE || result > INT_MAX || result < INT_MIN) { - exit_with_error("'%s' is out of range", string); - } - if (*endptr != '\0') { - exit_with_error("cannot parse '%s' as an integer", string); - } - return (int) result; -} - - -// Attempts to parse a string as a double value, exiting on failure. -static double try_str_to_double(const char* string) { - char *endptr; - errno = 0; - double result = strtod(string, &endptr); - if (errno == ERANGE) { - exit_with_error("'%s' is out of range", string); - } - if (*endptr != '\0') { - exit_with_error("cannot parse '%s' as a floating-point value", string); - } - return result; -} - - -/* --------------------------------- */ -/* Vec: a dynamic array of pointers. */ -/* --------------------------------- */ - - -typedef struct { - int count; - int capacity; - void** entries; -} Vec; - - -static Vec* vec_new(void) { - Vec* vec = malloc(sizeof(Vec)); - if (!vec) { - return NULL; - } - vec->count = 0; - vec->capacity = 0; - vec->entries = NULL; - return vec; -} - - -static void vec_free(Vec* vec) { - if (vec) { - free(vec->entries); - free(vec); - } -} - - -static bool vec_add(Vec* vec, void* entry) { - if (vec->count + 1 > vec->capacity) { - int new_capacity = vec->capacity < 8 ? 8 : vec->capacity * 2; - void** new_array = realloc(vec->entries, sizeof(void*) * new_capacity); - if (!new_array) { - return false; - } - vec->entries = new_array; - vec->capacity = new_capacity; - } - vec->entries[vec->count] = entry; - vec->count++; - return true; -} - - -/* ------------------------------------------------------------------- */ -/* Map: a linear-probing hash map with string-keys and pointer-values. */ -/* ------------------------------------------------------------------- */ - - -// The map automatically grows to keep count/capacity < MAP_MAX_LOAD. -// This data structure performs optimally when it's less than half-full. -#define MAP_MAX_LOAD 0.5 - - -typedef struct { - char* key; - void* value; - uint32_t key_hash; -} MapEntry; - - -typedef struct { - int count; - int capacity; - int max_load_threshold; - MapEntry* entries; -} Map; - - -static Map* map_new(void) { - Map* map = malloc(sizeof(Map)); - if (!map) { - return NULL; - } - map->count = 0; - map->capacity = 0; - map->max_load_threshold = 0; - map->entries = NULL; - return map; -} - - -static void map_free(Map* map) { - if (map) { - for (int i = 0; i < map->capacity; i++) { - MapEntry* entry = &map->entries[i]; - if (entry->key != NULL) { - free(entry->key); - } - } - free(map->entries); - free(map); - } -} - - -static MapEntry* map_find(Map* map, const char* key, uint32_t key_hash) { - // Capacity is always a power of 2 so we can use bitwise-AND as a fast - // modulo operator, i.e. this is equivalent to: index = key_hash % capacity. - size_t index = key_hash & (map->capacity - 1); - - for (;;) { - MapEntry* entry = &map->entries[index]; - if (entry->key == NULL) { - return entry; - } else if (key_hash == entry->key_hash && strcmp(key, entry->key) == 0) { - return entry; - } - index = (index + 1) & (map->capacity - 1); - } -} - - -static bool map_grow(Map* map) { - MapEntry* old_entries = map->entries; - int old_capacity = map->capacity; - int new_capacity = old_capacity < 8 ? 8 : old_capacity * 2; - - MapEntry* new_entries = malloc(sizeof(MapEntry) * new_capacity); - if (!new_entries) { - return false; - } - - for (int i = 0; i < new_capacity; i++) { - new_entries[i].key = NULL; - } - - map->count = 0; - map->capacity = new_capacity; - map->max_load_threshold = new_capacity * MAP_MAX_LOAD; - map->entries = new_entries; - - for (int i = 0; i < old_capacity; i++) { - MapEntry* src = &old_entries[i]; - if (src->key == NULL) continue; - - MapEntry* dst = map_find(map, src->key, src->key_hash); - dst->key = src->key; - dst->value = src->value; - dst->key_hash = src->key_hash; - map->count++; - } - - free(old_entries); - return true; -} - - -// Returns true if the key was found. -static bool map_get(Map* map, const char* key, void** value) { - if (map->count == 0) return false; - - uint32_t key_hash = str_hash(key); - MapEntry* entry = map_find(map, key, key_hash); - if (entry->key == NULL) return false; - - *value = entry->value; - return true; -} - - -// Adds a new entry to the map or updates the value of an existing entry. -// (Note that the map stores its own internal copy of the key string.) -static bool map_set(Map* map, const char* key, void* value) { - if (map->count == map->max_load_threshold) { - if (!map_grow(map)) { - return false; - } - } - - uint32_t key_hash = str_hash(key); - MapEntry* entry = map_find(map, key, key_hash); - if (entry->key == NULL) { - char* key_copy = str_dup(key); - if (!key_copy) { - return false; - } - map->count++; - entry->key = key_copy; - entry->value = value; - entry->key_hash = key_hash; - } else { - entry->value = value; - } - - return true; -} - - -// Convenience wrapper for map_set(). This splits the key string into space- -// separated words and adds a separate entry to the map for each word. -static bool map_set_splitkey(Map* map, const char* key_string, void* value) { - char *copy_of_key_string = str_dup(key_string); - if (!copy_of_key_string) { - return false; - } - - char* word_start = copy_of_key_string; - char* string_end = word_start + strlen(copy_of_key_string); - - while (word_start < string_end) { - if (*word_start == ' ') { - word_start++; - continue; - } - - char* word_end = word_start; - while (true) { - if (*word_end == ' ' || *word_end == '\0') { - break; - } - word_end++; - } - *word_end = '\0'; - - if (!map_set(map, word_start, value)) { - free(copy_of_key_string); - return false; - } - - word_start = word_end + 1; - } - - free(copy_of_key_string); - return true; -} - - -/* -------- */ -/* Options. */ -/* -------- */ - - -typedef enum { - OPT_FLAG, - OPT_STR, - OPT_INT, - OPT_DBL, -} OptionType; - - -typedef union { - char* str_val; - int int_val; - double dbl_val; -} OptionValue; - - -typedef struct { - OptionType type; - int count; - int capacity; - OptionValue* values; - OptionValue fallback; - bool is_greedy; -} Option; - - -static void option_free(Option* opt) { - if (opt) { - free(opt->values); - free(opt); - } -} - - -static bool option_append_value(Option* opt, OptionValue value) { - if (opt->count + 1 > opt->capacity) { - int new_capacity = opt->capacity < 4 ? 4 : opt->capacity * 2; - OptionValue* new_array = realloc(opt->values, sizeof(OptionValue) * new_capacity); - if (!new_array) { - return false; - } - opt->capacity = new_capacity; - opt->values = new_array; - } - opt->values[opt->count] = value; - opt->count++; - return true; -} - - -static bool option_try_set(Option* opt, char* arg) { - if (opt->type == OPT_STR) { - return option_append_value(opt, (OptionValue){.str_val = arg}); - } - else if (opt->type == OPT_INT) { - int value = try_str_to_int(arg); - return option_append_value(opt, (OptionValue){.int_val = value}); - } - else if (opt->type == OPT_DBL) { - double value = try_str_to_double(arg); - return option_append_value(opt, (OptionValue){.dbl_val = value}); - } - assert(false); - return false; -} - - -static Option* option_new(void) { - Option *option = malloc(sizeof(Option)); - if (!option) { - return NULL; - } - option->count = 0; - option->capacity = 0; - option->values = NULL; - option->is_greedy = false; - return option; -} - - -static Option* option_new_flag(void) { - Option *opt = option_new(); - if (!opt) { - return NULL; - } - opt->type = OPT_FLAG; - return opt; -} - - -static Option* option_new_str(char* fallback) { - Option *opt = option_new(); - if (!opt) { - return NULL; - } - opt->type = OPT_STR; - opt->fallback = (OptionValue){.str_val = fallback}; - return opt; -} - - -static Option* option_new_int(int fallback) { - Option *opt = option_new(); - if (!opt) { - return NULL; - } - opt->type = OPT_INT; - opt->fallback = (OptionValue){.int_val = fallback}; - return opt; -} - - -static Option* option_new_double(double fallback) { - Option *opt = option_new(); - if (!opt) { - return NULL; - } - opt->type = OPT_DBL; - opt->fallback = (OptionValue){.dbl_val = fallback}; - return opt; -} - - -static char* option_get_str(Option* opt) { - if (opt->count > 0) { - return opt->values[opt->count - 1].str_val; - } - return opt->fallback.str_val; -} - - -static int option_get_int(Option* opt) { - if (opt->count > 0) { - return opt->values[opt->count - 1].int_val; - } - return opt->fallback.int_val; -} - - -static double option_get_double(Option* opt) { - if (opt->count > 0) { - return opt->values[opt->count - 1].dbl_val; - } - return opt->fallback.dbl_val; -} - - -// Returns the option's values as a freshly-allocated array of string pointers. -static char** option_get_str_list(Option* opt) { - if (opt->count == 0) { - return NULL; - } - char** list = malloc(sizeof(char*) * opt->count); - if (!list) { - return NULL; - } - for (int i = 0; i < opt->count; i++) { - list[i] = opt->values[i].str_val; - } - return list; -} - - -// Returns the option's values as a freshly-allocated array of integers. -static int* option_get_int_list(Option* opt) { - if (opt->count == 0) { - return NULL; - } - int* list = malloc(sizeof(int) * opt->count ); - if (!list) { - return NULL; - } - for (int i = 0; i < opt->count ; i++) { - list[i] = opt->values[i].int_val; - } - return list; -} - - -// Returns the option's values as a freshly-allocated array of doubles. -static double* option_get_double_list(Option* opt) { - if (opt->count == 0) { - return NULL; - } - double* list = malloc(sizeof(double) * opt->count ); - if (!list) { - return NULL; - } - for (int i = 0; i < opt->count ; i++) { - list[i] = opt->values[i].dbl_val; - } - return list; -} - - -// Returns a freshly-allocated state-string for debugging. -static char* option_to_str(Option* opt) { - if (opt->type == OPT_FLAG) { - return str("%i", opt->count); - } - - char *fallback = NULL; - if (opt->type == OPT_STR) { - fallback = str_dup(opt->fallback.str_val); - } else if (opt->type == OPT_INT) { - fallback = str("%i", opt->fallback.int_val); - } else if (opt->type == OPT_DBL) { - fallback = str("%f", opt->fallback.dbl_val); - } - - char *values = str_dup(""); - for (int i = 0; i < opt->count; i++) { - char *value = NULL; - if (opt->type == OPT_STR) { - value = str_dup(opt->values[i].str_val); - } else if (opt->type == OPT_INT) { - value = str("%i", opt->values[i].int_val); - } else if (opt->type == OPT_DBL) { - value = str("%f", opt->values[i].dbl_val); - } - char *old_values = values; - if (i == 0) { - values = str_dup(value); - } else { - values = str("%s, %s", old_values, value); - } - free(old_values); - free(value); - } - - char *output = str("(%s) [%s]", fallback, values); - free(fallback); - free(values); - return output; -} - - -/* ----------------------------------------------------- */ -/* ArgStream: a wrapper for an array of string pointers. */ -/* ----------------------------------------------------- */ - - -typedef struct ArgStream { - int count; - int index; - char** args; -} ArgStream; - - -static ArgStream* argstream_new(int count, char** args) { - ArgStream* stream = malloc(sizeof(ArgStream)); - if (!stream) { - return NULL; - } - stream->count = count; - stream->index = 0; - stream->args = args; - return stream; -} - - -static void argstream_free(ArgStream* stream) { - free(stream); -} - - -static char* argstream_next(ArgStream* stream) { - return stream->args[stream->index++]; -} - - -static bool argstream_has_next(ArgStream* stream) { - return stream->index < stream->count; -} - - -/* ----------------- */ -/* ArgParser: setup. */ -/* ----------------- */ - - -struct ArgParser { - char* helptext; - char* version; - Vec* option_vec; - Map* option_map; - Vec* command_vec; - Map* command_map; - Vec* positional_args; - ap_callback_t cmd_callback; - int cmd_callback_exit_code; - char* cmd_name; - struct ArgParser* cmd_parser; - struct ArgParser* root_parser; - bool enable_help_command; - bool had_memory_error; - struct ArgParser* parent; - bool first_pos_arg_ends_option_parsing; - bool all_args_as_pos_args; - char* zeroth_root_arg; -}; - - -ArgParser* ap_new_parser(void) { - ArgParser *parser = malloc(sizeof(ArgParser)); - if (!parser) { - return NULL; - } - - parser->helptext = NULL; - parser->version = NULL; - parser->cmd_callback = NULL; - parser->cmd_callback_exit_code = 0; - parser->cmd_name = NULL; - parser->cmd_parser = NULL; - parser->enable_help_command = false; - parser->had_memory_error = false; - parser->parent = NULL; - parser->first_pos_arg_ends_option_parsing = false; - parser->all_args_as_pos_args = false; - parser->option_vec = NULL; - parser->option_map = NULL; - parser->command_vec = NULL; - parser->command_map = NULL; - parser->positional_args = NULL; - parser->root_parser = parser; - parser->zeroth_root_arg = NULL; - - parser->option_vec = vec_new(); - if (!parser->option_vec) { - ap_free(parser); - return NULL; - } - - parser->option_map = map_new(); - if (!parser->option_map) { - ap_free(parser); - return NULL; - } - - parser->command_vec = vec_new(); - if (!parser->command_vec) { - ap_free(parser); - return NULL; - } - - parser->command_map = map_new(); - if (!parser->command_map) { - ap_free(parser); - return NULL; - } - - parser->positional_args = vec_new(); - if (!parser->positional_args) { - ap_free(parser); - return NULL; - } - - return parser; -} - - -void ap_free(ArgParser* parser) { - if (!parser) { - return; - } - - free(parser->helptext); - free(parser->version); - - if (parser->option_map) { - map_free(parser->option_map); - } - - if (parser->option_vec) { - for (int i = 0; i < parser->option_vec->count; i++) { - option_free(parser->option_vec->entries[i]); - } - vec_free(parser->option_vec); - } - - if (parser->command_map) { - map_free(parser->command_map); - } - - if (parser->command_vec) { - for (int i = 0; i < parser->command_vec->count; i++) { - ap_free(parser->command_vec->entries[i]); - } - vec_free(parser->command_vec); - } - - if (parser->positional_args) { - vec_free(parser->positional_args); - } - - free(parser); -} - - -static void ap_set_memory_error_flag(ArgParser* parser) { - parser->had_memory_error = true; - - ArgParser* parent = parser->parent; - while (parent) { - parent->had_memory_error = true; - parent = parent->parent; - } -} - - -void ap_set_helptext(ArgParser* parser, const char* helptext) { - free(parser->helptext); - parser->helptext = NULL; - - if (helptext) { - parser->helptext = str_dup(helptext); - if (!parser->helptext) { - ap_set_memory_error_flag(parser); - } - } -} - - -char* ap_get_helptext(ArgParser* parser) { - return parser->helptext; -} - - -void ap_set_version(ArgParser* parser, const char* version) { - free(parser->version); - parser->version = NULL; - - if (version) { - parser->version = str_dup(version); - if (!parser->version) { - ap_set_memory_error_flag(parser); - } - } -} - - -char* ap_get_version(ArgParser* parser) { - return parser->version; -} - - -void ap_first_pos_arg_ends_option_parsing(ArgParser* parser) { - parser->first_pos_arg_ends_option_parsing = true; -} - - -void ap_all_args_as_pos_args(ArgParser* parser) { - parser->all_args_as_pos_args = true; -} - - -/* -------------------------------------- */ -/* ArgParser: register flags and options. */ -/* -------------------------------------- */ - - -static void ap_register_option(ArgParser* parser, const char* name, Option* opt) { - if (!opt) { - ap_set_memory_error_flag(parser); - return; - } - - if (vec_add(parser->option_vec, opt)) { - if (map_set_splitkey(parser->option_map, name, opt)) { - return; - } else { - ap_set_memory_error_flag(parser); - parser->option_vec->count--; - option_free(opt); - return; - } - } else { - ap_set_memory_error_flag(parser); - option_free(opt); - return; - } -} - - -// Register a new flag. -void ap_add_flag(ArgParser *parser, const char* name) { - Option* opt = option_new_flag(); - ap_register_option(parser, name, opt); -} - - -// Register a new string-valued option. -void ap_add_str_opt(ArgParser* parser, const char* name, const char* fallback) { - Option* opt = option_new_str((char*)fallback); - ap_register_option(parser, name, opt); -} - - -// Register a new greedy string-valued option. -void ap_add_greedy_str_opt(ArgParser* parser, const char* name) { - Option* opt = option_new_str((char*)""); - if (opt) { - opt->is_greedy = true; - } - ap_register_option(parser, name, opt); -} - - -// Register a new integer-valued option. -void ap_add_int_opt(ArgParser* parser, const char* name, int fallback) { - Option* opt = option_new_int(fallback); - ap_register_option(parser, name, opt); -} - - -// Register a new double-valued option. -void ap_add_dbl_opt(ArgParser* parser, const char* name, double fallback) { - Option* opt = option_new_double(fallback); - ap_register_option(parser, name, opt); -} - - -/* ---------------------------------- */ -/* ArgParser: flag and option values. */ -/* ---------------------------------- */ - - -// Retrieve an Option instance by name. -static Option* ap_get_opt(ArgParser* parser, const char* name) { - void* opt; - if (!map_get(parser->option_map, name, &opt)) { - exit_with_error("'%s' is not a registered flag or option name", name); - } - return (Option*)opt; -} - - -// Returns the number of times the specified flag or option was found. -int ap_count(ArgParser* parser, const char* name) { - Option* opt = ap_get_opt(parser, name); - return opt->count; -} - - -// Returns true if the specified flag or option was found. -bool ap_found(ArgParser* parser, const char* name) { - Option* opt = ap_get_opt(parser, name); - return opt->count > 0; -} - - -// Returns the value of the specified string option. -char* ap_get_str_value(ArgParser* parser, const char* name) { - Option* opt = ap_get_opt(parser, name); - return (char*)option_get_str(opt); -} - - -// Returns the string value at the specified index. -char* ap_get_str_value_at_index(ArgParser* parser, const char* name, int index) { - Option* opt = ap_get_opt(parser, name); - return opt->values[index].str_val; -} - - -// Returns the value of the specified integer option. -int ap_get_int_value(ArgParser* parser, const char* name) { - Option* opt = ap_get_opt(parser, name); - return option_get_int(opt); -} - - -// Returns the integer value at the specified index. -int ap_get_int_value_at_index(ArgParser* parser, const char* name, int index) { - Option* opt = ap_get_opt(parser, name); - return opt->values[index].int_val; -} - - -// Returns the value of the specified floating-point option. -double ap_get_dbl_value(ArgParser* parser, const char* name) { - Option* opt = ap_get_opt(parser, name); - return option_get_double(opt); -} - - -// Returns the floating-point value at the specified index. -double ap_get_dbl_value_at_index(ArgParser* parser, const char* name, int index) { - Option* opt = ap_get_opt(parser, name); - return opt->values[index].dbl_val; -} - - -// Returns an option's values as a freshly-allocated array of string pointers. -// The array's memory is not affected by calls to ap_free(). -// Returns NULL if memory cannot be allocated for the array. -char** ap_get_str_values(ArgParser* parser, const char* name) { - Option* opt = ap_get_opt(parser, name); - return (char**)option_get_str_list(opt); -} - - -// Returns an option's values as a freshly-allocated array of integers. The -// array's memory is not affected by calls to ap_free(). -// Returns NULL if memory cannot be allocated for the array. -int* ap_get_int_values(ArgParser* parser, const char* name) { - Option* opt = ap_get_opt(parser, name); - return option_get_int_list(opt); -} - - -// Returns an option's values as a freshly-allocated array of doubles. The -// array's memory is not affected by calls to ap_free(). -// Returns NULL if memory cannot be allocated for the array. -double* ap_get_dbl_values(ArgParser* parser, const char* name) { - Option* opt = ap_get_opt(parser, name); - return option_get_double_list(opt); -} - - -/* -------------------------------- */ -/* ArgParser: positional arguments. */ -/* -------------------------------- */ - - -// Returns true if the parser has found one or more positional arguments. -bool ap_has_args(ArgParser* parser) { - return parser->positional_args->count > 0; -} - - -// Returns the number of positional arguments. -int ap_count_args(ArgParser* parser) { - return parser->positional_args->count; -} - - -// Returns the positional argument at the specified index. -char* ap_get_arg_at_index(ArgParser* parser, int index) { - return (char*)parser->positional_args->entries[index]; -} - - -// Returns the positional arguments as a freshly-allocated array of string -// pointers. The memory occupied by the returned array is not affected by -// calls to ap_free(). -// Returns NULL if memory cannot be allocated for the array. -char** ap_get_args(ArgParser* parser) { - int count = ap_count_args(parser); - char** args = malloc(sizeof(char*) * count); - if (!args) { - return NULL; - } - memcpy(args, parser->positional_args->entries, sizeof(char*) * count); - return args; -} - - -// Attempts to parse and return the positional arguments as a freshly -// allocated array of integers. Exits with an error message on failure. The -// memory occupied by the returned array is not affected by calls to -// ap_free(). -// Returns NULL if memory cannot be allocated for the array. -int* ap_get_args_as_ints(ArgParser* parser) { - int count = ap_count_args(parser); - int* args = malloc(sizeof(int) * count); - if (!args) { - return NULL; - } - for (int i = 0; i < count; i++) { - *(args + i) = try_str_to_int(parser->positional_args->entries[i]); - } - return args; -} - - -// Attempts to parse and return the positional arguments as a freshly -// allocated array of doubles. Exits with an error message on failure. The -// memory occupied by the returned array is not affected by calls to -// ap_free(). -// Returns NULL if memory cannot be allocated for the array. -double* ap_get_args_as_doubles(ArgParser* parser) { - int count = ap_count_args(parser); - double *args = malloc(sizeof(double) * count); - if (!args) { - return NULL; - } - for (int i = 0; i < count; i++) { - *(args + i) = try_str_to_double(parser->positional_args->entries[i]); - } - return args; -} - - -/* -------------------- */ -/* ArgParser: commands. */ -/* -------------------- */ - - -ArgParser* ap_new_cmd(ArgParser* parent_parser, const char* name) { - ArgParser* cmd_parser = ap_new_parser(); - if (!cmd_parser) { - return NULL; - } - - cmd_parser->root_parser = parent_parser->root_parser; - - if (vec_add(parent_parser->command_vec, cmd_parser)) { - if (map_set_splitkey(parent_parser->command_map, name, cmd_parser)) { - parent_parser->enable_help_command = true; - return cmd_parser; - } else { - parent_parser->command_vec--; - ap_free(cmd_parser); - return NULL; - } - } else { - ap_free(cmd_parser); - return NULL; - } -} - - -void ap_set_cmd_callback(ArgParser* cmd_parser, ap_callback_t cmd_callback) { - cmd_parser->cmd_callback = cmd_callback; -} - - -bool ap_found_cmd(ArgParser* parent_parser) { - return parent_parser->cmd_name != NULL; -} - - -char* ap_get_cmd_name(ArgParser* parent_parser) { - return parent_parser->cmd_name; -} - - -ArgParser* ap_get_cmd_parser(ArgParser* parent_parser) { - return parent_parser->cmd_parser; -} - - -int ap_get_cmd_exit_code(ArgParser* parent_parser) { - return parent_parser->cmd_callback_exit_code; -} - - -void ap_enable_help_command(ArgParser* parent_parser, bool enable) { - parent_parser->enable_help_command = enable; -} - - -ArgParser* ap_get_parent(ArgParser* parser) { - return parser->parent; -} - - -/* --------------------------- */ -/* ArgParser: parse arguments. */ -/* --------------------------- */ - - -// Parse an option of the form --name=value or -n=value. -static void ap_handle_equals_opt(ArgParser* parser, const char* prefix, char* arg, ArgStream* stream) { - char* array = str_dup(arg); - if (!array) { - ap_set_memory_error_flag(parser); - return; - } - - *strchr(array, '=') = '\0'; - char* name = array; - char* value = strchr(arg, '=') + 1; - - Option* option; - bool found = map_get(parser->option_map, name, (void**)&option); - - if (!found) { - /* free(array); */ - exit_with_error("%s%s is not a recognised option name", prefix, name); - } - - if (option->type == OPT_FLAG) { - /* free(array); */ - exit_with_error("flag %s%s does not accept an argument", prefix, name); - } - - if (strlen(value) == 0) { - /* free(array); */ - exit_with_error("missing argument for %s%s", prefix, name); - } - - if (!option_try_set(option, value)) { - ap_set_memory_error_flag(parser); - } - - if (option->is_greedy) { - while (argstream_has_next(stream)) { - if (!option_try_set(option, argstream_next(stream))) { - ap_set_memory_error_flag(parser); - } - } - } - - free(array); -} - - -// Parse a long-form option, i.e. an option beginning with a double dash. -static void ap_handle_long_opt(ArgParser* parser, const char* arg, ArgStream* stream) { - Option* option; - - if (map_get(parser->option_map, arg, (void**)&option)) { - if (option->type == OPT_FLAG) { - option->count++; - return; - } - - if (argstream_has_next(stream) && option->is_greedy) { - while (argstream_has_next(stream)) { - if (!option_try_set(option, argstream_next(stream))) { - ap_set_memory_error_flag(parser); - } - } - return; - } - - if (argstream_has_next(stream)) { - if (!option_try_set(option, argstream_next(stream))) { - ap_set_memory_error_flag(parser); - } - return; - } - - exit_with_error("missing argument for --%s", arg); - } - - if (strcmp(arg, "help") == 0 && parser->helptext != NULL) { - puts(parser->helptext); - exit(0); - } - - if (strcmp(arg, "version") == 0 && parser->version != NULL) { - puts(parser->version); - exit(0); - } - - exit_with_error("--%s is not a recognised flag or option name", arg); -} - - -// Parse a short-form option, i.e. an option beginning with a single dash. -static void ap_handle_short_opt(ArgParser* parser, const char* arg, ArgStream* stream) { - for (size_t i = 0; i < strlen(arg); i++) { - char keystr[] = {arg[i], 0}; - Option* option; - - bool found = map_get(parser->option_map, keystr, (void**)&option); - if (!found) { - if (arg[i] == 'h' && parser->helptext != NULL) { - puts(parser->helptext); - exit(0); - } - if (arg[i] == 'v' && parser->version != NULL) { - puts(parser->version); - exit(0); - } - if (strlen(arg) > 1) { - exit_with_error("'%c' in -%s is not a recognised flag or option name", arg[i], arg); - } - exit_with_error("-%s is not a recognised flag or option name", arg); - } - - if (option->type == OPT_FLAG) { - option->count++; - continue; - } - - if (argstream_has_next(stream) && option->is_greedy) { - while (argstream_has_next(stream)) { - if (!option_try_set(option, argstream_next(stream))) { - ap_set_memory_error_flag(parser); - } - } - continue; - } - - if (argstream_has_next(stream)) { - if (!option_try_set(option, argstream_next(stream))) { - ap_set_memory_error_flag(parser); - } - continue; - } - - if (strlen(arg) > 1) { - exit_with_error("missing argument for '%c' in -%s", arg[i], arg); - } - - exit_with_error("missing argument for -%s", arg); - } -} - - -// Parse a stream of string arguments. -static void ap_parse_stream(ArgParser* parser, ArgStream* stream) { - if (parser->had_memory_error) { - return; - } - - if (parser->all_args_as_pos_args) { - while (argstream_has_next(stream)) { - if (!vec_add(parser->positional_args, argstream_next(stream))) { - ap_set_memory_error_flag(parser); - } - } - return; - } - - while (argstream_has_next(stream)) { - ArgParser* cmd_parser; - char* arg = argstream_next(stream); - - // If we encounter a '--' argument, turn off option-parsing. - if (strcmp(arg, "--") == 0) { - while (argstream_has_next(stream)) { - if (!vec_add(parser->positional_args, argstream_next(stream))) { - ap_set_memory_error_flag(parser); - } - } - } - - // Is the argument a long-form option or flag? - else if (strncmp(arg, "--", 2) == 0) { - if (strstr(arg, "=") != NULL) { - ap_handle_equals_opt(parser, "--", arg + 2, stream); - } else { - ap_handle_long_opt(parser, arg + 2, stream); - } - } - - // Is the argument a short-form option or flag? - else if (arg[0] == '-') { - if (strlen(arg) == 1 || isdigit(arg[1])) { - if (!vec_add(parser->positional_args, arg)) { - ap_set_memory_error_flag(parser); - } - } else if (strstr(arg, "=") != NULL) { - ap_handle_equals_opt(parser, "-", arg + 1, stream); - } else { - ap_handle_short_opt(parser, arg + 1, stream); - } - } - - // Is the argument a registered command? - else if (parser->positional_args->count == 0 && map_get(parser->command_map, arg, (void**)&cmd_parser)) { - parser->cmd_name = arg; - parser->cmd_parser = cmd_parser; - ap_parse_stream(cmd_parser, stream); - if (cmd_parser->cmd_callback && !parser->had_memory_error) { - parser->cmd_callback_exit_code = cmd_parser->cmd_callback(arg, cmd_parser); - } - } - - // Is the argument the automatic 'help' command? - else if (parser->positional_args->count == 0 && parser->enable_help_command && strcmp(arg, "help") == 0) { - if (argstream_has_next(stream)) { - char* name = argstream_next(stream); - if (map_get(parser->command_map, name, (void**)&cmd_parser)) { - if (cmd_parser->helptext) { - puts(cmd_parser->helptext); - } - exit(0); - } else { - exit_with_error("'%s' is not a recognised command", name); - } - } else { - exit_with_error("the 'help' command requires an argument"); - } - } - - // Otherwise add the argument to our list of positionals. - else { - if (!vec_add(parser->positional_args, arg)) { - ap_set_memory_error_flag(parser); - } - if (parser->first_pos_arg_ends_option_parsing) { - while (argstream_has_next(stream)) { - if (!vec_add(parser->positional_args, argstream_next(stream))) { - ap_set_memory_error_flag(parser); - } - } - } - } - } -} - - -// Parse an array of string arguments. We assume that [argc] and [argv] are the arguments passed to -// main(), i.e. we ignore the first element in the array. In some situations [argv] can be empty, -// i.e. [argc == 0], which can lead to security vulnerabilities if not explicitly handled. -bool ap_parse(ArgParser* parser, int argc, char** argv) { - if (parser->had_memory_error) { - return false; - } - - if (argc == 0) { - return true; - } - - parser->zeroth_root_arg = argv[0]; - - ArgStream* stream = argstream_new(argc - 1 , argv + 1); - if (!stream) { - return false; - } - - ap_parse_stream(parser, stream); - argstream_free(stream); - - return !parser->had_memory_error; -} - - -/* --------------------- */ -/* ArgParser: utilities. */ -/* --------------------- */ - - -bool ap_had_memory_error(ArgParser* parser) { - return parser->had_memory_error; -} - - -void ap_print(ArgParser* parser) { - puts("Flags/Options:"); - if (parser->option_map->count > 0) { - for (int i = 0; i < parser->option_map->capacity; i++) { - MapEntry* entry = &parser->option_map->entries[i]; - if (entry->key != NULL) { - Option* opt = entry->value; - char* opt_str = option_to_str(opt); - printf(" %s: %s\n", entry->key, opt_str); - free(opt_str); - } - } - } else { - puts(" [none]"); - } - - puts("\nArguments:"); - if (parser->positional_args->count > 0) { - for (int i = 0; i < parser->positional_args->count; i++) { - printf(" %s\n", ap_get_arg_at_index(parser, i)); - } - } else { - puts(" [none]"); - } - - puts("\nCommand:"); - if (ap_found_cmd(parser)) { - printf(" %s\n", ap_get_cmd_name(parser)); - } else { - puts(" [none]"); - } -} - - -char* ap_get_zeroth_root_arg(ArgParser* parser) { - return parser->root_parser->zeroth_root_arg; -} |
