diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2a4feb1..7b9a7bf 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,6 +1,4 @@ { - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { @@ -51,6 +49,20 @@ "kind": "test", "isDefault": true } + }, + { + "type": "shell", + "label": "gcc build active file", + "command": "/usr/bin/gcc", + "args": [ + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "/usr/bin" + } } ] } \ No newline at end of file diff --git a/makefile b/makefile index 28e9619..4cd5aea 100644 --- a/makefile +++ b/makefile @@ -38,9 +38,10 @@ UNITTEST_SOURCES := test/unittest/test_main.cpp test/unittest/shellmatta_history/test_appendHistoryByte.cpp \ test/unittest/shellmatta/test_shellmatta_doInit.cpp -INTEGRATIONTEST_SOURCES := test/integrationtest/test_main.cpp \ - test/integrationtest/test_integration.cpp \ - test/integrationtest/test_integration_opt.cpp +INTEGRATIONTEST_SOURCES := test/integrationtest/test_main.cpp \ + test/integrationtest/test_integration.cpp \ + test/integrationtest/test_integration_opt.cpp \ + test/integrationtest/test_integration_optLong.cpp UNITTEST_CPPOBJ := $(patsubst %.cpp,$(OBJ_DIR)%.o,$(UNITTEST_SOURCES)) diff --git a/src/shellmatta_opt.c b/src/shellmatta_opt.c index fcd511b..ed08f15 100644 --- a/src/shellmatta_opt.c +++ b/src/shellmatta_opt.c @@ -119,7 +119,7 @@ static char peekNextHunk(shellmatta_instance_t *inst) * @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) + * @param[out] argtype pointer to var of type #shellmatta_opt_argtype_t != NULL * @return errorcode #SHELLMATTA_OK - option parsable and found in option String * #SHELLMATTA_ERROR - format error or option unknown */ @@ -135,30 +135,39 @@ static shellmatta_retCode_t parseShortOpt( shellmatta_instance_t *inst, /*! -# 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; + *option = '\0'; - /*! -# check if an argument is required or optional */ - if(':' == optionString[i + 1u]) + /*! -# search for option character in option string */ + for(i = 0u; ('\0' != optionString[i]) && ('\0' == *option); i ++) + { + if(buffer[1u] == optionString[i]) { - *argtype = SHELLMATTA_OPT_ARG_REQUIRED; - if(':' == optionString[i + 2u]) + ret = SHELLMATTA_OK; + + /*! -# return found option character */ + *option = buffer[1u]; + + /*! -# check if an argument is required or optional */ + if(':' == optionString[i + 1u]) { - *argtype = SHELLMATTA_OPT_ARG_OPTIONAL; + *argtype = SHELLMATTA_OPT_ARG_REQUIRED; + if(':' == optionString[i + 2u]) + { + *argtype = SHELLMATTA_OPT_ARG_OPTIONAL; + } + } + else + { + *argtype = SHELLMATTA_OPT_ARG_NONE; } } - else - { - *argtype = SHELLMATTA_OPT_ARG_NONE; - } } } + /*! -# skip "--" */ + else if((2u == inst->optionParser.len) && ('-' == buffer[0u]) && ('-' == buffer[1u])) + { + ret = SHELLMATTA_CONTINUE; + } else { *option = '\0'; @@ -167,6 +176,189 @@ static shellmatta_retCode_t parseShortOpt( shellmatta_instance_t *inst, return ret; } +/** + * @brief tries to parse the current input hunk and check if this is a configured option + * @param[in] handle shellmatta handle + * @param[in] longOptions option structure - pointer to array of type #shellmatta_opt_long_t + * @param[out] option pointer to store the detected option to + * @param[out] argtype pointer to var of type #shellmatta_opt_argtype_t != NULL + * @return errorcode #SHELLMATTA_OK - option parsable and found in option String + * #SHELLMATTA_ERROR - format error or option unknown + */ +static shellmatta_retCode_t parseLongOpt( shellmatta_instance_t *inst, + shellmatta_opt_long_t *longOptions, + 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 for short options */ + if((2u == inst->optionParser.len) && ('-' == buffer[0u]) && ('-' != buffer[1u]) && ('\0' != buffer[1u])) + { + /*! -# search for option character in option list */ + for(i = 0u; ('\0' != longOptions[i].paramShort) && ('\0' == *option); i ++) + { + if(buffer[1u] == longOptions[i].paramShort) + { + ret = SHELLMATTA_OK; + + /*! -# return found option character */ + *option = longOptions[i].paramShort; + *argtype = longOptions[i].argtype; + } + } + } + /*! -# check for correct syntax for long options */ + else if((3u <= inst->optionParser.len) && ('-' == buffer[0u]) && ('-' == buffer[1u])) + { + /*! -# search for long option in option list */ + for(i = 0u; ('\0' != longOptions[i].paramShort) && ('\0' == *option); i ++) + { + if(0 == strcmp(&buffer[2u], longOptions[i].paramLong)) + { + ret = SHELLMATTA_OK; + + /*! -# return found option character */ + *option = longOptions[i].paramShort; + *argtype = longOptions[i].argtype; + } + } + } + /*! -# ignore "--" */ + else if((2u == inst->optionParser.len) && ('-' == buffer[0u]) && ('-' == buffer[1u])) + { + *option = '\0'; + ret = SHELLMATTA_CONTINUE; + } + else + { + *option = '\0'; + } + + return ret; +} + +/** + * @brief Scans the current input and parses options in getopt style - pass either optionString or longOptions + * This is an internal funtion to handle both getopt styles and remove duplicated code... + * The standard functions are just wrapper around this one. + * @param[in] handle shellmatta handle + * @param[in] optionString option string e.g. "cd:e::" + * @param[in] longOptions option structure - pointer to array of type #shellmatta_opt_long_t + * @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 + */ +static shellmatta_retCode_t shellmatta_opt_int( shellmatta_handle_t handle, + char *optionString, + shellmatta_opt_long_t *longOptions, + char *option, + char **argument, + uint32_t *argLen) +{ + 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) + && (SHELLMATTA_MAGIC == inst->magic) + && (NULL != option)) + { + *option = '\0'; + if(NULL != argument) + { + *argument = NULL; + } + if(NULL != argLen) + { + *argLen = 0u; + } + + /*! -# do this until we find a not skipable argument */ + do + { + ret = findNextHunk(inst); + if(SHELLMATTA_OK == ret) + { + /*! -# call the matching parse function */ + if(NULL != optionString) + { + ret = parseShortOpt(inst, optionString, option, &argtype); + } + else if(NULL != longOptions) + { + ret = parseLongOpt(inst, longOptions, option, &argtype); + } + else + { + ret = SHELLMATTA_USE_FAULT; + } + + /*! -# when no option is found return this as raw argument */ + if(SHELLMATTA_ERROR == ret) + { + if(NULL != argument) + { + *argument = &(inst->buffer[inst->optionParser.offset]); + } + if(NULL != argLen) + { + *argLen = inst->optionParser.len; + } + ret = SHELLMATTA_OK; + } + else if(SHELLMATTA_USE_FAULT == ret) + { + /*! -# nothing to do - just return errorcode */ + } + else + { + switch(argtype) + { + case SHELLMATTA_OPT_ARG_REQUIRED: + ret = findNextHunk(inst); + if((NULL == argument) || (NULL == argLen)) + { + ret = SHELLMATTA_USE_FAULT; + } + 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((NULL == argument) || (NULL == argLen)) + { + ret = SHELLMATTA_USE_FAULT; + } + if(SHELLMATTA_OK == ret) + { + *argument = &(inst->buffer[inst->optionParser.offset]); + *argLen = inst->optionParser.len; + } + } + break; + default: + /* nothing to do */ + break; + } + } + } + } while(SHELLMATTA_CONTINUE == ret); + } + return ret; +} + /** * @brief scans the current input and parses options in getopt style * @param[in] handle shellmatta handle @@ -183,68 +375,12 @@ 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_opt_argtype_t argtype = SHELLMATTA_OPT_ARG_NONE; - - /** -# check parameters for plausibility */ - if( (NULL != inst) - && (SHELLMATTA_MAGIC == inst->magic) - && (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; - (void)argLen; - - return ret; + return shellmatta_opt_int( handle, + optionString, + NULL, + option, + argument, + argLen); } /** @@ -261,22 +397,12 @@ shellmatta_retCode_t shellmatta_opt_long( shellmatta_handle_t handle, char **argument, uint32_t *argLen) { - shellmatta_retCode_t ret = SHELLMATTA_USE_FAULT; - shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; - - /** -# check parameters for plausibility */ - if( (NULL != inst) - && (SHELLMATTA_MAGIC == inst->magic) - && (NULL != longOptions) - && (NULL != option)) - { - - } - - (void)argument; - (void)argLen; - - return ret; + return shellmatta_opt_int( handle, + NULL, + longOptions, + option, + argument, + argLen); } /** diff --git a/src/shellmatta_utils.c b/src/shellmatta_utils.c index e202291..7d8c97e 100644 --- a/src/shellmatta_utils.c +++ b/src/shellmatta_utils.c @@ -347,12 +347,12 @@ static shellmatta_retCode_t helpCmdFct(shellmatta_handle_t handle, const char *a return ret; } -shellmatta_cmd_t helpCmd = { SHELLMATTA_HELP_COMMAND - , SHELLMATTA_HELP_ALIAS - , SHELLMATTA_HELP_HELP_TEXT - , SHELLMATTA_HELP_USAGE_TEXT - , helpCmdFct - , NULL}; +const shellmatta_cmd_t helpCmd = {SHELLMATTA_HELP_COMMAND + , SHELLMATTA_HELP_ALIAS + , SHELLMATTA_HELP_HELP_TEXT + , SHELLMATTA_HELP_USAGE_TEXT + , helpCmdFct + , NULL}; /** * @brief terminates an input and prints the prompt again diff --git a/src/shellmatta_utils.h b/src/shellmatta_utils.h index 8478f19..fa368c6 100644 --- a/src/shellmatta_utils.h +++ b/src/shellmatta_utils.h @@ -36,7 +36,7 @@ (fct)((buffer), (cnt)); \ } -extern shellmatta_cmd_t helpCmd; +extern const shellmatta_cmd_t helpCmd; #define SHELLMATTA_MAGIC 0x5101E110u diff --git a/test/integrationtest/test_integration_opt.cpp b/test/integrationtest/test_integration_opt.cpp index a2b579d..1564eb4 100644 --- a/test/integrationtest/test_integration_opt.cpp +++ b/test/integrationtest/test_integration_opt.cpp @@ -140,13 +140,6 @@ static shellmatta_retCode_t parseOpts(shellmatta_handle_t handle, const char *ar } shellmatta_cmd_t parseOptsCmd = {(char*)"parseOpts", (char*)"opt", NULL, NULL, parseOpts, NULL}; -static shellmatta_retCode_t empty(shellmatta_handle_t handle, const char *arguments, uint32_t length) -{ - shellmatta_printf(handle, "empty - %s - length: %u", arguments, length); - return SHELLMATTA_OK; -} -shellmatta_cmd_t emptyCmd = {(char*)"empty", NULL, NULL, NULL, empty, NULL}; - TEST_CASE( "shellmatta option parser 1" ) { shellmatta_instance_t inst; @@ -181,6 +174,45 @@ TEST_CASE( "shellmatta option parser 1" ) { 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); + CHECK( write_length == strlen(dummyData)); REQUIRE( strcmp(dummyData, write_data) == 0); } + +TEST_CASE( "shellmatta option parser 2 - ignore \"--\"" ) { + + shellmatta_instance_t inst; + shellmatta_handle_t handle; + char buffer[1024]; + char historyBuffer[1024]; + char *dummyData = (char*)"parseOpts -a -e meow --\r\nparseOpts - cnt: 2\r\n\r\nshellmatta->"; + + shellmatta_doInit( &inst, + &handle, + buffer, + sizeof(buffer), + historyBuffer, + sizeof(historyBuffer), + "shellmatta->", + NULL, + writeFct); + + initTestcase(); + write_callCnt = 0u; + memset(write_data, 0, sizeof(write_data)); + write_length = 0u; + + shellmatta_addCmd(handle, &parseOptsCmd); + + shellmatta_processData(handle, (char*)"parseOpts -a -e meow --\r", 24); + + 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 == strlen(dummyData)); + REQUIRE( strcmp(dummyData, write_data) == 0); +} + diff --git a/test/integrationtest/test_integration_optLong.cpp b/test/integrationtest/test_integration_optLong.cpp new file mode 100644 index 0000000..1e7cf42 --- /dev/null +++ b/test/integrationtest/test_integration_optLong.cpp @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2019 Stefan Strobel + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/** + * @file test_integration_optLong.cpp + * @brief integration test implementation for the long option parser of the shellmatta + * @author Stefan Strobel + */ + +#include "test/framework/catch.hpp" +extern "C" { +#include "shellmatta.h" +} +#include + +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) +{ + write_callCnt ++; + while((length > 0) && (write_length < sizeof(write_data))) + { + write_data[write_length] = *data; + data ++; + length --; + write_length ++; + } + + return SHELLMATTA_OK; +} + +static shellmatta_retCode_t parseOptsLong(shellmatta_handle_t handle, const char *arguments, uint32_t length) +{ + (void) arguments; + (void) length; + char option; + char *argumentString; + uint32_t argumentLength; + uint32_t optionCount = 0u; + + shellmatta_opt_long_t longOptions[] = { + {"auto", 'a', SHELLMATTA_OPT_ARG_NONE}, + {"build", 'b', SHELLMATTA_OPT_ARG_NONE}, + {"cat", 'c', SHELLMATTA_OPT_ARG_NONE}, + {"doom", 'd', SHELLMATTA_OPT_ARG_NONE}, + {"erase", 'e', SHELLMATTA_OPT_ARG_REQUIRED}, + {"fuck", 'f', SHELLMATTA_OPT_ARG_OPTIONAL}, + {NULL, '\0', SHELLMATTA_OPT_ARG_NONE} + }; + + while(SHELLMATTA_OK == shellmatta_opt_long(handle, longOptions, &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 parseOptsLongCmd = {(char*)"parseOpts", (char*)"opt", NULL, NULL, parseOptsLong, NULL}; + +TEST_CASE( "shellmatta long option parser 1" ) { + + shellmatta_instance_t inst; + shellmatta_handle_t handle; + char buffer[1024]; + char historyBuffer[1024]; + char *dummyData = (char*)"parseOpts -a -e meow\r\nparseOpts - cnt: 2\r\n\r\nshellmatta->"; + + shellmatta_doInit( &inst, + &handle, + buffer, + sizeof(buffer), + historyBuffer, + sizeof(historyBuffer), + "shellmatta->", + NULL, + writeFct); + + initTestcase(); + write_callCnt = 0u; + memset(write_data, 0, sizeof(write_data)); + write_length = 0u; + + shellmatta_addCmd(handle, &parseOptsLongCmd); + + 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 == strlen(dummyData)); + REQUIRE( strcmp(dummyData, write_data) == 0); +} + +TEST_CASE( "shellmatta long option parser 2" ) { + + shellmatta_instance_t inst; + shellmatta_handle_t handle; + char buffer[1024]; + char historyBuffer[1024]; + char *dummyData = (char*)"parseOpts --auto --erase meow -- --lalelu -u\r\nparseOpts - cnt: 4\r\n\r\nshellmatta->"; + + shellmatta_doInit( &inst, + &handle, + buffer, + sizeof(buffer), + historyBuffer, + sizeof(historyBuffer), + "shellmatta->", + NULL, + writeFct); + + initTestcase(); + write_callCnt = 0u; + memset(write_data, 0, sizeof(write_data)); + write_length = 0u; + + shellmatta_addCmd(handle, &parseOptsLongCmd); + + shellmatta_processData(handle, (char*)"parseOpts --auto --erase meow -- --lalelu -u\r", 45); + + 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) ); + CHECK( cntDef == 2u ); + CHECK( lenDef == 2u ); + CHECK( write_length == strlen(dummyData)); + REQUIRE( strcmp(dummyData, write_data) == 0); +}