From 5365f8fa8d6ebfbf3ba92816abe55a99feff6f25 Mon Sep 17 00:00:00 2001 From: Jack Jamison Date: Wed, 22 Jul 2026 09:09:18 -0400 Subject: clean up --- snag/.gitignore | 5 +- snag/configure | 55 ++ snag/gen.sh | 57 -- snag/get_pkg_attrs.sh | 22 - snag/libargs/args.c | 1416 --------------------------------------- snag/libargs/args.h | 217 ------ snag/libargs/license.txt | 10 - snag/libargs/readme.txt | 1 - snag/main.c | 52 -- snag/makefile | 22 - snag/pkg_db.c | 157 ----- snag/pkg_db.h | 27 - snag/run_pkg_install.sh | 16 - snag/scripts/get_pkg_attrs.sh | 22 + snag/scripts/run_pkg_install.sh | 16 + snag/src/libargs/args.c | 1416 +++++++++++++++++++++++++++++++++++++++ snag/src/libargs/args.h | 217 ++++++ snag/src/libargs/license.txt | 10 + snag/src/libargs/readme.txt | 1 + snag/src/main.c | 52 ++ snag/src/pkg_db.c | 157 +++++ snag/src/pkg_db.h | 27 + snag/src/util.c | 76 +++ snag/src/util.h | 31 + snag/util.c | 67 -- snag/util.h | 27 - todo.txt | 2 +- 27 files changed, 2084 insertions(+), 2094 deletions(-) create mode 100755 snag/configure delete mode 100755 snag/gen.sh delete mode 100755 snag/get_pkg_attrs.sh delete mode 100644 snag/libargs/args.c delete mode 100644 snag/libargs/args.h delete mode 100644 snag/libargs/license.txt delete mode 100644 snag/libargs/readme.txt delete mode 100644 snag/main.c delete mode 100644 snag/makefile delete mode 100644 snag/pkg_db.c delete mode 100644 snag/pkg_db.h delete mode 100755 snag/run_pkg_install.sh create mode 100755 snag/scripts/get_pkg_attrs.sh create mode 100755 snag/scripts/run_pkg_install.sh create mode 100644 snag/src/libargs/args.c create mode 100644 snag/src/libargs/args.h create mode 100644 snag/src/libargs/license.txt create mode 100644 snag/src/libargs/readme.txt create mode 100644 snag/src/main.c create mode 100644 snag/src/pkg_db.c create mode 100644 snag/src/pkg_db.h create mode 100644 snag/src/util.c create mode 100644 snag/src/util.h delete mode 100644 snag/util.c delete mode 100644 snag/util.h diff --git a/snag/.gitignore b/snag/.gitignore index 9a9d059..ca60f7e 100644 --- a/snag/.gitignore +++ b/snag/.gitignore @@ -1,6 +1,7 @@ +/objs/ +/build/ *.o snag compile_flags.txt TAGS -build.ninja -objs/ \ No newline at end of file +build.ninja \ No newline at end of file diff --git a/snag/configure b/snag/configure new file mode 100755 index 0000000..9b73f97 --- /dev/null +++ b/snag/configure @@ -0,0 +1,55 @@ +#!/bin/bash + +bin="snag" +libs="" +builddir="build" +PREFIX="/usr" + +while [ "$1" ]; do + case "$1" in + -*) exit 1 ;; + esac + shift +done + +CFLAGS=${CFLAGS:-""} + +CFLAGS_RELEASE="$CFLAGS -O2" +CFLAGS_DEBUG="$CFLAGS -Wall -g -DDEBUG=1" + +write() { echo "$@" >> build.ninja; } + +echo "$CFLAGS_DEBUG $(pkg-config --cflags $libs 2>/dev/null)" | tr ' ' '\n' > compile_flags.txt +rm -f build.ninja + +write "builddir = $builddir" +write "libs = $(pkg-config --libs $libs 2>/dev/null)" + +write 'rule link' +write ' description = LD $out' +write ' command = gcc $libs $in -o $out' +for target in $(echo "debug release"); do + eval write "cflags_$target = \$CFLAGS_${target^^} $(pkg-config --cflags $libs 2>/dev/null)" + + write "rule cc-$target" + write ' deps = gcc' + write ' depfile = $out.d' + write ' description = CC $out' + write " command = gcc -MD -MF \$out.d \$cflags_$target -c \$in -o \$out" + + + files="$(find . -name '*.c')" + sedstr=$(printf 's,^\./\(.*\)\.c$,%s\/%s\/\\1.o,g' ${builddir} ${target}) + ofiles=$(echo "$files" | sed ${sedstr}) + + for f in $files; do + of=$(echo $f | sed ${sedstr}) + write "build $of: cc-$target $f" + done + write "build $builddir/$target/$bin: link $(echo $ofiles)" + write "build $target: phony $builddir/$target/$bin" +done + +write "default release" + +echo "Wrote ./build.ninja file" diff --git a/snag/gen.sh b/snag/gen.sh deleted file mode 100755 index 3e420f1..0000000 --- a/snag/gen.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash - -bin="snag" -libs="" -builddir="objs" -PREFIX="/usr" - -while [ "$1" ]; do - case "$1" in - -*) exit 1 ;; - esac - shift -done - -CFLAGS=${CFLAGS:-""} - -CFLAGS_RELEASE="$CFLAGS -O2" -CFLAGS_DEBUG="$CFLAGS -Wall -g -DDEBUG=1" - -write() { - echo "$@" >> build.ninja -} - -echo "$CFLAGS_DEBUG $(pkg-config --cflags $libs 2>/dev/null)" | tr ' ' '\n' > compile_flags.txt -rm -f build.ninja - -write "builddir = $builddir" -write "libs = $(pkg-config --libs $libs 2>/dev/null)" - -write 'rule link' -write ' description = LD $out' -write ' command = gcc $libs $in -o $out' -for target in $(echo "debug release"); do - eval write "cflags_$target = \$CFLAGS_${target^^} $(pkg-config --cflags $libs 2>/dev/null)" - - write "rule cc-$target" - write ' deps = gcc' - write ' depfile = $out.d' - write ' description = CC $out' - write " command = gcc -MD -MF \$out.d \$cflags_$target -c \$in -o \$out" - - - files="$(find . -name '*.c')" - sedstr=$(printf 's,^\./\(.*\)\.c$,%s\/%s\/\\1.o,g' ${builddir} ${target}) - ofiles=$(echo "$files" | sed ${sedstr}) - - for f in $files; do - of=$(echo $f | sed ${sedstr}) - write "build $of: cc-$target $f" - done - write "build $builddir/$target/$bin: link $(echo $ofiles)" - write "build $target: phony $builddir/$target/$bin" -done - -write "default release" - -echo "Wrote ./build.ninja file" diff --git a/snag/get_pkg_attrs.sh b/snag/get_pkg_attrs.sh deleted file mode 100755 index 25f9177..0000000 --- a/snag/get_pkg_attrs.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -# reads a package description script and outputs the attributes in a format easy -# for the snag program to read. it does not do any other validation, and should -# not be used by other programs - -if [ -z "$1" ]; then - echo "Usage: $0 " >&2 - exit 1 -fi - -# load the description script -. $1 - -# output package attributes (type char, value, null delimiter) -echoatt() { [ -n "$2" ] && printf "$1$2\0"; } -echoatt i "$pkg_id" -echoatt n "$pkg_name" -echoatt d "$pkg_desc" -echoatt w "$pkg_webpage" -echoatt l "$pkg_license" -echoatt v "$pkg_version" diff --git a/snag/libargs/args.c b/snag/libargs/args.c deleted file mode 100644 index b004525..0000000 --- a/snag/libargs/args.c +++ /dev/null @@ -1,1416 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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 deleted file mode 100644 index ea91142..0000000 --- a/snag/libargs/args.h +++ /dev/null @@ -1,217 +0,0 @@ -// ----------------------------------------------------------------------------- -// Args: a C99 library for parsing command line arguments. -// Version: 3.3.0 -// ----------------------------------------------------------------------------- - -#ifndef args_h -#define args_h - -#include - -// ----------------------------------------------------------------------------- -// 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 deleted file mode 100644 index d5710a2..0000000 --- a/snag/libargs/license.txt +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index d725590..0000000 --- a/snag/libargs/readme.txt +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index 0429fda..0000000 --- a/snag/main.c +++ /dev/null @@ -1,52 +0,0 @@ -#include - -#include "util.h" -#include "libargs/args.h" -#include "pkg_db.h" - -int cmd_install(char* cmd_name, ArgParser* parser); - -int main(int argc, char* argv[]) { - - // create argument parser - ArgParser* parser = ap_new_parser(); - if (parser == NULL) die("failed to initialize cli parsing"); - ap_set_helptext(parser, "Usage: snag "); - ap_set_version(parser, "0.0"); - - // add install subcommand - ArgParser* install = ap_new_cmd(parser, "install inst i"); - if (install == NULL) die("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)) die("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)) die("failed to parse arguments"); - cmd_install("install", install); - } - - ap_free(parser); - return 0; -} - -int cmd_install(char* cmd_name, ArgParser* parser) { - - // install each package specified - for (int i = 0; i < ap_count_args(parser); i++) { - if (i != 0) printf("\n"); - - // load package description - char* pkgid = ap_get_arg_at_index(parser, i); - package_info_t info; - if (load_package_info(pkgid, &info)) { - printf("Installing %s - %s\n", info.attrs.name, info.attrs.desc); - run_package_install(&info); - } - } - - return 0; -} diff --git a/snag/makefile b/snag/makefile deleted file mode 100644 index 8770725..0000000 --- a/snag/makefile +++ /dev/null @@ -1,22 +0,0 @@ -CC = gcc -CFLAGS = -Wall -g - -TARGET = snag - -SRCS = main.c pkg_db.c util.c libargs/args.c -OBJS = $(SRCS:.c=.o) - -.SILENT: $(OBJS) $(TARGET) - -all: $(TARGET) - -clean: - rm -f $(OBJS) $(TARGET) - -$(TARGET): $(OBJS) - printf " LD %s\n" $@ - $(CC) $(CFLAGS) -o $@ $^ - -%.o: %.c - printf " CC %s\n" $< - $(CC) $(CFLAGS) -c $< -o $@ diff --git a/snag/pkg_db.c b/snag/pkg_db.c deleted file mode 100644 index 2bfa686..0000000 --- a/snag/pkg_db.c +++ /dev/null @@ -1,157 +0,0 @@ -#include "pkg_db.h" -#include "util.h" - -#include -#include -#include -#include -#include -#include - -#define PKG_INFO_CMD "./get_pkg_attrs.sh" -#define PKG_INST_CMD "./run_pkg_install.sh" - -// note: fixed location of package database for now -const char pkg_db[] = "../packages"; -const size_t pkg_db_len = sizeof(pkg_db) - 1; - -bool load_package_info(char* pkgid, package_info_t* info) { - - memset(info, 0, sizeof(*info)); - size_t pkgid_len = strlen(pkgid); - - // construct package description directory path and ensure it exists - // pkg_dir = "pkg_db/pkgid" - size_t pkg_dir_len = pkg_db_len + pkgid_len + 1; - char* pkg_dir = emalloc(pkg_dir_len + 1, "pkg_dir"); - { - memcpy(pkg_dir, pkg_db, pkg_db_len); - pkg_dir[pkg_db_len] = '/'; - memcpy(pkg_dir + pkg_db_len + 1, pkgid, pkgid_len); - pkg_dir[pkg_dir_len] = '\0'; - if (access(pkg_dir, F_OK) != 0) { - log("Package '%s' not found\n", pkgid); - free(pkg_dir); - return false; - } - } - - // construct package description script path and ensure it exists - // script_path = "pkg_dir/pkgid.sh" - size_t script_path_len = pkg_dir_len + pkgid_len + 4; - info->script_path = emalloc(script_path_len + 1, "info->script_path"); - { - memcpy(info->script_path, pkg_dir, pkg_dir_len); - info->script_path[pkg_dir_len] = '/'; - memcpy(&info->script_path[pkg_dir_len + 1], pkgid, pkgid_len); - memcpy(&info->script_path[script_path_len - 3], ".sh", 3); - info->script_path[script_path_len] = '\0'; - free(pkg_dir); - if (access(info->script_path, F_OK) != 0) { - log("Package '%s' broken, doesn't have description script\n", pkgid); - free(info->script_path); - return false; - } - } - - // PIPE and FORK to run pkg attrs script - int pipefds[2] = {0}; - if (pipe(pipefds) < 0) die("failed to pipe: %s", strerror(errno)); - pid_t pid = fork(); - if (pid < 0) die("failed to fork: %s", strerror(errno)); - - if (pid == 0) { - // child proc - // connect my stdout to the pipe - close(pipefds[0]); - dup2(pipefds[1], STDOUT_FILENO); - close(pipefds[1]); - // exec into get attr script - char* argv[] = { PKG_INFO_CMD, info->script_path, NULL }; - char* envp[] = { NULL }; - execve(PKG_INFO_CMD, argv, envp); - // what are you still doing here? - die("execve failed"); - } else { - // parent proc, close write end of pipe - close(pipefds[1]); - } - - // wait for child to finish - int wstatus; - if (waitpid(pid, &wstatus, 0) < 0) die("waitpid failed: %s", strerror(errno)); - if (WEXITSTATUS(wstatus) != 0) die("failed to execute get pkg attr script"); - - // read all bytes from pipe into attr buffer - ssize_t bytes_read = read(pipefds[0], info->attrs.buffer, sizeof(info->attrs.buffer)); - if (bytes_read < 0) die("failed to read from pipe: %s", strerror(errno)); - close(pipefds[0]); - - // read attributes from attr buffer - // attribute format: type (char), data value (null terminated str) - // todo(jqj): switch over to dynamically allocated buffer - for (int i = 0; i < bytes_read;) { - char atype = info->attrs.buffer[i]; - i++; - switch (atype) { - case 'i': - info->attrs.id = &info->attrs.buffer[i]; break; - case 'n': - info->attrs.name = &info->attrs.buffer[i]; break; - case 'd': - info->attrs.desc = &info->attrs.buffer[i]; break; - case 'w': - info->attrs.webpage = &info->attrs.buffer[i]; break; - case 'l': - info->attrs.license = &info->attrs.buffer[i]; break; - case 'v': - info->attrs.version = &info->attrs.buffer[i]; break; - } - for(;info->attrs.buffer[i] && i < bytes_read; i++); - i++; - } - - return true; -} - -void run_package_install(const package_info_t* info) { - - // PIPE and FORK to run install script - int pipefds[2] = {0}; - if (pipe(pipefds) < 0) die("failed to pipe: %s", strerror(errno)); - pid_t pid = fork(); - if (pid < 0) die("failed to fork: %s", strerror(errno)); - - if (pid == 0) { - // child proc - // connect my stdout and stderr to the pipe - close(pipefds[0]); - dup2(pipefds[1], STDOUT_FILENO); - dup2(pipefds[1], STDERR_FILENO); - close(pipefds[1]); - // exec into install script - char* argv[] = { PKG_INST_CMD, info->script_path, NULL }; - char* envp[] = { NULL }; - execve(PKG_INST_CMD, argv, envp); - // what are you still doing here? - die("execve failed"); - } else { - // parent proc, close write end of pipe - close(pipefds[1]); - } - - // read from the pipe until EOF - char buf[4096]; - ssize_t bytes_read = 0; - while ((bytes_read = read(pipefds[0], buf, sizeof(buf))) > 0) { - // print build out, can log and stuff in future - printf("%.*s", (int)bytes_read, buf); - } - if (bytes_read < 0) die("failed to read from pipe: %s", strerror(errno)); - close(pipefds[0]); - - // reap the child to finish - int wstatus; - if (waitpid(pid, &wstatus, 0) < 0) die("waitpid failed: %s", strerror(errno)); - if (WEXITSTATUS(wstatus) != 0) die("failed to execute install script"); -} diff --git a/snag/pkg_db.h b/snag/pkg_db.h deleted file mode 100644 index 0d34a61..0000000 --- a/snag/pkg_db.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef PKG_DB_H -#define PKG_DB_H - -#include - -// loads and allocates package attributes for the info struct. does not do -// excessive validation, so this should be fine when loading lots of package -// data -typedef struct { - char* id; - char* name; - char* desc; - char* webpage; - char* license; - char* version; - char buffer[4069]; -} package_attrs_t; - -typedef struct { - char* script_path; - package_attrs_t attrs; -} package_info_t; - -bool load_package_info(char* pkgid, package_info_t* info); -void run_package_install(const package_info_t* info); - -#endif diff --git a/snag/run_pkg_install.sh b/snag/run_pkg_install.sh deleted file mode 100755 index d48d831..0000000 --- a/snag/run_pkg_install.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -# reads a package description script and runs the configure, build, and install -# steps. intended only for use by snag program -if [ -z "$1" ]; then - echo "Usage: $0 " >&2 - exit 1 -fi - -# load the description script -. $1 - -# run all install steps for package -configure -build -install diff --git a/snag/scripts/get_pkg_attrs.sh b/snag/scripts/get_pkg_attrs.sh new file mode 100755 index 0000000..25f9177 --- /dev/null +++ b/snag/scripts/get_pkg_attrs.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# reads a package description script and outputs the attributes in a format easy +# for the snag program to read. it does not do any other validation, and should +# not be used by other programs + +if [ -z "$1" ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +# load the description script +. $1 + +# output package attributes (type char, value, null delimiter) +echoatt() { [ -n "$2" ] && printf "$1$2\0"; } +echoatt i "$pkg_id" +echoatt n "$pkg_name" +echoatt d "$pkg_desc" +echoatt w "$pkg_webpage" +echoatt l "$pkg_license" +echoatt v "$pkg_version" diff --git a/snag/scripts/run_pkg_install.sh b/snag/scripts/run_pkg_install.sh new file mode 100755 index 0000000..d48d831 --- /dev/null +++ b/snag/scripts/run_pkg_install.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# reads a package description script and runs the configure, build, and install +# steps. intended only for use by snag program +if [ -z "$1" ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +# load the description script +. $1 + +# run all install steps for package +configure +build +install diff --git a/snag/src/libargs/args.c b/snag/src/libargs/args.c new file mode 100644 index 0000000..b004525 --- /dev/null +++ b/snag/src/libargs/args.c @@ -0,0 +1,1416 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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/src/libargs/args.h b/snag/src/libargs/args.h new file mode 100644 index 0000000..ea91142 --- /dev/null +++ b/snag/src/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 + +// ----------------------------------------------------------------------------- +// 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/src/libargs/license.txt b/snag/src/libargs/license.txt new file mode 100644 index 0000000..d5710a2 --- /dev/null +++ b/snag/src/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/src/libargs/readme.txt b/snag/src/libargs/readme.txt new file mode 100644 index 0000000..d725590 --- /dev/null +++ b/snag/src/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/src/main.c b/snag/src/main.c new file mode 100644 index 0000000..0429fda --- /dev/null +++ b/snag/src/main.c @@ -0,0 +1,52 @@ +#include + +#include "util.h" +#include "libargs/args.h" +#include "pkg_db.h" + +int cmd_install(char* cmd_name, ArgParser* parser); + +int main(int argc, char* argv[]) { + + // create argument parser + ArgParser* parser = ap_new_parser(); + if (parser == NULL) die("failed to initialize cli parsing"); + ap_set_helptext(parser, "Usage: snag "); + ap_set_version(parser, "0.0"); + + // add install subcommand + ArgParser* install = ap_new_cmd(parser, "install inst i"); + if (install == NULL) die("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)) die("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)) die("failed to parse arguments"); + cmd_install("install", install); + } + + ap_free(parser); + return 0; +} + +int cmd_install(char* cmd_name, ArgParser* parser) { + + // install each package specified + for (int i = 0; i < ap_count_args(parser); i++) { + if (i != 0) printf("\n"); + + // load package description + char* pkgid = ap_get_arg_at_index(parser, i); + package_info_t info; + if (load_package_info(pkgid, &info)) { + printf("Installing %s - %s\n", info.attrs.name, info.attrs.desc); + run_package_install(&info); + } + } + + return 0; +} diff --git a/snag/src/pkg_db.c b/snag/src/pkg_db.c new file mode 100644 index 0000000..c35e47b --- /dev/null +++ b/snag/src/pkg_db.c @@ -0,0 +1,157 @@ +#include "pkg_db.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include + +#define PKG_INFO_CMD "./scripts/get_pkg_attrs.sh" +#define PKG_INST_CMD "./scripts/run_pkg_install.sh" + +// note: fixed location of package database for now +const char pkg_db[] = "../packages"; +const size_t pkg_db_len = sizeof(pkg_db) - 1; + +bool load_package_info(char* pkgid, package_info_t* info) { + + memset(info, 0, sizeof(*info)); + size_t pkgid_len = strlen(pkgid); + + // construct package description directory path and ensure it exists + // pkg_dir = "pkg_db/pkgid" + size_t pkg_dir_len = pkg_db_len + pkgid_len + 1; + char* pkg_dir = emalloc(pkg_dir_len + 1, "pkg_dir"); + { + memcpy(pkg_dir, pkg_db, pkg_db_len); + pkg_dir[pkg_db_len] = '/'; + memcpy(pkg_dir + pkg_db_len + 1, pkgid, pkgid_len); + pkg_dir[pkg_dir_len] = '\0'; + if (access(pkg_dir, F_OK) != 0) { + print("Package '%s' not found\n", pkgid); + free(pkg_dir); + return false; + } + } + + // construct package description script path and ensure it exists + // script_path = "pkg_dir/pkgid.sh" + size_t script_path_len = pkg_dir_len + pkgid_len + 4; + info->script_path = emalloc(script_path_len + 1, "info->script_path"); + { + memcpy(info->script_path, pkg_dir, pkg_dir_len); + info->script_path[pkg_dir_len] = '/'; + memcpy(&info->script_path[pkg_dir_len + 1], pkgid, pkgid_len); + memcpy(&info->script_path[script_path_len - 3], ".sh", 3); + info->script_path[script_path_len] = '\0'; + free(pkg_dir); + if (access(info->script_path, F_OK) != 0) { + print("Package '%s' broken, doesn't have description script\n", pkgid); + free(info->script_path); + return false; + } + } + + // PIPE and FORK to run pkg attrs script + int pipefds[2] = {0}; + if (pipe(pipefds) < 0) die("failed to pipe: %s", strerror(errno)); + pid_t pid = fork(); + if (pid < 0) die("failed to fork: %s", strerror(errno)); + + if (pid == 0) { + // child proc + // connect my stdout to the pipe + close(pipefds[0]); + dup2(pipefds[1], STDOUT_FILENO); + close(pipefds[1]); + // exec into get attr script + char* argv[] = { PKG_INFO_CMD, info->script_path, NULL }; + char* envp[] = { NULL }; + execve(PKG_INFO_CMD, argv, envp); + // what are you still doing here? + die("CHILD - execve to %s failed: %s", PKG_INFO_CMD, strerror(errno)); + } else { + // parent proc, close write end of pipe + close(pipefds[1]); + } + + // wait for child to finish + int wstatus; + if (waitpid(pid, &wstatus, 0) < 0) die("waitpid failed: %s", strerror(errno)); + if (WEXITSTATUS(wstatus) != 0) die("the child failed to execute get_pkg_attr script"); + + // read all bytes from pipe into attr buffer + ssize_t bytes_read = read(pipefds[0], info->attrs.buffer, sizeof(info->attrs.buffer)); + if (bytes_read < 0) die("failed to read from pipe: %s", strerror(errno)); + close(pipefds[0]); + + // read attributes from attr buffer + // attribute format: type (char), data value (null terminated str) + // todo(jqj): switch over to dynamically allocated buffer + for (int i = 0; i < bytes_read;) { + char atype = info->attrs.buffer[i]; + i++; + switch (atype) { + case 'i': + info->attrs.id = &info->attrs.buffer[i]; break; + case 'n': + info->attrs.name = &info->attrs.buffer[i]; break; + case 'd': + info->attrs.desc = &info->attrs.buffer[i]; break; + case 'w': + info->attrs.webpage = &info->attrs.buffer[i]; break; + case 'l': + info->attrs.license = &info->attrs.buffer[i]; break; + case 'v': + info->attrs.version = &info->attrs.buffer[i]; break; + } + for(;info->attrs.buffer[i] && i < bytes_read; i++); + i++; + } + + return true; +} + +void run_package_install(const package_info_t* info) { + + // PIPE and FORK to run install script + int pipefds[2] = {0}; + if (pipe(pipefds) < 0) die("failed to pipe: %s", strerror(errno)); + pid_t pid = fork(); + if (pid < 0) die("failed to fork: %s", strerror(errno)); + + if (pid == 0) { + // child proc + // connect my stdout and stderr to the pipe + close(pipefds[0]); + dup2(pipefds[1], STDOUT_FILENO); + dup2(pipefds[1], STDERR_FILENO); + close(pipefds[1]); + // exec into install script + char* argv[] = { PKG_INST_CMD, info->script_path, NULL }; + char* envp[] = { NULL }; + execve(PKG_INST_CMD, argv, envp); + // what are you still doing here? + die("CHILD - execve to %s failed: %s", PKG_INST_CMD, strerror(errno)); + } else { + // parent proc, close write end of pipe + close(pipefds[1]); + } + + // read from the pipe until EOF + char buf[4096]; + ssize_t bytes_read = 0; + while ((bytes_read = read(pipefds[0], buf, sizeof(buf))) > 0) { + // print build out, can log and stuff in future + printf("%.*s", (int)bytes_read, buf); + } + if (bytes_read < 0) die("failed to read from pipe: %s", strerror(errno)); + close(pipefds[0]); + + // reap the child to finish + int wstatus; + if (waitpid(pid, &wstatus, 0) < 0) die("waitpid failed: %s", strerror(errno)); + if (WEXITSTATUS(wstatus) != 0) die("the child failed to execute install script"); +} diff --git a/snag/src/pkg_db.h b/snag/src/pkg_db.h new file mode 100644 index 0000000..0d34a61 --- /dev/null +++ b/snag/src/pkg_db.h @@ -0,0 +1,27 @@ +#ifndef PKG_DB_H +#define PKG_DB_H + +#include + +// loads and allocates package attributes for the info struct. does not do +// excessive validation, so this should be fine when loading lots of package +// data +typedef struct { + char* id; + char* name; + char* desc; + char* webpage; + char* license; + char* version; + char buffer[4069]; +} package_attrs_t; + +typedef struct { + char* script_path; + package_attrs_t attrs; +} package_info_t; + +bool load_package_info(char* pkgid, package_info_t* info); +void run_package_install(const package_info_t* info); + +#endif diff --git a/snag/src/util.c b/snag/src/util.c new file mode 100644 index 0000000..8cc9b18 --- /dev/null +++ b/snag/src/util.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include + +#include "util.h" + +#if !DEBUG +void log(const char* format, ...) { +#else +void _log(int line, const char* file, const char* format, ...) { + fprintf(stderr, "%s:%d: info: ", file, line); +#endif + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} + +#if !DEBUG +void die(const char* format, ...) { +#else +void _die(int line, const char* file, const char* format, ...) { + fprintf(stderr, "%s:%d: error: ", file, line); +#endif + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + exit(1); +} + +#if !DEBUG +void* emalloc(size_t size, char* alloc_reason) { + void* d = malloc(size); + if (d == NULL) _die("failed to allocate '%s': %s", alloc_reason, strerror(errno)); + return d; +} +#else +void* _emalloc(int line, const char* file, size_t size, char* alloc_reason) { + void* d = malloc(size); + if (d == NULL) _die(line, file, "failed to allocate '%s': %s", alloc_reason, strerror(errno)); + return d; +} +#endif + +char* vastrcat_(int dummy, ...) { + + va_list args; + va_list args2; + va_start(args, dummy); + va_copy(args2, args); + + // get len of output string + size_t len = 0; + char* arg = NULL; + while ((arg = va_arg(args, char*))) { + len += strlen(arg); + } + va_end(args); + + // allocate and build output + char* output = malloc(len + 1); + if (!output) return NULL; + char* p = output; + while ((arg = va_arg(args, char*))) { + size_t alen = strlen(arg); + memcpy(p, arg, alen); + p += alen; + } + output[len] = '\0'; + va_end(args2); + + return output; +} diff --git a/snag/src/util.h b/snag/src/util.h new file mode 100644 index 0000000..3813903 --- /dev/null +++ b/snag/src/util.h @@ -0,0 +1,31 @@ +#ifndef UTIL_H +#define UTIL_H + +#include +#include + +// utility for errors +#if !DEBUG + void log(const char* format, ...) __attribute__((format(printf, 1, 2))); + void die(const char* format, ...) __attribute__((format(printf, 1, 2))); + void* emalloc(size_t size, char* alloc_reason); +#else + #define log(fmt, ...) _log(__LINE__, __FILE__, fmt, ##__VA_ARGS__) + void _log(int line, const char* file, const char* format, ...) __attribute__((format(printf, 3, 4))); + + #define die(fmt, ...) _die(__LINE__, __FILE__, fmt, ##__VA_ARGS__) + void _die(int line, const char* file, const char* format, ...) __attribute__((format(printf, 3, 4))); + + #define emalloc(size, alloc_reason) _emalloc(__LINE__, __FILE__, size, alloc_reason) + void* _emalloc(int line, const char* file, size_t size, char* alloc_reason); +#endif + +// functions for outputting and displaying text +#define printout(fmt, ...) fprintf(stdout, fmt, ##__VA_ARGS__) +#define print(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) + +// string functions +char* vastrcat_(int dummy, ...); +#define vastrcat(...) vastrcat_(0, __VA_ARGS__, NULL) + +#endif diff --git a/snag/util.c b/snag/util.c deleted file mode 100644 index 9a962b0..0000000 --- a/snag/util.c +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include -#include - -#include "util.h" - -#if DEBUG -void _log(int line, const char* file, const char* format, ...) { - fprintf(stderr, "%s:%d: info: ", file, line); -#else -void _log(const char* format, ...) { -#endif - va_list args; - va_start(args, format); - vfprintf(stderr, format, args); - va_end(args); -} - -#if DEBUG -void _die(int line, const char* file, const char* format, ...) { - fprintf(stderr, "%s:%d: error: ", file, line); -#else -void _die(const char* format, ...) { -#endif - va_list args; - va_start(args, format); - vfprintf(stderr, format, args); - va_end(args); - exit(1); -} - - -void* emalloc(size_t size, char* alloc_reason) { - void* d = malloc(size); - if (d == NULL) die("Failed to allocate %s", alloc_reason); - return d; -} - -char* vastrcat_(int dummy, ...) { - - va_list args; - va_list args2; - va_start(args, dummy); - va_copy(args2, args); - - // get len of output string - size_t len = 0; - char* arg = NULL; - while ((arg = va_arg(args, char*))) { - len += strlen(arg); - } - va_end(args); - - // allocate and build output - char* output = malloc(len + 1); - if (!output) return NULL; - char* p = output; - while ((arg = va_arg(args, char*))) { - size_t alen = strlen(arg); - memcpy(p, arg, alen); - p += alen; - } - output[len] = '\0'; - va_end(args2); - - return output; -} diff --git a/snag/util.h b/snag/util.h deleted file mode 100644 index fbf9018..0000000 --- a/snag/util.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef UTIL_H -#define UTIL_H - -#include -#include - -// utility for errors -#if DEBUG - #define log(fmt, ...) _log(__LINE__, __FILE__, fmt, ##__VA_ARGS__) - void _log(int line, const char* file, const char* format, ...) __attribute__((format(printf, 3, 4))); - - #define die(fmt, ...) _die(__LINE__, __FILE__, fmt, ##__VA_ARGS__) - void _die(int line, const char* file, const char* format, ...) __attribute__((format(printf, 3, 4))); -#else - #define log(fmt, ...) _log(fmt, ##__VA_ARGS__) - void _log(const char* format, ...) __attribute__((format(printf, 1, 2))); - - #define die(fmt, ...) _die(fmt, ##__VA_ARGS__) - void _die(const char* format, ...) __attribute__((format(printf, 1, 2))); -#endif -void* emalloc(size_t size, char* alloc_reason); - -// string functions -char* vastrcat_(int dummy, ...); -#define vastrcat(...) vastrcat_(0, __VA_ARGS__, NULL) - -#endif diff --git a/todo.txt b/todo.txt index 54aac73..1d35394 100644 --- a/todo.txt +++ b/todo.txt @@ -1,7 +1,7 @@ SNAG - [x] make install subcommand actually run the install script - [x] multiple subcommand handling and proper argument parsing -- [ ] add output and error functions for regular program output, make emalloc also have file info, also gen sh to configure +- [x] add output and error functions for regular program output, make emalloc also have file info, also gen sh to configure - [ ] download packages to download db and verify hash - [ ] actually create install environment for packages in install script - [ ] snag api -- cgit v1.2.3