From ecc43307afde8de5dac1c89a5e067192ebfe2944 Mon Sep 17 00:00:00 2001 From: prozessorkern Date: Mon, 16 Mar 2020 22:08:06 +0100 Subject: [PATCH] added first working option parser for short options --- api/shellmatta.h | 2 + makefile | 1 + src/shellmatta.c | 7 +- src/shellmatta_opt.c | 206 +++++++++++++++++- src/shellmatta_opt.h | 3 +- test/integrationtest/test_integration_opt.cpp | 89 +++++++- .../shellmatta_opt/test_opt_findNextHunk.cpp | 162 ++++++++++++++ .../test_utils_insertChars.cpp | 4 + 8 files changed, 459 insertions(+), 15 deletions(-) create mode 100644 test/unittest/shellmatta_opt/test_opt_findNextHunk.cpp diff --git a/api/shellmatta.h b/api/shellmatta.h index 625f621..55ef256 100644 --- a/api/shellmatta.h +++ b/api/shellmatta.h @@ -77,6 +77,8 @@ typedef struct typedef struct { uint32_t offset; /**< current offset of the option parser */ + uint32_t nextOffset; /**< offset of the next hunk */ + uint32_t len; /**< length of the current hunk */ } shellmatta_opt_t; /** diff --git a/makefile b/makefile index 8d0edcf..28e9619 100644 --- a/makefile +++ b/makefile @@ -22,6 +22,7 @@ SOURCES := src/shellmatta.c \ INCLUDES := api . UNITTEST_SOURCES := test/unittest/test_main.cpp \ + test/unittest/shellmatta_opt/test_opt_findNextHunk.cpp \ test/unittest/shellmatta_utils/test_utils_writeEcho.cpp \ test/unittest/shellmatta_utils/test_utils_shellItoa.cpp \ test/unittest/shellmatta_utils/test_utils_saveCursorPos.cpp \ diff --git a/src/shellmatta.c b/src/shellmatta.c index 78a8029..b474c91 100644 --- a/src/shellmatta.c +++ b/src/shellmatta.c @@ -22,6 +22,7 @@ #include "shellmatta_history.h" #include "shellmatta_utils.h" #include "shellmatta_escape.h" +#include "shellmatta_opt.h" #include #include #include @@ -502,8 +503,8 @@ shellmatta_retCode_t shellmatta_processData(shellmatta_handle_t handle, { /** -# compare command and alias string and length */ if ( ((0 == strncmp( argumentString, - cmd->cmd, - cmdLen)) + cmd->cmd, + cmdLen)) && (cmdLen == strlen(cmd->cmd))) || ((NULL != cmd->cmdAlias) && ((0 == strncmp( argumentString, @@ -512,7 +513,7 @@ shellmatta_retCode_t shellmatta_processData(shellmatta_handle_t handle, && (cmdLen == strlen(cmd->cmdAlias))))) { utils_writeEcho(inst, "\r\n", 2u); - + shellmatta_opt_init(inst, cmdLen + 1u); cmdExecuted = 1u; cmd->cmdFct(inst, argumentString, argumentLength); cmd = NULL; diff --git a/src/shellmatta_opt.c b/src/shellmatta_opt.c index 9e4df44..fcd511b 100644 --- a/src/shellmatta_opt.c +++ b/src/shellmatta_opt.c @@ -22,6 +22,151 @@ #include "shellmatta.h" #include + +/** + * @brief finds the next parsable hunk of data in the input + * @param[in] inst shellmatta instance + * @return errorcode #SHELLMATTA_OK - new hunk found + * #SHELLMATTA_ERROR - error parsing or end of input + */ +static shellmatta_retCode_t findNextHunk(shellmatta_instance_t *inst) +{ + shellmatta_retCode_t ret = SHELLMATTA_ERROR; + uint32_t newOffset = inst->optionParser.nextOffset; + uint32_t exeptionOffset = 0u; + char quotation = '\0'; /* holds the current quotation mark if any */ + + /*! -# find beginning of next hunk */ + while( (newOffset < inst->inputCount) + && ((' ' == inst->buffer[newOffset]) + || ('\0' == inst->buffer[newOffset]))) + { + newOffset ++; + } + + inst->optionParser.offset = newOffset; + + /*! -# determine length */ + while((newOffset < inst->inputCount) + && (((' ' != inst->buffer[newOffset]) && ('\0' != inst->buffer[newOffset])) || '\0' != quotation)) + { + /*! -# check for new quotation */ + if((('\'' == inst->buffer[newOffset]) || ('"' == inst->buffer[newOffset])) && (quotation == '\0')) + { + quotation = inst->buffer[newOffset]; + exeptionOffset ++; + } + /*! -# check if quotation has ended */ + else if(quotation == inst->buffer[newOffset]) + { + exeptionOffset ++; + /*! -# check if quotation is excaped */ + if('\\' != inst->buffer[newOffset - 1u]) + { + quotation = '\0'; + } + else + { + inst->buffer[newOffset - exeptionOffset] = inst->buffer[newOffset]; + } + } + else + { + /*! -# shift back chars */ + if(0u != exeptionOffset) + { + inst->buffer[newOffset - exeptionOffset] = inst->buffer[newOffset]; + } + } + newOffset ++; + } + + inst->optionParser.nextOffset = newOffset; + inst->optionParser.len = newOffset - inst->optionParser.offset - exeptionOffset; + + /*! -# add terminating 0 */ + inst->buffer[inst->optionParser.offset + inst->optionParser.len] = '\0'; + + if((inst->optionParser.offset < inst->inputCount) && (0u != inst->optionParser.len) && ('\0' == quotation)) + { + ret = SHELLMATTA_OK; + } + + return ret; +} + +/** + * @brief peeks the first char of the next hunk + * @param[in] inst shellmatta instance + * @return char first char of next hunk \0 if not existing + */ +static char peekNextHunk(shellmatta_instance_t *inst) +{ + uint32_t newOffset = inst->optionParser.nextOffset; + + /*! -# find beginning of next hunk */ + while( (newOffset < inst->inputCount) + && ((' ' == inst->buffer[newOffset]) + || ('\0' == inst->buffer[newOffset]))) + { + newOffset ++; + } + return inst->buffer[newOffset]; +} + +/** + * @brief tries to parse the current input hunk and check if this is a configured option + * @param[in] handle shellmatta handle + * @param[in] optionString option string e.g. "cd:e::" + * @param[out] option pointer to store the detected option to + * @param[out] argtype pointer to store the argument string to (can be NULL) + * @return errorcode #SHELLMATTA_OK - option parsable and found in option String + * #SHELLMATTA_ERROR - format error or option unknown + */ +static shellmatta_retCode_t parseShortOpt( shellmatta_instance_t *inst, + char *optionString, + char *option, + shellmatta_opt_argtype_t *argtype) +{ + shellmatta_retCode_t ret = SHELLMATTA_ERROR; + char *buffer = &inst->buffer[inst->optionParser.offset]; + uint32_t i; + + /*! -# check for correct syntax */ + if((2u == inst->optionParser.len) && ('-' == buffer[0u]) && ('-' != buffer[1u]) && ('\0' != buffer[1u])) + { + /*! -# search for option character in option string */ + for(i = 0u; ('\0' != optionString[i]) && (buffer[1u] != optionString[i]); i ++); + + if(buffer[1u] == optionString[i]) + { + /*! -# return found option character */ + *option = buffer[1u]; + ret = SHELLMATTA_OK; + + /*! -# check if an argument is required or optional */ + if(':' == optionString[i + 1u]) + { + *argtype = SHELLMATTA_OPT_ARG_REQUIRED; + if(':' == optionString[i + 2u]) + { + *argtype = SHELLMATTA_OPT_ARG_OPTIONAL; + } + } + else + { + *argtype = SHELLMATTA_OPT_ARG_NONE; + } + } + } + else + { + *option = '\0'; + } + + return ret; +} + /** * @brief scans the current input and parses options in getopt style * @param[in] handle shellmatta handle @@ -29,6 +174,8 @@ * @param[out] option pointer to store the detected option to * @param[out] argument pointer to store the argument string to (can be NULL) * @param[out] argLen pointer to store the argument lengh to (can be NULL) + * @return errorcode #SHELLMATTA_OK - no error - keep on calling + * #SHELLMATTA_ERROR - error occured - e.g. argument missing */ shellmatta_retCode_t shellmatta_opt( shellmatta_handle_t handle, char *optionString, @@ -36,8 +183,9 @@ shellmatta_retCode_t shellmatta_opt( shellmatta_handle_t handle, char **argument, uint32_t *argLen) { - shellmatta_retCode_t ret = SHELLMATTA_USE_FAULT; - shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; + shellmatta_retCode_t ret = SHELLMATTA_USE_FAULT; + shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; + shellmatta_opt_argtype_t argtype = SHELLMATTA_OPT_ARG_NONE; /** -# check parameters for plausibility */ if( (NULL != inst) @@ -45,7 +193,52 @@ shellmatta_retCode_t shellmatta_opt( shellmatta_handle_t handle, && (NULL != optionString) && (NULL != option)) { - + *option = '\0'; + *argument = NULL; + *argLen = 0u; + + ret = findNextHunk(inst); + if(SHELLMATTA_OK == ret) + { + ret = parseShortOpt(inst, optionString, option, &argtype); + + /*! -# when no option is found return this as raw argument */ + if(SHELLMATTA_ERROR == ret) + { + *argument = &(inst->buffer[inst->optionParser.offset]); + *argLen = inst->optionParser.len; + ret = SHELLMATTA_OK; + } + else + { + switch(argtype) + { + case SHELLMATTA_OPT_ARG_REQUIRED: + ret = findNextHunk(inst); + if(SHELLMATTA_OK == ret) + { + *argument = &(inst->buffer[inst->optionParser.offset]); + *argLen = inst->optionParser.len; + } + break; + case SHELLMATTA_OPT_ARG_OPTIONAL: + /*! -# treat anything not starting with '-' as argument */ + if('-' != peekNextHunk(inst)) + { + ret = findNextHunk(inst); + if(SHELLMATTA_OK == ret) + { + *argument = &(inst->buffer[inst->optionParser.offset]); + *argLen = inst->optionParser.len; + } + } + break; + default: + /* nothing to do */ + break; + } + } + } } (void)argument; @@ -88,12 +281,13 @@ shellmatta_retCode_t shellmatta_opt_long( shellmatta_handle_t handle, /** * @brief initializes the option parser instance - * @param[in, out] inst pointer to a shellmatta instance + * @param[in, out] inst pointer to a shellmatta instance + * @param[in] argStart start offset of the arguments (after command name/alias) */ -shellmatta_retCode_t shellmatta_opt_init(shellmatta_instance_t *inst) +shellmatta_retCode_t shellmatta_opt_init(shellmatta_instance_t *inst, uint32_t argStart) { /*! -# initialize all relevant option parser variables */ - inst->optionParser.offset = 0u; + inst->optionParser.nextOffset = argStart; return SHELLMATTA_OK; } diff --git a/src/shellmatta_opt.h b/src/shellmatta_opt.h index f148e05..df1c554 100644 --- a/src/shellmatta_opt.h +++ b/src/shellmatta_opt.h @@ -34,7 +34,8 @@ shellmatta_retCode_t shellmatta_opt_long( shellmatta_handle_t handle, char **argument, uint32_t *argLen); -shellmatta_retCode_t shellmatta_opt_init( shellmatta_instance_t *inst); +shellmatta_retCode_t shellmatta_opt_init( shellmatta_instance_t *inst, + uint32_t argStart); #endif diff --git a/test/integrationtest/test_integration_opt.cpp b/test/integrationtest/test_integration_opt.cpp index 25a47bc..a2b579d 100644 --- a/test/integrationtest/test_integration_opt.cpp +++ b/test/integrationtest/test_integration_opt.cpp @@ -21,7 +21,52 @@ extern "C" { static uint32_t write_callCnt = 0u; static char write_data[1024]; static uint32_t write_length; +static uint32_t cntA = 0u; +static uint32_t cntB = 0u; +static uint32_t cntC = 0u; +static uint32_t cntD = 0u; +static uint32_t cntE = 0u; +static uint32_t cntF = 0u; +static uint32_t cntDef = 0u; +static char *argA = NULL; +static char *argB = NULL; +static char *argC = NULL; +static char *argD = NULL; +static char *argE = NULL; +static char *argF = NULL; +static char *argDef = NULL; +static uint32_t lenA = 0u; +static uint32_t lenB = 0u; +static uint32_t lenC = 0u; +static uint32_t lenD = 0u; +static uint32_t lenE = 0u; +static uint32_t lenF = 0u; +static uint32_t lenDef = 0u; +static void initTestcase(void) +{ + cntA = 0u; + cntB = 0u; + cntC = 0u; + cntD = 0u; + cntE = 0u; + cntF = 0u; + cntDef = 0u; + argA = NULL; + argB = NULL; + argC = NULL; + argD = NULL; + argE = NULL; + argF = NULL; + argDef = NULL; + lenA = 0u; + lenB = 0u; + lenC = 0u; + lenD = 0u; + lenE = 0u; + lenF = 0u; + lenDef = 0u; +} static shellmatta_retCode_t writeFct(const char* data, uint32_t length) { @@ -44,28 +89,53 @@ static shellmatta_retCode_t parseOpts(shellmatta_handle_t handle, const char *ar char option; char *argumentString; uint32_t argumentLength; + uint32_t optionCount = 0u; while(SHELLMATTA_OK == shellmatta_opt(handle, (char*)"abcde:f::", &option, &argumentString, &argumentLength)) { + optionCount ++; switch(option) { case 'a': + cntA ++; + argA = argumentString; + lenA = argumentLength; break; case 'b': + cntB ++; + argB = argumentString; + lenB = argumentLength; break; case 'c': + cntC ++; + argC = argumentString; + lenC = argumentLength; break; case 'd': + cntD ++; + argD = argumentString; + lenD = argumentLength; break; case 'e': + cntE ++; + argE = argumentString; + lenE = argumentLength; break; case 'f': + cntF ++; + argF = argumentString; + lenF = argumentLength; break; default: + cntDef ++; + argDef = argumentString; + lenDef = argumentLength; break; } } + shellmatta_printf(handle, "parseOpts - cnt: %u\r\n", optionCount); + return SHELLMATTA_OK; } shellmatta_cmd_t parseOptsCmd = {(char*)"parseOpts", (char*)"opt", NULL, NULL, parseOpts, NULL}; @@ -83,7 +153,7 @@ TEST_CASE( "shellmatta option parser 1" ) { shellmatta_handle_t handle; char buffer[1024]; char historyBuffer[1024]; - //char *dummyData = (char*)"\r\nshellmatta->"; + char *dummyData = (char*)"parseOpts -a -e meow\r\nparseOpts - cnt: 2\r\n\r\nshellmatta->"; shellmatta_doInit( &inst, &handle, @@ -95,13 +165,22 @@ TEST_CASE( "shellmatta option parser 1" ) { NULL, writeFct); + initTestcase(); write_callCnt = 0u; memset(write_data, 0, sizeof(write_data)); write_length = 0u; - shellmatta_processData(handle, (char*)"parseOpts -a -e meow\r", 1); + shellmatta_addCmd(handle, &parseOptsCmd); - // CHECK( write_length == 14u); - // REQUIRE( strcmp(dummyData, write_data) == 0); - REQUIRE( 1 == 1 ); + shellmatta_processData(handle, (char*)"parseOpts -a -e meow\r", 21); + + CHECK( cntA == 1u ); + CHECK( NULL == argA); + CHECK( 0u == lenA ); + CHECK( cntE == 1u ); + CHECK(((NULL != argE) && (0u == memcmp(argE, "meow", 4)))); + CHECK( lenE == 4u ); + CHECK( (cntB == 0u && cntC == 0u && cntD == 0u && cntF == 0u && cntDef == 0u) ); + CHECK( write_length == 56u); + REQUIRE( strcmp(dummyData, write_data) == 0); } diff --git a/test/unittest/shellmatta_opt/test_opt_findNextHunk.cpp b/test/unittest/shellmatta_opt/test_opt_findNextHunk.cpp new file mode 100644 index 0000000..1dea8e6 --- /dev/null +++ b/test/unittest/shellmatta_opt/test_opt_findNextHunk.cpp @@ -0,0 +1,162 @@ +#include "test/framework/catch.hpp" +#include "src/shellmatta_opt.c" +#include + + +TEST_CASE( "shellmatta_opt findNextHunk easy" ) { + + shellmatta_retCode_t ret = SHELLMATTA_OK; + shellmatta_instance_t inst; + char *dummyData = (char*) "This is Sparta"; + char buffer[1024u]; + + memcpy(buffer, dummyData, strlen(dummyData)); + + inst.buffer = buffer; + inst.bufferSize = sizeof(buffer); + inst.inputCount = 14; + inst.optionParser.nextOffset = 4u; + + ret = findNextHunk(&inst); + CHECK( ret == SHELLMATTA_OK ); + CHECK( inst.optionParser.offset == 5u); + CHECK( inst.optionParser.len == 2u); + + ret = findNextHunk(&inst); + CHECK( ret == SHELLMATTA_OK ); + CHECK( inst.optionParser.offset == 8u); + CHECK( inst.optionParser.len == 6u); + + ret = findNextHunk(&inst); + REQUIRE( ret == SHELLMATTA_ERROR ); +} + +TEST_CASE( "shellmatta_opt findNextHunk quotation 1" ) { + + shellmatta_retCode_t ret = SHELLMATTA_OK; + shellmatta_instance_t inst; + char *dummyData = (char*) "This is Sparta \"argument with spaces\""; + char buffer[1024u]; + + memcpy(buffer, dummyData, strlen(dummyData)); + + inst.buffer = buffer; + inst.bufferSize = sizeof(buffer); + inst.inputCount = strlen(dummyData); + inst.optionParser.nextOffset = 4u; + + ret = findNextHunk(&inst); + CHECK( ret == SHELLMATTA_OK ); + CHECK( inst.optionParser.offset == 5u); + CHECK( inst.optionParser.len == 2u); + + ret = findNextHunk(&inst); + CHECK( ret == SHELLMATTA_OK ); + CHECK( inst.optionParser.offset == 8u); + CHECK( inst.optionParser.len == 6u); + + ret = findNextHunk(&inst); + CHECK( ret == SHELLMATTA_OK ); + CHECK( inst.optionParser.offset == 15u); + CHECK( inst.optionParser.len == 20u); + CHECK( 0 == memcmp(&(inst.buffer[inst.optionParser.offset]), "argument with spaces", 20)); + + ret = findNextHunk(&inst); + REQUIRE( ret == SHELLMATTA_ERROR ); +} + +TEST_CASE( "shellmatta_opt findNextHunk quotation 2" ) { + + shellmatta_retCode_t ret = SHELLMATTA_OK; + shellmatta_instance_t inst; + char *dummyData = (char*) "This is Sparta 'argument with spaces'"; + char buffer[1024u]; + + memcpy(buffer, dummyData, strlen(dummyData)); + + inst.buffer = buffer; + inst.bufferSize = sizeof(buffer); + inst.inputCount = strlen(dummyData); + inst.optionParser.nextOffset = 4u; + + ret = findNextHunk(&inst); + CHECK( ret == SHELLMATTA_OK ); + CHECK( inst.optionParser.offset == 5u); + CHECK( inst.optionParser.len == 2u); + + ret = findNextHunk(&inst); + CHECK( ret == SHELLMATTA_OK ); + CHECK( inst.optionParser.offset == 8u); + CHECK( inst.optionParser.len == 6u); + + ret = findNextHunk(&inst); + CHECK( ret == SHELLMATTA_OK ); + CHECK( inst.optionParser.offset == 15u); + CHECK( inst.optionParser.len == 20u); + CHECK( 0 == memcmp(&(inst.buffer[inst.optionParser.offset]), "argument with spaces", 20)); + + ret = findNextHunk(&inst); + REQUIRE( ret == SHELLMATTA_ERROR ); +} + +TEST_CASE( "shellmatta_opt findNextHunk quotation escaped" ) { + + shellmatta_retCode_t ret = SHELLMATTA_OK; + shellmatta_instance_t inst; + char *dummyData = (char*) "This is Sparta \"argument with \\\"spaces\""; + char buffer[1024u]; + + memcpy(buffer, dummyData, strlen(dummyData)); + + inst.buffer = buffer; + inst.bufferSize = sizeof(buffer); + inst.inputCount = strlen(dummyData); + inst.optionParser.nextOffset = 4u; + + ret = findNextHunk(&inst); + CHECK( ret == SHELLMATTA_OK ); + CHECK( inst.optionParser.offset == 5u); + CHECK( inst.optionParser.len == 2u); + + ret = findNextHunk(&inst); + CHECK( ret == SHELLMATTA_OK ); + CHECK( inst.optionParser.offset == 8u); + CHECK( inst.optionParser.len == 6u); + + ret = findNextHunk(&inst); + CHECK( ret == SHELLMATTA_OK ); + CHECK( inst.optionParser.offset == 15u); + CHECK( inst.optionParser.len == 21u); + CHECK( 0 == memcmp(&(inst.buffer[inst.optionParser.offset]), "argument with \"spaces", 21)); + + ret = findNextHunk(&inst); + REQUIRE( ret == SHELLMATTA_ERROR ); +} + +TEST_CASE( "shellmatta_opt findNextHunk quotation missing closing quotation" ) { + + shellmatta_retCode_t ret = SHELLMATTA_OK; + shellmatta_instance_t inst; + char *dummyData = (char*) "This is Sparta \"argument with \\\"spaces"; + char buffer[1024u]; + + memcpy(buffer, dummyData, strlen(dummyData)); + + inst.buffer = buffer; + inst.bufferSize = sizeof(buffer); + inst.inputCount = strlen(dummyData); + inst.optionParser.nextOffset = 4u; + + ret = findNextHunk(&inst); + CHECK( ret == SHELLMATTA_OK ); + CHECK( inst.optionParser.offset == 5u); + CHECK( inst.optionParser.len == 2u); + + ret = findNextHunk(&inst); + CHECK( ret == SHELLMATTA_OK ); + CHECK( inst.optionParser.offset == 8u); + CHECK( inst.optionParser.len == 6u); + + ret = findNextHunk(&inst); + REQUIRE( ret == SHELLMATTA_ERROR ); +} diff --git a/test/unittest/shellmatta_utils/test_utils_insertChars.cpp b/test/unittest/shellmatta_utils/test_utils_insertChars.cpp index 973bb0c..78424a0 100644 --- a/test/unittest/shellmatta_utils/test_utils_insertChars.cpp +++ b/test/unittest/shellmatta_utils/test_utils_insertChars.cpp @@ -19,6 +19,7 @@ TEST_CASE( "shellmatta_insertChars normal call" ) { shellmatta_instance_t inst; char buffer[20] = "abcdefghij\0\0\0\0\0\0\0\0\0"; + memset(&inst, 0, sizeof(inst)); inst.buffer = buffer; inst.bufferSize = 20; inst.cursor = 8; @@ -45,6 +46,7 @@ TEST_CASE( "shellmatta_insertChars overwrite" ) { shellmatta_instance_t inst; char buffer[20] = "abcdefghij\0\0\0\0\0\0\0\0\0"; + memset(&inst, 0, sizeof(inst)); inst.buffer = buffer; inst.bufferSize = 20; inst.cursor = 8; @@ -72,6 +74,7 @@ TEST_CASE( "shellmatta_insertChars append" ) { shellmatta_instance_t inst; char buffer[20] = "abcdefghij\0\0\0\0\0\0\0\0\0"; + memset(&inst, 0, sizeof(inst)); inst.buffer = buffer; inst.bufferSize = 20; inst.cursor = 10; @@ -99,6 +102,7 @@ TEST_CASE( "shellmatta_insertChars 0 length" ) { shellmatta_instance_t inst; char buffer[20] = "abcdefghij\0\0\0\0\0\0\0\0\0"; + memset(&inst, 0, sizeof(inst)); inst.buffer = buffer; inst.bufferSize = 20; inst.cursor = 8;