--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "opts.h"
+
+/* Type and Function Declarations
+ *****************************************************************************/
+typedef struct {
+ char* name;
+ char* tag;
+ char* value;
+} option_t;
+
+typedef struct entry_t {
+ void* value;
+ struct entry_t* next;
+} entry_t;
+
+typedef enum { LONG, SHORT } opt_type_t;
+
+typedef struct {
+ unsigned int line_idx;
+ unsigned int col_idx;
+ unsigned int arg_count;
+ char** arg_vect;
+ int current;
+ opts_cfg_t* options;
+} stream_ctx_t;
+
+static void opts_parse_short_option( stream_ctx_t* ctx );
+static void opts_parse_long_option( stream_ctx_t* ctx );
+static char* opts_parse_optarg(stream_ctx_t* ctx, char* opt_name);
+static void opts_parse_argument( stream_ctx_t* ctx );
+static void opts_parse_error(const char* msg, char* opt_name);
+static opts_cfg_t* opts_get_option_config( opts_cfg_t* opts, opt_type_t typ, char* name );
+static char* opts_next_token( stream_ctx_t* ctx );
+static void opts_consume_ws( stream_ctx_t* ctx );
+static char opts_next_char( stream_ctx_t* ctx );
+static char* opts_append_char( char* str, char ch );
+static char* strclone(const char* p_old);
+static void opts_add_option(char* name, char* tag, char* arg);
+static void opts_add_argument(char* arg);
+
+/* Global State
+ *****************************************************************************/
+static const char* Program_Name = NULL;
+static entry_t* Options = NULL;
+static entry_t* Arguments = NULL;
+static opts_err_cbfn_t Error_Callback = &opts_parse_error;
+
+/* The Options Parser
+ *****************************************************************************/
+void opts_parse(opts_cfg_t* opts, opts_err_cbfn_t err_cb, int argc, char** argv) {
+ /* Setup the stream */
+ stream_ctx_t ctx;
+ ctx.line_idx = 0;
+ ctx.col_idx = -1;
+ ctx.arg_count = argc-1;
+ ctx.arg_vect = &argv[1];
+ ctx.options = opts;
+ (void)opts_next_char( &ctx ); /* Loads up the first char */
+
+ /* Record the error handler if one was provided */
+ if (NULL != err_cb)
+ Error_Callback = err_cb;
+
+ /* Record the program name */
+ Program_Name = argv[0];
+
+ /* Until we run out of characters */
+ while (ctx.current != EOF) {
+ opts_consume_ws( &ctx );
+ /* If we have an option */
+ if ('-' == ctx.current) {
+ /* And it's a long one */
+ if ('-' == opts_next_char( &ctx )) {
+ /* Consume the second '-' */
+ opts_next_char( &ctx );
+ /* Parse the option */
+ opts_parse_long_option( &ctx );
+ } else {
+ /* Parse the option */
+ opts_parse_short_option( &ctx );
+ }
+ } else {
+ /* It's not an option so add it to the "extra" bucket */
+ opts_parse_argument( &ctx );
+ }
+ }
+}
+
+static void opts_parse_short_option( stream_ctx_t* ctx ) {
+ char opt[2] = { ctx->current, '\0' };
+ char* opt_name = strclone(opt);
+ opts_cfg_t* config = opts_get_option_config( ctx->options, SHORT, opt_name );
+ if (config != NULL) {
+ char* opt_arg = NULL;
+ (void)opts_next_char( ctx );
+ /* Check if the flag has an argument */
+ if (config->has_arg)
+ opt_arg = opts_parse_optarg( ctx, opt_name );
+ /* If there are more flags in the flag group */
+ else if ((' ' != ctx->current) && (EOF != ctx->current))
+ opts_parse_short_option( ctx );
+ opts_add_option( opt_name, config->tag, opt_arg );
+ } else {
+ Error_Callback("Unknown Option", opt_name);
+ }
+}
+
+static void opts_parse_long_option( stream_ctx_t* ctx ) {
+ char* opt_name = opts_next_token( ctx );
+ opts_cfg_t* config = opts_get_option_config( ctx->options, LONG, opt_name );
+ if (config != NULL) {
+ char* opt_arg = NULL;
+ /* Parse the argument if one is expected */
+ if (config->has_arg)
+ opt_arg = opts_parse_optarg( ctx, opt_name );
+ /* Store off the option value */
+ opts_add_option( opt_name, config->tag, opt_arg );
+ } else {
+ Error_Callback("Unknown Option", opt_name);
+ }
+}
+
+static char* opts_parse_optarg(stream_ctx_t* ctx, char* opt_name) {
+ opts_consume_ws( ctx );
+ if (('-' == ctx->current) || (EOF == ctx->current))
+ Error_Callback("Expected an argument, none received", opt_name);
+ return opts_next_token( ctx );
+}
+
+static void opts_parse_error(const char* msg, char* opt_name) {
+ fprintf(stderr, "Option '%s' : %s\n", opt_name, msg);
+ free(opt_name);
+ opts_reset();
+ exit(1);
+}
+
+static void opts_parse_argument( stream_ctx_t* ctx ) {
+ char* arg_val = opts_next_token( ctx );
+ if (NULL != arg_val)
+ opts_add_argument(arg_val);
+}
+
+static opts_cfg_t* opts_get_option_config( opts_cfg_t* opts, opt_type_t type, char* name ) {
+ opts_cfg_t* cfg = NULL;
+ int i = 0;
+ while( opts[i].name != NULL ) {
+ opt_type_t curr_type = (strlen(opts[i].name) > 1) ? LONG : SHORT;
+ if ((curr_type == type) && (0 == strcmp(opts[i].name, name))) {
+ cfg = &(opts[i]);
+ break;
+ }
+ i++;
+ }
+ return cfg;
+}
+
+static char* opts_next_token( stream_ctx_t* ctx ) {
+ char* tok = NULL;
+
+ opts_consume_ws( ctx );
+ if (EOF != ctx->current) {
+ // Setup the string
+ tok = (char*)malloc(2);
+ tok[0] = ctx->current;
+ tok[1] = '\0';
+
+ (void)opts_next_char( ctx );
+ while ((EOF != ctx->current) && (' ' != ctx->current)) {
+ tok = opts_append_char( tok, ctx->current );
+ (void)opts_next_char( ctx );
+ }
+ }
+ return tok;
+}
+
+static void opts_consume_ws( stream_ctx_t* ctx ) {
+ while (' ' == ctx->current)
+ (void)opts_next_char( ctx );
+}
+
+static char opts_next_char( stream_ctx_t* ctx ) {
+ char current = EOF;
+ ctx->current = EOF;
+
+ if (ctx->line_idx < ctx->arg_count) {
+ ctx->col_idx++;
+ if (ctx->line_idx < ctx->arg_count) {
+ char temp = ctx->arg_vect[ ctx->line_idx ][ ctx->col_idx ];
+ if (temp == '\0') {
+ ctx->col_idx = -1;
+ ctx->line_idx++;
+ current = ' ';
+ ctx->current = current;
+ } else {
+ temp = (temp == '=') ? ' ' : temp;
+ current = temp;
+ ctx->current = current;
+ }
+ }
+ }
+
+ return current;
+}
+
+static char* opts_append_char( char* str, char ch ) {
+ unsigned int new_size = strlen( str ) + 2;
+ str = (char*)realloc( str, new_size );
+ str[ new_size - 2 ] = ch;
+ str[ new_size - 1 ] = '\0';
+ return str;
+}
+
+static void opts_add_option(char* name, char* tag, char* arg) {
+ option_t* option = (option_t*)malloc(sizeof(option_t));
+ option->name = name;
+ option->tag = strclone(tag);
+ option->value = (NULL == arg) ? name : arg;
+ entry_t* entry = (entry_t*)malloc(sizeof(entry_t));
+ entry->value = (void*)option;
+ entry->next = Options;
+ Options = entry;
+}
+
+static void opts_add_argument(char* arg_val) {
+ entry_t* entry = (entry_t*)malloc(sizeof(entry_t));
+ entry->value = (void*)arg_val;
+ entry->next = Arguments;
+ Arguments = entry;
+}
+
+/* Parser Cleanup
+ *****************************************************************************/
+void opts_reset(void) {
+ while (Options != NULL) {
+ entry_t* entry = Options;
+ option_t* opt = (option_t*)entry->value;
+ Options = entry->next;
+ free(opt->name);
+ free(opt->tag);
+ if(opt->name != opt->value)
+ free(opt->value);
+ free(opt);
+ free(entry);
+ }
+
+ while (Arguments != NULL) {
+ entry_t* entry = Arguments;
+ char* arg = (char*)entry->value;
+ Arguments = entry->next;
+ free(arg);
+ free(entry);
+ }
+}
+
+/* Utility Functions
+ *****************************************************************************/
+static char* strclone(const char* p_old) {
+ size_t length = strlen(p_old);
+ char* p_str = (char*)malloc(length+1);
+ memcpy(p_str, p_old, length);
+ p_str[length] = '\0';
+ return p_str;
+}
+
+/* Query Functions
+ *****************************************************************************/
+static option_t* find_option(const char* name, const char* tag) {
+ option_t* p_opt = NULL;
+ entry_t* current = Options;
+ while (current != NULL) {
+ option_t* curr_opt = (option_t*)current->value;
+ if (((NULL == name) || (0 == strcmp(name, curr_opt->name))) &&
+ ((NULL == tag) || (0 == strcmp(tag, curr_opt->tag)))) {
+ p_opt = curr_opt;
+ break;
+ }
+ current = current->next;
+ }
+ return p_opt;
+}
+
+bool opts_is_set(const char* name, const char* tag) {
+ return (NULL != find_option(name,tag));
+}
+
+const char* opts_get_value(const char* name, const char* tag) {
+ option_t* p_opt = find_option(name,tag);
+ return (NULL == p_opt) ? NULL : p_opt->value;
+}
+
+bool opts_equal(const char* name, const char* tag, const char* value) {
+ return (0 == strcmp(value, opts_get_value(name,tag)));
+}
+
+const char** opts_select(const char* name, const char* tag) {
+ size_t index = 0;
+ const char** ret = (const char**)malloc(sizeof(const char*));
+ ret[index] = NULL;
+
+ entry_t* current = Options;
+ while (current != NULL) {
+ option_t* curr_opt = (option_t*)current->value;
+ if (((NULL == name) || (0 == strcmp(name, curr_opt->name))) &&
+ ((NULL == tag) || (0 == strcmp(tag, curr_opt->tag)))) {
+ ret = (const char**)realloc(ret, (index+2)*sizeof(const char*));
+ ret[index++] = curr_opt->value;
+ ret[index] = NULL;
+ }
+ current = current->next;
+ }
+
+ return ret;
+}
+
+const char** opts_arguments(void) {
+ size_t index = 0;
+ const char** ret = (const char**)malloc(sizeof(const char*));
+ ret[0] = NULL;
+ entry_t* entry = Arguments;
+ while (NULL != entry) {
+ ret = (const char**)realloc(ret, (index+2)*sizeof(const char*));
+ ret[index++] = (const char*)entry->value;
+ ret[index] = NULL;
+ entry = entry->next;
+ }
+ return ret;
+}
+
+const char* opts_prog_name(void) {
+ return Program_Name;
+}
+
+/* Help Message Printing
+ *****************************************************************************/
+static int opts_calc_padding(opts_cfg_t* opts) {
+ bool opts_have_args = false;
+ size_t sz = 0;
+ /* Figure out the longest option name */
+ while (NULL != opts->name) {
+ size_t name_sz = strlen(opts->name);
+ if (name_sz > sz) {
+ sz = name_sz;
+ }
+ if (opts->has_arg) {
+ opts_have_args = true;
+ }
+ opts++;
+ }
+ return sz + 4 + ((opts_have_args) ? 4 : 0);
+}
+
+void opts_print_help(FILE* ofile, opts_cfg_t* opts) {
+ int padding = opts_calc_padding(opts);
+ char* buffer = (char*)malloc(padding+1);
+ while (NULL != opts->name) {
+ if (1 == strlen(opts->name))
+ sprintf(buffer, " -%s", opts->name);
+ else
+ sprintf(buffer, " --%s", opts->name);
+ if (opts->has_arg) sprintf(&buffer[strlen(buffer)], "=ARG ");
+ fprintf(ofile, "%-*s%s\n", padding, buffer, opts->desc);
+ opts++;
+ }
+ free(buffer);
+}
+
--- /dev/null
+/**
+ * @file
+ * @author Mike D. Lowis
+ *
+ * This project is an implementation of a very lightweight command-line options
+ * parser. It is capable of interpreting the Unix/GNU style command line
+ * argument flags. It is designed to be platform independent and can be used as
+ * a static library or directly as source.
+ *
+ * The library is designed to work like something of a database for your
+ * command line. At initialization time you feed in the arguments vector (your
+ * data records) and a list of option definitions (your schema). The library
+ * then parses the options and stores them internally for querying later.
+ * Several querying functions are provided that cover the most common use cases
+ * for options handling.
+ *
+ * All of the query functions take an option name and an option tag as
+ * parameters. These two arguments will be used to search the list of parsed
+ * options for any that match both parameters. In some cases only one of the
+ * parameters is needed for a query. To facilitate this, the query functions
+ * will interpret a NULL pointer to match everything. An example of this would
+ * be selecting all parsed options with the tag "foo":
+ *
+ * const char** foo_lst = opts_select(NULL,"foo");
+ *
+ * In the example above, the NULL would match *all* parsed options and from that
+ * group only those having the tag "foo" would be returned. Another common
+ * scenario would be searching by parameter name:
+ *
+ * const char** includes = opts_select("I", NULL);
+ *
+ * In this scenario all of the options that were parsed having the name 'I'
+ * would be returned regardless of their tag value. This would equate to all of
+ * the instances of '-I' that would occur on the command line for an invocation
+ * of gcc.
+ *
+ * With this design you should be able to let the library handle your options
+ * parsing while you focus on your application logic using appropriate queries
+ * to change behavior where necessary.
+ *
+ */
+#ifndef OPTS_H
+#define OPTS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+
+/** Structure representing an option to be parsed */
+typedef struct {
+ /** The name of the option as it will appear on the command line. If the
+ * name is a single character in length the parse will treat it as a short
+ * option and require a single dash. Otherwise, it is considered a long
+ * option and will require double dashes */
+ char* name;
+ /** Flag indicating whether the option expects an argument or not */
+ bool has_arg;
+ /** An optional tag which can be used to group related options together
+ * logically */
+ char* tag;
+ /** A short description of the flag used for displaying help messages */
+ char* desc;
+} opts_cfg_t;
+
+typedef void (*opts_err_cbfn_t)(const char* msg, char* opt_name);
+
+/**
+ * Parse the command line options using the provided option definition list.
+ *
+ * @param opts Pointer to a list of option definitions
+ * @param argc The number of arguments in the vector
+ * @param argv The vector of command line arguments
+ */
+void opts_parse(opts_cfg_t* opts, opts_err_cbfn_t err_cb, int argc, char** argv);
+
+/**
+ * Resets the global state back to defaults. This includes freeing any
+ * allocated memory and clearing any saved pointers to NULL.
+ */
+void opts_reset(void);
+
+/**
+ * Determines if a given option is set. This function searches the list of
+ * parsed options for an entry with the given name and/or the given tag. A value
+ * of NULL for either parameter will match everything.
+ *
+ * @param name The name of the option to find.
+ * @param tag The tag of the option to find.
+ *
+ * @return true if an entry was found, false otherwise.
+ */
+bool opts_is_set(const char* name, const char* tag);
+
+/**
+ * Searches for the last received option with the given name and/or tag and
+ * does a string comparison of it's value. The parsed options are stored
+ * internally in reverse order so the first match equates to the last option
+ * that the parser saw on the command line. The value returned is the text of
+ * any argument that was received for the option or the name of the option
+ * itself if the option does not take an argument.
+ *
+ * @param name The name of the option to find.
+ * @param tag The tag of the option to find.
+ * @param value The value to compare.
+ *
+ * @return true if the value matches exactly, false otherwise.
+ */
+bool opts_equal(const char* name, const char* tag, const char* value);
+
+/**
+ * Search for a parsed option value with the given name and/or tag. If multiple
+ * matches are found, only the first match is found. The parsed options are
+ * stored internally in reverse order so the first match equates to the last
+ * option that the parser saw on the command line. The value returned is the
+ * text of any argument that was received for the option or the name of the
+ * option itself if the option does not take an argument.
+ *
+ * @param name The name of the option to search for.
+ * @param tag The tag of the option to search for.
+ *
+ * @return The text of the value of the parsed option.
+ */
+const char* opts_get_value(const char* name, const char* tag);
+
+/**
+ * Search for a group of parsed option values with the given name and/or tag.
+ * The value returned for each matching option is the text of the argument that
+ * was received for the option. If the option does not expect an argument then
+ * the value is returned as the name of the option itself.
+ *
+ * @param name The name of the options to search for.
+ * @param tag The tag of the options to search for.
+ *
+ * @return Pointer to the array of options.
+ */
+const char** opts_select(const char* name, const char* tag);
+
+/**
+ * Returns a null terminated array of strings representing the arguments of the
+ * executable. These are the entries provided on the command line that are not
+ * parsed as options. A common example would be the list of files passed to the
+ * unix "cat" command.
+ *
+ * @return Pointer to the array of arguments.
+ */
+const char** opts_arguments(void);
+
+/**
+ * Returns the program name as received on the command line.
+ *
+ * @return String representing the program name.
+ */
+const char* opts_prog_name(void);
+
+/**
+ * Prints out the options and their descriptions in a tabular format to the
+ * given file handle.
+ *
+ * @param ofile The file handle to use for output.
+ * @param opts The list of option definitions.
+ */
+void opts_print_help(FILE* ofile, opts_cfg_t* opts);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif