diff options
Diffstat (limited to 'snag/libargs')
| -rw-r--r-- | snag/libargs/args.c | 1416 | ||||
| -rw-r--r-- | snag/libargs/args.h | 217 | ||||
| -rw-r--r-- | snag/libargs/license.txt | 10 | ||||
| -rw-r--r-- | snag/libargs/readme.txt | 1 |
4 files changed, 1644 insertions, 0 deletions
diff --git a/snag/libargs/args.c b/snag/libargs/args.c new file mode 100644 index 0000000..b004525 --- /dev/null +++ b/snag/libargs/args.c @@ -0,0 +1,1416 @@ +#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; +} diff --git a/snag/libargs/args.h b/snag/libargs/args.h new file mode 100644 index 0000000..ea91142 --- /dev/null +++ b/snag/libargs/args.h @@ -0,0 +1,217 @@ +// ----------------------------------------------------------------------------- +// Args: a C99 library for parsing command line arguments. +// Version: 3.3.0 +// ----------------------------------------------------------------------------- + +#ifndef args_h +#define args_h + +#include <stdbool.h> + +// ----------------------------------------------------------------------------- +// Types. +// ----------------------------------------------------------------------------- + +// An ArgParser instance stores registered flags, options and commands. +typedef struct ArgParser ArgParser; + +// A callback function should accept two arguments: the command's name and the +// command's ArgParser instance. It should return an integer status code. +typedef int (*ap_callback_t)(char* cmd_name, ArgParser* cmd_parser); + +// ----------------------------------------------------------------------------- +// Initialization, parsing, teardown. +// ----------------------------------------------------------------------------- + +// Allocates and initializes a new ArgParser instance. Returns NULL if memory +// allocation fails. +ArgParser* ap_new_parser(void); + +// Specifies a helptext string for the parser. If [helptext] is not NULL, this +// activates an automatic --help/-h flag. (Either --help or -h can be overridden +// by explicitly registered flags.) The parser stores and manages its own copy +// of the [helptext] string so [helptext] can be freed immediately after this +// call if it was dynamically constructed. +void ap_set_helptext(ArgParser* parser, const char* helptext); + +// Returns a pointer to the parser's helptext string. +char* ap_get_helptext(ArgParser* parser); + +// Specifies a version string for the parser. If [version] is not NULL, this +// activates an automatic --version/-v flag. (Either --version or -v can be +// overridden by explicitly registered flags.) The parser stores and manages its +// own copy of the [version] string so [version] can be freed immediately after +// this call if it was dynamically constructed. +void ap_set_version(ArgParser* parser, const char* version); + +// Returns a pointer to the parser's version string. +char* ap_get_version(ArgParser* parser); + +// Parses an array of string arguments. +// - Exits with an error message and a non-zero status code if the arguments are +// invalid. +// - The parameters are assumed to be [argc] and [argv] as supplied to main(), +// i.e. the first element of the array is assumed to be the binary name and +// is therefore ignored. +// - Returns true if the arguments were successfully parsed. +// - Returns false if parsing failed because sufficient memory could not be +// allocated. +bool ap_parse(ArgParser* parser, int argc, char** argv); + +// Frees the memory associated with the parser and any subparsers. +void ap_free(ArgParser* parser); + +// ----------------------------------------------------------------------------- +// Parsing modes. +// ----------------------------------------------------------------------------- + +// If set, the first positional argument ends option-parsing; all subsequent +// arguments will be treated as positionals. +void ap_first_pos_arg_ends_option_parsing(ArgParser* parser); + +// If set, all arguments will be treated as positionals. +void ap_all_args_as_pos_args(ArgParser* parser); + +// ----------------------------------------------------------------------------- +// Register flags and options. +// ----------------------------------------------------------------------------- + +// Registers a new flag. +void ap_add_flag(ArgParser* parser, const char* name); + +// Registers a new string-valued option. +void ap_add_str_opt(ArgParser* parser, const char* name, const char* fallback); + +// Registers a new integer-valued option. +void ap_add_int_opt(ArgParser* parser, const char* name, int fallback); + +// Registers a new double-valued option. +void ap_add_dbl_opt(ArgParser* parser, const char* name, double fallback); + +// Registers a new greedy string-valued option. +void ap_add_greedy_str_opt(ArgParser* parser, const char* name); + +// ----------------------------------------------------------------------------- +// Inspect flags and options. +// ----------------------------------------------------------------------------- + +// Returns the number of times the specified flag or option was found. +int ap_count(ArgParser* parser, const char* name); + +// Returns true if the specified flag or option was found. +bool ap_found(ArgParser* parser, const char* name); + +// Returns the value of a string option. +char* ap_get_str_value(ArgParser* parser, const char* name); + +// Returns the string value at the specified index. +char* ap_get_str_value_at_index(ArgParser* parser, const char* name, int index); + +// Returns the value of an integer option. +int ap_get_int_value(ArgParser* parser, const char* name); + +// Returns the integer value at the specified index. +int ap_get_int_value_at_index(ArgParser* parser, const char* name, int index); + +// Returns the value of a floating-point option. +double ap_get_dbl_value(ArgParser* parser, const char* name); + +// Returns the floating-point value at the specified index. +double ap_get_dbl_value_at_index(ArgParser* parser, const char* name, int index); + +// 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 allocation fails. +char** ap_get_str_values(ArgParser* parser, const char* name); + +// 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 allocation fails. +int* ap_get_int_values(ArgParser* parser, const char* name); + +// 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 allocation fails. +double* ap_get_dbl_values(ArgParser* parser, const char* name); + +// ----------------------------------------------------------------------------- +// Positional arguments. +// ----------------------------------------------------------------------------- + +// Returns true if the parser has found one or more positional arguments. +bool ap_has_args(ArgParser* parser); + +// Returns the number of positional arguments. +int ap_count_args(ArgParser* parser); + +// Returns the positional argument at the specified index. +char* ap_get_arg_at_index(ArgParser* parser, int 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 allocation fails. +char** ap_get_args(ArgParser* parser); + +// 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 allocation fails. +int* ap_get_args_as_ints(ArgParser* parser); + +// 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 allocation fails. +double* ap_get_args_as_doubles(ArgParser* parser); + +// ----------------------------------------------------------------------------- +// Commands. +// ----------------------------------------------------------------------------- + +// Registers a new command. Returns the ArgParser instance for the command. +// Returns NULL if sufficient memory cannot be allocated for the new parser. +ArgParser* ap_new_cmd(ArgParser* parent_parser, const char* name); + +// Registers a callback function on a command parser. +void ap_set_cmd_callback(ArgParser* cmd_parser, ap_callback_t cmd_callback); + +// Returns true if [parent_parser] has found a command. +bool ap_found_cmd(ArgParser* parent_parser); + +// If [parent_parser] has found a command, returns its name, otherwise NULL. +char* ap_get_cmd_name(ArgParser* parent_parser); + +// If [parent_parser] has found a command, returns its ArgParser instance, +// otherwise NULL. +ArgParser* ap_get_cmd_parser(ArgParser* parent_parser); + +// If [parent_parser] has found a command, and if that command has a callback +// function, returns the exit code from the callback, otherwise 0. +int ap_get_cmd_exit_code(ArgParser* parent_parser); + +// This boolean switch toggles support for an automatic 'help' command that +// prints subcommand helptext. The value defaults to false but gets toggled +// automatically to true whenever a command is registered. You can use this +// function to disable the feature if required. +void ap_enable_help_command(ArgParser* parent_parser, bool enable); + +// If [parser] has a parent -- i.e. if [parser] is a command parser -- returns +// its parent, otherwise NULL. +ArgParser* ap_get_parent(ArgParser* parser); + +// ----------------------------------------------------------------------------- +// Utilities. +// ----------------------------------------------------------------------------- + +// Dumps a parser instance to stdout for debugging. +void ap_print(ArgParser* parser); + +// Returns true if an attempt to allocate memory failed. +bool ap_had_memory_error(ArgParser* parser); + +// Returns the argument supplied at index-zero to the root parser. Typically +// this is the filepath of the binary. This function can be called on the root +// parser or any command sub-parser. +char* ap_get_zeroth_root_arg(ArgParser* parser); + +#endif diff --git a/snag/libargs/license.txt b/snag/libargs/license.txt new file mode 100644 index 0000000..d5710a2 --- /dev/null +++ b/snag/libargs/license.txt @@ -0,0 +1,10 @@ +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/snag/libargs/readme.txt b/snag/libargs/readme.txt new file mode 100644 index 0000000..d725590 --- /dev/null +++ b/snag/libargs/readme.txt @@ -0,0 +1 @@ +args by Darren Mulholland, https://github.com/dmulholl/args
\ No newline at end of file |
