]> git.mdlowis.com Git - projs/opts.git/commitdiff
Version 0.1 complete and fully unit tested
authorMichael D. Lowis <mike@mdlowis.com>
Mon, 4 Jun 2012 00:11:28 +0000 (20:11 -0400)
committerMichael D. Lowis <mike@mdlowis.com>
Mon, 4 Jun 2012 00:11:28 +0000 (20:11 -0400)
Makefile
README.md
source/opts.c
source/opts.h
tests/test_opts.cpp

index eaa613bfb49e9d1275ef46730dbe9a73e35cc9ef..fe8b5909e5835ff9134701d420fd0df25f15ce1f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -66,7 +66,7 @@ INC_DIRS = $(call incdirs, $(SRC_ROOT)) \
 # Compiler and Linker Options
 #----------------------------
 CXXFLAGS      = $(INC_DIRS) -Wall -fPIC
-TEST_CXXFLAGS = $(INC_DIRS) -Wall
+TEST_CXXFLAGS = $(INC_DIRS)
 ARFLAGS       = rcs
 
 # Build Rules
index 5846355bed616f6f7f3884e4919c714eb08a50c8..1488c583aeba4b836e5bd6431e2de07b86c9a742 100644 (file)
--- a/README.md
+++ b/README.md
@@ -8,17 +8,32 @@ Opts
 About This Project
 ----------------------------------------------
 
+This project is an implementation of a very simple command-line options parser.
+It is capable of interpreting the Unix/GNU style command line argument flags.
+
 License
 ----------------------------------------------
+Unless explicitly stated otherwise, all code and documentation contained within
+this repository is released under the BSD 2-Clause license. The text for this
+license can be found in the LICENSE.md file.
 
 Requirements For Building
 ----------------------------------------------
 
+ * GNU Make
+ * Unix sed and find utilities
+ * A C and C++ compiler such as GCC
+
 Build Instructions
 ----------------------------------------------
 
-Installation
-----------------------------------------------
+You can build the project and run all unit tests with the following command:
+
+    make
+
+You can execute just the unit tests with the following command:
+
+    make test
 
 Project Files and Directories
 ----------------------------------------------
@@ -31,15 +46,3 @@ Project Files and Directories
     LICENSE.md     The software license notification.
     README.md      You're reading this file right now!
 
-Known Issues or Bugs
-----------------------------------------------
-
-Version History
-----------------------------------------------
-
-Feature Wish List
-----------------------------------------------
-
-More Info
-----------------------------------------------
-
index db41d0fe60ef08f41de791639b393c5472f549a8..9c81f490a9be67a7dbf13d9ba267d7b30bb80198 100644 (file)
@@ -13,30 +13,31 @@ Result_T* OPTS_ParseOptions( OptionConfig_T* opts, int argc, char** argv )
     // Until we run out of characters
     while( ctx.current != EOF )
     {
-        // Get rid of any whitespace
-        while ( ' ' == ctx.current )
-        {
-            (void)OPTS_NextCharacter( &ctx );
-        }
+        OPTS_ConsumeWhitespace( &ctx );
 
         // If we have an option
         if ( '-' == ctx.current )
         {
-            OPTS_ParseShortOption( &ctx );
-            //// And its a long one
-            //if( '-' == OPTS_NextCharacter( &ctx ) )
-            //{
-            //    (void)OPTS_ParseLongOption( &ctx );
-            //}
-            //// or a short one
-            //else
-            //{
-            //}
+            // And it's a long one
+            if( '-' == OPTS_NextCharacter( &ctx ) )
+            {
+                // Consume the second '-'
+                OPTS_NextCharacter( &ctx );
+
+                // Parse the option
+                OPTS_ParseLongOption( &ctx );
+            }
+            // Or a short one
+            else
+            {
+                // Parse the option
+                OPTS_ParseShortOption( &ctx );
+            }
         }
         // Or we have a floating argument
         else
         {
-            (void)OPTS_ParseArgument( &ctx );
+            OPTS_ParseArgument( &ctx );
         }
     }
 
