/* * Copyright (c) 2019 - 2024 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 shellmatta_opt.c * @brief option parser implementation of the shellmatta * @author Stefan Strobel */ /** * @addtogroup shellmatta_opt * @{ */ #include "shellmatta_opt.h" #include "shellmatta_utils.h" #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])) && ('\0' == quotation)) { 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] inst pointer to shellmatta instance * @param[in] optionString option string e.g. "cd:e::" * @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 parseShortOpt( shellmatta_instance_t *inst, const char *optionString, char *option, shellmatta_opt_argtype_t *argtype) { shellmatta_retCode_t ret = SHELLMATTA_ERROR; const 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])) { *option = '\0'; /** -# search for option character in option string */ for(i = 0u; ('\0' != optionString[i]) && ('\0' == *option); i ++) { if(buffer[1u] == optionString[i]) { 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_REQUIRED; if(':' == optionString[i + 2u]) { *argtype = SHELLMATTA_OPT_ARG_OPTIONAL; } } else { *argtype = SHELLMATTA_OPT_ARG_NONE; } } } } /** -# skip "--" */ else if((2u == inst->optionParser.len) && ('-' == buffer[0u]) && ('-' == buffer[1u])) { ret = SHELLMATTA_CONTINUE; } else { *option = '\0'; } return ret; } /** * @brief tries to parse the current input hunk and check if this is a configured option * @param[in] inst pointer to shellmatta instance * @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, const shellmatta_opt_long_t *longOptions, char *option, shellmatta_opt_argtype_t *argtype) { shellmatta_retCode_t ret = SHELLMATTA_ERROR; const 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((inst->optionParser.len >= 3u) && ('-' == 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, const char *optionString, const 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 * @param[in] optionString option string e.g. "cd:e::" * @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, const char *optionString, char *option, char **argument, uint32_t *argLen) { return shellmatta_opt_int( handle, optionString, NULL, option, argument, argLen); } /** * @brief scans the current input and parses options in getopt_long style * @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] argument pointer to store the argument string to (can be NULL) * @param[out] argLen pointer to store the argument lengh to (can be NULL) */ shellmatta_retCode_t shellmatta_opt_long( shellmatta_handle_t handle, const shellmatta_opt_long_t *longOptions, char *option, char **argument, uint32_t *argLen) { return shellmatta_opt_int( handle, NULL, longOptions, option, argument, argLen); } /** * @brief initializes the option parser instance * @param[in, out] inst pointer to a shellmatta instance * @param[in] argStart start offset of the arguments (after command name/alias) */ void shellmatta_opt_init(shellmatta_instance_t *inst, uint32_t argStart) { /** -# initialize all relevant option parser variables */ inst->optionParser.argStart = argStart; inst->optionParser.nextOffset = argStart; } /** * @brief re-initializes the option parser instance using the data from the last init * @param[in, out] inst pointer to a shellmatta instance */ void shellmatta_opt_reInit(shellmatta_instance_t *inst) { /** -# initialize all relevant option parser variables */ inst->optionParser.nextOffset = inst->optionParser.argStart; } /** * @} */