From 7c783adc6ecd173f0fe8db0d54edf67d6061be6e Mon Sep 17 00:00:00 2001
From: overflowerror <mail@overflowerror.com>
Date: Sat, 10 Feb 2018 18:12:47 +0100
Subject: [PATCH] getopt wrapper

---
 README.md     |   1 -
 src/example.c |  16 ++++-
 src/seaboot.c | 168 +++++++++++++++++++++++++++++++++++++++++++++++++-
 src/seaboot.h |  26 ++++++++
 4 files changed, 208 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 0fc8e93..a109c10 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,5 @@ The compiled files are in `./bin/`.
 
 ## TODO
 
-- automated getopt-wrapper (ev with help message generator)
 - simplified socket-management
 - simplified process-management (fork/exec, shm, semaphores, pthread, ...)
diff --git a/src/example.c b/src/example.c
index 067515a..479128a 100644
--- a/src/example.c
+++ b/src/example.c
@@ -2,6 +2,7 @@
 #include "seaboot.h"
 
 #include <stdio.h>
+#include <stdlib.h>
 #include <time.h>
 
 BOOT(init);
@@ -10,7 +11,20 @@ void init() {
 	printf("Hello World!\n");
 
 	boot.mode = WAIT;
-	boot.debug = true;
+	boot.debug = false;
+
+	int test = 0;
+	boot.options.add('t', "test", OPTIONAL_ARGUMENT, true, lambda(bool, (const char* argument) {
+		if (argument != NULL)
+			test = strtol(argument, NULL, 10);
+		return true;
+	}));
+	if (boot.options.parse() < 0) {
+		fprintf(stderr, "Error: %s\n", boot.error);
+		fprintf(stderr, "The only possible option is -t.\n");
+		exit(EXIT_ERROR);
+	}
+	printf("Test-value is %d.\n", test);
 
 	boot.events.addEventListener(SHUTDOWN, lambda(void, (event_t event) {
 		fprintf(stderr, "Shuting down.\n");
diff --git a/src/seaboot.c b/src/seaboot.c
index ace5a0a..b03f420 100644
--- a/src/seaboot.c
+++ b/src/seaboot.c
@@ -11,8 +11,21 @@
 #include <unistd.h>
 #include <time.h>
 #include <errno.h>
+#include <getopt.h>
+#include <assert.h>
 
 static const char* getEventName(event_t event) {
+	// TODO
+	if (IS_SIGNAL(event))
+		return strsignal(event);
+	switch(event) {
+		case SHUTDOWN: return "Shutdown"; break;
+		case LIBERROR: return "Lib-Error"; break;
+		default: return "Unknown"; break;
+	}
+}
+
+static const char* getEventDescription(event_t event) {
 	if (IS_SIGNAL(event))
 		return strsignal(event);
 	switch(event) {
@@ -45,11 +58,17 @@ static void eventHandler(event_t);
 static void* allocate(size_t);
 static void* reallocate(void*, size_t);
 
+static bool addOption(char, const char*, optionArgument_t, bool, optionHandler_t);
+static int parseOptions(void);
+static const char* getNextArgument();
+
 struct boot boot = {
 	.events.addEventListener = addEventListener,
 	.events.removeEventListener = removeEventListener,
 	.events.enableSignal = enableSignal,
 	.events.disableSignal = disableSignal,
+	.events.getName = getEventName,
+	.events.getDescription = getEventDescription,
 
 	.time.getRealTime = getRealTime,
 	.time.getRelativeTime = getRelativeTime,
@@ -63,6 +82,10 @@ struct boot boot = {
 	.time.stopTimer = stopTimer,
 	.time.deleteTimer = deleteTimer,
 
+	.options.add = addOption,
+	.options.parse = parseOptions,
+	.options.getNextArgument = getNextArgument,
+
 	.allocate = allocate,
 	.reallocate = reallocate,
 
@@ -332,7 +355,150 @@ void* reallocate(void* pointer, size_t size) {
 	return result;
 }
 
-int main(char** argv, int argc) {
+struct optionHandlers {
+	char shortOption;
+	const char* longOption;
+	optionArgument_t argument;
+	bool required;
+	optionHandler_t handler;
+	int seen;
+};
+
+int optionNumber = 0;
+struct optionHandlers options[MAX_OPTIONS];
+char** arguments;
+int argumentCount;
+
+bool addOption(char shortOption, const char* longOption, optionArgument_t argument, bool required, optionHandler_t handler) {
+	if ((shortOption == NO_SHORT_OPTION && longOption == NO_LONG_OPTION) || handler == NULL) {
+		boot.error = "Option settings are invalid.";
+		return false;
+	}
+
+	options[optionNumber++] = (struct optionHandlers) {
+		.shortOption = shortOption,
+		.longOption = longOption,
+		.argument = argument,
+		.required = required,
+		.handler = handler,
+		.seen = 0
+	};
+
+	return true;
+}
+
+int parseOptions() {
+	debug("Parseing options. Got %d options.\n", optionNumber);
+	if (optionNumber == 0)
+		return argumentCount - 1;
+
+	debug("Generating short-option-string & long-option-array.\n");
+	int number_short = 0;
+	char short_options[optionNumber * 3 + 1];
+	memset(short_options, 0, optionNumber * 3 + 1);
+	int number_long = 0;
+	struct option long_options[optionNumber + 1];
+	memset(long_options, 0, (optionNumber + 1) * sizeof(struct option));
+	for (int i = 0; i < optionNumber; i++) {
+		struct optionHandlers handler = options[i];
+		if (handler.shortOption != NO_SHORT_OPTION) {
+			short_options[number_short++] = handler.shortOption;
+			debug("Got short option: %c\n", handler.shortOption);
+			if (handler.argument == REQUIRED_ARGUMENT)
+				short_options[number_short++] = ':';
+			if (handler.argument == OPTIONAL_ARGUMENT) {
+				short_options[number_short++] = ':';
+				short_options[number_short++] = ':';
+			}
+		}
+		if (handler.longOption != NO_LONG_OPTION) {
+			int arg;
+			switch(handler.argument) {
+				case NO_ARGUMENT:
+					arg = no_argument;
+					break;
+				case OPTIONAL_ARGUMENT:
+					arg = optional_argument;
+					break;
+				case REQUIRED_ARGUMENT:
+					arg = required_argument;
+					break;
+				default:
+					assert(false);
+			}
+			debug("Got long option: %s (arg: %d, short: %c)\n", handler.longOption, arg, handler.shortOption);
+			long_options[number_long++] = (struct option) {
+				.name = handler.longOption, 
+				.has_arg = arg, 
+				.flag = NULL, 
+				.val = handler.shortOption
+			};
+		}
+	}
+
+	long_options[number_long] = (struct option) {
+		.name = NULL, 
+		.has_arg = 0, 
+		.flag = NULL, 
+		.val = 0
+	};
+	
+	int tmp, option_index;
+	debug("getopt_long(%d, %x, %s, %x, %x)\n", argumentCount, arguments, short_options, long_options, option_index);
+	while((tmp = getopt_long(argumentCount, arguments, short_options, long_options, &option_index)) != -1) {
+		debug("getopt: %d\n", tmp);
+		struct optionHandlers* handler = NULL;
+		if (tmp == '?') {
+			boot.error = "Unknown option.";
+			return OPTION_UNKNOWN;
+		} else if (tmp == NO_SHORT_OPTION) {
+			for (int i = 0; i < optionNumber; i++) {
+				if (strcmp(options[i].longOption, long_options[option_index].name) == 0) {
+					handler = &(options[i]);
+					break;
+				} 
+			}
+			if (handler == NULL)
+				assert(false);
+		} else {
+			for (int i = 0; i < optionNumber; i++) {
+				debug("Checking %c (%d) - %c (%d).\n", tmp, tmp, options[i].shortOption, options[i].shortOption);
+				if (tmp == options[i].shortOption) {
+					handler = &(options[i]);
+					break;
+				}
+			}
+			if (handler == NULL)
+				assert(false);
+		}
+
+		handler->seen++;
+		debug("Starting handler for %s (%c).\n", handler->longOption, (handler->shortOption == NO_SHORT_OPTION) ? '-' : handler->shortOption);
+		if (!(handler->handler(optarg))) {
+			boot.error = "Option handler returned an error.";
+			return OPTION_HANDLER_ERROR;
+		}
+	}
+
+	for (int i = 0; i < optionNumber; i++) {
+		if (options[i].seen == 0 && options[i].required) {
+			boot.error = "Required option is missing.";
+			return OPTION_MISSING;
+		}
+	}
+
+	return argumentCount - optind;
+}
+
+const char* getNextArgument() {
+	if (argumentCount == optind)
+		return NULL;
+	return arguments[optind++];
+}
+
+int main(int argc, char** argv) {
+	arguments = argv;
+	argumentCount = argc;
 	for(int i = 0; i < NUMBER_OF_EVENTS; i++) {		
 		debug("Setup %s %d (%s)...\n", IS_SIGNAL(i) ? "signal" : "event", i, getEventName(i));
 		events[i].number = 0;
diff --git a/src/seaboot.h b/src/seaboot.h
index 8e200aa..b54e44a 100644
--- a/src/seaboot.h
+++ b/src/seaboot.h
@@ -14,6 +14,8 @@
 
 #define IS_SIGNAL(v) (v >= 1 && v <= 31)
 
+#define MAX_OPTIONS 20
+
 typedef unsigned long long int nstime_t;
 
 typedef enum bootmode {
@@ -72,6 +74,8 @@ struct events {
 	bool (*removeEventListener)(event_t, eventListener_t);
 	bool (*enableSignal)(event_t);
 	bool (*disableSignal)(event_t);
+	const char* (*getName)(event_t event);
+	const char* (*getDescription)(event_t event);
 };
 
 struct time {
@@ -89,12 +93,34 @@ struct time {
 	void (*deleteTimer)(timer_t);
 };
 
+#define OPTION_UNKNOWN (-1)
+#define OPTION_MISSING (-2)
+#define OPTION_HANDLER_ERROR (-3)
+
+#define NO_SHORT_OPTION 0
+#define NO_LONG_OPTION NULL
+
+typedef enum optionArgument {
+	NO_ARGUMENT,
+	OPTIONAL_ARGUMENT,
+	REQUIRED_ARGUMENT
+} optionArgument_t;
+
+typedef bool (*optionHandler_t)(const char*);
+
+struct options {
+	bool (*add)(char, const char*, optionArgument_t, bool, optionHandler_t);
+	int (*parse)(void);
+	const char* (*getNextArgument)();
+};
+
 extern struct boot {
 	init_t init;
 	loop_t loop;
 	
 	struct events events;
 	struct time time;
+	struct options options;
 
 	void* (*allocate)(size_t);
 	void* (*reallocate)(void*, size_t);