@@ -65,31 +66,80 @@ void OPTS_InitContext( StreamContext_T* ctx, int argc, char** argv )
 void OPTS_ParseShortOption( StreamContext_T* ctx )
 {
     // Get Config
-    // if( current != ' ' )
-    // {
-    //     Get Config
-    //     if( config != NULL )
-    //     {
-    //         Save option
-    //         if( has arg )
-    //         {
-    //             Save arg
-    //         }
-    //         Add option to list
-    //     }
-    //     else
-    //     {
-    //         Exit: "Unknown option: %c\n"
-    //     }
-    // }
+    char opt_name[2] = { ctx->current, '\0' };
+    OptionConfig_T* config = OPTS_GetOptConfig( ctx->options, SHORT, opt_name );
+    if( config != NULL )
+    {
+        char* opt_arg = NULL;
+        (void)OPTS_NextCharacter( ctx );
+        if( 1 == config->has_arg )
+        {
+            OPTS_ConsumeWhitespace( ctx );
+            if( ('-' == ctx->current) || (EOF  == ctx->current) )
+            {
+                printf("Expected an argument for option '%s'\n", opt_name);
+                exit(1);
+            }
+            opt_arg = OPTS_NextToken( ctx );
+        }
+        else if( (' ' != ctx->current) && (EOF != ctx->current) )
+        {
+            OPTS_ParseShortOption( ctx );
+        }
+        OPTS_AddOption( ctx->results, opt_name, opt_arg );
+    }
+    else
+    {
+        printf("Unknown Option '%s'\n", opt_name);
+        exit(1);
+    }
 }
 
 void OPTS_ParseLongOption( StreamContext_T* ctx )
 {
+    char* opt_name = OPTS_NextToken( ctx );
+    OptionConfig_T* config = OPTS_GetOptConfig( ctx->options, LONG, opt_name );
+    if( config != NULL )
+    {
+        char* opt_arg = NULL;
+        if( 1 == config->has_arg )
+        {
+            OPTS_ConsumeWhitespace( ctx );
+            if( ('-' == ctx->current) || (EOF == ctx->current) )
+            {
+                printf("Expected an argument for option '%s'\n", opt_name);
+                exit(1);
+            }
+            opt_arg = OPTS_NextToken( ctx );
+        }
+        OPTS_AddOption( ctx->results, opt_name, opt_arg );
+    }
+    else
+    {
+        printf("Unknown Option '%s'\n", opt_name);
+        exit(1);
+    }
 }
 
 void OPTS_ParseArgument( StreamContext_T* ctx )
 {
+    char* arg_val = OPTS_NextToken( ctx );
+    if( NULL != arg_val )
+    {
+        Argument_T* arg = (Argument_T*)malloc( sizeof( Argument_T ) );
+        arg->val  = arg_val;
+        arg->next = NULL;
+        if( ctx->results->arguments->head == NULL )
+        {
+            ctx->results->arguments->head = arg;
+            ctx->results->arguments->tail = arg;
+        }
+        else
+        {
+            ctx->results->arguments->tail->next = arg;
+            ctx->results->arguments->tail = arg;
+        }
+    }
 }
 
 OptionConfig_T* OPTS_GetOptConfig( OptionConfig_T* opts, OptionType_T typ, char* name )
