aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--snag/libargs/args.c1416
-rw-r--r--snag/libargs/args.h217
-rw-r--r--snag/libargs/license.txt10
-rw-r--r--snag/libargs/readme.txt1
-rw-r--r--snag/main.c38
-rw-r--r--snag/makefile2
-rw-r--r--snag/pkg_db.c4
-rw-r--r--snag/readme.txt2
-rw-r--r--todo.txt7
9 files changed, 1682 insertions, 15 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
diff --git a/snag/main.c b/snag/main.c
index ff7ac64..c6abcb1 100644
--- a/snag/main.c
+++ b/snag/main.c
@@ -1,23 +1,43 @@
#include <stdio.h>
+#include "util.h"
+#include "libargs/args.h"
#include "pkg_db.h"
-void subcmd_install(char* pkgs[], size_t count);
+int cmd_install(char* cmd_name, ArgParser* parser);
int main(int argc, char* argv[]) {
- // run install subcommand with each argument as a package
- subcmd_install(argv+1, argc-1);
+ // create argument parser
+ ArgParser* parser = ap_new_parser();
+ if (parser == NULL) error_out("failed to initialize cli parsing");
+ ap_set_helptext(parser, "Usage: snag <command>");
+ ap_set_version(parser, "0.0");
+
+ // add install subcommand
+ ArgParser* install = ap_new_cmd(parser, "install inst i");
+ if (install == NULL) error_out("failed to initialize install cli subcommand");
+ ap_set_helptext(install, "Usage: snag install [packages]");
+ ap_set_cmd_callback(install, cmd_install);
+
+ // parse the arguments (automatically executes subcommands)
+ if (!ap_parse(parser, argc, argv)) error_out("failed to parse arguments");
+
+ // if no subcommand was specified, parse and execute install as the root command
+ if (!ap_found_cmd(parser)) {
+ if (!ap_parse(install, argc, argv)) error_out("failed to parse arguments");
+ cmd_install("install", install);
+ }
+ ap_free(parser);
return 0;
}
-void subcmd_install(char* pkgs[], size_t count) {
-
- // install each package listed
- for (int i = 0; i < count; ++i) {
+int cmd_install(char* cmd_name, ArgParser* parser) {
- char* pkgid = pkgs[i];
+ // install each package specified
+ for (int i = 0; i < ap_count_args(parser); i++) {
+ char* pkgid = ap_get_arg_at_index(parser, i);
// load package description
package_info_t info;
@@ -26,4 +46,6 @@ void subcmd_install(char* pkgs[], size_t count) {
run_package_install(&info);
}
}
+
+ return 0;
}
diff --git a/snag/makefile b/snag/makefile
index 7af765e..8770725 100644
--- a/snag/makefile
+++ b/snag/makefile
@@ -3,7 +3,7 @@ CFLAGS = -Wall -g
TARGET = snag
-SRCS = main.c pkg_db.c util.c
+SRCS = main.c pkg_db.c util.c libargs/args.c
OBJS = $(SRCS:.c=.o)
.SILENT: $(OBJS) $(TARGET)
diff --git a/snag/pkg_db.c b/snag/pkg_db.c
index 032c089..a02fe93 100644
--- a/snag/pkg_db.c
+++ b/snag/pkg_db.c
@@ -62,7 +62,7 @@ bool load_package_info(char* pkgid, package_info_t* info) {
if (pid == 0) {
// child proc
- // write my stdout to the pipe
+ // connect my stdout to the pipe
close(pipefds[0]);
dup2(pipefds[1], STDOUT_FILENO);
close(pipefds[1]);
@@ -121,7 +121,7 @@ void run_package_install(const package_info_t* info) {
if (pid == 0) {
// child proc
- // write my stdout and stderr to the pipe
+ // connect my stdout and stderr to the pipe
close(pipefds[0]);
dup2(pipefds[1], STDOUT_FILENO);
dup2(pipefds[1], STDERR_FILENO);
diff --git a/snag/readme.txt b/snag/readme.txt
index 1ce6b15..5e3fd9a 100644
--- a/snag/readme.txt
+++ b/snag/readme.txt
@@ -14,7 +14,7 @@ why - list why this package is installed (manually installed, as a depe
## Important Directories
Package Database - /var/snag/pkgs/
Install Database - /var/snag/install/
-Logs/In Progress Builds - /var/snag/logs/
+Logs/In Progress Builds - /var/snag/builds/
SNAG Configuration - /etc/snag/
## Terminology
diff --git a/todo.txt b/todo.txt
index 7cf102e..fc5b6bb 100644
--- a/todo.txt
+++ b/todo.txt
@@ -1,9 +1,10 @@
SNAG
-- [ ] make install subcommand actually run the install script
-- [ ] multiple subcommand handling and proper argument parsing
+- [x] make install subcommand actually run the install script
+- [x] multiple subcommand handling and proper argument parsing
+- [ ] actually try to handle a proper package installation
- [ ] figure out how we handle make configuration, and tools for build scripts
- [ ] dependencies
Things to come back to
- [ ] Optional package info attributes
- - [ ] better memory allocation strategies for snag (temporary, arena) \ No newline at end of file
+ - [ ] better memory allocation strategies for snag (temporary, arena)