@@ -111,6 +161,8 @@ OptionConfig_T* OPTS_GetOptConfig( OptionConfig_T* opts, OptionType_T typ, char*
 char* OPTS_NextToken( StreamContext_T* ctx )
 {
     char* tok = NULL;
+
+    OPTS_ConsumeWhitespace( ctx );
     if ( EOF != ctx->current )
     {
         // Setup the string
@@ -119,7 +171,7 @@ char* OPTS_NextToken( StreamContext_T* ctx )
         tok[1] = '\0';
 
         (void)OPTS_NextCharacter( ctx );
-        while( (EOF != ctx->current) && (' ' != ctx->current) && ('=' != ctx->current) )
+        while( (EOF != ctx->current) && (' ' != ctx->current) )
         {
             OPTS_AppendCharacter( tok, ctx->current );
             (void)OPTS_NextCharacter( ctx );
@@ -128,6 +180,35 @@ char* OPTS_NextToken( StreamContext_T* ctx )
     return tok;
 }
 
+void OPTS_AddOption( Result_T* res, char* name, char* arg )
+{
+    if( (NULL != res) && (NULL != res->options) )
+    {
+        Option_T* opt = (Option_T*)malloc( sizeof( Option_T ) );
+        opt->key = strdup( name );
+        opt->val = arg;
+        opt->next = NULL;
+        if( res->options->head == NULL )
+        {
+            res->options->head = opt;
+            res->options->tail = opt;
+        }
+        else
+        {
+            res->options->tail->next = opt;
+            res->options->tail = opt;
+        }
+    }
+}
+
+char OPTS_ConsumeWhitespace( StreamContext_T* ctx )
+{
+    while( ' ' == ctx->current )
+    {
+        (void)OPTS_NextCharacter( ctx );
+    }
+}
+
 char OPTS_NextCharacter( StreamContext_T* ctx )
 {
     char current = EOF;
@@ -139,7 +220,8 @@ char OPTS_NextCharacter( StreamContext_T* ctx )
 
         if( ctx->line_idx < ctx->arg_count )
         {
-            if(ctx->arg_vect[ ctx->line_idx ][ ctx->col_idx ] == '\0')
+            char temp = ctx->arg_vect[ ctx->line_idx ][ ctx->col_idx ];
+            if( temp == '\0' )
             {
                 ctx->col_idx = -1;
                 ctx->line_idx++;
@@ -148,7 +230,8 @@ char OPTS_NextCharacter( StreamContext_T* ctx )
             }
             else
             {
-                current = ctx->arg_vect[ ctx->line_idx ][ ctx->col_idx ];
+                temp = (temp == '=') ? ' ' : temp;
+                current = temp;
                 ctx->current = current;
             }
         }
index 50c6bed20831c7239e800a9932b4ae0736e70f4e..08dc7859e87f67254b87e6cc78c658a544fd94ab 100644 (file)
@@ -65,6 +65,10 @@ OptionConfig_T* OPTS_GetOptConfig( OptionConfig_T* opts, OptionType_T typ, char*
 
 char* OPTS_NextToken( StreamContext_T* ctx );
 
+void OPTS_AddOption( Result_T* res, char* name, char* arg );
+
+char OPTS_ConsumeWhitespace( StreamContext_T* ctx );
+
 char OPTS_NextCharacter( StreamContext_T* ctx );
 
 char* OPTS_AppendCharacter( char* str, char ch );
index 47dc2099090d78198a27f224cc9caf8921f4acc4..cd548cd673e14a89cb323d4f1735c54b1a8aea41 100644 (file)
@@ -3,6 +3,7 @@
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
+#include <csetjmp>
 
 // File To Test
 #include "opts.h"
@@ -13,13 +14,28 @@ using namespace UnitTest;
 // Sample Option Configuration
 //-----------------------------------------------------------------------------
 OptionConfig_T Options_Config[] = {
-    { SHORT, "a",   "test_a", 0, "A simple test option" },
-    { SHORT, "b",   "test_b", 1, "A simple test option" },
-    { LONG,  "foo", "test_d", 0, "A simple test option" },
-    { LONG,  "bar", "test_e", 1, "A simple test option" },
-    { END,   NULL,  NULL,     0, NULL }
+    { SHORT, (char*)"a",   (char*)"test_a", 0, (char*)"A simple test option" },
+    { SHORT, (char*)"b",   (char*)"test_b", 1, (char*)"A simple test option" },
+    { LONG,  (char*)"foo", (char*)"test_d", 0, (char*)"A simple test option" },
+    { LONG,  (char*)"bar", (char*)"test_e", 1, (char*)"A simple test option" },
+    { END,   (char*)NULL,  (char*)NULL,     0, (char*)NULL }
 };
 
+//-----------------------------------------------------------------------------
+// Global Test Variables
+//-----------------------------------------------------------------------------
+static int Expected_Exit_Code = -1;
+static jmp_buf Exit_Point = {0};
+
+//-----------------------------------------------------------------------------
+// Helper Functions
+//-----------------------------------------------------------------------------
+void exit(int code)
+{
+    // Simulate an exit by longjmping to the jmp_buf with the desired error code
+    longjmp( Exit_Point, Expected_Exit_Code);
+}
+
 //-----------------------------------------------------------------------------
 // Begin Unit Tests
 //-----------------------------------------------------------------------------
@@ -30,7 +46,7 @@ namespace {
     TEST(Verify_InitContext_Initializes_The_StreamContext)
     {
         StreamContext_T ctx;
-        char* args[] = { "-a", "b", "--foo", "--foo=bar" };
+        char* args[] = { (char*)"-a", (char*)"b", (char*)"--foo", (char*)"--foo=bar" };
         OPTS_InitContext( &ctx, 4, args );
 
         CHECK( 0 == ctx.line_idx );
@@ -50,60 +66,488 @@ namespace {
     //-------------------------------------------------------------------------
     // Test ParseOptions Function
     //-------------------------------------------------------------------------
-    //TEST(Verify_ParseOptions_Parses_A_Short_Option_With_No_Param)
-    //{
-    //    Result_T* results = NULL;
-    //    char* args[] = { "-a" };
-    //    results = OPTS_ParseOptions( Options_Config, 1, args );
-    //    CHECK( 0 == 1 );
-    //    free(results);
-    //}
-
-    //TEST(Verify_ParseOptions_Parses_A_Short_Option_With_A_Param_And_A_Space)
-    //{
-    //    Result_T* results = NULL;
-    //    char* args[] = { "-b5" };
-    //    results = OPTS_ParseOptions( Options_Config, 1, args );
-    //    CHECK( 0 == 1 );
-    //    free(results);
-    //}
-
-    //TEST(Verify_ParseOptions_Parses_A_Short_Option_With_A_Param_And_No_Space)
-    //{
-    //    Result_T* results = NULL;
-    //    char* args[] = { "-b", "5" };
-    //    results = OPTS_ParseOptions( Options_Config, 1, args );
-    //    CHECK( 0 == 1 );
-    //    free(results);
-    //}
-
-    //TEST(Verify_ParseOptions_Parses_A_Long_Option_With_No_Param)
-    //{
-    //    Result_T* results = NULL;
-    //    char* args[] = { "--foo" };
-    //    results = OPTS_ParseOptions( Options_Config, 1, args );
-    //    CHECK( 0 == 1 );
-    //    free(results);
-    //}
-
-    //TEST(Verify_ParseOptions_Parses_A_Long_Option_With_A_Param_And_A_Space)
-    //{
-    //    Result_T* results = NULL;
-    //    char* args[] = { "--bar=5" };
-    //    results = OPTS_ParseOptions( Options_Config, 1, args );
-    //    CHECK( 0 == 1 );
-    //    free(results);
-    //}
-
-    //TEST(Verify_ParseOptions_Parses_A_Long_Option_With_A_Param_And_An_Equals_Sign)
-    //{
-    //    Result_T* results = NULL;
-    //    char* args[] = { "--bar", "5" };
-    //    results = OPTS_ParseOptions( Options_Config, 1, args );
-    //    CHECK( 0 == 1 );
-    //    free(results);
-    //}
+    TEST(Verify_ParseOptions_Parses_A_Short_Option_With_No_Param)
+    {
+        char* args[] = { (char*)"-a" };
+
+        Result_T* results = OPTS_ParseOptions( Options_Config, 1, args );
+
+        OptionList_T* result = results->options;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head == result->tail );
+        CHECK( NULL != result->tail->key );
+        CHECK( 0 == strcmp( result->tail->key, "a" ) );
+        CHECK( NULL == result->tail->val );
+        CHECK( NULL == result->tail->next );
+    }
+
+    TEST(Verify_ParseOptions_Parses_A_Short_Option_With_A_Param_And_A_Space)
+    {
+        char* args[] = { (char*)"-b", (char*)"5" };
+
+        Result_T* results = OPTS_ParseOptions( Options_Config, 2, args );
+
+        OptionList_T* result = results->options;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head == result->tail );
+        CHECK( NULL != result->tail->key );
+        CHECK( 0 == strcmp( result->tail->key, "b" ) );
+        CHECK( NULL != result->tail->val );
+        CHECK( 0 == strcmp( result->tail->val, "5" ) );
+        CHECK( NULL == result->tail->next );
+    }
+
+    TEST(Verify_ParseOptions_Parses_A_Short_Option_With_A_Param_And_No_Space)
+    {
+        char* args[] = { (char*)"-b5" };
+
+        Result_T* results = OPTS_ParseOptions( Options_Config, 1, args );
+
+        OptionList_T* result = results->options;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head == result->tail );
+        CHECK( NULL != result->tail->key );
+        CHECK( 0 == strcmp( result->tail->key, "b" ) );
+        CHECK( NULL != result->tail->val );
+        CHECK( 0 == strcmp( result->tail->val, "5" ) );
+        CHECK( NULL == result->tail->next );
+    }
+
+    TEST(Verify_ParseOptions_Parses_A_Long_Option_With_No_Param)
+    {
+        char* args[] = { (char*)"--foo" };
+
+        Result_T* results = OPTS_ParseOptions( Options_Config, 1, args );
+
+        OptionList_T* result = results->options;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head == result->tail );
+        CHECK( NULL != result->tail->key );
+        CHECK( 0 == strcmp( result->tail->key, "foo" ) );
+        CHECK( NULL == result->tail->val );
+        CHECK( NULL == result->tail->next );
+    }
+
+    TEST(Verify_ParseOptions_Parses_A_Long_Option_With_A_Param_And_A_Space)
+    {
+        char* args[] = { (char*)"--bar", (char*)"baz" };
+
+        Result_T* results = OPTS_ParseOptions( Options_Config, 2, args );
+
+        OptionList_T* result = results->options;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head == result->tail );
+        CHECK( NULL != result->tail->key );
+        CHECK( 0 == strcmp( result->tail->key, "bar" ) );
+        CHECK( NULL != result->tail->val );
+        CHECK( 0 == strcmp( result->tail->val, "baz" ) );
+        CHECK( NULL == result->tail->next );
+    }
+
+    TEST(Verify_ParseOptions_Parses_A_Long_Option_With_A_Param_And_An_Equals_Sign)
+    {
+        char* args[] = { (char*)"--bar=baz" };
+
+        Result_T* results = OPTS_ParseOptions( Options_Config, 1, args );
+
+        OptionList_T* result = results->options;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head == result->tail );
+        CHECK( NULL != result->tail->key );
+        CHECK( 0 == strcmp( result->tail->key, "bar" ) );
+        CHECK( NULL != result->tail->val );
+        CHECK( 0 == strcmp( result->tail->val, "baz" ) );
+        CHECK( NULL == result->tail->next );
+    }
+
+    TEST(Verify_ParseOptions_Parses_A_Floating_Argument)
+    {
+        char* args[] = { (char*)"baz1" };
+
+        Result_T* results = OPTS_ParseOptions( Options_Config, 1, args );
+
+        ArgumentList_T* result = results->arguments;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head == result->tail );
+        CHECK( 0 == strcmp( result->tail->val, (char*)"baz1" ) );
+        CHECK( NULL == result->tail->next );
+    }
+
+    //-------------------------------------------------------------------------
+    // Test ParseShortOption Function
+    //-------------------------------------------------------------------------
+    TEST(Verify_ParseShortOption_Exits_With_Error_On_Invalid_Option)
+    {
+        int exit_code = 0;
+        char* args[] = { (char*)"z" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 1, args );
+        Expected_Exit_Code = 1;
+        exit_code = setjmp( Exit_Point );
+
+        if( 0 == exit_code )
+        {
+            OPTS_ParseShortOption( &ctx );
+            // If we fail to call exit then this breaks our test
+            CHECK( false );
+        }
+        else
+        {
+            OptionList_T* result = ctx.results->options;
+            CHECK( 1 == exit_code );
+            CHECK( NULL != result );
+            CHECK( NULL == result->head );
+            CHECK( NULL == result->tail );
+        }
+    }
+
+    TEST(Verify_ParseShortOption_Parses_A_Single_Valid_Option_With_No_Arg)
+    {
+        char* args[] = { (char*)"a", (char*)"a" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 2, args );
+
+        OPTS_ParseShortOption( &ctx );
+
+        OptionList_T* result = ctx.results->options;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head == result->tail );
+        CHECK( NULL != result->tail->key );
+        CHECK( 0 == strcmp( result->tail->key, "a" ) );
+        CHECK( NULL == result->tail->val );
+        CHECK( NULL == result->tail->next );
+    }
+
+    TEST(Verify_ParseShortOption_Parses_A_Single_Valid_Option_With_Arg_And_No_Space)
+    {
+        char* args[] = { (char*)"b5" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 1, args );
+
+        OPTS_ParseShortOption( &ctx );
+
+        OptionList_T* result = ctx.results->options;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head == result->tail );
+        CHECK( NULL != result->tail->key );
+        CHECK( 0 == strcmp( result->tail->key, "b" ) );
+        CHECK( NULL != result->tail->val );
+        CHECK( 0 == strcmp( result->tail->val, "5" ) );
+        CHECK( NULL == result->tail->next );
+    }
+
+    TEST(Verify_ParseShortOption_Parses_A_Single_Valid_Option_With_Arg_And_Space)
+    {
+        char* args[] = { (char*)"b", (char*)"5" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 2, args );
 
+        OPTS_ParseShortOption( &ctx );
+
+        OptionList_T* result = ctx.results->options;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head == result->tail );
+        CHECK( NULL != result->tail->key );
+        CHECK( 0 == strcmp( result->tail->key, "b" ) );
+        CHECK( NULL != result->tail->val );
+        CHECK( 0 == strcmp( result->tail->val, "5" ) );
+        CHECK( NULL == result->tail->next );
+    }
+
+    TEST(Verify_ParseShortOption_Exits_With_Error_When_No_Arg_Received_When_Expected)
+    {
+        int exit_code = 0;
+        char* args[] = { (char*)"b" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 1, args );
+        Expected_Exit_Code = 1;
+        exit_code = setjmp( Exit_Point );
+
+        if( 0 == exit_code )
+        {
+            OPTS_ParseShortOption( &ctx );
+            // If we fail to call exit then this breaks our test
+            CHECK( false );
+        }
+        else
+        {
+            OptionList_T* result = ctx.results->options;
+            CHECK( 1 == exit_code );
+            CHECK( NULL != result );
+            CHECK( NULL == result->head );
+            CHECK( NULL == result->tail );
+        }
+    }
+
+    TEST(Verify_ParseShortOption_Parses_A_Group_Of_Valid_Options_With_No_Arg)
+    {
+        char* args[] = { (char*)"aa" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 1, args );
+
+        OPTS_ParseShortOption( &ctx );
+
+        OptionList_T* result = ctx.results->options;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head != result->tail );
+        // First option
+        CHECK( NULL != result->head->key );
+        CHECK( 0 == strcmp( result->head->key, "a" ) );
+        CHECK( NULL == result->head->val );
+        CHECK( NULL != result->head->next );
+        // Second option
+        CHECK( NULL != result->tail->key );
+        CHECK( 0 == strcmp( result->tail->key, "a" ) );
+        CHECK( NULL == result->tail->val );
+        CHECK( NULL == result->tail->next );
+    }
+
+    TEST(Verify_ParseShortOption_Parses_A_Group_Of_Valid_Options_With_The_Last_Having_An_Arg)
+    {
+        char* args[] = { (char*)"ab5" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 1, args );
+
+        OPTS_ParseShortOption( &ctx );
+
+        OptionList_T* result = ctx.results->options;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head != result->tail );
+        // First option
+        CHECK( NULL != result->head->key );
+        CHECK( 0 == strcmp( result->head->key, "b" ) );
+        CHECK( NULL != result->head->val );
+        CHECK( 0 == strcmp( result->head->val, "5" ) );
+        CHECK( NULL != result->head->next );
+        // Second option
+        CHECK( NULL != result->tail->key );
+        CHECK( 0 == strcmp( result->tail->key, "a" ) );
+        CHECK( NULL == result->tail->val );
+        CHECK( NULL == result->tail->next );
+    }
+
+    //-------------------------------------------------------------------------
+    // Test ParseLongOption Function
+    //-------------------------------------------------------------------------
+    TEST(Verify_ParseLongOption_Errors_And_Exits_On_Invalid_Option)
+    {
+        int exit_code = 0;
+        char* args[] = { (char*)"baz" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 1, args );
+        Expected_Exit_Code = 1;
+        exit_code = setjmp( Exit_Point );
+
+        if( 0 == exit_code )
+        {
+            OPTS_ParseLongOption( &ctx );
+            // If we fail to call exit then this breaks our test
+            CHECK( false );
+        }
+        else
+        {
+            OptionList_T* result = ctx.results->options;
+            CHECK( 1 == exit_code );
+            CHECK( NULL != result );
+            CHECK( NULL == result->head );
+            CHECK( NULL == result->tail );
+        }
+    }
+
+    TEST(Verify_ParseLongOption_Errors_And_Exits_Next_Option_Instead_Of_Argument)
+    {
+        int exit_code = 0;
+        char* args[] = { (char*)"bar", (char*)"--foo" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 2, args );
+        Expected_Exit_Code = 1;
+        exit_code = setjmp( Exit_Point );
+
+        if( 0 == exit_code )
+        {
+            OPTS_ParseLongOption( &ctx );
+            // If we fail to call exit then this breaks our test
+            CHECK( false );
+        }
+        else
+        {
+            OptionList_T* result = ctx.results->options;
+            CHECK( 1 == exit_code );
+            CHECK( NULL != result );
+            CHECK( NULL == result->head );
+            CHECK( NULL == result->tail );
+        }
+    }
+
+    TEST(Verify_ParseLongOption_Errors_And_Exits_EOF_Instead_Of_Argument)
+    {
+        int exit_code = 0;
+        char* args[] = { (char*)"bar" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 1, args );
+        Expected_Exit_Code = 1;
+        exit_code = setjmp( Exit_Point );
+
+        if( 0 == exit_code )
+        {
+            OPTS_ParseLongOption( &ctx );
+            // If we fail to call exit then this breaks our test
+            CHECK( false );
+        }
+        else
+        {
+            OptionList_T* result = ctx.results->options;
+            CHECK( 1 == exit_code );
+            CHECK( NULL != result );
+            CHECK( NULL == result->head );
+            CHECK( NULL == result->tail );
+        }
+    }
+
+    TEST(Verify_ParseLongOption_Parses_Option_With_No_Arg)
+    {
+        char* args[] = { (char*)"foo" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 1, args );
+
+        OPTS_ParseLongOption( &ctx );
+
+        OptionList_T* result = ctx.results->options;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head == result->tail );
+        CHECK( 0 == strcmp( result->head->key, (char*)"foo") );
+        CHECK( NULL == result->tail->val );
+    }
+
+    TEST(Verify_ParseLongOption_Parses_Option_With_Arg_Using_Equals_Sign)
+    {
+        char* args[] = { (char*)"bar=baz" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 1, args );
+
+        OPTS_ParseLongOption( &ctx );
+
+        OptionList_T* result = ctx.results->options;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head == result->tail );
+        CHECK( 0 == strcmp( result->head->key, (char*)"bar") );
+        CHECK( 0 == strcmp( result->head->val, (char*)"baz") );
+    }
+
+    TEST(Verify_ParseLongOption_Parses_Option_With_Arg_Not_Using_Equals_Sign)
+    {
+        char* args[] = { (char*)"bar", (char*)"baz" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 2, args );
+
+        OPTS_ParseLongOption( &ctx );
+
+        OptionList_T* result = ctx.results->options;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head == result->tail );
+        CHECK( 0 == strcmp( result->head->key, (char*)"bar") );
+        CHECK( 0 == strcmp( result->head->val, (char*)"baz") );
+    }
+
+    //-------------------------------------------------------------------------
+    // Test ParseArgument Function
+    //-------------------------------------------------------------------------
+    TEST(Verify_ParseArgument_Does_Nothing_If_No_Tokens_Left)
+    {
+        char* args[] = { (char*)"" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 1, args );
+
+        OPTS_ParseArgument( &ctx );
+
+        ArgumentList_T* result = ctx.results->arguments;
+        CHECK( NULL != result );
+        CHECK( NULL == result->head );
+        CHECK( NULL == result->tail );
+    }
+
+    TEST(Verify_ParseArgument_Adds_Argument_To_Empty_List)
+    {
+        char* args[] = { (char*)"baz1" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 1, args );
+
+        OPTS_ParseArgument( &ctx );
+
+        ArgumentList_T* result = ctx.results->arguments;
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head == result->tail );
+        CHECK( 0 == strcmp( result->tail->val, (char*)"baz1" ) );
+        CHECK( NULL == result->tail->next );
+    }
+
+    TEST(Verify_ParseArgument_Appends_Argument_To_End_Of_List)
+    {
+        char* args[] = { (char*)"baz1" };
+        StreamContext_T ctx;
+        ctx.options = Options_Config;
+        OPTS_InitContext( &ctx, 1, args );
+        ArgumentList_T* result = ctx.results->arguments;
+        Argument_T arg = { 0 };
+        result->head = &arg;
+        result->tail = &arg;
+
+        OPTS_ParseArgument( &ctx );
+
+        CHECK( NULL != result );
+        CHECK( NULL != result->head );
+        CHECK( NULL != result->tail );
+        CHECK( result->head != result->tail );
+        CHECK( 0 == strcmp( result->tail->val, (char*)"baz1" ) );
+        CHECK( NULL == result->tail->next );
+    }
 
     //-------------------------------------------------------------------------
     // Test GetOptConfig Function
@@ -111,21 +555,21 @@ namespace {
     TEST(Verify_GetOptConfig_Can_Retrieve_A_Short_Config_By_Name_And_Type)
     {
         OptionConfig_T* result = NULL;
-        result = OPTS_GetOptConfig( Options_Config, SHORT, "a" );
+        result = OPTS_GetOptConfig( Options_Config, SHORT, (char*)"a" );
         CHECK( result == &(Options_Config[0]) );
     }
 
     TEST(Verify_GetOptConfig_Can_Retrieve_A_Long_Config_By_Name_And_Type)
     {
         OptionConfig_T* result = NULL;
-        result = OPTS_GetOptConfig( Options_Config, LONG, "foo" );
+        result = OPTS_GetOptConfig( Options_Config, LONG, (char*)"foo" );
         CHECK( result == &(Options_Config[2]) );
     }
 
     TEST(Verify_GetOptConfig_Returns_Null_If_No_Config_Found)
     {
         OptionConfig_T* result = NULL;
-        result = OPTS_GetOptConfig( Options_Config, LONG, "baz" );
+        result = OPTS_GetOptConfig( Options_Config, LONG, (char*)"baz" );
         CHECK( result == NULL );
     }
 
@@ -135,17 +579,98 @@ namespace {
             { END,   NULL,  NULL,     0, NULL }
         };
         OptionConfig_T* result = NULL;
-        result = OPTS_GetOptConfig( config, LONG, "foo" );
+        result = OPTS_GetOptConfig( config, LONG, (char*)"foo" );
         CHECK( result == NULL );
     }
 
+    //-------------------------------------------------------------------------
+    // Test AddOption Function
+    //-------------------------------------------------------------------------
+    TEST(Verify_AddOption_Ignores_NULL_Result_Set)
+    {
+        Result_T results;
+        results.options = NULL;
+
+        // If we don't segfault here I call it a win
+        OPTS_AddOption( NULL, NULL, NULL );
+        OPTS_AddOption( &results, NULL, NULL );
+    }
+
+    TEST(Verify_AddOption_Adds_Result_To_Empty_List)
+    {
+        Result_T results;
+        char name[] = "foo";
+        char arg[]  = "bar";
+        results.options = (OptionList_T*)malloc( sizeof( OptionList_T ) );
+        results.options->head = NULL;
+        results.options->tail = NULL;
+        OPTS_AddOption( &results, name, arg );
+
+        CHECK( NULL != results.options->head );
+        CHECK( NULL != results.options->tail );
+        CHECK( NULL != results.options->head->key );
+        CHECK( name != results.options->head->key );
+        CHECK( 0 == strcmp( name, results.options->head->key ) );
+        CHECK( NULL != results.options->head->val );
+        CHECK( arg == results.options->head->val );
+        CHECK( NULL == results.options->head->next );
+        CHECK( results.options->head == results.options->tail );
+
+        delete results.options->head->key;
+        delete results.options->head;
+        delete results.options;
+    }
+
+    TEST(Verify_AddOption_Appends_Result_To_Tail_Of_List)
+    {
+        Result_T results;
+        Option_T option = {0};
+        char name[] = "foo";
+        char arg[]  = "bar";
+        results.options = (OptionList_T*)malloc( sizeof( OptionList_T ) );
+        results.options->head = &option;
+        results.options->tail = &option;
+        OPTS_AddOption( &results, name, arg );
+
+        CHECK( NULL != results.options->head );
+        CHECK( NULL != results.options->tail );
+        CHECK( results.options->head != results.options->tail );
+        CHECK( NULL != results.options->tail->key );
+        CHECK( name != results.options->tail->key );
+        CHECK( 0 == strcmp( name, results.options->tail->key ) );
+        CHECK( NULL != results.options->tail->val );
+        CHECK( arg == results.options->tail->val );
+        CHECK( NULL == results.options->tail->next );
+
+        delete results.options->tail->key;
+        delete results.options->tail;
+        delete results.options;
+    }
+
+    //-------------------------------------------------------------------------
+    // Test ConsumeWhitespaace Function
+    //-------------------------------------------------------------------------
+    TEST(Verify_ConsumeWhitespace_Removes_Whitespace_From_The_Stream_Till_The_Next_Token)
+    {
+        char* args[] = { (char*)"    " };
+        StreamContext_T ctx;
+        ctx.line_idx  = 0;
+        ctx.col_idx   = -1;
+        ctx.arg_count = 1;
+        ctx.arg_vect  = args;
+        (void)OPTS_NextCharacter( &ctx );
+
+        OPTS_ConsumeWhitespace( &ctx );
+        CHECK( EOF == OPTS_NextCharacter( &ctx ) );
+    }
+
     //-------------------------------------------------------------------------
     // Test NextToken Function
     //-------------------------------------------------------------------------
     TEST(Verify_NextToken_Properly_Tokenizes_Stream)
     {
         char* token = NULL;
-        char* args[] = { "-a", "b", "--foo", "--foo=bar" };
+        char* args[] = { (char*)"-a", (char*)"b", (char*)"--foo", (char*)"--foo=bar" };
         StreamContext_T ctx;
         ctx.line_idx  = 0;
         ctx.col_idx   = -1;
@@ -154,27 +679,27 @@ namespace {
 
         (void)OPTS_NextCharacter( &ctx );
         token = OPTS_NextToken( &ctx );
-        CHECK( 0 == strcmp( token, "-a" ) );
+        CHECK( 0 == strcmp( token, (char*)"-a" ) );
         free( token );
 
         (void)OPTS_NextCharacter( &ctx );
         token = OPTS_NextToken( &ctx );
-        CHECK( 0 == strcmp( token, "b" ) );
+        CHECK( 0 == strcmp( token, (char*)"b" ) );
         free( token );
 
         (void)OPTS_NextCharacter( &ctx );
         token = OPTS_NextToken( &ctx );
-        CHECK( 0 == strcmp( token, "--foo" ) );
+        CHECK( 0 == strcmp( token, (char*)"--foo" ) );
         free( token );
 
         (void)OPTS_NextCharacter( &ctx );
         token = OPTS_NextToken( &ctx );
-        CHECK( 0 == strcmp( token, "--foo" ) );
+        CHECK( 0 == strcmp( token, (char*)"--foo" ) );
         free( token );
 
         (void)OPTS_NextCharacter( &ctx );
         token = OPTS_NextToken( &ctx );
-        CHECK( 0 == strcmp( token, "bar" ) );
+        CHECK( 0 == strcmp( token, (char*)"bar" ) );
         free( token );
     }
 
@@ -183,7 +708,7 @@ namespace {
     //-------------------------------------------------------------------------
     TEST(Verify_NextCharacter_Can_Loop_Through_Characters)
     {
-        char* args[] = { "abc", "123" };
+        char* args[] = { (char*)"abc", (char*)"123=4" };
         StreamContext_T ctx;
         ctx.line_idx  = 0;
         ctx.col_idx   = -1;
@@ -206,6 +731,10 @@ namespace {
         CHECK( '3' == ctx.current );
         CHECK( ' ' == OPTS_NextCharacter( &ctx ) );
         CHECK( ' ' == ctx.current );
+        CHECK( '4' == OPTS_NextCharacter( &ctx ) );
+        CHECK( '4' == ctx.current );
+        CHECK( ' ' == OPTS_NextCharacter( &ctx ) );
+        CHECK( ' ' == ctx.current );
         CHECK( EOF == OPTS_NextCharacter( &ctx ) );
         CHECK( EOF == ctx.current );
         CHECK( EOF == OPTS_NextCharacter( &ctx ) );
@@ -237,6 +766,6 @@ namespace {
         str[1] = '\0';
         str = OPTS_AppendCharacter( str, 'b' );
 
-        CHECK( 0 == strcmp( str, "ab" ) );
+        CHECK( 0 == strcmp( str, (char*)"ab" ) );
     }
 }